Introduction
In this article, I will continue from where I had left off in Part 2 of this series. The main objective of this article is to continue to build up over the Hyper-V management WMI class and methods. In this part, I basically focus on how to do some advanced operations such as Export and Import operations. These operations essentially help you to build an implicit "clone" functionality within your application.
Background
In the previous article, my focus was mainly on showing how a basic operation on Hyper-V works. I went through the steps that will help us change the state of the VM, essentially setting up the context for some more operations. In this article, we move on to some more advanced topics with respect to VM management, like Import and Export operations. Hopefully, these concepts will help you build more functionality into your applications. I am not saying that Import and Export operations complete all the operations that are supported by the VM, but essentially, it gives you a head start into writing other routines. This is just the tip of the iceberg, and once you start exploring the root\virtualization
namespace, you will find several new routines to build into your own custom application.
VM management routines and support routines
The best part about using Virtual Machines is the ability to manipulate them like applications or processes running within the context of an Operating System. Extending what I explained in Part 2, I will now move on to explaining how to Import and Export a VM and essentially provide the functionally for cloning Virtual Machines.
Exporting a VM
Exporting a VM allows users to select a specific VM and then export the state and virtual hard-disk (VHD) files along with configuration information of the VM. This makes it possible to transport the exported VM to another system and import it, and essentially create a clone of the parent Virtual Machine.
In the snippet below, I have a generic function that allows you to export a VM. Most of WMI usage semantics are the same as we had discussed in Part 1 and 2. The only difference is that this method uses the VirtualSystemManagementService
class which consists of the bulk of methods that enable efficient VM management.
static void ExportVM(string vmName, string exportDirectory, string Node)
{
String ConnectionString =
@"\\" + Node + @"\" +
@"root\virtualization";
ManagementScope scope =
new ManagementScope(ConnectionString, null);
ManagementObject virtualSystemService =
Utility.GetServiceObject(scope,
"Msvm_VirtualSystemManagementService");
ManagementObject vm = Utility.GetTargetComputer(vmName, scope);
ManagementBaseObject inParams =
virtualSystemService.GetMethodParameters(
"ExportVirtualSystem");
inParams["ComputerSystem"] = vm.Path.Path;
inParams["CopyVmState"] = true;
if (!Directory.Exists(exportDirectory))
{
Directory.CreateDirectory(exportDirectory);
}
inParams["ExportDirectory"] = exportDirectory;
ManagementBaseObject outParams =
virtualSystemService.InvokeMethod(
"ExportVirtualSystem", inParams, null);
if ((UInt32)outParams["ReturnValue"] == ReturnCode.Started)
{
if (Utility.JobCompleted(outParams, scope))
{
Console.WriteLine("VM '{0}' were " +
"exported successfully.",
vm["ElementName"]);
}
else
{
Console.WriteLine("Failed to export VM");
}
}
else if ((UInt32)outParams["ReturnValue"] == ReturnCode.Completed)
{
Console.WriteLine("VM '{0}' were exported successfully.",
vm["ElementName"]);
}
else
{
Console.WriteLine("Export virtual system failed " +
"with error {0}", outParams["ReturnValue"]);
}
inParams.Dispose();
outParams.Dispose();
vm.Dispose();
virtualSystemService.Dispose();
}
In the above snippet, I have defined the ExportVM
function as static
. When you use it in a specific class, you may choose to change it in any way that suits your class design. If you notice, the initial parts of the method is an exact copy of any other piece of WMI routine. As usual, we connect to the scope with our connection object. We connect to the namespace root\virtualization
, which is the standard namespace for all Hyper-V Virtual Machine management routines. The class that we need to initialize and use is the Msvm_VirtualSystemManagementService
class. This provides us with several useful methods that allow us to do several operations with a VM, including exporting and importing (which is what we will focus here).
Once we have the object that represents the class, we then need to get the parameters needed for the ExportVirtualSystem
method. Remember at this point, we have already initialized all the management infrastructure and connected to the specific VM instance (based on the VM name provided). There are three parameters that need to be passed to the VM - one is a CIM reference that represents this virtual machine object, this we can get from the Management object we had created previously; the second parameter is whether one wants to do a full or a partial export (I will discuss that just after this); and finally, the last input parameter is the path to the export directory where we want the exported files to be placed. Now, coming back to the parameter CopyVmState
- this basically tells the system whether a full export or a partial export needs to be done. A full export would mean that all the files associated with the VM - the state files, Virtual Hard disk files, and snapshots - would be exported. A partial export will just create a container file with configuration information for export, and not export the state and VHD files. Since we have provided an export directory, this function first validates if there is a valid directory path, and then creates one if it does not exist. This is then passed to the ExportDirectory
parameter.
Once we InvokeMethod
and execute this method, we get the return value in outParam
. As we have done in Part 2, we will use our utility function to track the completion of the ExportVirtualSystem
method. You can find the code for this utility function in Part 2, or I have provided it at the end of this article.
Importing a VM
The next part is importing an exported VM. This is just the opposite of the export process. The code flow is almost similar, except for small changes to the parameters passed to the ExportVirtualSystem
method. The ImportVirtualSystem
takes only two input parameters - the import directory (which is the same as the directory to which we exported), and a boolean value called GenerateNewID
. All Virtual Machines are associated with a Virtual Machine Identifier which should be unique per physical system. Two VMs on a single physical system can have the same name, but must have different IDs. However, when you import from one machine to another, you can still retain the same Virtual Machine ID. The code snippet is fairly self-explanatory, and along the same lines as the export method.
One small difference between the export and import functions is that in the Export function I assume that it executes locally and hence do not take a user name and password parameter. However in the Import function I pass the Username and Password parameters also. You can add this piece to the function above to make it more complete.
static void ImportVM(string importDirectory, String Node,
string UserName, string Password)
{
String ConnectionString = @"\\" + Node +
@"\" + @"root\virtualization";
ConnectionOptions co = new ConnectionOptions();
co.Username = UserName;
co.Password = Password;
ManagementScope scope = new ManagementScope(ConnectionString, co);
scope.Connect();
ManagementObject virtualSystemService =
Utility.GetServiceObject(scope,
"Msvm_VirtualSystemManagementService");
ManagementBaseObject inParams =
virtualSystemService.GetMethodParameters(
"ImportVirtualSystem");
inParams["GenerateNewID"] = "false";
inParams["ImportDirectory"] = importDirectory;
ManagementBaseObject outParams =
virtualSystemService.InvokeMethod(
"ImportVirtualSystem", inParams, null);
if ((UInt32)outParams["ReturnValue"] == ReturnCode.Started)
{
if (Utility.JobCompleted(outParams, scope))
{
Console.WriteLine("VM were imported successfully.");
}
else
{
Console.WriteLine("Failed to import VM");
}
}
else if ((UInt32)outParams["ReturnValue"] == ReturnCode.Completed)
{
Console.WriteLine("VM were imported successfully.");
}
else
{
Console.WriteLine("Import virtual system failed " +
"with error {0}", outParams["ReturnValue"]);
}
inParams.Dispose();
outParams.Dispose();
virtualSystemService.Dispose();
}
For most parts, the above snippet is similar to the export function. I am setting the GenerateNewID
input parameter to false
- which means that we don't want a new VM ID to be generated. Setting it to true
will allow you to create a new machine ID, in which case the exported VM can be imported on the same system as the VM from which it was exported.
This completes this part where we primarily focused on exporting and importing Virtual Machines (either partially or fully). With this, you can implement a clone functionality in your custom application that manages VMs. Just as a note, some of the methods like ImportVirtualSystem
and ExportVirtualSystem
have been deprecated in Windows 2008 R2 and beyond. They have been replaced with the ImportVirtualSystemEx
and ExportVirtualSystemEx
methods. I encourage the readers to go through the API documentation in MSDN to see what has changed.
In order for the code here to be complete, I am repeating the JobCcmplete
function that we use in all the functions to track the completion of a task. As you are aware, tasks such as import, export, stopping, starting, etc., are not deterministic, as in they do not complete as soon as you execute. In order to track the completion of the task, we have mechanisms available where we wait for the job to be complete. This function just generically enables you to track any such job object returned from executing your WMI method.
public static bool JobCompleted(ManagementBaseObject outParams,
ManagementScope scope)
{
bool jobCompleted = true;
string JobPath = (string)outParams["Job"];
ManagementObject Job = new ManagementObject(scope,
new ManagementPath(JobPath), null);
Job.Get();
while ((UInt16)Job["JobState"] == JobState.Starting
|| (UInt16)Job["JobState"] == JobState.Running)
{
Console.WriteLine("In progress... {0}% completed.",
Job["PercentComplete"]);
System.Threading.Thread.Sleep(1000);
Job.Get();
}
UInt16 jobState = (UInt16)Job["JobState"];
if (jobState != JobState.Completed)
{
UInt16 jobErrorCode = (UInt16)Job["ErrorCode"];
Console.WriteLine("Error Code:{0}", jobErrorCode);
Console.WriteLine("ErrorDescription: {0}",
(string)Job["ErrorDescription"]);
jobCompleted = false;
}
return jobCompleted;
}
References
MSDN Online API Reference and WMI SDK Samples and documentation.