Working with Virtual Machines in PowerShell, SCVMM and C#

Lately I've been experimenting with SCVMM and PowerShell, and I wondered how hard it would be to automate the creation of a Virtual Machine via C#.

Well the good news is that's it not that hard at all!

In this post I'm going to demonstrate how to:

  • Create a new VM
  • Get an existing VM / Get all existing VMs
  • Delete an existing VM

 So let's get started!

Prerequisites

In order to build and run your project, you will need to have the following installed and configured:

You'll also need to have some defined templates in SCVMM, with operating systems installed.

Creating the Application

The first thing you need to do is to create windows forms app (I've called mine "VMManager") and add the required references which can be found in the following folders:

  • C:\Program Files\Reference Assemblies\Microsoft\WindowsPowerShell\v1.0\
  • C:\Program Files\Microsoft System Center Virtual Machine Manager 2007\bin\

When finished, your list of references should look like this:

Next we need to create a basic UI for testing. Mine looks like this:

Basically, it's just a textbox to enter the name of the new VM we want to create and a listview to display the list of VMs we have created.

Next we need to add a new class to the project, which we'll call 'PowerShellHelper.cs'. This class should look like this:

    1: using System;
    2: using System.Collections.Generic;
    3: using System.Linq;
    4: using System.Text;
    5: using System.Management.Automation.Runspaces;
    6:  
    7: namespace VMManager
    8: {
    9:     internal class PowerShellHelper
   10:     {
   11:         public static Runspace InitPowerShell()
   12:         {
   13:             RunspaceConfiguration config = RunspaceConfiguration.Create();
   14:             PSSnapInException ex = null;
   15:  
   16:             config.AddPSSnapIn("VirtualMachineManagerSnapIn", out ex);
   17:             if (ex != null)
   18:                 throw ex;
   19:  
   20:             return RunspaceFactory.CreateRunspace(config);
   21:         }
   22:     }
   23: }

Add another class called 'ExtensionMethods.cs' to contain our (suprise!) Extension Methods. It should look like this:

    1: using System;
    2: using System.Collections.Generic;
    3: using System.Linq;
    4: using System.Text;
    5:  
    6: namespace VMManager
    7: {
    8:     public static class ExtensionMethods
    9:     {
   10:         public static bool EqualsIgnoreCase(this string s, string stringToCompare)
   11:         {
   12:             return s.Equals(stringToCompare, StringComparison.CurrentCultureIgnoreCase);
   13:         }
   14:     }
   15: }

We also need to add another class, this time calling it 'VMHelper.cs'.  Inside this class we're going to add a fair bit of code, so I'm going to break it down into sections.

First off, we need to add a few private methods to do most of the work for us:

1. GetCommandPipe - This returns a PowerShell command pipeline

    1: /// <summary>
    2: /// Creates a command pipeline
    3: /// </summary>
    4: /// <param name="runspace"></param>
    5: /// <returns></returns>
    6: private Pipeline GetCommandPipe(Runspace runspace)
    7: {
    8:     return runspace.CreatePipeline();
    9: }

 2. GetVM - This queries the SCVMM server and returns an instance of a VM

    1: /// <summary>
    2: /// Gets a VM object by name
    3: /// </summary>
    4: /// <param name="vmName"></param>
    5: /// <param name="runspace"></param>
    6: /// <returns></returns>
    7: private VM GetVM(string vmName, Runspace runspace)
    8: {
    9:     //get the VM
   10:     VM vm = null;
   11:     Command getVM = new Command("get-vm");
   12:     getVM.Parameters.Add("VMMServer", this.VMMServer);
   13:     getVM.Parameters.Add("Name", vmName);
   14:  
   15:     using (Pipeline pipeline = GetCommandPipe(runspace))
   16:     {
   17:         pipeline.Commands.Add(getVM);
   18:         Collection<PSObject> result = pipeline.Invoke();
   19:         vm = (Microsoft.SystemCenter.VirtualMachineManager.VM)result[0].BaseObject;
   20:         pipeline.Stop();
   21:     }
   22:  
   23:     if (vm == null)
   24:         throw new NullReferenceException(string.Format("No VM found with name: {0}", vmName));
   25:  
   26:     return vm;
   27: }

 3. GetProcessorType - This queries the SCVMM server and returns a ProcessorType instance

    1: /// <summary>
    2: /// Gets the processor type
    3: /// </summary>
    4: /// <param name="processorTypeName"></param>
    5: /// <param name="runspace"></param>
    6: /// <returns></returns>
    7: private ProcessorType GetProcessorType(string processorTypeName, Runspace runspace)
    8: {
    9:     //get the ProcessorType
   10:     ProcessorType processorType = null;
   11:     Command getProc = new Command("get-processortype");
   12:     getProc.Parameters.Add("VMMServer", this.m_SCVMMServer);
   13:  
   14:     using (Pipeline pProcType = GetCommandPipe(runspace))
   15:     {
   16:         pProcType.Commands.Add(getProc);
   17:  
   18:         Collection<PSObject> results = pProcType.Invoke();
   19:         if (results.Count == 0)
   20:             throw new NullReferenceException("No proc types found!");
   21:  
   22:         foreach (PSObject item in results)
   23:         {
   24:             ProcessorType p = (ProcessorType)item.BaseObject;
   25:             if (p.Name.EqualsIgnoreCase(processorTypeName))
   26:             {
   27:                 processorType = p;
   28:                 break;
   29:             }
   30:         }
   31:  
   32:         pProcType.Stop();
   33:     }
   34:  
   35:     if (processorType == null)
   36:         throw new NullReferenceException(string.Format("No Processor Type found with for following configuration: {0}.", processorTypeName));
   37:  
   38:     return processorType;
   39: }

 4. CreateHardwareProfile - This creates a new HardwareProfile, containing the hardware configuration for the new VM 

    1: /// <summary>
    2: /// Creates a hardware Profile
    3: /// </summary>
    4: /// <param name="processorType"></param>
    5: /// <param name="ramMB"></param>
    6: /// <param name="runspace"></param>
    7: /// <returns></returns>
    8: private HardwareProfile CreateHardwareProfile(ProcessorType processorType, int ramMB, Runspace runspace)
    9: {
   10:     //create the hardware profile
   11:     Command createHardware = new Command("New-HardwareProfile");
   12:     createHardware.Parameters.Add("VMMServer", this.m_SCVMMServer);
   13:     createHardware.Parameters.Add("owner", @"domain\user"); // must be replaced with a valid SCVMM admin account
   14:     createHardware.Parameters.Add("CPUType", processorType);
   15:     createHardware.Parameters.Add("Name", m_HardwareProfileName);
   16:     createHardware.Parameters.Add("MemoryMB", ramMB);
   17:     createHardware.Parameters.Add("Jobgroup", m_JobGroup);
   18:  
   19:     using (Pipeline pCreateHardware = GetCommandPipe(runspace))
   20:     {
   21:         pCreateHardware.Commands.Add(createHardware);
   22:         pCreateHardware.Invoke();
   23:         pCreateHardware.Stop();
   24:     }
   25:  
   26:     //get the hardware profile
   27:     HardwareProfile hardwareProfile = null;
   28:     Command getHW = new Command("Get-HardwareProfile");
   29:     getHW.Parameters.Add("VMMServer", this.m_SCVMMServer);
   30:  
   31:     using (Pipeline hardwarePipeline = GetCommandPipe(runspace))
   32:     {
   33:         hardwarePipeline.Commands.Add(getHW);
   34:         Collection<PSObject> profiles = hardwarePipeline.Invoke();
   35:         foreach (PSObject item in profiles)
   36:         {
   37:             HardwareProfile hw = (HardwareProfile)item.BaseObject;
   38:             if (hw.Name.EqualsIgnoreCase(m_HardwareProfileName))
   39:             {
   40:                 hardwareProfile = hw;
   41:                 break;
   42:             }
   43:         }
   44:  
   45:         hardwarePipeline.Stop();
   46:     }
   47:  
   48:     if (hardwareProfile == null)
   49:         throw new NullReferenceException(string.Format("No Hardware Profile found with name: {0}.", m_HardwareProfileName));
   50:  
   51:     return hardwareProfile;
   52: }

 5. GetTemplate - Gets a Template object from the SCVMM Server based on the name 

    1: /// <summary>
    2: /// Gets an OS template
    3: /// </summary>
    4: /// <param name="OSTemplate"></param>
    5: /// <param name="runspace"></param>
    6: /// <returns></returns>
    7: private Template GetTemplate(string OSTemplate, Runspace runspace)
    8: {
    9:     // Get a template
   10:     Template template = null;
   11:  
   12:     Command getTemplate = new Command("get-template");
   13:     getTemplate.Parameters.Add("VMMServer", this.m_SCVMMServer);
   14:  
   15:     using (Pipeline pTemplate = GetCommandPipe(runspace))
   16:     {
   17:         pTemplate.Commands.Add(getTemplate);
   18:  
   19:         Collection<PSObject> templates = pTemplate.Invoke();
   20:         foreach (PSObject item in templates)
   21:         {
   22:             Template t = (Template)item.BaseObject;
   23:             if (t.Name.EqualsIgnoreCase(OSTemplate))
   24:             {
   25:                 template = t;
   26:                 break;
   27:             }
   28:         }
   29:  
   30:         pTemplate.Stop();
   31:     }
   32:  
   33:     if (template == null)
   34:         throw new NullReferenceException(string.Format("No Template found with name: {0}.", OSTemplate));
   35:  
   36:     return template;
   37: }

 6. AddVirtualHardDisk - Adds a new virtual hard disk to the server - used for creating a D: drive 

    1: /// <summary>
    2: /// Add a virtual hard disk (D: Drive for example)
    3: /// </summary>
    4: /// <param name="diskSizeGB"></param>
    5: /// <param name="runspace"></param>
    6: private void AddVirtualHardDisk(int diskSizeGB, Runspace runspace)
    7: {
    8:     Command newVirtualHardDisk = new Command("New-VirtualHardDisk");
    9:     newVirtualHardDisk.Parameters.Add("JobGroup", m_VirtualHardDiskJobGroup);
   10:     newVirtualHardDisk.Parameters.Add("Bus", "0");
   11:     newVirtualHardDisk.Parameters.Add("Lun", "1");
   12:     newVirtualHardDisk.Parameters.Add("IDE", true);
   13:     newVirtualHardDisk.Parameters.Add("Dynamic", true);
   14:     newVirtualHardDisk.Parameters.Add("VMMServer", this.m_SCVMMServer);
   15:     newVirtualHardDisk.Parameters.Add("Filename", m_VmName + "_disk_1.vhd");
   16:     newVirtualHardDisk.Parameters.Add("Size", (diskSizeGB * 1024));
   17:  
   18:     using (Pipeline pNewHardDisk = GetCommandPipe(runspace))
   19:     {
   20:         pNewHardDisk.Commands.Add(newVirtualHardDisk);
   21:         pNewHardDisk.Invoke();
   22:         pNewHardDisk.Stop();
   23:     }
   24: }

 7. GetBestHost - This method queries the available hosts managed by SCVMM, and return the host deemed most suitable for the new VM to be created on

    1: /// <summary>
    2: /// Gets the best rated host for the requested vm
    3: /// </summary>
    4: /// <param name="hardwareProfile"></param>
    5: /// <param name="totalHardDiskSize"></param>
    6: /// <param name="runspace"></param>
    7: /// <returns></returns>
    8: private Host GetBestHost(HardwareProfile hardwareProfile, long totalHardDiskSize, Runspace runspace)
    9: {
   10:     string hostname = string.Empty;
   11:  
   12:     //get all host groups
   13:     HostGroup hostGroup = null;
   14:     Command getHostGroup = new Command("Get-VMHostGroup");
   15:     getHostGroup.Parameters.Add("VMMServer", this.m_SCVMMServer);
   16:  
   17:     using (Pipeline hostGroupPipeline = GetCommandPipe(runspace))
   18:     {
   19:         hostGroupPipeline.Commands.Add(getHostGroup);
   20:         Collection<PSObject> foundhostGroups = hostGroupPipeline.Invoke();
   21:         hostGroup = (HostGroup)foundhostGroups[0].BaseObject;
   22:  
   23:         hostGroupPipeline.Stop();
   24:     }
   25:  
   26:     if (hostGroup == null)
   27:         throw new NullReferenceException(string.Format("No Host Group could be found on server: {0}.", this.m_SCVMMServer));
   28:  
   29:     //get vm host ratings
   30:     Command getHostRating = new Command("Get-VMHostRating");
   31:     getHostRating.Parameters.Add("VMHostGroup", hostGroup);
   32:     getHostRating.Parameters.Add("HardwareProfile", hardwareProfile);
   33:     getHostRating.Parameters.Add("VMName", m_VmName);
   34:     getHostRating.Parameters.Add("DiskSpaceGB", totalHardDiskSize);
   35:  
   36:     using (Pipeline hostsPipeline = GetCommandPipe(runspace))
   37:     {
   38:         hostsPipeline.Commands.Add(getHostRating);
   39:         Collection<PSObject> foundhosts = hostsPipeline.Invoke();
   40:  
   41:         foreach (PSObject item in foundhosts)
   42:         {
   43:             ClientObject client = (ClientObject)item.BaseObject;
   44:             hostname = client.Name;
   45:             break;
   46:         }
   47:  
   48:         hostsPipeline.Stop();
   49:     }
   50:  
   51:     Host host = null;
   52:     Command getHost = new Command("Get-VMHost");
   53:     getHost.Parameters.Add("VMMServer", this.m_SCVMMServer);
   54:  
   55:     using (Pipeline hostPipeline = GetCommandPipe(runspace))
   56:     {
   57:         hostPipeline.Commands.Add(getHost);
   58:         Collection<PSObject> hosts = hostPipeline.Invoke();
   59:  
   60:         foreach (PSObject item in hosts)
   61:         {
   62:             Host h = (Host)item.BaseObject;
   63:             if (h.Name == hostname)
   64:             {
   65:                 host = h;
   66:                 break;
   67:             }
   68:         }
   69:  
   70:         hostPipeline.Stop();
   71:     }
   72:  
   73:     if (host == null)
   74:         throw new NullReferenceException(string.Format("No Host could be found with name: {0}.", hostname));
   75:  
   76:     return host;
   77: }

 8. CreateVM - 'Just like it says on the can', it creates the VM!

    1: /// <summary>
    2: /// creates the actual vm
    3: /// </summary>
    4: /// <param name="host"></param>
    5: /// <param name="template"></param>
    6: /// <param name="hardwareProfile"></param>
    7: /// <param name="runspace"></param>
    8: private void CreateVM(Host host, Template template, HardwareProfile hardwareProfile, Runspace runspace)
    9: {
   10:     Command newVM = new Command("new-vm");
   11:     newVM.Parameters.Add("Template", template);
   12:     newVM.Parameters.Add("VMHost", host);
   13:     newVM.Parameters.Add("HardwareProfile", hardwareProfile);
   14:     newVM.Parameters.Add("Name", m_VmName);
   15:     newVM.Parameters.Add("Description", string.Format("VM created by '{0}' on {1}", Environment.UserName, DateTime.Now));
   16:     newVM.Parameters.Add("Path", host.VMPaths[0]);
   17:     newVM.Parameters.Add("Owner", @"domain\user");
   18:     newVM.Parameters.Add("StartVM", true);
   19:     newVM.Parameters.Add("JobGroup", m_VirtualHardDiskJobGroup);
   20:  
   21:     using (Pipeline pNewVM = GetCommandPipe(runspace))
   22:     {
   23:         pNewVM.Commands.Add(newVM);
   24:         pNewVM.Invoke();
   25:         pNewVM.Stop();
   26:  
   27:         if (pNewVM.Error.Count > 0)
   28:             throw new Exception("Could not create vm. Error is: " + pNewVM.Error.ReadToEnd().ToString());
   29:     }
   30: }

 

Next we need to add our public methods. These are the methods we're going to expose to the UI

1. CreateVM - This creates the VM and copies all associated files to the host

    1: public void CreateVM(string vmName, string processorType, string OSTemplate, int ramMB, int diskSizeGB)
    2: {
    3:     // Code to actually create a VM
    4:     using (Runspace runspace = PowerShellHelper.InitPowerShell())
    5:     {
    6:         m_VmName = vmName;
    7:         m_JobGroup = Guid.NewGuid().ToString();
    8:         m_HardwareProfileName = "Profile" + Guid.NewGuid().ToString();
    9:         m_VirtualHardDiskJobGroup = Guid.NewGuid().ToString();
   10:  
   11:         try
   12:         {
   13:             runspace.Open();
   14:  
   15:             //get processor type
   16:             ProcessorType processor = GetProcessorType(processorType, runspace);
   17:  
   18:             //create the hardware profile
   19:             HardwareProfile hardwareProfile = CreateHardwareProfile(processor, ramMB, runspace);
   20:  
   21:             //get the template
   22:             Template template = GetTemplate(OSTemplate, runspace);
   23:  
   24:             //add the extra virtual hard disk (D: Drive)
   25:             AddVirtualHardDisk(diskSizeGB, runspace);
   26:  
   27:             //get the total hard disk space requested
   28:             double virtualHardDiskSize = 0;
   29:  
   30:             //loop through each virtual hard disk in the template and add the disk size
   31:             foreach (VirtualHardDisk hardDisk in template.VirtualHardDisks)
   32:                 virtualHardDiskSize += (((double)hardDisk.Size) / 1024 / 1024 / 1024);
   33:  
   34:             //add the extra virtual hard disk (d: drive?)
   35:             virtualHardDiskSize += (diskSizeGB);
   36:             virtualHardDiskSize = Math.Ceiling(virtualHardDiskSize);
   37:  
   38:             //get the best host
   39:             Host host = GetBestHost(hardwareProfile, (long)virtualHardDiskSize, runspace);
   40:  
   41:             //create the vm
   42:             CreateVM(host, template, hardwareProfile, runspace);
   43:         }
   44:         catch (Exception ex)
   45:         {
   46:             //todo: additional logic here?
   47:             throw ex;
   48:         }
   49:         finally
   50:         {
   51:             //close the runspace
   52:             runspace.Close();
   53:         }
   54:     }
   55: }

2. Delete VM - Deletes the VM from the host and removes all associated files.

    1: public void DeleteVM(string vmName)
    2: {
    3:     // Code to actually delete a VM
    4:     using (Runspace runspace = PowerShellHelper.InitPowerShell())
    5:     {
    6:         try
    7:         {
    8:             runspace.Open();
    9:  
   10:             //get the VM
   11:             VM vm = GetVM(vmName, runspace);
   12:  
   13:             if (vm != null)
   14:             {
   15:                 //you cannot delete a vm that is running.
   16:                 //loop whilst the vm is not powered off (stopped)
   17:                 int loopCount = 1; //max loop count = 12 - try for 1 minute only
   18:                 while (vm.Status != VMComputerSystemState.PowerOff && loopCount <= 12)
   19:                 {
   20:                     Command stopVM = new Command("Stop-VM");
   21:                     stopVM.Parameters.Add("VM", vm);
   22:  
   23:                     using (Pipeline pStop = GetCommandPipe(runspace))
   24:                     {
   25:                         pStop.Commands.Add(stopVM);
   26:                         pStop.Invoke();
   27:                         pStop.Stop();
   28:                     }
   29:  
   30:                     //sleep for 5 seconds and check the status again
   31:                     Thread.Sleep(5000);
   32:  
   33:                     ///append the loop count
   34:                     loopCount++;
   35:                 }
   36:  
   37:                 //delete the vm
   38:                 Command deleteVM = new Command("Remove-VM");
   39:                 deleteVM.Parameters.Add("VM", vm);
   40:  
   41:                 using (Pipeline pDelete = GetCommandPipe(runspace))
   42:                 {
   43:                     pDelete.Commands.Add(deleteVM);
   44:                     pDelete.Invoke();
   45:                     pDelete.Stop();
   46:                 }
   47:             }
   48:         }
   49:         catch (Exception ex)
   50:         {
   51:             //todo: additional logic here?
   52:             throw ex;
   53:         }
   54:         finally
   55:         {
   56:             //close the runspace
   57:             runspace.Close();
   58:         }
   59:     }
   60: }

Creating the VM

Now that we've got our classes defined, let's add some code to the application to call the public methods we defined earlier and create our VM!

Modify the click event of your button to include the following code: (NOTE: you will have to modify line 5 to match the Template you have created. The list of processor Types are available in SCVMM - I have merely selected one for the purposes of this article, but you can choose whichever one that best suits your needs)

    1: private void btnCreateVM_Click(object sender, EventArgs e)
    2: {
    3:     string vmName = txtVMName.Text;
    4:     string processorType = "1-processor 1.80 GHz Pentium 4";
    5:     string osTemplate = "Win Server 2003";
    6:     int ramMB = 512;
    7:     int diskSizeGB = 10;
    8:  
    9:     VMHelper helper = new VMHelper("your_SCVMM_server");
   10:     helper.CreateVM(vmName, processorType, osTemplate, ramMB, diskSizeGB);
   11: }

Compile the solution and run the app (making sure you've updated the code to reflect your SCVMM server and domain accounts. If all goes well you should see your VM bring created in SCVMM!

Getting all existing VMs

Modify the 'VMHelper.cs' class to include the following methods:

    1: /// <summary>
    2: /// Gets a list of VMs
    3: /// </summary>
    4: /// <param name="runspace"></param>
    5: /// <returns></returns>
    6: private List<VM> GetVMs(Runspace runspace)
    7: {
    8:     List<VM> vms = new List<VM>();
    9:  
   10:     //get the VM
   11:     VM vm = null;
   12:     Command getVM = new Command("get-vm");
   13:     getVM.Parameters.Add("VMMServer", this.m_SCVMMServer);
   14:  
   15:     using (Pipeline pipeline = GetCommandPipe(runspace))
   16:     {
   17:         pipeline.Commands.Add(getVM);
   18:         Collection<PSObject> result = pipeline.Invoke();
   19:         foreach (PSObject item in result)
   20:         {
   21:             vms.Add((VM)item.BaseObject);
   22:         }
   23:         
   24:         pipeline.Stop();
   25:     }
   26:  
   27:     return vms;
   28: }

 

    1: public List<VM> GetVMs()
    2: {
    3:     using (Runspace runspace = PowerShellHelper.InitPowerShell())
    4:     {
    5:         try
    6:         {
    7:             runspace.Open();
    8:  
    9:             return GetVMs(runspace);
   10:         }
   11:         catch (Exception ex)
   12:         {
   13:             //todo: additional logic here?
   14:             throw ex;
   15:         }
   16:         finally
   17:         {
   18:             //close the runspace
   19:             runspace.Close();
   20:         }
   21:     }
   22:     
   23:     return null;
   24: }

Now we need to modify the UI. Add an additional method called GetVMs:

    1: private void GetVMs()
    2: {
    3:     listView1.Items.Clear();
    4:     listView1.View = View.Details;
    5:  
    6:     listView1.Columns.Add("Name");
    7:     listView1.Columns.Add("Status");
    8:  
    9:     VMHelper helper = new VMHelper("your_SCVMM_server");
   10:     List<VM> vms = helper.GetVMs();
   11:     
   12:     foreach (VM vm in vms)
   13:     {
   14:         ListViewItem lvi = new ListViewItem(vm.Name);
   15:         lvi.SubItems.Add(vm.Status.ToString());
   16:  
   17:         listView1.Items.Add(lvi);
   18:     }
   19: }

Then modify the form_load event to call the GetVMs() method:

    1: private void Form1_Load(object sender, EventArgs e)
    2: {
    3:     GetVMs();
    4: }

Also, add the call to GetVMs to the button click event. This will cause the list to refresh once the VM is created.

Run your app and you should see a list of the existing VMs! :)

Deleting an existing VM

Deleting an existing VM is quite straightforward. We simply need to pass the name of the VM to remove and issue the command.

Let's add another button to the form called 'btnDelete'. Your form should look similar to this:

 Add the following code to the click event of btnDelete: (NOTE: you will need to modify the server name in the VMHelper constructor - line 7)

    1: private void btnDelete_Click(object sender, EventArgs e)
    2: {
    3:     if (listView1.SelectedItems.Count > 0)
    4:     {
    5:         string vmName = listView1.SelectedItems[0].Text;
    6:  
    7:         VMHelper helper = new VMHelper("your_SCVMM_server");
    8:         helper.DeleteVM(vmName);
    9:  
   10:         GetVMs();
   11:     }           
   12: }

 Now run the app. To delete a VM, simply select it in the list view and click the delete button!

Conclusion

By combining PowerShell with SCVMM, automating the creation of Virtual Machines becomes fairly straightforward. Mix in C#, and you have an excellent platform to build upon.

I hope this article has given you some good insights and enables you to get started building VM tools with these great technologies.

Jason