Save to My DOJO
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.
Not a DOJO Member yet?
Join thousands of other IT pros and receive a weekly roundup email with the latest content & updates!
9 thoughts on "How to Create a New Virtual Machine from a Hyper-V Snapshot Part 2"
You can do the same for NICs as for HDs…
ForEach ($nic in $snapshot.networkadapters) {
$addNicHash=@{
VMName=$clone.name
SwitchName=$nic.SwitchName
Name=$nic.Name
}
Add-VMNetworkAdapter @addNicHash
}
Actually the snapshot method has a problem in that it ‘mangles’ the hard drive name to be avhd/avhdx drive and not the actual drive. Here is my script:
$vm = get-VM -Name $VMName -ComputerName $Server
if (-Not (Test-Path -Path .$snapname) ) {
New-Item -Path .$snapname -Itemtype directory
}
$export = Export-VM -Name $VMName -Path .$snapname –Passthru
New-VM -Name $snapname -NoVHD -MemoryStartupBytes $vm.MemoryStartup -ComputerName $Server
sleep 5
$clone = Get-VM $snapname
Remove-VMNetworkAdapter -VMName $($clone.name) -Name ‘Network Adapter’
sleep 5
ForEach ($nic in $vm.networkadapters) {
$addNicHash=@{
VMName=$($clone.name)
SwitchName=$nic.SwitchName
Name=$nic.Name
Computername=$Server
}
Add-VMNetworkAdapter @addNicHash
}
ForEach ($drive in $vm.HardDrives) {
$VHD = Split-Path -leaf ($drive.path)
$VHDPath = Join-Path (Join-Path (Join-Path .$($clone.name) $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
}
$paramhash=@{
MemoryStartupBytes=$vm.MemoryStartup
ProcessorCount=$vm.ProcessorCount
Notes=”Cloned $(Get-Date)”
Name=$($clone.Name)
Computername=$Server
}
if ($vm.DynamicMemoryEnabled) {
$paramhash.Add(“DynamicMemory”,$vm.DynamicMemoryEnabled)
$paramhash.Add(“MemoryMinimumBytes”,$vm.MemoryMinimum)
$paramhash.Add(“MemoryMaximumBytes”,$vm.MemoryMaximum)
}
Set-VM @paramhash
NOTE: The ‘sleep 5’ is there to ensure that the VM is actually *fully* registered on the HyperV host, without it creation will fail at random because of what appears to be a race condition.
There is a problem that I believe I missed in my original post if the virtual machine uses differencing disks. The exported snapshot merges them into a new parent file but the configuration (which I’m using in the script) points to the differencing disk. Your code is a bit different than mine. I am exporting the snapshot, which is the whole point to the article. You are exporting the virtual machine and cloning that, which is still useful. But you’ve got me working on a revision to my code for exporting a snapshot to a virtual machine which was my goal all along. Thanks for reading and taking the time to comment.
Creating the snapshot with PowerShell____
Hello Jeffery under this I tried to take a snapshot using the given powershell commands but it saying about an error like this:
$snapshot = Checkpoint-VM <<<< -Name $VMName -SnapshotName $SnapName -ComputerName $Server –passthru
CategoryInfo : ObjectNotFound: (Checkpoint-VM:String) [], CommandNotFoundException
FullyQualifiedErrorId : CommandNotFoundException
It seems to me based on the error that you are trying to Checkpoint a VM that doesn’t exist. I don’t know what you’ve put in $VMname. If you put in a valid VM name, you should be able to test it:
Get-VM $VMName
If that fails, check the value of $VMName.
Usually I use Get-VM first to make sure I have the VM and then repeat piping to Checkpoint-VM
get-vm “demo rig” | checkpoint-vm -SnapshotName “Test snapshot”
Hello Jeffry, thankx for the help 🙂 I think the “Checkpoint-VM” is not working the existing powershell version(powershell v1.0) on my server. Could u please mention the powershell version that u used to run this powershell script or any other script that supports powershell v1.0.
Thank You.. 🙂
Everything I was showing was using PowerShell 3.0 and the version of Hyper-V that shipped with Windows Server 2012/Windows 8. Now, you might be able to run the commands from a Windows 8 client with they Hyper-V feature from RSAT installed. What I don’t know is what version of Hyper-V needs to be running on the server. But if you are stuck running PowerShell 1.0 everywhere, you can’t really do anything.
I want to export an specific snapshot, and create a new vm from that snapshot.
The first issue is the exportation results in 3 files, one vhdx, and two avhdx.
the second issue is, i just want to create a New-VM and point to that specific vhdx, but having those other two files (avhdx) i have the feeling that it will not work.
Hi Tshk,
The avhdx disk is created by a Hyper-V differencing disk (see this Altaro blog for details: https://www.altaro.com/hyper-v/how-to-verify-storage-before-using-checkpoints-hyper-v/). A differencing disk is uses when you have a base VM (in a VHDX file) with the differences tracked in the AVHDX file.
When you export the file, it must export both the base and differencing disk. You can go through an additional process of merging them if you desire so that you can work with a single disk file.
You should be able to create a new VM assuming you have all the (A)VHDX files.
Thanks,
Symon Perriman
Altaro Editor