How To: Import VMs into Hyper-V
Windows Research Kernel @ HPIWe use Hyper-V to run some of our WRK-related experiments. In order to keep management simple, I created a virtual machine that contains all the setup for those experiments. After creating that image, I used Hyper-V (running on Windows Server 2008 R2) to export this machine to have the template image ready at hand. Once this was done, I thought I would be able to import that image multilple times so that would have enough instances for running our experiment (30 in our case). However, when I imported the second instance, Hyper-V failed to import the template since the VHD file already existed. In this post I will explain how you can easily import multiple virtual machines from one single template image using PowerShell.
The only GUI Hyper-V provides for importing a VM from a template is as follows:
The GUI dialog does not provide any options for specifying a different import location. If you import a virtual machine through that GUI, Hyper-V will attempt to import the VHD file into its base directory (usually \Users\Public\Public Documents\Hyper-V\Virtual hard disks). This behavior leads to the file collision mentioned above. Looking for another alternative, I stumbled upon the PowerShell Management Library for Hyper-V (PsHyperV), a cool and rich PowerShell scripting library for Hyper-V.
PsHyperV provides the
Import-VM
commandlet, which allows you to import a virtual
machine programmatically via PowerShell. Unfortunately, it only
provides the same amount of paramters as the GUI version and thus
fails to import a second instance of the same virtual machine, since
it does not allow you to specify a location to which the VM should
be imported to. However, the source code of the commandlet was a
good starting point for learning about WMI management classes for
Hyper-V.
Digging into the documentation of Hyper-V's WMI classes, I found the following method, which is available since Server 2008 R2:
uint32 ImportVirtualSystemEx( [in] string ImportDirectory, [in] string ImportSettingData, [out] CIM_ConcreteJob REF Job );
This method allows you to import a virtual computer system from a
specified folder (ImportDirectory
) and to specify
import settings (ImportSettingData
). The
ImportSettingData
parameter, prior to serialization,
has the following type:
class Msvm_VirtualSystemImportSettingData : CIM_SettingData { string Description; string Caption; string InstanceID; string ElementName; boolean GenerateNewId; boolean CreateCopy; string Name; string SourceVmDataRoot; string SourceSnapshotDataRoot; string SourceVhdDataRoot; string TargetVmDataRoot; string TargetSnapshotDataRoot; string TargetVhdDataRoot; string SecurityScope; string CurrentResourcePaths[]; string SourceResourcePaths[]; string TargetResourcePaths[]; string SourceNetworkConnections[]; string TargetNetworkConnections[]; };
The properties TargetVmDataRoot
,
TargetSnapshotDataRoot
, and
TargetVhdDataRoot
allow you to specify a directory for
where the respective information is stored. So, back to my original
problem: I ended up writing a small PowerShell script that creates a
directory for each instance that should be imported and then points
the Target*
properties to that location. Finally the
script calls the ImportVirtualSystemEx
method to import
the VM. It takes quite a while for importing one single instance,
but at the end all 30 instances were imported successfully. Here is
my script:
$exportDir = "C:\Exports\InstantLab Test Instance Debugger" $cloneRoot = "C:\Users\Public\Documents\Hyper-V\Clones" $i = 0 # Load management class $managementService = Get-WmiObject -Namespace "root\virtualization" -Class "Msvm_VirtualSystemManagementService" $importSettings = $managementService.GetVirtualSystemImportSettingData($exportDir) $importSettings = $importSettings.ImportSettingData # set global import settings $importSettings.GenerateNewId = $true $importSettings.CreateCopy = $true # now do the following in a loop per instance while ($i -le 30) { echo "Creating debugger instance $i" $importDir = "$cloneRoot\Debugger$i" echo " Making import directory: $importDir" mkdir $importDir echo " Updating import settings" $importSettings.Name = "InstantLab Debugger $i" $importSettings.TargetVhdDataRoot = $importDir $importSettings.TargetSnapshotDataRoot = $importDir $importSettings.TargetVmDataRoot = $importDir $result = $managementService.ImportVirtualSystemEx($exportDir, $importSettings.GetText(1)) # wait for the job to be finished Test-WMIJob $result.Job -wait -StatusOnly -Description " Importing VM" $i += 1 echo "Done." } echo "Done."