GeekAfterFive

Infrastructure as Code

home

Add vCloud Harddisks with PowerCLI

29 Jun 2012

In vCloud API/GUI Throwdown part 2, I showed you how to modify VM disk sizes in vCloud Director while the VM is still powered on. However, we did *not* cover adding new disks to a VM. Adding new harddisks is a little tricky because of the implementation of the object in the SDK.. The conversion from XML to a .NET object is not an easy thing, especially with the vCloud API's intricate  nodes, elements, attributes, etc. Because of this, you can't create a representation of a harddisk as a .NET object. Trust me, I've tried. That being said, we're working with a REST API, and Powershell LOVES REST APIs. Using the [System.Net.WebRequest] class, we can talk HTTP just like any web browser. That means we can use the 4 HTTP methods used in REST APIs: GET, POST, PUT, DELETE. So, with a mix of PowerCLI and some Powershell .NET magic, I give you... New-CIHarddisk! I made this one an advanced function, so you can just pipe a vCloud VM to it, like so: *Note the harddisk size is in bytes. Here are a couple different ways to run the command:
# Passing VM via pipeline
Get-CIVM "MyVM" | New-CIHarddisk -size 10240

# Or just using a VM name
New-CIHarddisk -vm "MyVM" -size 10240
Here's what it looks like after you add more harddisks to a running VM...I may have had a little too much fun with this one: So enjoy, but be careful!
function New-CIHarddisk
{
 [cmdletbinding()]
 param
 (
 [Parameter (ParameterSetName="pipeline",ValueFromPipeline=$true,Mandatory=$true)]$vm,
 [Parameter (Position=0, Mandatory=$true)]$size
 )

Process
 {
 # Get indexes to create unique IDs for our new disk.
 if ($vm -is [System.String])
 {
 $vm = get-civm $vm
 }
 $vmHardware = $vm.ExtensionData.GetVirtualHardwareSection()
 $scsiController = $vmHardware.item | where {$_.resourceType.value -eq "6"}
 $highaddress = ($vmHardware.item | where {$_.resourcetype.value -eq "17"} | Sort-Object addressonParent)[-1].addressonParent.value
 $addressOnParent = [int]$highaddress + 1
 $highInstance = ($vmHardware.item | where {$_.resourcetype.value -eq "17"} | Sort-Object instanceID)[-1].instanceId.value
 $instanceId = [int]$highInstance + 1
 $highElement = ($vmHardware.item | where {$_.resourcetype.value -eq "17"} | Sort-Object elementName)[-1].elementName.value
 $elementName = [int]$highElement.Split()[-1] + 1

# We need to get the raw XML via vCloud API to add the new disk.
 $hardwareContentType = "application/vnd.vmware.vcloud.virtualHardwareSection+xml"
 $webclient = New-Object system.net.webclient
 $webclient.Headers.Add("x-vcloud-authorization",$vmHardware.Client.SessionKey)
 $webclient.Headers.Add("accept",$hardwareContentType)
 [xml]$hardwareConfXML = $webclient.DownloadString($vmhardware.href)

# Clone the XML node, assign our variables, and insert child into the XML
 $newDisk = ($hardwareConfXML.VirtualHardwareSection.Item | where {$_.resourcetype -eq 17})[-1].clonenode(1)
 $newDisk.AddressOnParent = "$addressOnParent"
 $newDisk.InstanceId = "$instanceId"
 $newDisk.elementName = "Hard Disk $elementName"
 $newdisk.hostresource.capacity = "$size"
 $newDisk.parent = "$($scsiController.instanceID.value)"
 [void]$hardwareConfXML.VirtualHardwareSection.InsertAfter($newDisk,($hardwareConfXML.VirtualHardwareSection.Item | where {$_.resourcetype -eq 17})[-1])

# PUT the data back through vCloud API
 $request = [System.Net.WebRequest]::Create($vmHardware.href);
 $request.Headers.Add("x-vCloud-authorization",$vmHardware.Client.SessionKey)
 $request.Accept="application/vnd.vmware.vcloud.task+xml"
 $request.Method="PUT"
 $request.ContentType = $hardwareContentType
 $postData = $hardwareConfXML.virtualhardwaresection.outerxml
 $xmlString = $postData.replace("
","") # adding strings to xml puts in silly newlines.
 [byte[]]$xmlEnc = [System.Text.Encoding]::UTF8.GetBytes($xmlString)
 $request.ContentLength = $xmlEnc.length
 [System.IO.Stream]$requestStream = $request.GetRequestStream()
 $requestStream.write($xmlEnc, 0, $xmlEnc.Length)
 $requestStream.Close()
 $response = $request.GetResponse()
 $responseStream = $response.getResponseStream()
 $streamReader = new-object System.IO.StreamReader($responseStream)
 $result = $streamReader.ReadtoEnd()
 $streamReader.close()
 $response.close()
 }
}
comments powered by Disqus