Prepare VM: Create VM programmatically, Hyper-V API, C# version
Create VM (Hyper-V) via code - .NET version Create VM programmatically
[Sergei Meleshchuk. https://blogs.msdn.com/sergeim/\]
From code, you can create many VMs per second. Those will be “bare-metal” VMs of course – you still will need to load image or just install OS on them. My example does not work on remote hosts, but you can modify the code in few minutes to do same on remote boxes.
PowerShell vs. C#
It looks everyone likes PowerShell, and for Hyper-V management in particular.
Sometimes I feel more traditional languages (like C#) give more control/error handling. Another C# benefit is what I think is unsurpassed tracing and debugging facilities of Visual Studio (especially if to forget WinDbg for a second). Sometimes you can learn API by just stepping thru the code.
Credits
Here below is the PS1 à C# port of some code written originally by James O’Neill. I might have changed something a little, no offence taken.
About this document
I briefly 1) outline needed steps, 2) comment code fragments and 3) provide full code. Add System.Management.dll to references, and start the VS elevated (that is, right-click, select ‘run as administrator).
Steps needed
The steps are:
- Get parameters from command line
- Obtain the “MsVM_VirtualSystemManagementService” object, which will do all the work for us. I will call it VSMS for brevity.
- Step 1.
Ask VSMS to create a sort of empty VM definition. I think about this thing as of a template.
- Step 2.
Obtain set of VM settings; fill in those settings
- Step 3.
Modify the newly created ComputerSystem (sort of template) with the settings we just prepared.
Comments on steps
First, some abbreviations I use:
using MO = ManagementObject;
using MBO = ManagementBaseObject;
using MOS = ManagementObjectCollection;
Step 1. Create VM definition.
Trick here is a late-bound call on VSMS; see how managed WMI calls are coded (search for WMI InvokeMethod). The code is:
// Create VM with empty settings
MBO definition = sysMan.InvokeMethod(
Constants.DefineVirtualSystem,
sysMan.GetMethodParameters(Constants.DefineVirtualSystem), // empty set
null);
uint retCode = (uint)definition["returnvalue"];
if (retCode != Constants.ERROR_SUCCESS)
throw new InvalidOperationException("DefineVirtualSystem failed");
Next we get the WMI’s ManagementObject, which represents the instance of MSVM_ComputerSystem WMI class:
string vmPath = definition["DefinedSystem"] as string;
MO computerSystemTemplate = new MO(vmPath);
At this point, we are half-done.
Step 2. Fill in the VM settings
First, get the generated VM name, which is actually a GUID (the display name is called ‘elementname’ in Hyper-V API world). Then we locate the ‘settings’ object:
string vmName = (string)computerSystemTemplate["name"];
// this is GUID; will need to locate settings for this VM
MO settings = GetMsvm_VirtualSystemSettingData(vmName);
Now fill in the settings object:
// Now, set settings of this MSVM_ComputerSystem as we like
settings["elementname"] = displayName;
settings["notes"] = notes;
settings["BIOSGUID"] = new Guid();
settings["BIOSSerialNumber"] = "1234567890";
settings["BIOSNumLock"] = "true";
// settings["..."] = ...;
// ... set whatever you like; see list at
// https://msdn.microsoft.com/en-us/library/cc136944(VS.85).aspx
settings.Put();
Step 3. Finalize.
Use VSMS (management service) again to propagate settings to VM object.
// Now, set the settings which were build above to newly created ComputerSystem
MBO inParams = sysMan.GetMethodParameters(Constants.ModifyVirtualSystem);
string settingsText = settings.GetText(TextFormat.WmiDtd20);
inParams["ComputerSystem"] = computerSystemTemplate;
inParams["SystemSettingData"] = settingsText;
MBO resultToCheck = sysMan.InvokeMethod(
Constants.ModifyVirtualSystem,
inParams,
null);
// Almost done - now apply the settings to newly created ComputerSystem
MO settingsAsSet = (MO)resultToCheck["ModifiedSettingData"];
// Optionally print settingsAsSet here
Log("Created: VM with name '{0}' and GUID name '{1}'", displayName, vmName);
CreateVm
Helper functions
Useful functions (to be coded once) are:
#region Wmi Helpers
private MO GetWmiObject(string classname, string where)
{
MOS resultset = GetWmiObjects(classname, where);
if (resultset.Count != 1)
throw new InvalidOperationException(
string.Format(
"Cannot locate {0} where {1}",
classname,
where));
MOS.ManagementObjectEnumerator en = resultset.GetEnumerator();
en.MoveNext();
MO result = en.Current as MO;
if (result == null)
throw new InvalidOperationException("Failure retrieving " + classname +
return result;
}
private MOS GetWmiObjects(string classname, string where)
{
string query;
ManagementScope scope = new ManagementScope(@"root\virtualization", null);
if (where != null)
{
query = string.Format(
"select * from {0} where {1}",
classname,
where);
}
else
{
query = string.Format(
CultureInfo.InvariantCulture,
"select * from {0}",
classname);
}
ManagementObjectSearcher searcher = new ManagementObjectSearcher(
scope,
new ObjectQuery(query));
ManagementObjectCollection resultset = searcher.Get();
return resultset;
}
#endregion Wmi helpers
Full code
// This is port to C# from a Powershell script written by James O'Neill
using System;
using System.Globalization;
using System.Management;
namespace Hyperv.Misc
{
using MO = ManagementObject;
using MBO = ManagementBaseObject;
using MOS = ManagementObjectCollection;
class MainCreateVm
{
static void Main(string[] args)
{
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(Oops);
new MainCreateVm().CreateVm(args);
}
void CreateVm(string[] args)
{
if (args.Length < 1)
{
Console.ForegroundColor = ConsoleColor.Red;
Log("Usage: createvm <vmname> [<notes>]");
Console.ForegroundColor = ConsoleColor.White;
Log("Example: createvm vm1");
Console.ResetColor();
Environment.Exit((int)Constants.ERROR_INV_ARGUMENTS);
}
string displayName = args[0];
string notes;
if (args.Length > 1)
notes = args[1];
else
notes = "Created " + DateTime.Now;
MO sysMan = GetMsVM_VirtualSystemManagementService();
// Create VM with empty settings
MBO definition = sysMan.InvokeMethod(
Constants.DefineVirtualSystem,
sysMan.GetMethodParameters(Constants.DefineVirtualSystem), // empty set
null);
uint retCode = (uint)definition["returnvalue"];
if (retCode != Constants.ERROR_SUCCESS)
throw new InvalidOperationException("DefineVirtualSystem failed");
// Obtain WMI root\virtualization:ComputerSystem object.
// we will need "Name" of it, which is GUID
string vmPath = definition["DefinedSystem"] as string;
MO computerSystemTemplate = new MO(vmPath);
string vmName = (string)computerSystemTemplate["name"];
// this is GUID; will need to locate settings for this VM
MO settings = GetMsvm_VirtualSystemSettingData(vmName);
// Now, set settings of this MSVM_ComputerSystem as we like
settings["elementname"] = displayName;
settings["notes"] = notes;
settings["BIOSGUID"] = new Guid();
settings["BIOSSerialNumber"] = "1234567890";
settings["BIOSNumLock"] = "true";
// settings["..."] = ...;
// ... set whatever you like; see list at
// https://msdn.microsoft.com/en-us/library/cc136944(VS.85).aspx
settings.Put();
// Now, set the settings which were build above to newly created ComputerSystem
MBO inParams = sysMan.GetMethodParameters(Constants.ModifyVirtualSystem);
string settingsText = settings.GetText(TextFormat.WmiDtd20);
inParams["ComputerSystem"] = computerSystemTemplate;
inParams["SystemSettingData"] = settingsText;
MBO resultToCheck = sysMan.InvokeMethod(
Constants.ModifyVirtualSystem,
inParams,
null);
// Almost done - now apply the settings to newly created ComputerSystem
MO settingsAsSet = (MO)resultToCheck["ModifiedSettingData"];
// Optionally print settingsAsSet here
Log("Created: VM with name '{0}' and GUID name '{1}'", displayName, vmName);
} // CreateVm
private MO GetMsVM_VirtualSystemManagementService()
{
return GetWmiObject("MsVM_VirtualSystemManagementService", null);
}
private MO GetMsvm_VirtualSystemSettingData(string vmName)
{
return GetWmiObject(
"Msvm_VirtualSystemSettingData",
string.Format("systemname='{0}'", vmName));
}
#region Wmi Helpers
private MO GetWmiObject(string classname, string where)
{
MOS resultset = GetWmiObjects(classname, where);
if (resultset.Count != 1)
throw new InvalidOperationException(
string.Format(
"Cannot locate {0} where {1}",
classname,
where));
MOS.ManagementObjectEnumerator en = resultset.GetEnumerator();
en.MoveNext();
MO result = en.Current as MO;
if (result == null)
throw new InvalidOperationException("Failure retrieving " + classname + " where " + where);
return result;
}
private MOS GetWmiObjects(string classname, string where)
{
string query;
ManagementScope scope = new ManagementScope(@"root\virtualization", null);
if (where != null)
{
query = string.Format(
"select * from {0} where {1}",
classname,
where);
}
else
{
query = string.Format(
CultureInfo.InvariantCulture,
"select * from {0}",
classname);
}
ManagementObjectSearcher searcher = new ManagementObjectSearcher(
scope,
new ObjectQuery(query));
ManagementObjectCollection resultset = searcher.Get();
return resultset;
}
#endregion Wmi helpers
private static void Log(string message, params object[] data)
{
Console.WriteLine(message, data);
}
private static void Oops(object sender, UnhandledExceptionEventArgs e)
{
Console.BackgroundColor = ConsoleColor.White;
Console.ForegroundColor = ConsoleColor.Black;
Exception ex = e.ExceptionObject as Exception;
Log(ex.Message);
Console.ResetColor();
Log(ex.ToString());
}
} // class MainCreateVm
class Constants
{
internal const string DefineVirtualSystem = "DefineVirtualSystem";
internal const string ModifyVirtualSystem = "ModifyVirtualSystem";
internal const uint ERROR_SUCCESS = 0;
internal const uint ERROR_INV_ARGUMENTS = 87;
}
}
Comments
Anonymous
June 16, 2009
PingBack from http://fixmycrediteasily.info/story.php?id=6389Anonymous
September 08, 2010
Is it possible to set the number of processors at the time the vm is created or does the processor resource settings need to be modified post create?Anonymous
September 27, 2012
Hey Sergei, I am working on a service which needs to be do tasks like getting all vhds attached to a vm, attaching a vhd and stuff like that. It will not be a one time script, it is like service is listening for requests and based on those requests getting vhds attached,attaching vhd etc. For me it seems like C# is the solution instead of powershell. Can you comment? Also if I use C# isthe functionality given by powershell for hyper-v same as the one for c#?