From c8d4ef2b2445f4ae49183dfc96857b07314483b0 Mon Sep 17 00:00:00 2001 From: thomsonac Date: Fri, 21 Oct 2016 20:17:31 -0400 Subject: [PATCH 1/3] Update xMove-VM.ps1 Added several new features -Unless specified, the move script will use the datastores that the VM currently resides on. This includes disks that reside on different datastores -Unless specified, the move script will use the same VDS switch name and portgroup name. This works for multiple NICs -Script will now read in vCenter tags (if any) on the source VC, move the VM, then re-apply the tags on the destination. **Note that the tags and categories will need to be created on the destination VC first Added logic to find the host with the least memory usage inside a cluster. Also added logic to prevent memory overcommitment during moves. Added logic to prevent the moves of VM Hardware Version 4. As specificed in the script, there appears to be a bug that will allow you to move a v4 VM and it will continue to work. However any change (powercycle, vmotion, etc) will cause the VM's NICs to drop their assigned portgroup. The only fix (post move that is) appears to be to power it off so the portgroup disappears then re-set the portgroup. I've only tested the multi-NIC move with a VDS switch but I wrote the logic for a VSS multi-NIC. Might need troubleshooting. I've moved approximately 3000 VMs with the script. Only major issue/annoyance is that large VMs won't move unless there is a ton of freespace on the same datastore. E.g. if you have a 4TB VM on a 5TB datastore, the move will fail the free space check because the move spec doesn't know that the datastore isn't changing. Bummer :( --- powershell/xMove-VM.ps1 | 196 +++++++++++++++++++++++++++++++--------- 1 file changed, 151 insertions(+), 45 deletions(-) diff --git a/powershell/xMove-VM.ps1 b/powershell/xMove-VM.ps1 index 916e88a..6c92903 100644 --- a/powershell/xMove-VM.ps1 +++ b/powershell/xMove-VM.ps1 @@ -1,4 +1,4 @@ -<# +<# .SYNOPSIS This script demonstrates an xVC-vMotion where a running Virtual Machine is live migrated between two vCenter Servers which are NOT part of the @@ -11,7 +11,8 @@ .NOTES File Name : xMove-VM.ps1 Author : William Lam - @lamw - Version : 1.0 + Modified : Alex Thomson (disk spec components from Grzegorz Kulikowski) + Version : 1.1 .LINK http://www.virtuallyghetto.com/2016/05/automating-cross-vcenter-vmotion-xvc-vmotion-between-the-same-different-sso-domain.html .LINK @@ -26,26 +27,34 @@ Function xMove-VM { param( - [Parameter( - Position=0, - Mandatory=$true, - ValueFromPipeline=$true, - ValueFromPipelineByPropertyName=$true) - ] + [Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] [VMware.VimAutomation.ViCore.Util10.VersionedObjectImpl]$sourcevc, + [Parameter(Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)] [VMware.VimAutomation.ViCore.Util10.VersionedObjectImpl]$destvc, + [Parameter(Mandatory=$true)] [String]$vm, + [Parameter(Mandatory=$true)] [String]$switchtype, + [Parameter(Mandatory=$false)] [String]$switch, + [Parameter(Mandatory=$true)] [String]$cluster, + [parameter(Mandatory=$false)] [String]$datastore, + [Paraneter(Mandatory=$true)] + [String]$sourceVMHost, + [Parameter(Mandatory=$true)] [String]$vmhost, + [Parameter(Mandatory=$false)] [String]$vmnetworks ) + #Create Relocation Spec for use in the function + $spec = New-Object VMware.Vim.VirtualMachineRelocateSpec + # Retrieve Source VC SSL Thumbprint $vcurl = "https://" + $destVC -add-type @" + add-type @" using System.Net; using System.Security.Cryptography.X509Certificates; @@ -68,15 +77,58 @@ add-type @" # Source VM to migrate $vm_view = Get-View (Get-VM -Server $sourcevc -Name $vm) -Property Config.Hardware.Device + $src_vm_tags = @(get-TagAssignment -server $sourcevc -entity (Get-VM -Server $sourcevc -Name $vm)) - # Dest Datastore to migrate VM to - $datastore_view = (Get-Datastore -Server $destVCConn -Name $datastore) + + # Determine the destination datastore to migrate VM to + # If you passed a datastore string we will use it here instead of figuring out the current location of disks. + if($datastore -eq $null){ + $datastore_view = (Get-VMHost -Server $destVC -Name $vmhost | Get-Datastore -Server $destVC -Name $datastore) + #Add the datastore to the relocation spec object + $spec.datastore = $datastore_view.Id + } + else{ + # No datastore was passed so we need to build the move spec with the current disk information + $sourceDisks = $vm_view.Config.Hardware.Device | where {$_ -is [vmware.vim.virtualdisk]} + $VMXDestinationDisk = ($vm_view.Config.Hardware.Device | where {$_ -is [vmware.vim.virtualdisk]}) | where {$_.DeviceInfo.Label -eq "Hard disk 1"} + $numberOfDisks = @($sourceDisks).Length + + #Convert source VC VMX datastore backing into target VC datastore backing + $sourceVMXBacking = "Datastore-"+$VMXDestinationDisk.Backing.Datastore.Value + $destVMXBacking = get-datastore -server $destVC (get-datastore -server $sourceVC -id $sourceVMXBacking | select name -ExpandProperty name -Unique) | select id -ExpandProperty id -Unique + $destVMXBacking = $destVMXBacking.substring(10) + + $spec.datastore = New-Object VMware.Vim.ManagedObjectReference + $spec.datastore.type = “Datastore” + $spec.datastore.Value = $destVMXBacking + + #Specs to make our disks stay where they are + $spec.disk = New-Object VMware.Vim.VirtualMachineRelocateSpecDiskLocator[] ($numberOfDisks) + $i=0 + Foreach($disk in $sourceDisks) { + #Add the string "Datastore" before the ID to be able to search for the datastore by ID + $sourceDiskBacking = "Datastore-"+$disk.Backing.Datastore.Value + + #Convert source VC datastore backing into target VC datastore backing + $destDiskBacking = get-datastore -server $destVC (get-datastore -server $sourceVC -id $sourceDiskBacking | select name -ExpandProperty name -Unique) | select id -ExpandProperty id -Unique + + #Strip off the first "datastore" in the string + $destDiskBacking = $destDiskBacking.substring(10) + + $spec.disk[$i] = New-Object VMware.Vim.VirtualMachineRelocateSpecDiskLocator + $spec.disk[$i].diskId = $disk.Key + $spec.disk[$i].datastore = New-Object VMware.Vim.ManagedObjectReference + $spec.disk[$i].datastore.type = “Datastore” + $spec.disk[$i].datastore.Value = $destDiskBacking + $i++ + } + } # Dest Cluster to migrate VM to - $cluster_view = (Get-Cluster -Server $destVCConn -Name $cluster) + $cluster_view = (Get-Cluster -Server $destVC -Name $cluster) # Dest ESXi host to migrate VM to - $vmhost_view = (Get-VMHost -Server $destVCConn -Name $vmhost) + $vmhost_view = (Get-VMHost -Server $destVC -Name $vmhost) # Find all Etherenet Devices for given VM which # we will need to change its network at the destination @@ -89,8 +141,6 @@ add-type @" } # Relocate Spec for Migration - $spec = New-Object VMware.Vim.VirtualMachineRelocateSpec - $spec.datastore = $datastore_view.Id $spec.host = $vmhost_view.Id $spec.pool = $cluster_view.ExtensionData.ResourcePool @@ -112,12 +162,24 @@ add-type @" $count = 0 if($switchtype -eq "vds") { foreach ($vmNetworkAdapter in $vmNetworkAdapters) { - # New VM Network to assign vNIC - $vmnetworkname = ($vmnetworks -split ",")[$count] - - # Extract Distributed Portgroup required info - $dvpg = Get-VDPortgroup -Server $destvc -Name $vmnetworkname - $vds_uuid = (Get-View $dvpg.ExtensionData.Config.DistributedVirtualSwitch).Uuid + if($vmnetworks -eq $null -and $switch -ne $null){ + #Set VDS variable to the string for the destination switch + $sourceVDS = $switch + $sourceDVPG = Get-VDSwitch -server $sourceVC $switch | Get-VDPortgroup -server $sourceVC | where {$_.key -eq $vmNetworkAdapter.backing.port.portgroupkey} + } + elseif($switch -eq $null -and $vmnetworks -ne $null){ + #Get current VDS name and set the portgroup to the passed in string + $sourceVDS = Get-VDSwitch -server $sourceVC | where {$_.key -eq $vmNetworkAdapter.backing.port.switchuuid} + $sourceDVPG = ($vmnetworks -split ",")[$count] + } + else{ + # Extract Source VDS and Portgroup names + $sourceVDS = Get-VDSwitch -server $sourceVC | where {$_.key -eq $vmNetworkAdapter.backing.port.switchuuid} + $sourceDVPG = $sourceVDS | Get-VDPortgroup -server $sourceVC | where {$_.key -eq $vmNetworkAdapter.backing.port.portgroupkey} + } + #Get the destination switch information + $dvpg = Get-VDSwitch -server $destvc $sourceVDS.name | Get-VDPortgroup -Server $destvc -Name $sourceDVPG.name + $vds_uuid = (Get-View -server $destvc $dvpg.ExtensionData.Config.DistributedVirtualSwitch).Uuid $dvpg_key = $dvpg.ExtensionData.Config.key # Device Change spec for VSS portgroup @@ -131,51 +193,95 @@ add-type @" $spec.DeviceChange += $dev $count++ } - } else { + } + else { foreach ($vmNetworkAdapter in $vmNetworkAdapters) { - # New VM Network to assign vNIC - $vmnetworkname = ($vmnetworks -split ",")[$count] - + if($vmnetworks -ne $null){ + #Set Portgroup variable to the string for the destination portgroup + $sourcePG = ($vmnetworks -split ",")[$count] + } + else{ + $sourcePG = Get-VirtualSwitch -server $sourceVC -vmhost $sourceVMHost | Get-VirtualPortgroup -server $sourceVC | where {$_.key -eq $vmNetworkAdapter.backing.devicename} + } + # Device Change spec for VSS portgroup $dev = New-Object VMware.Vim.VirtualDeviceConfigSpec $dev.Operation = "edit" $dev.Device = $vmNetworkAdapter $dev.device.backing = New-Object VMware.Vim.VirtualEthernetCardNetworkBackingInfo - $dev.device.backing.deviceName = $vmnetworkname + $dev.device.backing.deviceName = $sourcePG.name $spec.DeviceChange += $dev $count++ - } + } } + try{ + Write-Host "`nMigrating $vmname from $sourceVC to $destVC ...`n" - Write-Host "`nMigrating $vmname from $sourceVC to $destVC ...`n" + # Issue Cross VC-vMotion + $task = $vm_view.RelocateVM_Task($spec,"defaultPriority") + $task1 = Get-Task -Id ("Task-$($task.value)") -server $sourceVC + $task1 | Wait-Task -Verbose - # Issue Cross VC-vMotion - $task = $vm_view.RelocateVM_Task($spec,"defaultPriority") - $task1 = Get-Task -Id ("Task-$($task.value)") - $task1 | Wait-Task -Verbose + if($src_vm_tags.count -gt 0){ + Write-Host "`nAssigning tags to $vm on $destVC`n" + foreach ($tag in $src_vm_tags){ + New-TagAssignment -tag (get-tag -Name $tag.tag.name -server $destVC) -entity (Get-VM -Server $destvc -Name $vm) -server $destVC + } + } + } + catch{ + Write-Host "There was some error trying to submit the move task." -ForegroundColor Red + } } +Set-StrictMode -version 2 +$ErrorActionPreference = "Stop" + # Variables that must be defined -$vmname = "TinyVM-2" -$sourceVC = "vcenter60-1.primp-industries.com" -$sourceVCUsername = "administrator@vghetto.local" -$sourceVCPassword = "VMware1!" -$destVC = "vcenter60-3.primp-industries.com" -$destVCUsername = "administrator@vghetto.local" -$destVCpassword = "VMware1!" -$datastorename = "la-datastore1" -$clustername = "LA-Cluster" -$vmhostname = "vesxi60-5.primp-industries.com" -$vmnetworkname = "LA-VM-Network1,LA-VM-Network2" -$switchname = "LA-VDS" +$importedVMList = get-content C:\vms_to_move.csv +$sourceVC = "sourceVC" +$sourceVCUsername = "administrator@vsphere.local" +$sourceVCPassword = "password1" +$destVC = "destinationVC" +$destVCUsername = "administrator@vsphere.local" +$destVCPassword = "password2" +$destClusterName = "TargetDRSCluster" +#Set the switchname variable to $null if the destination switch uses the same name as the source switch. The script to determine the source switch name. +#Set the switchname variable to the target switch name if the source switch name and target switch name differ +$switch = $null +#Switch type should be set to either "vds" or "vss" $switchtype = "vds" +#Set the vmnetworkname variable to $null if the destination portgroup uses the same name as the source portgroup. The script will determine the source portgroup name +#Set the vmnetworkname variable to the destination portgroup name if you want to set a destination network for ALL VMs. Be careful! +$vmnetworkname = $null +#Set the datastore variable to $null to allow the script to determine the VM's current disk location and to create the move spec. +#Set the datastore to a string to move all VMs in $importedVMList to that datastore +$datastore = $null # Connect to Source/Destination vCenter Server $sourceVCConn = Connect-VIServer -Server $sourceVC -user $sourceVCUsername -password $sourceVCPassword $destVCConn = Connect-VIServer -Server $destVC -user $destVCUsername -password $destVCpassword -xMove-VM -sourcevc $sourceVCConn -destvc $destVCConn -VM $vmname -switchtype $switchtype -switch $switchname -cluster $clustername -vmhost $vmhostname -datastore $datastorename -vmnetwork $vmnetworkname + +foreach ($vmname in $importedVMList){ + Write-Host "`nNow evaluating $vmname to determine VM hardware version...`n" -ForegroundColor White + $vmobject = get-vm $vmname -server $sourceVC + $vmhostobject = get-cluster $destClusterName -server $destVC | get-vmhost | sort-object MemoryUsageGB | select -First 1 + if($vmhostobject.MemoryUsageGB + $vmobject.MemoryGB -gt $vmhostobject.MemoryTotalGB){ + Write-Error "`nCluster does not appear to have enough memory resources.` + Exiting script to allow for manual intervention.`n" + } + if($vmobject.version -eq "v4"){ + Write-Error "`nVM is hardware version 4.` + There appears to be a bug that will successfully move the VM but upon ` + power-cycle or vMotion on the target side will lose the assigned port-group. ` + This is a sanity check to make sure you don't lose connection to your VM at ` + a random time post-move!" + } + Write-Verbose "The destination host will be $($vmhostobject).name" + xMove-VM -sourcevc $sourceVCConn -destvc $destVCConn -VM $vmname -switchtype $switchtype -cluster $destClusterName -vmhost $vmhostobject.name -sourceVMHost $vmobject.vmHost +} # Disconnect from Source/Destination VC Disconnect-VIServer -Server $sourceVCConn -Confirm:$false From bf8976c7ba72679931f9596d4b1fea83cfb611d9 Mon Sep 17 00:00:00 2001 From: thomsonac Date: Fri, 21 Oct 2016 20:33:33 -0400 Subject: [PATCH 2/3] Update xMove-VM.ps1 Typo fix --- powershell/xMove-VM.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/powershell/xMove-VM.ps1 b/powershell/xMove-VM.ps1 index 6c92903..133f26d 100644 --- a/powershell/xMove-VM.ps1 +++ b/powershell/xMove-VM.ps1 @@ -39,9 +39,9 @@ Function xMove-VM { [String]$switch, [Parameter(Mandatory=$true)] [String]$cluster, - [parameter(Mandatory=$false)] + [Parameter(Mandatory=$false)] [String]$datastore, - [Paraneter(Mandatory=$true)] + [Parameter(Mandatory=$true)] [String]$sourceVMHost, [Parameter(Mandatory=$true)] [String]$vmhost, From 7f0bcb5f9f9f724752b22fd7478298acec208bfe Mon Sep 17 00:00:00 2001 From: thomsonac Date: Fri, 21 Oct 2016 20:36:47 -0400 Subject: [PATCH 3/3] Update xMove-VM.ps1 Another typo --- powershell/xMove-VM.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powershell/xMove-VM.ps1 b/powershell/xMove-VM.ps1 index 133f26d..3693be7 100644 --- a/powershell/xMove-VM.ps1 +++ b/powershell/xMove-VM.ps1 @@ -280,7 +280,7 @@ foreach ($vmname in $importedVMList){ a random time post-move!" } Write-Verbose "The destination host will be $($vmhostobject).name" - xMove-VM -sourcevc $sourceVCConn -destvc $destVCConn -VM $vmname -switchtype $switchtype -cluster $destClusterName -vmhost $vmhostobject.name -sourceVMHost $vmobject.vmHost + xMove-VM -sourcevc $sourceVCConn -destvc $destVCConn -VM $vmname -switchtype $switchtype -cluster $destClusterName -vmhost $vmhostobject.name -sourceVMHost [string]$vmobject.vmHost } # Disconnect from Source/Destination VC