Migrating ESXi Hosts with a vDS between vCenter servers using Powershell

Most vSphere admins are probably in the process of moving over to to vSphere 6.x or planning for it as end of general support for vSphere 5.5 is appraoching later this year.
I wrote about how to go about migrating from the Windows vCenter 5.5 to the VCSA 6.5 in an earlier post here, however that is not always an option. Some may choose to go with a greenfield deployment, which leaves you having to move your hosts over to the new environment.
There are a couple of different scenarios here, but in this post I am going to focus on what I believe to be a common one. That being, we have an existing vSphere 5.5 environment, utilizing Distributed Switches and a greenfield vSphere 6.5 environment which we want to move our hosts and VMs over to.

Now, there are a few posts out there that do this quite easily, but as they point out its not an officially supported method by VMware, KB here. The supported steps are outlined but as you can quickly see, that is a lot of clicking when you have a moderate to large number of hosts. Having to do this for a customer with a few hundred hosts, I wasnt even going to entertain the idea of doing it all manually, so I came up with a couple of scripts ( with the help of some code that was published by vmware community members).
I split up the scripts into two parts:

Part 1 – Migrating the host and VMs from the vDS to a newly created vSS
Part 2 – Moving the host between vCenter servers and migrating it to a vDS

I have made the assumptions that the vSphere 6.5 environment has already had the target constructs already created (i.e Clusters, vDS, Folders, etc). Also that the management kernel port already resides on a seperate standard switch.

I have not included any automation for host profiles and autodeploy, but I plan to put out another post which covers the environments which utilize those features.

Enough rambling, here are the scripts:
Part 1 here
Part 2 here

Lets take a look and breakdown the code:

Part 1:

First we have to define a couple of variables which outline what we want to target:

#The vCenter server to connect to
$srcvCenter = "VC01.karpslab.local"
#The target Host
$vmHost = "ESXi-01.karpslab.local"
#Name of the first physical adapter we want to target
$pNIC1 = "vmnic2"
#Name of the second physical adapter we want to target
$pNIC2 = "vmnic3"
#Name of the distributed switch we want to move away from
$vDS = "vDS-Cluster-01"
$vSS = "MigSwitch" #Name of the temporary vSwitch that we create
#name of the VMK1 port group
$vmk1pg = "vDS-Cluster-01-VL10-NFS"
#name of the VMK2 port group
$vmk2pg = "vDS-Cluster-01-VL20-vMotion1"
#name of the VMK3 port group
$vmk3pg = "vDS-Cluster-01-VL20-vMotion2"

It is really important to note that the VMKernel port number lines up with the correct port group here, otherwise you will run into problems. If you have more VMkernel ports, you can quite easily add extra variables here and in the section where we migrate them over later in the code.

Next the script connects us to the defined vCenter server, after which we grab the details of our vDS and its port groups and place them into variables.

$vDSobj = Get-VDSwitch -Name $vDS
$vDSpg = $vdsObj  Get-VDPortgroup

Now we create the new Standard vSwitch, using the MTU value we got form our vDS:

$vSSObj = New-VirtualSwitch -VMHost $vmHost -Name $vss -mtu $vDSObj.mtu

Here we step through each port group in the $vDSpg variable which contains the port groups that exist on the vDS. Firstly, we get the vlan id of the port group, then check if it is the uplink port group which is created with a vDS, if it is the uplink port group, we skip that and move on. Otherwise if it is a regular port group, we then create the respective port group on the vSS we created earlier.

foreach($pg in $vDSpg){
#Get port group VLAN ID
$pgVLAN = $pg.Extensiondata.Config.DefaultPortConfig.Vlan.VlanID
#Check if it is the uplink pg
If ($pg.IsUplink -eq "True"){Write-Host "Skipping Uplink PortGroup" -ForegroundColor yellow}
#If it is not the uplink pg, create it on the vSS
New-VirtualPortGroup -Name $pg.name -VirtualSwitch $vSSObj -VLanId $pgVLAN  Out-null
Write-Host "Created PortGroup $pg with Vlan ID $pgVLAN" -ForegroundColor Cyan

Now that we have the vSS configured the way we want it, we can begin to migrate the host from the vDS. Firstly we need to move one of the interfaces off the vDS and onto the vSS. In this scenario this will be “vmnic2”.

$pNIC1Obj = Get-VMHostNetworkAdapter -VMhost $vmhost -Physical -name $pNIC1 #Gets the NIC object
$pNIC1Obj  Remove-VDSwitchPhysicalNetworkAdapter -Confirm:$false #Removes adapter from vDS
Add-VirtualSwitchPhysicalNetworkAdapter -VirtualSwitch $vSSObj -VMHostPhysicalNic $pnic1Obj -Confirm:$false #adds adapter to vSS

Within the scripts I have placed blocks of code that asks the user to continue to the next step. This has been put in as there are parts where you might want to go and validate that things are workings as expected before proceeding. For example, if one of your interfaces has the incorrect VLAN configuration on the underlying switch port, you wont have much joy when you cut over a VMs network or an NFS kernel port. It does slow thing down a bit, but if you’re working on a production environment I think its a good practice. No-one wants to be the guy who brings down a business critical application because we messed up a vlan configuration or mtu size.

The while loop validates that the entry is a yes or a no. If no, the script exits.

$continue = Read-Host "Would you like to continue to migrating VM networks (Y/N)?"
while("Y","N" -notcontains $continue)
	$continue = Read-Host "Please enter Y or N"
if ($continue -eq "N")
Write-Host "Exiting Script" -ForegroundColor Red
}elseif ($continue -eq "Y")
Write-Host "Continuing to VM Network Migration" -ForegroundColor Green

So now our host should have a new vSS and one interface attached. Next we need to target the virtual machines and ensure they start communicating over the vSS and the uplink we associated with it. Before we do that, its a good idea to set the cluster DRS mode to manual. This is done with the below snippet:

#Set Cluster DRS Setting to Manual
$VMhostObj = Get-VMHost $VMhost
$ClusterObj = Get-Cluster -Name $VMhostObj.Parent
Write-Host "Setting DRS for Cluster $clusterObj to Manual" -ForegroundColor Cyan
Set-Cluster -Cluster $clusterObj -DrsAutomationLevel Manual -Confirm:$false Out-Null

Let’s move on to re-configuring the VMs to the vSS ports:

We need to get a list of the virtual machines that are currently running on the host and put them in a variable.

$VMlist = $VMhostObj | get-VM

We now loop through each vm in the list and do two things:

  1. Get the details of the current network adapter attached to the VM
  2. Set the VMs network adapter to the port group on the vSS. We do this by getting the portgroup object from the standard switch, which is the same name as the port group of the vDS.

The below code will prompt you when modifying each VM, but this can be avoided by adding -Confirm:$false at the end of the command. I suggest using a tool such as pinginfoview to validate that the VM networks cutover properly, that way you can get ontop of it quickly, should there be any issues. I have not tested this on virtual machines with mutliple interfaces (yet).

foreach ($VM in $VMlist){
$VMnic = Get-NetworkAdapter $vm
$VMnic | Set-NetworkAdapter -PortGroup (Get-VirtualPortGroup -VMhost  $VMHost -Standard -Name $vmnic.NetworkName)
Write-Host "Migrated $VM network to $vSS on $VMhost" -ForegroundColor Cyan

The next step is to cutover the VMkernel ports, along with the last adapter that remains on the vDS. I couldn’t find a way to do one at a time as you do when moving the other direction (vSS to vDS), but I did come across this post from @Lamw here which cuts them all over along with the adapter. I have modified it slightly for this scenario. This is also the section where you will need to add or remove vmkernel interfaces to suit your environment:

#Get pNic and swtich objects
$pNIC2Obj = Get-VMHostNetworkAdapter -VMhost $vmhost -Physical -name $pNIC2
$vSSObj = Get-VirtualSwitch -VMhost $VMhost -Name $vSS

#Get VMK ports to migrate
$vmk1 = Get-VMHostNetworkAdapter -VMhost $vmhost -VMKernel -name vmk1
$vmk2 = Get-VMHostNetworkAdapter -VMhost $vmhost -VMKernel -name vmk2
$vmk3 = Get-VMHostNetworkAdapter -VMhost $vmhost -VMKernel -name vmk3

#get VMK port groups to migrate to
$vmk1pgObj = Get-virtualportgroup -virtualswitch $vssObj -name $vmk1pg
$vmk2pgObj = Get-virtualportgroup -virtualswitch $vssObj -name $vmk2pg
$vmk3pgObj = Get-virtualportgroup -virtualswitch $vssObj -name $vmk3pg

#create array of VMKports and VMKPortGroups
$vmkArray =@($vmk1,$vmk2,$vmk3)
$vmkpgArray =@($vmk1pgObj,$vmk2pgObj,$vmk3pgObj)

#Move physical nic and VMK ports from vDS to vSS
Write-Host "Migrating $vmhost from $vds to $vss" -ForegroundColor Cyan
Add-VirtualSwitchPhysicalNetworkAdapter -VirtualSwitch $vssObj -VMHostPhysicalNic $pNIC2Obj -VMHostVirtualNic $vmkarray -VirtualNicPortgroup $vmkpgarray  -Confirm:$false

Finally, we remove the host from the vDS:

$vdsObj | Remove-VDSwitchVMHost -VMHost $vmhost -Confirm:$false 

If all goes to plan, we will now have the target host entirely on the vSS and all the VMs are still running without anyone noticing.

Now lets breakdown the script that does Part 2:

Lines 30 – 143 have two functions defined which have been writted by http://kunaludapi.blogspot.com.au. The first function Get-VMFolderPath, does exactly that, this is so we know which folder the VM was in prior to moving it. The Move-VMtoFolderPath funciton takes the output from the previous function and moved the specified list of VMs to the appropriate folder. As mentioned earlier, it is assumed that the folder structure is already created. There are a number of scripts out there that can help you export / import folder structures in vCenter.

The next part of the script defines the same variables as the previous one, although we have added the $dstvCenter and $dstCluster variables which define where we want to move the host.

Before we move the host, we need to get the VM folder list with this snippet of code:

$VMhostObj = Get-VMhost $VMhost
$VMlist = $VMhostObj | get-VM
$VMFolders = $VMlist | Get-VMFolderPath

Now, to remove the host from the current vCenter:

First we need to set it to a disconnected state, then remove it from vCenter

Set-VMhost $vmhost -State "Disconnected" | out-null
Remove-VMhost $vmhost -server $srcvCenter -Confirm:$false

Next we want to disconnect from the source vCenter and connect to the destination vCenter:

#disconnect from source vCenter
disconnect-viserver $srcvCenter -confirm:$false
#Connect to $dst vCenter
$dstVCCreds = Get-Credential -Message "Enter credentials for $dstvCenter"
if (Connect-VIServer -Server $dstvCenter -Credential $dstVCCreds -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -force) {
  Write-Host "Connected to $dstvCenter" -ForegroundColor green
else {
  Write-Host "Could not connect to vCenter server $dstvCenter" -ForegroundColor Red
  Write-host "Error:" -ForegroundColor red $Error[0]

Now that we’re connected we can add the host to the new vCenter, as we need a root password to do so, we get the users input here. This can be easily stored in a variable. See my previous post on how, otherwise there are plently of blogs out there explaining it. We also need to specify our target cluster that the host is going to be a member of.

$ESXcreds = Get-Credential -Username root -Message "Enter the root password for $vmhost"
$location = Get-Cluster $dstCluster
Add-VMhost -Server $dstvCenter -Name $vmHost -Location $location -Credential $ESXcreds | out-null

Once this host is added, all the virtual machines should be added to the inventory along with it and land in the Discovered Virtual Machines folder, we will sort this out further in the script. The next thing we need to do is to step the host into the vDS so we can migrate our VMKernel ports and VM networks over. Once again, it is assumed that you’ve already imported or re-created your vDS in the new vCenter.

#add host to vDS
$vdsObj = Get-VDSwitch $vDS
$vdsObj | Add-VDSwitchVMHost -VMHost $vmhost | Out-Null
#Migrate first adapter to vDS
$pNIC1Obj = Get-VMHostNetworkAdapter -VMhost $vmhost -Physical -name $pNIC1
$vdsObj | Add-VDSwitchPhysicalNetworkAdapter -VMHostNetworkAdapter $pNIC1Obj -Confirm:$false

As there is better support for the vDS when it comes to moing VMkernel ports than the vSS in powershell, we can add one VMKernel port at a time. We get the port group object from the vDS and the VMkernel port we wish to move and then migrate it using the Set-VMhostNetworkAdapter cmdlet.

$vmk1pgObj = Get-VDPortgroup -name $vmk1pg -VDSwitch $vdsObj
$vmk1 = Get-VMHostNetworkAdapter -Name vmk1 -VMHost $vmhost
Set-VMHostNetworkAdapter -PortGroup $vmk1pgObj -VirtualNic $vmk1 -confirm:$false | Out-Null

We repeat this for each VMKernel port and prompt to continue between each one, so that we can validate that everytihng is ok. Now, this could be put into a for loop to go through each adapter, but it was easier to do it this way (not necessarily better).

Once all the VMKernel ports have moved over, we can now move the VM networking back to the vDS. This is done muc the same way as we did before, although we are using the -Distributed switch in the Get-VirtualPortGroup cmdlet.

$VMhostObj = Get-VMhost $VMhost
$VMlist = $VMhostObj | get-VM
Write-Host "Now migrating VM networks from $vss to $vds" -ForegroundColor Yellow
foreach ($VM in $VMlist){
$VMnic = Get-NetworkAdapter $vm
$VMnic | Set-NetworkAdapter -PortGroup (Get-VirtualPortGroup -VMhost  $VMHost -Distributed -Name
$vmnic.NetworkName) -Confirm:$false | out-null
Write-Host "Migrated $VM network to vSS on $VMhost" -ForegroundColor Cyan

Note that we had to get the $VMlist again as the vm object ids are now difference since we have changed vCenter servers.

Next we swing the last adapter over to the vDS:

$pNIC2Obj = Get-VMHostNetworkAdapter -VMhost $vmhost -Physical -name $pNIC2
$vdsObj | Add-VDSwitchPhysicalNetworkAdapter -VMHostNetworkAdapter $pNIC2Obj -Confirm:$false

If all went well, we can move onto putting the virtual machines into their correct folder location, thanks to the functions we touched on earlier, it’s an easy one liner:

$VMfolders | Move-VMtoFolderPath

Now all we need to do is cleanup after ourselves and delete the vSS off the host:

$vssObj = Get-VirtualSwitch -VMhost $VMhost -Name $vss
Remove-VirtualSwitch $vssObj -confirm:$False

This is probably not the most efficient code to do these steps, it can easily be modified to loop through each host in a cluster, or even a vCenter server, however that could also spell disaster. Use it at your own risk with your own discretion. It definitely beats doing it manually. Oh yeah, don’t forget to let your backup team and resolver groups know that the VMs have moved to a new vCenter.

I hope this helps you with your migrations and has been a bit informative, as opposed to just giving you a script to execute.