Summary: Microsoft Scripting Guy, Ed Wilson, shows how to automatically create Hyper-V virtual machines from VHD by using Windows PowerShell.
Microsoft Scripting Guy, Ed Wilson, is here. As the Scripting Wife and I get ready for our European Scripting Tour, I need to make sure my Windows 8 laptop is in top-working order. Good thing I checked. I have a really small disk drive on my laptop, but luckily, the Scripting Wife and friends stopped at a really cool computer store on the way back from the Windows PowerShell Saturday event in Atlanta and brought me a nice USB 3.0 key fob. USB 3.0 is decent enough to make for expanded storage without much of a performance impact on my laptop. Sweet.
I exported all of my virtual machines to the USB 3.0 disk to permit room on my laptop. There was just one problem—I formatted the key with EXFAT (not out of any personal identification that I know of), and I exported all of the virtual machines to the key fob. When I checked today, dude, nothing worked. I got a bogus file system unable to do something type of error, and none of the virtual machines worked.
A little checking with BING reveals that Hyper-V does not support EXFAT file systems. Bummer. So, I copied the 50-gig worth of virtual machines back to my laptop, formatted the USB fob with NTFS, copied the virtual machines back to the fob, and then nothing. Bummer. So, I decided to simply copy the VHDs to their own folder and use Windows PowerShell to recreate all of the machines.
Using the Hyper-V module to create virtual machines
The first thing I do is import the Hyper-V module, and I create three variables. The first is the path to the VHD files; the second is the path to where I wish to store the completed virtual machines; and lastly, I have a variable that holds the internal virtual switch.
The first two variables are straightforward string assignments, but the last variable, the $InternalSwitch variable, uses the Get-VMSwitch cmdlet to find the internal virtual switch. I only have one internal switch, so this works well for me. Here is the first portion of the code.
Import-Module hyper-V
$VHDPath = "E:\vhd"
$VMPath = "E:\VM"
$InternalSwitch = Get-VMSwitch -SwitchType Internal
Now, I need to use the Get-ChildItem cmdlet to retrieve all of the VHDs in the VHD folder. Because I name my VHDs with the same name as the virtual machine themselves, this technique works really well for me. For example, the DC1 virtual machine has a VHD named DC1.VHDX. This is not mandatory, but it simplifies my script.
After enumerating all of the VHDX files, I use the Foreach-Object cmdlet to create a new virtual machine for each of the disks. I use the BaseName property, for example, DC1, without the VHDX file extension or the path to the folder) as the virtual machine name. I then specify the memory in MB (this is required), the switch name, the path to store the virtual machine, and the path to the VHD file itself. Here is the complete command.
Get-ChildItem $VHDPath |
ForEach-Object {
new-vm -Name $_.BaseName -MemoryStartupBytes 512MB `
-SwitchName $InternalSwitch.name -VHDPath $_.FullName -Path $VMPath}
From here I want to enable dynamic memory for each of the virtual machines. To do this, I use the Get-VM cmdlet to collect all of the virtual machines on the system, and I pipe it to the Set-VMMemory cmdlet. I need to set the DynamicMemoryEnabled parameter to $true, and then specify the minimum and maximum memory. Remember to use the MB at the end of the numbers or an error generates. I also set a buffer value of 20%. Here is the command.
Get-VM | Set-VMMemory -DynamicMemoryEnabled $true `
-MinimumBytes 512MB -MaximumBytes 2048MB -Buffer 20
The complete script is shown here.
CreateVMfromVHD.ps1
Import-Module hyper-V
$VHDPath = "E:\vhd"
$VMPath = "E:\VM"
$InternalSwitch = Get-VMSwitch -SwitchType Internal
Get-ChildItem $VHDPath |
ForEach-Object {
new-vm -Name $_.BaseName -MemoryStartupBytes 512MB `
-SwitchName $InternalSwitch.name -VHDPath $_.FullName -Path $VMPath}
Get-VM | Set-VMMemory -DynamicMemoryEnabled $true `
-MinimumBytes 512MB -MaximumBytes 2048MB -Buffer 20
Enabling VM Integration services
To enable VM Integration services, I use the Enable-VMIntegrationService cmdlet. Unfortunately, each integration service must be specifically enumerated—there is no –all parameter. But, a little creative scripting makes this a piece of cake. I use the Get-VM cmdlet to retrieve all of my virtual machines and store them in a $vm variable. Next, I use the Foreach statement to walk through the collection. This portion of the code is shown here.
$vm = get-vm
Foreach($v in $vm)
{
To retrieve the status of all of the integration services on the virtual machines, I use the Get-VMIntegrationService cmdlet. I pipe the results of this cmdlet to the Foreach-Object cmdlet, and then use the if statement to see if each of the integration services is enabled or not. If the service is not enabled, I use the Enable-VMIntegrationService cmdlet to turn it on. Here is that portion of the code.
Get-VMIntegrationService -VM $v |
Foreach-object {
if(!($_.enabled))
{Enable-VMIntegrationService -Name $_.name -VM $v}} }
The complete script is shown here.
EnableAllVMIntegrationServices.ps1
$vm = get-vm
Foreach($v in $vm)
{
Get-VMIntegrationService -VM $v |
Foreach-object {
if(!($_.enabled))
{Enable-VMIntegrationService -Name $_.name -VM $v}} }
Sweet! Two simple scripts, and I recovered from a potentially time-consuming disaster. As seen in the following image, the virtual machines start up and are working fine.
It could have been a REAL problem next week, while in Europe, when I needed to make a Windows PowerShell presentation.
Join me tomorrow when I will have guest blogger and Microsoft PFE Chris Wu talk about using Windows PowerShell to work with libraries. It is good stuff.
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy