Script to Move a VM to a New Portgroup and Update its Network Identity

Hey everyone - I recently put together a script to make it easier to move a VM around between various networks.  It moves the VM onto the specified Port Group and then uses VMTools to run a PowerShell script in the guest (this is for Windows VMs only, sorry!) to update its IP/DNS/Gateway/etc.  Since my old Gist'ing technique isn't working any more, so it's below as simple text (and it's on my GitHub if you want to grab it that way).

But first, let's break it down a little bit!  The bulk of this script is really straight-forward.  The most interesting bit is using the PowerCLI Invoke-VMScript cmdlet to do the work inside the target VM's OS (since this operation will break network connectivity).  Invoke-VMScript is, on the surface, similar to the core PowerShell cmdlet Invoke-Command... but they way that they operate is completely different under the covers!  The practical difference that made the most impact for me is that Invoke-Command allows access to the parent context's variables (via the $using: syntax), but Invoke-VMScript simply passes a string to the Guest OS, which it then executes.

This difference means that, when you're using Invoke-VMScript, you're effectively writing code that writes code.  Not in any cool AI sense, mind you, but you need to produce a String that has all of your commands in it, that's properly formatted to execute.  In this case, I did that with a Here String, which is the bit that starts @" and ends with "@.  The syntax for a Here String is very important - it starts with that @" combo and everything after that is going to be part of the string until it gets to a new line that starts with the closing "@.  This is an easy way to deal with quotation marks inside of a string!  As you might expect, you can Here String with " or with ' and it behaves just like any string; the strong single quote will protect the contents from interpretation, so that's a really easy way to include special symbols like $ in your strings.  In this case, I couldn't use the strong quote though, as I had parameters in the parent script that contained data that needed to be included in the script that was going to be sent to the VM, so I used the weaker double quotes version.

That meant that I had to escape the special meaning of every $ that I wanted to pass on in the script, and not escape the special meaning of every $ that was a variable that the parent script was passing to the child script.  Fortunately, it's really easy to escape special meanings in a string via the backtick character (`)... but it did require a slightly different way of thinking as I had to remember which system I wanted to interpret each variable.  Similarly, I had to manually reconstruct the DNS Servers array object, as it would otherwise be converted to a string and that syntax would break everything!

Well, once I got that all sorted out, this is what I came up with!

#Change VM Network Configuration
#change-VMNetwork.ps1 -thisVM <VM Object> -portGroup <Destination Port Group Name> -IPAddress <new IP> -subnetCIDR <new subnet as CIDR> -gateway <new Gateway> -DNSServers <array of DNS server IP addresses>
param (
[VMware.VimAutomation.ViCore.Impl.V1.VM.UniversalVirtualMachineImpl]$thisVM,
[string]$portGroup,
[string]$IPAddress,
[string]$subnetCIDR,
[string]$gateway,
[string]$adapterName = "*",
[string[]]$DNSServers
)
$stepResults = "" | select IPResults,PortGroupResults
#Move the VM to the specified Port Group
$stepResults.PortGroupResults = $thisVM | get-networkAdapter | set-networkAdapter -portGroup (get-vdPortGroup $portGroup) -confirm:$false
#Build a script for the VM to execute via vmtools
$script = @"
`$adapter = @()
`$adapter += get-netAdapter | ? {`$_.name -like "$adapterName"}
if (`$adapter.count -eq 1){
new-netIPAddress -interfaceIndex `$adapter.ifIndex -ipAddress $IPAddress -prefixLength $subnetCIDR -defaultGateway $gateway
} else {
return "ERROR: " + `$adapter.count + " Network Adapters found that match the name: " + "$adapterName"
}
set-DNSClientServerAddress -InterfaceIndex `$adapter.ifIndex -serverAddresses @("$($DNSServers -join '","')")
"@
$stepResults.IPResults = ($thisVM | Invoke-VMScript $script).scriptOutput
$stepResults

The script is on GitHub if you want to get it that way (which is probably a good call, as HTML'ified characters can mess things up).  This is intended for educational purposes - use at your own risk!  Always make sure that you fully understand any scripts that you find online before running them!

Comments

Popular posts from this blog

PowerShell Sorting by Multiple Columns

Clone a Standard vSwitch from one ESXi Host to Another

Deleting Orphaned (AKA Zombie) VMDK Files