I've got a server set up running Hyper-V Server 2012 R2, so the code samples in this are likely to work against Server 2012 non-R2, but may not work against Server 2008.
All the virtual machines start off at a known clean snapshot, and have a startup script which runs on boot that goes and asks a central database for a testing job to execute. So, for our system to work, we need to accomplish the following:
- Roll back a VM to the last known snapshot / checkpoint
- Power it on
- Connect to the Hyper-V server (this includes authentication)
- List out or otherwise ask the server about it's VM's so that we can rollback the correct one
To achieve thisk I'm using Hyper-V's WMI provider (V2). I don't really know much about WMI, so these code samples are just things I've hacked together. They are NOT production quality and they don't handle errors well or anything else. Please use them as guidance, not for copy/pasting.
Pre-requisites
Using WMI from C# is done by the classes in theSystem.Management
namespace. To access this you'll want using System.Management
at the top of your C# file, as well as a reference to the Connecting and Logging on
To do this we need to create a
ManagementScope
object set to use the appropriate server and using the Hyper-V virtualization v2 namespace, and we also need to supply the username and password of a windows account that has privileges to administer the Hyper-V server. I did it like this:var connectionOptions = new ConnectionOptions(
@"en-US",
@"domain\user",
@"password",
null,
ImpersonationLevel.Impersonate, // I don't know if this is correct, but it worked for me
AuthenticationLevel.Default,
false,
null,
TimeSpan.FromSeconds(5);
var scope = new ManagementScope(new ManagementPath {
Server = "hostnameOrIpAddress",
NamespacePath = @"root\virtualization\v2" }, connectionOptions);
scope.Connect();
Note: Most of the other documentation or sample code I found refers to the
root\virtualization
namespace. This didn't work at all for me in Server 2012 R2, and I had to use dotPeek to decompile the Hyper-V powershell commandlets to figure out to put \v2 on the end. Perhaps the non-v2 one is for Server 2008?Note: If your PC and the target server are on the same domain, and your windows user account has privileges to administer the remote server, You don't need the
ConnectionOptions
object at all. WMI will use windows authentication, and it will magically work:Listing out the VM's and finding the one we want
Virtual machines in Hyper-V get exposed via the Msvm_ComputerSystem WMI class. In order to list them out, we simply ask WMI to give us all the objects of that class in the virtualization namespace. To do this, I'm going to create two helper extension methods, that we'll use from hereon:public static class WmiExtensionMethods
{
public static ManagementObject GetObject(this ManagementScope scope, string serviceName)
{
return GetObjects(scope, serviceName).FirstOrDefault();
}
public static IEnumerable<ManagementObject> GetObjects(this ManagementScope scope, string serviceName)
{
return new ManagementClass(scope, new ManagementPath(serviceName), null)
.GetInstances()
.OfType<ManagementObject>();
}
}
It turns out that the Host PC is also exposed via Msvm_ComputerSystem, so we need to filter out things that are not virtual machines. This helper method will return a list of all the virtual machines. Note: I like extension methods, so I'm using them a lot here:
public static IEnumerable<ManagementObject> GetVirtualMachines(this ManagementScope scope)
{
return scope.GetObjects("Msvm_ComputerSystem").Where(x => "Virtual Machine" == (string)x["Caption"]);
}
You can use it to get a specific virtual machine as follows:
var vm = scope.GetVirtualMachines().First(vm => vm["ElementName"] as string == "myvmname");
Rolling the VM back to it's latest snapshot / checkpoint
In order to roll a Hyper-V VM back to a snapshot, we need to get a reference to the snapshot object itself.There are ways to simply list all the snapshots for each VM, but there is also a special Msvm_MostCurrentSnapshotInBranch class which represents the "latest" snapshot. We can use it to create a helper method as follows:
public static ManagementObject GetLastSnapshot(this ManagementObject virtualMachine)
{
return virtualMachine.GetRelated(
"Msvm_VirtualSystemSettingData",
"Msvm_MostCurrentSnapshotInBranch",
null,
null,
"Dependent",
"Antecedent",
false,
null).OfType<ManagementObject>().FirstOrDefault();
}
And use it like this:
var snapshot = vm.GetLastSnapshot();
Now, to actually roll the VM back, we need to call the ApplySnapshot method on the Msvm_VirtualSystemSnapshotService class.
Note: Logically I thought that the snapshot methods should be on the Virtual Machine object, but Hyper-V puts them all in their own service for some reason. There is also only one global Snapshot service - not a service per VM. I've no idea why they've designed it this way.
We can create a helper method:
public static uint ApplySnapshot(this ManagementScope scope, ManagementObject snapshot)
{
var snapshotService = scope.GetObject("Msvm_VirtualSystemSnapshotService");
var inParameters = snapshotService.GetMethodParameters("ApplySnapshot");
inParameters["Snapshot"] = snapshot.Path.Path;
var outParameters = snapshotService.InvokeMethod("ApplySnapshot", inParameters, null);
return (uint)outParameters["ReturnValue"];
}
And use it like this:
scope.ApplySnapshot(snapshot);
When I execute this with valid parameters, the VM applied it's snapshot, and I always got a return value of 4096. According to the documentation, this indicates that there's a Job in progress to asynchronously track the actual snapshot applying and determine the final success or failure. We could fetch the job out of
outParameters["Job"]
and use it to determine when the apply completes, but I'm not going to worry about that here. Refer to the ApplySnapshot MSDN page for other possible return codes.Note: If the VM is not powered off, you are likely to find the ApplySnapshot call fails. You must power the VM off (and wait for the Power Off job to complete) first.
Powering on the VM to boot it up
Turning on the VM is done by the RequestStateChange method on the Msvm_ComputerSystem class. We already have the Msvm_ComputerSystem object representing the virtual machine we found earlier, so we can create a helper method and enumeration like this:
public enum VmRequestedState : ushort
{
Other = 1,
Running = 2,
Off = 3,
Saved = 6,
Paused = 9,
Starting = 10,
Reset = 11,
Saving = 32773,
Pausing = 32776,
Resuming = 32777,
FastSaved = 32779,
FastSaving = 32780,
}
public static uint RequestStateChange(this ManagementObject virtualMachine, VmRequestedState targetState)
{
var managementService = virtualMachine.Scope.GetObject("Msvm_VirtualSystemManagementService");
var inParameters = managementService.GetMethodParameters("RequestStateChange");
inParameters["RequestedState"] = (object)targetState;
var outParameters = virtualMachine.InvokeMethod("RequestStateChange", inParameters, null);
return (uint)outParameters["ReturnValue"];
}
And use it like this:
vm.RequestStateChange(VmRequestedState.Running);
As for ApplySnapshot, when things work, the return value is usually 4096, indicating there is a Job to asynchronously track the progress of the operation.
Note: Although we invoke the method on the Msvm_ComputerSystem object, we need to get the method parameters by asking the Msvm_VirtualSystemManagementService object (which represents the host server) instead. I've no idea why.
Final fixups
ApplySnapshot will fail if the virtual machine is running. To turn it off, we can simply call
vm.RequestStateChange(VmRequestedState.Off);
and wait a bit for Job to complete.RequestStateChange will fail if the state doesn't make sense. For example, if you try and turn the vm Off when it's already Off, the method will fail. You can check the current state of a Vm by reading it's
EnabledState
property. Valid values for EnabledState
are documented with the Msvm_ComputerSystem class. I created an enum, and used it as follows:public enum VmState : ushort
{
Unknown = 0,
Other,
Running, // Enabled
Off, // Disabled
ShuttingDown,
NotApplicable,
OnButOffline,
InTest,
Deferred,
Quiesce,
Starting
}
if ((VmState)vm["EnabledState"] != VmState.Off)
{
vm.RequestStateChange(VmRequestedState.Off); // needs to be off to apply snapshot
Thread.Sleep(2000); // todo wait for the state change properly
}
You can do many more things by using other methods and classes from the Hyper-V WMI API. Hopefully this gives you a decent starting point.
P.S. The Hyper-V powershell commandlets are all implemented on top of this WMI Api. Using a tool like .NET reflector or dotPeek is an interesting way to see how Microsoft calls the WMI API.