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:
- Windows PowerShell
- System Center Virtual Machine Manager (SCVMM)
- Visual Studio 2008
- .NET Framework 3.5 (included with VS2008)
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
Comments
Anonymous
April 16, 2008
This was really good info. Keep it up!Anonymous
May 01, 2008
hey any idea how to do the same for scvmm 2008Anonymous
March 10, 2009
Is there a way to use WMI to get similar information from SCVMM server directly (lets say enumerate all virtual machines) rather than go to each hyper-v server? There is no rootvirtualization WMI namespace on scvmm...Anonymous
April 02, 2009
what is the name space for the microsoft.systemcenter.virtualmachinemanager.dll?Anonymous
February 14, 2010
is there a way to download this projectAnonymous
February 25, 2010
Good, if support the project download?Anonymous
April 27, 2010
I done all the necessary setup which required for running this example script. When i tried to Create the new VM through Script, i am getting the Exception stating that "Could not create vm. Error is: System.Collections.ObjectModel.Collection`1[System.Object]"}. How can i solve this error? Thanks Sudhir KumarAnonymous
January 14, 2011
The comment has been removedAnonymous
May 31, 2012
any full source code in a sample ? thxAnonymous
June 10, 2012
Hi, is there any place where i can download a full source code sample?Anonymous
July 18, 2012
neno.rojcevic 15 Feb 2010 3:48 AM # is there a way to download this project Yeah We may use this method in SCVMM 2010 or 2012Anonymous
February 28, 2014
Any projects available for SCVMM 2012 R2?Anonymous
April 23, 2014
The comment has been removed