I’ve been working on an NSX-based project recently, and given the task of automating the addition of new DLR Logical Switches and Edge devices.
After a discussion around the alternatives with colleagues, we decided the best way forward (for now) was to do it in PowerShell/PowerCLI, and a quick google found Chris Wahl’s post here
This was a great basis to work from (Thanks Chris!), but lacked a number of things I needed: Creating DHCP Pools, attaching a Logical Switch to an existing Edge device, and some relatively minor amendments to DLR/Edge configurations.
Of these the attachment of a new LS to an existing Edge proved the most intellectually taxing, as Chris’ scripts work with building new raw XML to PUT/POST with the REST API, and I soon discovered that the only way to amend an Edge configuration through the REST API is to pull the existing config as an XML, amend it, and PUT it back.
On top of this, the XML retrieved through the “Invoke-WebRequest” PowerShell cmdlet is of type “System.Xml.XmlElement” whereas to do things like “CreateElement” – which we need to do to add new entries into the configuration – it needs to be of type “System.Xml.XmlDocument”.
After a number of failed workarounds, I found that dumping the XML to a file, and reimporting, gave me the XML in the correct object type – this is a little ugly though, and while the automation is for something that would only be used occasionally, I don’t like ugly hacks in my code!
A little more effort, and I had a suitable alternative – casting the XML to a string object and back to an XML object yielded the result I was looking for.
$edge = Invoke-WebRequest -Uri “$uri/api/4.0/edges/$routerid” -Headers $head -ContentType “application/xml” -ErrorAction:Stop
[xml]$edgexml = $edge.Content
$textxml = $edgexml.innerxml
[xml]$body = $textxml
I could then work with $body as a normal XmlDocument object in PowerShell.
The next issue I had was making the amendments to the XML.
First – make sure the new Logical Switch is not already attached, and then find the first unused interface:
foreach ($vnic in $body.edge.vnics.vnic) {
if ($vnic.name -match $config.newLS.name) {
$attached = "true"
Write-Host -BackgroundColor:Black -ForegroundColor:Red "Warning: $($config.newLS.name) already attached. Skipping."
break
if ($vnic.isConnected -match "false") {
Second – setting values for XML entities that were already in the XML. Easy:
$vnic.name = $config.newLS.name
$vnic.isConnected = "true"
Third – adding a new XML entity that wasn’t already there:
$elem = $body.CreateElement("portgroupId")
$vnic.AppendChild($elem)
$vnic.portgroupId = $switchvwire.get_Item($config.newLS.name)
Finally – adding entities to an empty node “<addressGroups />”. Not so easy! This took some considerable time, including many false starts! In the end I discovered that to “find” the empty node using SelectSingleNode I had to set up a namespace. Then I could find it and remove it (this seemed easier than trying to attach entries to the empty node). Then I could create some raw XML and attach it into the Edge configuration XML using ImportNode and AppendChild.
$ns = New-Object -TypeName System.Xml.XmlNamespaceManager -ArgumentList $body.NameTable
$ns.AddNamespace("ns",$body.DocumentElement.NamespaceURI)
$oldAddressGroups = $vnic.SelectSingleNode("//vnic[index=$inf]/addressGroups")
$vnic.RemoveChild($oldAddressGroups)
[xml] $addr = "<addressGroups>
<addressGroup>
<primaryAddress>$($config.newLS.edgeip)</primaryAddress>
<subnetMask>$($config.newLS.mask)</subnetMask>
</addressGroup>
</addressGroups>"
$vnic.AppendChild($body.ImportNode($addr.addressGroups, $true))
Once that was done all I had to do was send the XML back using Invoke-WebRequest
# Attach new logical switch to existing Edge
try {$r = Invoke-WebRequest -Uri "$uri/api/4.0/edges/$routerid" -Body $body -Method:Put -Headers $head -ContentType "application/xml" -ErrorAction:Stop -TimeoutSec 30} catch {Failure}
if ($r.StatusCode -match "204") {Write-Host -BackgroundColor:Black -ForegroundColor:Green "Status: Successfully attached new Logical Switch to $($config.edge.name)."}
else {
$body
throw "Was not able to add new Logical Switch to existing Edge. API status code was not 204."
}
break}
I’ve no doubt that there are probably some better ways of achieving some of what I’ve done here, but I thought I would post it up in case anyone is looking to do something similar.