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;

    }

}