How To: Import VMs into Hyper-V

By Alexander Schmidt

We 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:

Hyper-V dialog for importing a virtual machine

Hyper-V dialog for importing a virtual machine

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."

Comments

Comments are closed.