-- Ben Armstrong, Hyper-V Program Manager
Talking about Virtual PC and Hyper-V at Microsoft
As I mentioned yesterday – I have been working on a completely automated virtual machine build process that does not use templates (but build everything from scratch). One of the key tools that I have been using for this process is WIM2VHD – which allows me to generate new VHD files directly from Windows installation media.
However – I wanted to go a step further and have the virtual machines generated using this process boot in a completely unattended fashion. My ultimate goal here was to go from nothing to having a virtual machine logged into the desktop with no external interaction.
Luckily, WIM2VHD allows you to specify a unattended installation file to be injected into the virtual hard disk. I installed the Windows Automated Installation Kit for Windows 7 and then used the Windows System Image Manager to create some unattended installation files. One of the interesting problems though is that not all sections of the unattended file are referenced when used with WIM2VHD. In fact, only the specialize and oobeSystem sections are used. So I had to figure out how to do everything I wanted in these two sections.
Below are the unattended files that I created (with serial numbers struck out):
Unattended file for Windows 7 32-bit:
<?xml version="1.0" encoding="utf-8"?> <unattend xmlns="urn:schemas-microsoft-com:unattend"> <settings pass="specialize"> <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <AutoLogon> <Password> <Value>p@ssw0rd</Value> <PlainText>true</PlainText> </Password> <Domain>QuickSetup</Domain> <Enabled>true</Enabled> <LogonCount>1</LogonCount> <Username>Test</Username> </AutoLogon> <ComputerName>QuickSetup</ComputerName> <ProductKey>*****-*****-*****-*****-*****</ProductKey> <TimeZone>Pacific Standard Time</TimeZone> </component> <component name="Microsoft-Windows-UnattendedJoin" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <Identification> <JoinWorkgroup>QuickWorkgroup</JoinWorkgroup> <UnsecureJoin>true</UnsecureJoin> </Identification> </component> </settings> <settings pass="oobeSystem"> <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <OOBE> <HideEULAPage>true</HideEULAPage> <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE> <NetworkLocation>Work</NetworkLocation> <ProtectYourPC>3</ProtectYourPC> </OOBE> <UserAccounts> <AdministratorPassword> <Value>p@ssw0rd</Value> <PlainText>true</PlainText> </AdministratorPassword> <LocalAccounts> <LocalAccount wcm:action="add"> <Password> <Value>p@ssw0rd</Value> <PlainText>true</PlainText> </Password> <Description>Test</Description> <DisplayName>Test</DisplayName> <Group>Administrators</Group> <Name>Test</Name> </LocalAccount> </LocalAccounts> </UserAccounts> </component> <component name="Microsoft-Windows-International-Core" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <InputLocale>en-us</InputLocale> <SystemLocale>en-us</SystemLocale> <UILanguage>en-us</UILanguage> <UILanguageFallback>en-us</UILanguageFallback> <UserLocale>en-us</UserLocale> </component> </settings> </unattend>
Unattended file for Windows 7 64-bit:
<?xml version="1.0" encoding="utf-8"?> <unattend xmlns="urn:schemas-microsoft-com:unattend"> <settings pass="specialize"> <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <AutoLogon> <Password> <Value>p@ssw0rd</Value> <PlainText>true</PlainText> </Password> <Domain>QuickSetup</Domain> <Enabled>true</Enabled> <LogonCount>1</LogonCount> <Username>Test</Username> </AutoLogon> <ComputerName>QuickSetup</ComputerName> <ProductKey>*****-*****-*****-*****-*****</ProductKey> <TimeZone>Pacific Standard Time</TimeZone> </component> <component name="Microsoft-Windows-UnattendedJoin" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <Identification> <JoinWorkgroup>QuickWorkgroup</JoinWorkgroup> <UnsecureJoin>true</UnsecureJoin> </Identification> </component> </settings> <settings pass="oobeSystem"> <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <OOBE> <HideEULAPage>true</HideEULAPage> <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE> <NetworkLocation>Work</NetworkLocation> <ProtectYourPC>3</ProtectYourPC> </OOBE> <UserAccounts> <AdministratorPassword> <Value>p@ssw0rd</Value> <PlainText>true</PlainText> </AdministratorPassword> <LocalAccounts> <LocalAccount wcm:action="add"> <Password> <Value>p@ssw0rd</Value> <PlainText>true</PlainText> </Password> <Description>Test</Description> <DisplayName>Test</DisplayName> <Group>Administrators</Group> <Name>Test</Name> </LocalAccount> </LocalAccounts> </UserAccounts> </component> <component name="Microsoft-Windows-International-Core" processorArchitecture="x86" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <InputLocale>en-us</InputLocale> <SystemLocale>en-us</SystemLocale> <UILanguage>en-us</UILanguage> <UILanguageFallback>en-us</UILanguageFallback> <UserLocale>en-us</UserLocale> </component> </settings> </unattend>
Unattended file for Windows Server 2008 R2:
<?xml version="1.0" encoding="utf-8"?> <unattend xmlns="urn:schemas-microsoft-com:unattend"> <servicing></servicing> <settings pass="specialize"> <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <AutoLogon> <Password> <Value>p@ssw0rd</Value> <PlainText>True</PlainText> </Password> <Domain>QuickSetup</Domain> <Enabled>true</Enabled> <LogonCount>1</LogonCount> <Username>Administrator</Username> </AutoLogon> <ComputerName>QuickSetup</ComputerName> <ProductKey>*****-*****-*****-*****-*****</ProductKey> <RegisteredOrganization>Microsoft</RegisteredOrganization> <RegisteredOwner>Ben Armstrong</RegisteredOwner> <TimeZone>Pacific Standard Time</TimeZone> </component> <component name="Microsoft-Windows-UnattendedJoin" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <Identification> <JoinWorkgroup>QuickWorkgroup</JoinWorkgroup> <UnsecureJoin>true</UnsecureJoin> </Identification> </component> </settings> <settings pass="oobeSystem"> <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <OOBE> <HideEULAPage>true</HideEULAPage> <HideLocalAccountScreen>true</HideLocalAccountScreen> <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE> <NetworkLocation>Work</NetworkLocation> <ProtectYourPC>1</ProtectYourPC> </OOBE> <UserAccounts> <AdministratorPassword> <Value>p@ssw0rd</Value> <PlainText>True</PlainText> </AdministratorPassword> </UserAccounts> </component> <component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <InputLocale>en-us</InputLocale> <SystemLocale>en-us</SystemLocale> <UILanguage>en-us</UILanguage> <UILanguageFallback>en-us</UILanguageFallback> <UserLocale>en-us</UserLocale> </component> </settings> </unattend>
Using these files with WIM2VHD is quite simple – you just use a command like this:
cscript wim2vhd.wsf /wim:"I:\sources\install.wim" /sku:ULTIMATE /VHD:".\NewClient.vhd" /size:40000 /disktype:dynamic /unattend:unattendClient32.xml
Cheers, Ben
Over the years I have made a hobby of collecting real copies of the hardware that we emulate inside of our virtual machines. And this is one of my favorites – the quad-port Intel 21140 network adapter:
One of the interesting problems that we hit while working on Virtual Server was:
How do you support up to four Ethernet adapters and four SCSI adapters at the same time?
You see, you can only have so many PCI devices in a computer (even a virtual one). The answer ended up being quite simple: multi-function devices. So when you are using Virtual PC / Virtual Server / Hyper-V and you configure 2 or more network adapters (legacy network adapters for Hyper-V) what you are actually configuring is a single network adapter with multiple ports on it – each with its own MAC address.
Of course; this makes no difference to the user. It still looks and feels like you have multiple separate network adapters. The only noticeable benefit is that it actually works!
What happens when you use the Virtual PC / Virtual Server / Hyper-V user interface to shut down a virtual machine?
At the simplest level – we send a message over to the virtual machine that asks the guest operating system to shut down, and then we wait.
But this is where things get a little tricky.
We can send a request to shut down the guest operating system, and we can know whether the request to shut down was received or not. But once the shut down process begins – we have no idea what is happening. This comes from two issues:
So instead of trying to monitor the shut down process – we just fake it.
When you select to shut down a virtual machine – we send the request to shut down. Once we receive confirmation that the operating system has started shutting down we display a progress bar in the user interface. This progress bar is 100% fake. All that we are doing is watching to see if the virtual machine turns off, and counting to 5 minutes. We slowly increase the progress of the fake progress bar – and if we see the virtual machine turn off by itself we assume that shut down was successful. We then set the progress bar to 100% and report that shut down was successful.
If the 5 minute timer is met and the virtual machine has not shut down – this can pose another problem for us. It is highly likely that, while the virtual machine machine has failed to shut down, our integration services have been stopped. This means that we may not be able to issue a second request to shut down.
Because of this, if the virtual machine is still running after 5 minutes – we report that shutdown has failed and that you should go and have a look at at. This can lead to a strange experience if the virtual machine takes just over 5 minutes to shut down – as this will result in us displaying an error message moments before success.
We have discussed the idea of simulating an ACPI power off command instead of using an integration service. This would make shutting down a virtual machine with the shut down button equivalent to hitting the power button on a modern computer. The problem with this approach is that you can configure most operating systems to do nothing when the power button is pressed – which would be confusing for the end user.
Virtual PC, Virtual Server and Hyper-V all have a simple command to “Turn off” a running virtual machine.
Unfortunately, it is one that causes us a bit of consternation when we look at it with usability and user friendliness in mind.
A long time ago (just over 10 years ago) most computers had big mechanical power switches on them. When you “turned off” a computer you were immediately cutting the power to the system. But that is not the way the world works any more.
Today – when you press the power button on the front of the computer case (many, but not all, computers still have to “old school” power switch on the back of the case) the computer actually continues to run – and sends a message to the operating system to shut down as quickly as possible. This is a much safer and cleaner way to turn off a computer. It is also not as reliable.
Operating systems do not always shut down when asked to shut down.
To deal with this – physical computers allow you to hold down the power button. After a couple of seconds of being held down it will issue an “old school” power off.
But why is this relevant to virtual machines?
When you select to “turn off” a virtual machine – you are actually getting an “old school” “cut the power” kind of turn off. The concern here is that back in the days of yore, people were trained to always shutdown a computer before turning it off. These days it is almost always safe to just lean over and press the power button on your computer. The same cannot be said for turning off a virtual machine.
We have debated back and forward about how to correctly communicate to users exactly the risks they are taking when turning off a virtual machine – but have come up with no good answers.
We have joked that the turn off button should be labeled “cut the power” and have a picture of a power cord being pulled out of the wall. But this would doubtlessly cause a huge amount of confusion (also – we would have to create localized graphics that display the correct kind of power outlet for each different country ).
We have also discussed whether we should implement a modern “turn off” that does a quick shut down. The problem here is how do you handle when the virtual machine fails to shut down? You cannot expect people to hold down the turn off button with their mouse for a couple of seconds – waiting for the virtual machine to turn off.
So for now – things remain as they are. Just remember – the next time that you go to “turn off” a virtual machine – you really are pulling the power immediately.
Once you start to get more and more virtual machines, and more and more Hyper-V servers, in your environment it can get quite hard to keep track of where a specific virtual machine is actually running.
System Center Virtual Machine Manager can help you out here – but what if you are not sitting at the System Center Virtual Machine Manager Console? What if you have used Remote Desktop to connect to the virtual machine? What do you do then?
A long, long time ago – I posted information on how to figure out the host operating system from inside a virtual machine using a VBScript on Virtual PC and Virtual Server. And this script also works on Hyper-V!
But no one uses VBScript anymore! Right? So how do we do this in PowerShell?
The answer is with a one-liner of course! (or actually – three one liners).
To get the name of the physical computer that you are running on, open a PowerShell inside the virtual machine and type in:
(Get-ItemProperty –path “HKLM:\SOFTWARE\Microsoft\Virtual Machine\Guest\Parameters”).PhysicalHostName
You can also get the fully qualified name of the physical computer by running:
(Get-ItemProperty –path “HKLM:\SOFTWARE\Microsoft\Virtual Machine\Guest\Parameters”).PhysicalHostNameFullyQualified
Finally, you can get the name of the virtual machine itself by running:
(Get-ItemProperty –path “HKLM:\SOFTWARE\Microsoft\Virtual Machine\Guest\Parameters”).VirtualMachineName
And of course – this will work on Hyper-V, Virtual Server and Virtual PC.
When I was getting ready to head out to TechEd this week I copied a bunch of my Windows Virtual PC virtual machines onto a USB disk. 60 of them to be precise. But then I had the problem of how to get them all registered.
Sure, you can register a virtual machine with Windows Virtual PC by just double clicking on the .VMC file for the virtual machine – but this also starts the virtual machine. I did not want to have to start (and stop) all 60 virtual machines just to get them registered. So I threw together this little PowerShell script to do the job for me:
# Switch to using Single-Threaded Apartment model - needed by WinForms
if([Threading.Thread]::CurrentThread.ApartmentState -ne "STA")
{
PowerShell -NoProfile -STA -File $myInvocation.MyCommand.Definition
return
}
# Make new folder browse dialog - and display it
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") | Out-Null
$OpenFileDialog = New-Object System.Windows.Forms.folderbrowserdialog
$OpenFileDialog.Description = "Select a folder to register virtual machines from."
$OpenFileDialog.ShowDialog() | Out-Null
# Only move on if the user actually selected a folder
if ($OpenFileDialog.SelectedPath)
# Find all .VMC files under the directory selected by the user
$files = get-childitem $OpenFileDialog.SelectedPath -recurse | where {$_.extension -eq ".vmc"}
# Get the VPC object
$vpc = new-object –com VirtualPC.Application –Strict
# Go through each of the .VMC files and register them
Foreach ($file in $files)
$vpc.RegisterVirtualMachine($file.Name,$file.DirectoryName) | Out-Null
When you run this script – you will be presented with a folder selection dialog. Once you have selected a folder the script will then look for – and register – all .VMC files that exist underneath that folder. Very handy!
Unlike Hyper-V, Virtual Server allows you to boot a virtual machine off of a SCSI disk. Unfortunately - in some configurations (like mine) this can cause the initial operating system installation to be very slow – unless you load the Virtual Server SCSI Shunt driver during installation.
For Windows NT, Windows 2000, Windows XP and Windows Server 2003 this driver is loaded using an F6 Boot Floppy. But what about with Windows Vista and Windows Server 2008? The process is a bit different for these operating systems.
With these operating systems you need to:
Note that while Windows 7 is not officially supported as a guest operating system for Virtual Server, this process appears to work for Windows 7 virtual machines that are booting off of SCSI as well.
Virtual PC 2004 / 2007 always default to creating new virtual machines in the current users “My Documents” folder. Users who need to use an alternate location can do so by setting up the MYVIRTUALMACHINES environment variable.
Things have changed significantly with Windows Virtual PC.
The first change is that we no longer look at the MYVIRTUALMACHINES environment variable.
The second change is that we no longer enforce a static default location. Rather – every time you create a new virtual machine – the location that you specify for the new virtual machine:
Will be used as the default virtual machine location the next time you try to create a virtual machine.
Finally – if you do want to programmatically set a default location you can do so using some simple scripts:
PowerShell:
param([string]$path)
# Check for correct command-line arguments
If ($path -eq "")
write-host "Missing command-line argument."
write-host "USage: SetDefaultVMPath.ps1 -path `"Default path to use for new virtual machines`""
exit
# Connect to Virtual PC
$vpc=new-object –com VirtualPC.Application –Strict
# Set the new default VM path
$vpc.DefaultVMConfigurationPath = $path
VBScript:
Option Explicit
Dim namedArguments, vpc, defaultVMPath
' Check that the script is running at the command line.
If UCase(Right(Wscript.FullName, 11)) = "WSCRIPT.EXE" Then
WScript.Echo "This script must be run under CScript."
WScript.Quit
End If
' Get the new default VM path from the command-line arguments
Set namedArguments = WScript.Arguments.Named
If namedArguments.Exists("path") Then
defaultVMPath = namedArguments.Item("path")
Else
WScript.Echo "Missing command-line argument"
WScript.Echo
WScript.Echo "Usage: SetDefaultVMPath.vbs /path:" & chr(34) & "Default path to use for new virtual machines" & chr(34)
' Attempt to connect to Virtual PC
On Error Resume Next
Set vpc = CreateObject("VirtualPC.Application")
If Err.Number <> 0 Then
WScript.Echo "Unable to connect to Virtual PC."
End if
On Error Goto 0
' Set the new default VM path
vpc.DefaultVMConfigurationPath = defaultVMPath
Note that while these scripts will set the default location – if the user chooses a different location when creating a new virtual machine, that location will become the new default virtual machine location.
This means that these scripts are only really useful for customizing an initial deployment of Windows Virtual PC. Alternatively you could run these scripts on logon to set the default path back to your desired location.
Whether you are running Virtual Server on Windows 7, or just trying to manage Virtual Server from a Windows 7 computer – you will need to use the Virtual Server Administrative Web Site under Internet Explorer 8. This is possible – but there are a number of things that you need to do:
You should now be able to use Virtual Server from Internet Explorer 8 happily.
From time to time people have asked me how to install Virtual Server on Windows 7, and have even pointed me towards attempts made by other people with varying levels of success. But I have not looked into this until today – because it is unsupported.
Let me state that again – to be crystal clear – running Virtual Server on Windows 7 is not supported. If you try to do this and things do not work for you – do not contact me or Microsoft – as the answer will simply be that it is not supported and that you should not do that. If you need a server virtualization solution Microsoft recommends that you use Hyper-V in Windows Server 2008 R2. If this is not an option for you (most commonly this is because your computer does not support hardware virtualization) then you can run Virtual Server in a supported fashion by doing so on Windows Server 2008.
Let me state that again – to be crystal clear – running Virtual Server on Windows 7 is not supported. If you try to do this and things do not work for you – do not contact me or Microsoft – as the answer will simply be that it is not supported and that you should not do that.
If you need a server virtualization solution Microsoft recommends that you use Hyper-V in Windows Server 2008 R2. If this is not an option for you (most commonly this is because your computer does not support hardware virtualization) then you can run Virtual Server in a supported fashion by doing so on Windows Server 2008.
But why am I talking about how to do this today? Well – I needed to do so for the purposes of a demo that I am working on. I am setting up a demo that I want to run on a single computer, that needs to have some server infrastructure running in the background. Ordinarily I would just setup the servers inside Virtual PC – but in this case the demo is centered around Virtual PC – and I do not want to have a bunch of server virtual machines cluttering up my demo (petty – I know).
What is the deal with running Virtual Server 2005 R2 SP1 on Windows 7 anyway? Well – if you just try to download and install Virtual Server on Windows 7 you will be presented with the following dialog:
The reason why this dialog is presented is because Microsoft has explicitly decided to not support, or test, Virtual Server on Windows 7. Have I mentioned that this is not supported? Just to reiterate – this is not supported. As Virtual Server includes kernel mode drivers – the fact that they are untested and unsupported could be potentially catastrophic for your computer.
Because of this the Windows 7 application compatibility engine detects that you are trying to install Virtual Server and stops you. At this point you have two options (apart from the obvious one of not running Virtual Server on Windows 7):
The problem with trying to trick the application compatibility engine is that it usually involves renaming files / messing with the registry in ways that may potentially break Virtual Server anyway (thus defeating the point of the whole exercise). The problem with turning of the application compatibility engine is that you may encounter problems with other applications as a result.
Since I am only running a limited set of applications in my demo environment, I felt more comfortable with the second option. To turn off the application compatibility engine you need to:
You should now have something that looks like this:
Now you will need to reboot your computer (forcing a group policy update is not enough in this case).
Before moving on I would like to quickly discuss exactly what the impact of these policy changes are: Disabling the Program Compatibility Assistant means that Windows will no longer tell you when you are about to run a program with known compatibility problems. It will just sit by quietly and let you try (and most likely fail). Disabling the Application Compatibility Engine means that Windows will no longer try to correct known compatibility issues. This means that there may be programs that were working before that start failing – because Windows is no longer fixing things up for them. So the summary is that you should really only do this if you are running a limited set of applications and can confirm that they all can run with the application compatibility engine disabled.
Before moving on I would like to quickly discuss exactly what the impact of these policy changes are:
So the summary is that you should really only do this if you are running a limited set of applications and can confirm that they all can run with the application compatibility engine disabled.
Once the computer has rebooted you should be able to run the Virtual Server installer with no problems:
Once you have done this you will want to install the Virtual Server 2005 R2 SP1 update. This package added official support for running Virtual Server on Windows Vista and Windows Server 2008 – and solves a number of compatibility issues that you will encounter on Windows 7 without this update. Installing the update is a little tricky – as it is shipped as a .MSP file that will fail to install if you just double click on it. Instead what you need to do is to open an administrative command prompt and run to following command:
msiexec /update KB956124.msp
You should now be ready to go with Virtual Server. However, there are some potential issues with this configuration:
Finally – here are the latest links to the Virtual Server bits if you are going to try this out yourself: