Cross-vCentre vMotions

There has been lot's of "v" going on at my work of late, specifically vMotions. If you're like me you like to do things as tidily as possible and often when you take over a virtual infrastructure there is only so much patch/repair work you can do before you decide that a clean slate is the best way to move on.

However this can be painful as a migration plan. Short of disconnecting and connecting hosts between the vCenter servers you're unlikely to come up with many friendly ways of moving VMs unless they are in the same SSO domain. This is where the function script below comes in and allows you to queue up vMotions using the vCenters "initiate vMotion receive" API call.

Please note that after the script was written some months ago I have since learned of similar efforts from William Lam and VMware Labs cross-vcenter workload migraiton utility 

The main complex portion of the script below is the target VC API call:

$EndpointRequest = [System.Net.Webrequest]::Create(('https://' + $DestinationVC))
$DestinationVCThumbprint = ($EndpointRequest.ServicePoint.Certificate.GetCertHashString()) -Replace '(..(?!$))','$1:'
$DestinationVCService = New-Object VMware.Vim.ServiceLocator
$ServiceCredential = New-Object VMware.Vim.ServiceLocatorNamePassword
$ServiceCredential.username = $AdminCredentials.UserName
$ServiceCredential.password = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($AdminCredentials.Password))
$DestinationVCService.credential = $ServiceCredential
$DestinationVCService.instanceUuid = $DestinationVCConnection.InstanceUuid
$DestinationVCService.sslThumbprint = $DestinationVCThumbprint
$DestinationVCService.url = ('https://' + $DestinationVC)
$RelocateSpecification.service = $DestinationVCService

This segment is effectively a web request to the destination VC to retrieve its certificate thumbprint and then a vimAutomation ServiceLocator object populated with the required information to target the VM relocation specification object defined later in the script.

 

Function time:

Param
   ([Parameter(Mandatory=$True)]$VMName = '',
    [Parameter(Mandatory=$True)]$DatastoreName = '',
    [Parameter(Mandatory=$True)]$Cluster = '',
    $VMHostName = '',
    [Parameter(Mandatory=$True)]$VMNetworkName = '',
    [Parameter(Mandatory=$True)]$FolderName = '',
    $SourceVC = 'oldvcname',
    $DestinationVC = 'newvcname',
    $SwitchType = 'DVS',
    [Parameter(Mandatory=$True)][System.Management.Automation.CredentialAttribute()]$AdminCredentials = ''
   )

#Import those modules you need
Import-Module VMware.VimAutomation.core,VMware.VimAutomation.Vds

#Connect to source and destination VC
$SourceVCConnection = Connect-VIServer -Server $SourceVC -Credential $AdminCredentials
$DestinationVCConnection = Connect-VIServer -Server $DestinationVC -Credential $AdminCredentials

#Source VM View
$VMView = Get-View (Get-VM -Server $SourceVCConnection -Name $VMName) -Property Config.Hardware.Device

#Relocate Specification
$RelocateSpecification = New-Object VMware.Vim.VirtualMachineRelocateSpec
#Choose a host or select from target cluster
If ($VMHostName -ne '')
   {$RelocateSpecification.host = (Get-VMHost -Server $DestinationVCConnection -Name $VMHostName).Id}
Else
   {$RelocateSpecification.host = (Get-VMHost -Server $DestinationVCConnection -Location (Get-Cluster -Name $Cluster) | Where-Object {$_.ConnectionState -eq 'Connected' -and $_.PowerState -eq 'PoweredOn'})[0].Id}
Try 
   {$DestinationResourcePool = (Get-Cluster -Server $DestinationVCConnection -Name $Cluster -ErrorAction Stop).ExtensionData.resourcePool}
Catch
   {$DestinationResourcePool = (Get-ResourcePool -Server $DestinationVCConnection -Name $Cluster).ExtensionData.MoRef}
$RelocateSpecification.pool = $DestinationResourcePool
$RelocateSpecification.datastore = (Get-Datastore -Server $DestinationVCConnection -Name $DatastoreName).Id
$RelocateSpecification.Folder = (Get-Folder -Name $FolderName -Server $DestinationVC -Type VM).Id
    
#Service Endpoint
$EndpointRequest = [System.Net.Webrequest]::Create(('https://' + $DestinationVC))
$DestinationVCThumbprint = ($EndpointRequest.ServicePoint.Certificate.GetCertHashString()) -Replace '(..(?!$))','$1:'
$DestinationVCService = New-Object VMware.Vim.ServiceLocator
$ServiceCredential = New-Object VMware.Vim.ServiceLocatorNamePassword
$ServiceCredential.username = $AdminCredentials.UserName
$ServiceCredential.password = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($AdminCredentials.Password))
$DestinationVCService.credential = $ServiceCredential
$DestinationVCService.instanceUuid = $DestinationVCConnection.InstanceUuid
$DestinationVCService.sslThumbprint = $DestinationVCThumbprint
$DestinationVCService.url = ('https://' + $DestinationVC)
$RelocateSpecification.service = $DestinationVCService
    

$VMNetworkAdapters = @()
$VMDevices = $VMView.Config.Hardware.Device
foreach ($VMDevice in $VMDevices) {if($VMDevice -is [VMware.Vim.VirtualEthernetCard]) {$VMNetworkAdapters += $VMDevice}}

$Count = 0
ForEach ($VMNetworkAdapter in $VMNetworkAdapters)
   {$VMNetworkName = ($VMNetworkName -split ',')[$Count]
    $DeviceSpec = New-Object VMware.Vim.VirtualDeviceConfigSpec
    $DeviceSpec.Operation = 'edit'
    $DeviceSpec.Device = $VMNetworkAdapter
    Switch ($SwitchType)
       {'DVS'
           {$DVSPortGroup = Get-VDPortgroup -Server $DestinationVC -Name $VMNetworkName
            $DVSUUID = (Get-View $DVSPortGroup.ExtensionData.Config.DistributedVirtualSwitch).Uuid
            $DVSPortGroupKey = $DVSPortGroup.ExtensionData.Config.key
            $DeviceSpec.device.Backing = New-Object VMware.Vim.VirtualEthernetCardDistributedVirtualPortBackingInfo
            $DeviceSpec.device.backing.port = New-Object VMware.Vim.DistributedVirtualSwitchPortConnection
            $DeviceSpec.device.backing.port.switchUuid = $DVSUUID
            $DeviceSpec.device.backing.port.portgroupKey = $DVSPortGroupKey
           }
        Default
           {$DeviceSpec.device.backing = New-Object VMware.Vim.VirtualEthernetCardNetworkBackingInfo
            $DeviceSpec.device.backing.deviceName = $VMNetworkName
           }
       }
    $RelocateSpecification.DeviceChange += $DeviceSpec
    $Count++
   }

Write-Host ('Migrating ' + $VMName + ' from ' + $SourceVCConnection.Name + '\' + (Get-Datastore -Id (Get-VM -Server $SourceVCConnection -Name $VMName).DatastoreIdList).Name  + ' to ' + $DestinationVCConnection.Name)
$VMView.RelocateVM_Task($RelocateSpecification,'defaultPriority') | Out-Null


#Disconnect from source and destination VC
Disconnect-VIServer -Server $SourceVCConnection -Confirm:$False
Disconnect-VIServer -Server $DestinationVCConnection -Confirm:$False

Running this spits out a very simple migrating message followed by a "relocate virtual machine" task on the source host and, after that task hits 40%, an "initiate vMotion receive operation" task on the destination host. The source datastore is in there for good measure as I used one large datastore that was not the final destination in my migration.

Hope this helps those of you starting afresh.

o7

Add comment

Loading