Introduction
In the first part of this series, I took you through the basics of writing a WMI client application using C#. Now that you have got a good grip over the technology and how to write code to work with WMI classes - we move on to a little more advanced topic. In this part, I am going to take you through Windows Hyper-V based server virtualization and how you can write custom code to manage Virtual Machines from within your own applications.
Background
Server virtualization is today proliferating most datacenters. Products like VMware and Hyper-V have become common references when people talk about consolidating datacenters and providing a scalable infrastructure that is both easy to deploy as well as manage. While it's quite easy to deploy Virtual Machines and use them, it becomes much more complex when you consider deploying hundreds of them across several physical servers. These technologies generally come with some expensive management applications that can be used to automate the provisioning, deployment and life cycle management of VMs. Most of the vendors provide some sort of API that can be used to build your own custom management client for the Virtual Machine. In this article, I will deal with how a user can build a custom client application that can manage Virtual Machines in a Hyper-V based virtualization platform on Windows.
Microsoft exposes all management functionality through WMI, so it's easy for us to build upon what we learn in the previous article and create the building blocks for a custom Hyper-V management client. Microsoft sells a product called System Center Virtual Machine Manager (SCVMM). This comes at a very high price point for an enterprise. So it may really make sense for cost sensitive organizations to build their own custom manageability solutions for Hyper-V VM. I will only be showing you how to build the primitives to achieve manageability, it’s up to you to use this and further develop a complete manageability solution - you could try extending the ideas presented here.
Hyper-V A Brief Introduction
Hyper-V is the latest and greatest server virtualization product from Microsoft. Before Hyper-V, Windows used to ship with a product called Virtual PC which had a similar functionality. With Hyper-V, Microsoft has entered the mainstream server virtualization market to directly compete with VMware. Hyper-V is shipped free of cost with Windows 2008 (and later) server editions. However depending on the flavor of the server edition, there are limitations on the number of VMs that can actually be hosted on a single server. Standard editions can host 1 VM, Enterprise editions can host a maximum of 4 VMs and data Center editions can host unlimited number of VMs (subject to hardware availability).
In this article, I am not going to go way too deep into Hyper-V technology itself. I am assuming that the readers have a fairly good understanding of Hyper-V technology and need to write scripts or code to automate some of the VM management activities. To start with configuring a VM, one must first create a Hyper-V VM role from the Server Manager. Once this role is enabled, the user can create a Virtual Machine using the VM creation wizard provided. The Wizard usually takes the path to where your VM must reside as well as the location for your VM's VHD files (Virtual Hard Disk). Once you create a VM, you are all set to install an operating system on the VM. Hyper-V supports a large number of guest OSs and you may install any of those operating systems as guests on your Hyper-V VM. A VM can be started, suspended(paused) or stopped. The suspend state is also called save state. Besides these operations on the VM, you can also take snapshots of the VM and also Import or Export a VM (a process that allows you to essentially move VM's between hosts). This article mainly focuses on how you can write code using WMI and C# to quickly change the state of a Virtual Machine say from a stopped\paused state to a started state.
Wiring Virtual Machine Management Routines using C# and WMI
The readers must be fairly familiar with how to connect to a WMI provider and query various attributes of the exposed WMI classes. If you are not familiar with it, then I recommend that you read the article Working With Windows Management Instrumentation - Part 1. In this example, we will connect to the root\virtualization namespace and then query for relevant attributes of the Hyper-V VM class and also execute some methods that will change the state of the VM.
It is suggested that you create a VM named TestVM
on which you will try all management operations discussed in this article without putting any other Virtual Machines at risk. I am also assuming that these operations will be done on a remote system. First setup the ConnectionOptions
object with your remote system's username and password.
ConnectionOptions co = new ConnectionOptions();
co.Username = "administrator";
co.Password = "wmitest";
Now setup the management scope by setting the scope to the virtualization namespace which is root\virtualization:
ManagementScope scope = new ManagementScope(@"\\testnode\root\virtualization", co);
scope.Connect();
The '@
' symbol in the above snippet is used to indicate to the compiler to treat the backslash as valid symbol in the string
rather than an escape character. This saves us the effort of having to double up the backslashes used in the string
. So with this, we now have a new management scope object created. This connects us to that specific namespace within which the virtualization related classes exist. Our attempt here will be to try to change the running state of our test virtual machine (TestVM
). Virtual Machines can be in several states such as starting, started, saving, saved, paused, resuming, etc. I will show you how to change the state of the VM from stopped to enabled. The other operations will just be logical extension of this operation. At this point, you need to understand a few WMI classes that are relevant to managing Virtual Machines.
Msvm_ComputerSystem
is the first class that you need to understand. This class represents the hosting computer system or the Virtual computer system (Virtual Machine). The snippet below shows the attributes defined by this class. This class also defines one method - "RequestStateChange
", which is very useful for managing the various states of the Virtual Machine.
If you notice the definition below, you will not find it like traditional class definition. That is because this is in Managed Object Format (MOF). MOF is used to describe the classes in Common Information Model implementation. For the purpose of this article, you can consider this like a standard class definition that contains description of all the attributes and events exposed by that class. However this is more relevant for people developing WMI providers and so I will not go deeper into this subject in this article. I will keep WMI Provider development for a subsequent part of this series of articles.
[Dynamic,
Provider
("VmmsWmiInstanceAndMethodProvider")]class Msvm_ComputerSystem : CIM_ComputerSystem
{
string Caption;
string Description;
string ElementName;
datetime InstallDate;
uint16 OperationalStatus[];
string StatusDescriptions[];
string Status;
uint16 HealthState = 5;
uint16 EnabledState;
string OtherEnabledState;
uint16 RequestedState;
uint16 EnabledDefault = 2;
datetime TimeOfLastStateChange;
string CreationClassName;
string Name = "GUID";
string PrimaryOwnerName;
string PrimaryOwnerContact;
string Roles[];
string NameFormat;
string OtherIdentifyingInfo[];
string IdentifyingDescriptions[];
uint16 Dedicated[];
string OtherDedicatedDescriptions[];
uint16 ResetCapability = 1;
uint16 PowerManagementCapabilities[];
uint64 OnTimeInMilliseconds;
datetime TimeOfLastConfigurationChange;
uint32 ProcessID;
uint16 AssignedNumaNodeList[];
};
Now let’s get an instance of the "Msvm_ComputerSystem
" class so that we can then execute the method "RequestStateChange
" in order to eventually change the state of our TestVM
. We need to get an object that represents the instance of the Msvm_ComputerSystem
. For this, let's define a helper routine that will always query the namespace for all attributes and methods of this class. The helper routine will eventually return the Management
object for our Virtual Machine which we can use to execute the method for changing the VM state. The snippet below shows the call to get the ManagementObject
representing the instance of the Msvm_ComputerSystem
class.
ManagementObject vm = GetTargetComputer("TestVM", scope);
public static ManagementObject GetTargetComputer
(string vmElementName, ManagementScope scope)
{
string query = string.Format("select * from
Msvm_ComputerSystem Where ElementName = '{0}'", vmElementName);
ManagementObjectSearcher searcher = new
ManagementObjectSearcher(scope, new ObjectQuery(query));
ManagementObjectCollection computers = searcher.Get();
ManagementObject computer = null;
foreach (ManagementObject instance in computers)
{
computer = instance;
break;
}
return computer;
}
As you can see in the above snippet, we have used a WQL (WMI query Language) query to query our class in the relevant namespace and then return the ManagementObject
back to the calling function. This is shown below:
vm = Utility.GetTargetComputer(vmName, scope);
Now that we have an object to the instance of the Msvm_ComputerSystem
that represents our Virtual Machine, we can proceed to actually execute the method "RequestStateChange
". However before you can execute the method, you will need to pass the input parameters to the method. This can be done by using the GetMethodParameters
function which is defined in the ManagementObject
base class.
ManagementBaseObject inParams = vm.GetMethodParameters("RequestStateChange");
Now inParams
represents the object collection that holds the input parameters that we need to pass to the RequestStateChange
method. The snippet below shows how we can load the input parameters and finally execute the method.
inParams["RequestedState"] = Enabled;
ManagementBaseObject outParams =
vm.InvokeMethod("RequestStateChange", inParams, null);
if ((UInt32)outParams["ReturnValue"] == ReturnCode.Completed)
{
Console.WriteLine("{0} state was
changed successfully.", vmName);
}
else
{
Console.WriteLine("Change virtual system
state failed with error{0}", outParams["ReturnValue"]);
}
With this, we have successfully changed the state of our test virtual machine (TestVM
). You can also try changing to other states that are supported by a Virtual Machine.
Now that we were able to successfully change the state of our VM, we should focus on some finer points of handling the state change. In the above snippet, I have requested the state change to "Enabled
" without checking whether it is already in that state. A good practice would be to check the existing state and then execute the RequestStateChange
method if it’s not already in that state. However this can be a simple extension of what I showed in Part 1 of this article. So I encourage the readers to try this out. I can post that snippet later as a comment to this article.
Also with executing state change on a VM, we should note that the call cannot immediately return success, since in a case say where the VM is starting, it cannot yet return a started status back. Hence we should also handle a mechanism to wait on the state change and report the correct status. The snippet below shows how we can handle the above two scenarios so that our application is more reliable and complete. For this, I define a utility function that gets back the status of the Job event returned by the provider - this function is called "JobCompleted()
". I then use JobCompleted
function in my main code in order to track the completion of the result of the InvokeMethod
. More details of the code snippet will follow below:
if ((UInt32)outParams["ReturnValue"]
== ReturnCode.Started)
{
if (Utility.JobCompleted(outParams, scope))
{
Console.WriteLine("{0} state was changed
successfully.", vmName);
}
else
{
Console.WriteLine("Failed to change
virtual system state");
}
}
else if ((UInt32)outParams["ReturnValue"]
== ReturnCode.Completed)
{
Console.WriteLine("{0} state was
changed successfully.", vmName);
}
else
{
Console.WriteLine("Change virtual
system state failed with error {0}",
outParams ["ReturnValue"]);
}
As you might have noticed, in the above snippet I call the JobCompleted
function to wait on the actual completion of the "RequestStateChange()
" method. The JobCompleted
function is described below:
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;
}
Though the above looks like a whole lot of code for tracking progress of a function call, it’s really quite simple. WMI allows for asynchronously tracking the completion of a method. For example in our sample above, it’s really not possible for the system to immediately return a completed status. This is because enabling or starting a VM could take time. Hence in such cases, one can get the path to a "Job
" object, this is used to basically get the state of the method that was executed. In this case, we get the path to the "Job
" object from the outParams
returned when the method was invoked. As always, the path to an object is used to initialize a new ManagementObject
that is then used to constantly poll for the current status. Job.Get()
returns the current status which is then compared with the predefined JobState
constants like Starting
, Running
, etc. If you notice, we put a little sleep within the while
loop so as to avoid excessive polling. We finally check if the Job has failed or succeeded and then appropriately exit and return a Boolean value back to the caller.
With this, I complete this part of the article on how to manage Virtual Machines using WMI and C#. Hopefully you can use the ideas provided here to extend and add more complex functionality within your own applications in order to manage Hyper-V VM's.
References
- MSDN Online API Reference WMI SDK Samples and documentation
History
- 1st February, 2010: Initial post