In the first part of this series I walked through using the Hyper-V Manager to create a new virtual machine from a snapshot. If this is something you only need to do occasionally, there is no reason not to use a graphical tool. But if you find yourself doing this often, or would like a way to automate the process for the sake of efficiency, then you’ll need to turn to Windows PowerShell.
Using Hyper-V 3.0 PowerShell Cmdlets
When you install the Hyper-V role on Windows Server 2012 (or in Windows 8) and include the Management tools you should also get the Windows PowerShell module. You should see something like Figure 1 on Windows 8
Or on Windows Server 2012 something like Figure 2.
The PowerShell module includes commands for just about anything you would need to accomplish with Hyper-V. While I’m going to walk through using them interactively, you can just as easily take the commands and turn them into a PowerShell script or function, in essence creating your own Hyper-V PowerShell tools.
In PowerShell 3, modules will be autoloaded the first time you run one of its commands. But I’ll explicitly import the module anyway.
PS C:> Import-Module Hyper-V
I’m going to run the commands locally, but specify a computername so you can see how you could do this for a remote server.
PS C:> $Server = $env:COMPUTERNAME
Creating the snapshot with PowerShell
I’m going to take a snapshot of a virtual machine called Test Rig.
PS C:> $VMName= "Test Rig"
Because I want my imported VM to have a nice name, which is derived from the snapshot name, I’ll define one:
PS C:> $SnapName= "$VMName Clone"
Now to create the snapshot.
PS C:> $snapshot = Checkpoint-VM -Name $VMName -SnapshotName $SnapName -ComputerName $Server –passthru PS C:> $snapshot VMName Name SnapshotType CreationTime ParentSnapshotName ------ ---- ------------ ------------ ------------------ Test Rig Test Rig Clone Standard 2/18/2013 12:41:14 PM
Next, I’ll export the snapshot to disk.
PS C:> $export = Export-VMSnapshot -VMSnapshot $snapshot -Path $ExportPath –Passthru
The path is relative to the Hyper-V server and should be created if it doesn’t already exist. The computername is included as a property in the $snapshot object so PowerShell knows what server to use. From here I have some options.
Creating a new virtual machine
I could create a totally new virtual machine using the exported VHD or VHDX files. I’ll create a new virtual machine without any disk drives. I’ll also “copy” some settings from the snapshot.
PS C:> $clone = New-VM -Name $snapshot.Name -NoVHD -MemoryStartupBytes $snapshot.MemoryStartup -switch "Private Data" -ComputerName $Server
You can obtain the switch name from Get-VMSwitch.
PS C:> Get-VMSwitch -ComputerName $server | Select name Name ---- Private Data Work Network
So now I have a virtual machine without any drives. The tricky part is that I want to add the drives from the export folder, not the paths specified in the snapshot configuration. So I need to do a little parsing to extract the drive name from the snapshot, e.g. “Test Rig.vhdx” without the path. Then I need to construct a new path using the export folder path and this file name. Finally, I’ll construct a hash table of parameters which I’ll eventually pass to the Add-VMHardDiskDrive cmdlet which will ensure I get the drives hooked back up in their original positions.
PS C:> ForEach ($drive in $Snapshot.HardDrives) { >> $VHD = Split-Path -leaf ($drive.path) >> $VHDPath = Join-Path (Join-Path (Join-Path $ExportPath $VMName) "Virtual Hard Disks") $VHD >> $addDriveHash=@{ >> VMName=$Clone.Name >> Path=$VHDPath >> ControllerType=$drive.ControllerType >> ControllerNumber=$drive.ControllerNumber >> ControllerLocation=$drive.ControllerLocation >> Computername=$Server >> } >> Add-VMHardDiskDrive @addDriveHash >> } >>
You can see where eventually this is much easier accomplished in a script. The last step, and it is purely optional, is that I want to configure the new virtual machine with the same memory settings as the original. The Set-VM cmdlet offers a number of settings that I can pass all at once with a hash table.
PS C:> $paramhash=@{ >> MemoryStartupBytes=$snapshot.MemoryStartup >> ProcessorCount=$snapshot.ProcessorCount >> Notes="Cloned $(Get-Date)" >> Name=$($clone.Name) >> Computername=$Server >> } PS C:> if ($snapshot.DynamicMemoryEnabled) { >> $paramhash.Add("DynamicMemory",$snapshot.DynamicMemoryEnabled) >> $paramhash.Add("MemoryMinimumBytes",$snapshot.MemoryMinimum) >> $paramhash.Add("MemoryMaximumBytes",$snapshot.MemoryMaximum) >> } >> PS C:> Set-VM @paramhash
And it is done!
PS C:> Get-VM $clone.name -ComputerName $Server Name State CPUUsage(%) MemoryAssigned(M) Uptime Status ---- ----- ----------- ----------------- ------ ------ Test Rig Clone Off 0 0 00:00:00 Operating normally
Importing the snapshot
The other approach is to import the snapshot. When you import the snapshot it will use the snapshot name. Since I defined it at the time I created the snapshot I should be ok. But if you want to modify the name again, you can use PowerShell to adjust the XML configuration file. There should only be one XML file in the Virtual Machines folder.
PS C:> $VMPath = Join-Path (Join-Path $ExportPath $VMName) "Virtual Machines" PS C:> $VMConfigPath = (dir $vmpath -filter *.xml).FullName PS C:> [xml]$config = Get-Content -Path $VMConfigPath PS C:> $config.configuration.properties.name.'#text'="My Cloned VM" PS C:> $config.Save($VMConfigPath)
Note that if you are doing this remotely, you’ll need to modify this to run via PowerShell Remoting, most likely using Invoke-Command.
Importing is very easy. I can register the import in place.
PS C:> Import-VM -Path $VMConfigPath –Register
This will only work if you are importing to a different server or if the original virtual machine no longer exists. Or you can copy. If you are importing on a different server you can use the default paths. But if you are importing on the same server, and the disks exist from the original virtual machine in the default location, you will need to specify a new path. Because I’m copying the imported virtual machine to the same server, I’m going to use the export path as my disk path.
PS C:> $VHDPath = (Join-Path (Join-Path $ExportPath $VMName) "Virtual Hard Disks")
Finally, the import command.
PS C:> Import-VM -Path $VMConfigPath -Copy -GenerateNewId -VhdDestinationPath $VHDPath -ComputerName $server
Because the original virtual machine still exists I need to generate a new GUID. If you look at help for Import-VM you’ll see that the VhdDestinationPath is marked as obsolete. But it still works and in this case I need it to.
Summary
This may seem like a lot of work, but once I have the PowerShell commands in place it isn’t too difficult to turn them into a tool that automatically converts a snapshot to a virtual machine. If you are new to the PowerShell Hyper-V module, be sure to look at help for all of the commands I’ve shown here. You can also download my demo commands here.