Organizing for virtualization
You may have been wondering what is keeping me so busy that I missed my regular posting schedule. Among other things, I now have a machine at home that supports virtualization.
This gives a lot more possibilities to experiment with software and blog about those experiences. It became clear to me though that with multiple virtual machines to setup and maintain I would have to organize myself a little better. You can get away with a lot if you only have one machine.
First, it would be nice to set up development environments automatically. I already spend a lot of my time watching progress bars tick along. It would be nice to be automated as much as possible so I can start something running and walk away.
Second, my source files, examples, and install packages are split between my laptop and desktop machine in no particular pattern.
Lastly, I’ve spent some time learning PowerShell and like putting together useful scripts. Now is a perfect opportunity to create some useful utilities, and can organize them so I can possibly find them again.
After I spent a little time trying to organize the materials I had, and collecting some script snippets that could be useful soon, I decided to look into automated installation.
Windows Automated Installation Kit (AIK)
I found my way to the Windows AIK as a method for installing Windows automatically. There are a lot of features and tools there and my strategy of reading the documentation wasn’t particularly effective. I think this strategy might have gone better for me:
- Scan the documentation reading about what tools are available, the scenarios etc.
- Satisfy my curiosity how things worked.
- Go back and understand which scenarios best fit what you want to do. Make a plan.
- Hit the sections in detail that are needed.
I read the part that seemed interesting and skipped the planning part. It took me a while to realize I probably needed two installation plans. One for the reference computer and one for target computers. At first I figured I could use the same plan for both, but that isn’t necessarily the best option.
The other part that threw me a little, is that a setting up a development environment is not really a target scenario for the documentation. This comes into play when deciding which roles can be running on a VM and which need to be on the physical computer. In addition, I needed to learn a few things about Hyper-V and setting up virtual machines.
In any case, reading the documentation for Windows AIK carefully seems to be critical. Then experimenting to see what works and what does not to get familiar with it. This takes a bit of time waiting for OS installs to complete to see the results and get some experience.
Settings things up
After downloading all the ISOs I needed, I set up the Windows AIK on a virtual machine, and created another virtual machine to be my reference computer. The documentation recommends using a USB (removable drive) for storing the answer file for setup.
This seemed to be a bit of a problem at first. There was no way to get a USB attached to a VM that I could see. But floppy drives also were fine, so after a bit of searching the internet I found some script that would create a VFD that I could plug into the VM. I couldn’t find an automated way to format the floppy, so I did it manually. (I now think this is possible with diskpart.exe, but have not tried).
The PowerShell I used to create the VFD was:
1: $s = gwmi -namespace root\virtualization Msvm_ImageManagementService
2: $s.createvirtualfloppydisk("C:\floppy.vfd")
You can also translate from the C# example on MSDN.
I created an answer file with the minimum recommended settings, put it on the floppy, loaded both the floppy and the answer file into a VM and successfully deployed. The install was mostly automated, but I was disappointed to see that there was one menu (the OS selection) that I could not find a solution for. Since the dialog comes up at the start of the install, I lived with it for a while.
Recently I found the solution, however. To select an OS image from inside install.wim, you need to set the following:
ImageInstall\OSImage\InstallFrom
Path = install.wim
ImageInstall\OSImage\InstallFrom\MetaData
Key = /image/index
Value = 3
3 seems to be the 2008 R2 Enterprise Full install. How did I find this out? By looking at the Unattend.xml file generated by Microsoft Deployment Toolkit.
Adding additional software
The base image needs a lot of updates to bring it current. It needs to pull in IE9 and 2008R2 SP1 among others. The total number of updates is currently around 80. It seemed it would be more efficient if I could automatically install the big ones and then automate windows update.
I found some examples using PowerShell with the Windows Update COM Api. If you are interested search “Windows update PowerShell” or something equivalent. MSDN has an example in VBScript that can be easily translated to PowerShell.
After downloading the IE9 and 2008 R2 SP1 installer I ran into a bunch of problems automating the install. I still don’t have a really good solution so if you have suggestions, please leave some bread crumbs in the comment section.
There are few problems that have to be solved to automate this:
1) When you download installer packages, they are marked as “from the internet” and pop up all kinds of security dialogs.
2) Both IE9 and 2008 R2 SP1 installers need to reboot the machine. This means your automation need to figure out where it left off and continue.
3) 2008 R2 SP1 installer is 900M+. It didn’t seem like a good idea to copy it to the VM’s expandable HD and run it from there. I’d rather run it from a share on the host machine.
4) Running installer from a network share has its own set of problems. The most notable, is the open file security warning dialog.
Here are some of the solutions I have found.
Unblocking installers from the internet
It turns out that files downloaded from the internet onto an NTFS volume are marked by using alternate NTFS file streams with the zone it came from.
Normally, you can unblock content by right clicking the file and choosing properties. Then clicking the “unblock” button. There is an automated way to do this in PowerShell by deleting the alternate file stream.
You can search more about alternate file streams and PowerShell or you can read this blog post.
Running from a network share
I have successfully gotten rid of the dialog that happens running from the network share by modifying the registry with the following script:
1: $securityPath = HKCU:Software\Microsoft\Windows\CurrentVersion\Policies\Associations
2:
3: if (-not (test-path $securityPath))
4: {
5: new-item -itemtype directory -force -path $securityPath
6: set-itemproperty $securityPath -name ModRiskFileTypes ".exe;.msi"
7: }
There may be better ways of doing this, especially if you can integrate the installers into the setup installer. This is the best solution I’ve found thus far. There also may be security issues with relaxing the restriction. In a closed VM environment I think it is fine for my purpose.
I was able to net use the share, run this script, and run one of the IE9 or SP1 installers as separate steps, but not together as one smooth install yet. One installer also isn’t helpful since I have to have a solution that runs two installers with a reboot in between.
Running a second installer
I’ve tried various things to run a second installer.
1) Add the unattended command lines to the specialize step of unattended setup. Setup can handle reboots in the middle so it seemed like a good approach.
Result: Although I did see the IE9 installer dialog, the net result was IE8 was still the browser after setup completed. It may be that it is too early in setup to run installers. It seems that way if you read the AIK documentation carefully.
2) I did a bunch of net use and installer launches in the oobe setup step. This phase doesn’t seem to have the same reboot management support that the specialize step did so I needed to find a solution to this. The RunOnce registry key seems to be one possibility. Also, at the time I tried this I hadn’t found how to get rid of the open file security dialog yet so it would run the first installer and pop up a dialog.
3) Create a series of PowerShell scripts that manage the installation sequence.
Result: PowerShell execution policy has not been updated yet so you need to ensure you pass “-executionpolicy bypass” for scripts entered as synchronous commands in the unattend.xml file. This was the first solution I was able to get to work, in combination with the RunOnce registry key.
Working with RunOnce registry key
Since both installers reboot, I considered using the RunOnce registry key to initiate the second installer after reboot and auto logon. Here is a script I used to install an example registry entry:
1: pushd hklm:\Software\Microsoft\Windows\CurrentVersion
2: if (-not (test-path RunOnce))
3: {
4: new-item -type directory RunOnce
5: }
6:
7: set-itemproperty RunOnce -Name First -Value "C:\install\IE9-Windows7-x64-enu.exe /passive"
Note that when there are multiple values, it uses them in alphabetical order, so be aware.
The solution
So the solution I came up with to build the reference computer is made up of the following steps:
specialize
Copy the scripts and IE9 installer to the local VHD
Install RunOnce to install RunOnce for the SP1 installer
Install RunOnce to start the IE9 installer
oobe
Install IE9 and reboot
RunOnce
Install R2 SP1
Install windows update script into RunOnce
Install windows updates.
Specialize step in Autounattend.xml
net use z: \\host\install /u:host\install <password>
powershell.exe –executionpolicy bypass z:\reference.ps1
Reference.ps1
This script copies files to the c:\install directory and installs the RunOnce entry installing a RunOnce entry after reboot.
1: new-item -itemtype directory -force c:\install
2: copy-item z:\ie9\*.exe c:\install
3: copy-item z:\*.ps1 c:\install
4:
5: pushd hklm:\Software\Microsoft\Windows\CurrentVersion
6: if (-not (test-path RunOnce))
7: {
8: new-item -type directory RunOnce
9: }
10:
11: set-itemproperty RunOnce -Name 001 -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -executionpolicy bypass c:\install\install-sp1-runonce.ps1"
12: set-itemproperty RunOnce -Name 002 -Value "C:\install\IE9-Windows7-x64-enu.exe /passive"
13: popd
install-sp1-runonce.ps1
1: pushd hklm:\Software\Microsoft\Windows\CurrentVersion
2: if (-not (test-path RunOnce))
3: {
4: new-item -type directory RunOnce
5: }
6:
7: set-itemproperty RunOnce -Name sp1 -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -executionpolicy bypass c:\install\install-r2sp1.ps1"
8: popd
install-rs2sp1.ps1
This script uses the install share, installs the RunOnce entry for the windows update script, disables the dialog for the SP1 installer, and starts the install. It uses a registry setting available on VM clients to find the host name of the VM host machine.
1: rite-host "Setting up network share."
2:
3: # Find the VM host machine name by querying the registry
4: $hostMachine = (get-itemproperty "HKLM:\Software\Microsoft\Virtual Machine\Guest\Parameters").HostName
5:
6: # Get the network share.
7: net use z: /d
8: net use z: \\$hostMachine\install /u:$hostMachine\install <password>
9:
10: write-host "Network share setup complete."
11:
12: . c:\install\install-update-runonce.ps1
13:
14: # Set security update
15:
16: $securityPath = "HKCU:Software\Microsoft\Windows\CurrentVersion\Policies\Associations"
17:
18: if (-not (test-path $securityPath))
19: {
20: write-host "Making security changes to allow running from share."
21: new-item -itemtype directory -force -path $securityPath | out-null
22: set-itemproperty $securityPath -name ModRiskFileTypes ".exe;.msi"
23: }
24:
25: write-host "Starting install."
26: start-process -wait Z:\r2sp1\windows6.1-KB976932-X64.exe -arg /unattend,/nodialog
install-update-runonce.ps1
1: # Install windowsupdate into runonce
2:
3: pushd hklm:\Software\Microsoft\Windows\CurrentVersion
4: if (-not (test-path RunOnce))
5: {
6: new-item -type directory RunOnce
7: }
8:
9: set-itemproperty RunOnce -Name share -Value "net use z: /d"
10: set-itemproperty RunOnce -Name update -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -executionpolicy bypass c:\install\windowsupdate.ps1"
11:
12: popd
windowsupdate.ps1
This script searches for updates that need to be installed, downloads them and installs them. If a reboot is needed, it does one final reboot to finish the install. It doesn’t install the optional updates however.
1: function Convert-ToWIAStatusValue($value)
2: {
3: switch -exact ($value)
4: {
5: 0 {"NotStarted"}
6: 1 {"InProgress"}
7: 2 {"Succeeded"}
8: 3 {"SucceededWithErrors"}
9: 4 {"Failed"}
10: 5 {"Aborted"}
11: }
12:
13: }
14:
15: $needsReboot = $false
16: $UpdateSession = new-object -ComObject Microsoft.Update.Session
17: $updateSearcher = $updateSession.CreateUpdateSearcher()
18:
19: write-host "- Searching for updates."
20:
21: $searchResult = $updateSearcher.Search("IsAssigned=1 and IsHidden=0 and IsInstalled=0")
22:
23: write-host " -Found [$($searchResult.Updates.Count)] Updates"
24: write-host
25:
26: foreach ($update in $searchResult.Updates)
27: {
28: # Add Update to collection
29: $updateCollection = new-object -comobject Microsoft.Update.UpdateColl
30: if ($update.EualAccepted -eq 0)
31: {
32: $update.AcceptEula()
33: }
34: $updateCollection.Add($update) | out-null
35:
36: # Download
37: write-host " + Downloadin update $($update.Title)"
38: $updatesDownloader = $updateSession.CreateUpdateDownloader()
39: $updatesDownloader.Updates = $updateCollection
40: $downloadResult = $updatesDownloader.Download()
41: $message = " - Download {0} " -f (Convert-ToWIAStatusValue $downloadResult.ResultCode)
42: write-host $message
43:
44: # Install
45: write-host " - Installing update"
46:
47: $updateInstaller = $updateSession.CreateUpdateInstaller()
48: $updateInstaller.Updates = $updateCOllection
49: $installResult = $updateInstaller.Install()
50: $message = " - Install {0}" -f (Convert-ToWIAStatusValue $InstallResult.ResultCOde)
51: write-host $message
52: write-host
53:
54: if (-not $needsReboot)
55: {
56: $needsReboot = $installResult.rebootRequired
57: }
58: }
59:
60: if ($needsReboot)
61: {
62: restart-computer
63: }
Note: you can find example PowerShell scripts by searching for “Windows update PowerShell” . This variation came from a MSDN forums thread.
Summary
Although my solution works, there is probably a more elegant solution. Specifically, there is probably a way to add the installers to the autounatted.xml by creating a new ISO image. If you have suggestions for improvement, please leave a comment.
My next task is to try to deploy a domain controller and example corp deployment automatically. I would like to set it up on an internal Hyper-V network, but have not successfully gotten such a host to connect back to the VM host machine to access the install share. If you have a hint on that, I’d appreciate a comment on that as well.