Skip to content

Instantly share code, notes, and snippets.

@devblackops
Created February 15, 2016 18:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save devblackops/b4b07ef950f177ce14c4 to your computer and use it in GitHub Desktop.
Save devblackops/b4b07ef950f177ce14c4 to your computer and use it in GitHub Desktop.
PowerShell function to perform either local or Cross vCenter vMotion of a VM. Executes extra validation including pings, tracert, and Pester tests pre and post migration.
#Requires -RunAsAdministrator
#Requires -module VMware.VimAutomation.Core, VMware.VimAutomation.Vds, Pester, DrsRule
[cmdletbinding(SupportsShouldProcess)]
param(
[parameter(Mandatory, ValueFromPipelineByPropertyName = $true)]
[Alias('Name')]
[string]$VMName,
[parameter(Mandatory, ValueFromPipelineByPropertyName = $true)]
[string]$DestDatastoreName,
[parameter(Mandatory, ValueFromPipelineByPropertyName = $true)]
[string]$DestClusterName,
[parameter(Mandatory, ValueFromPipelineByPropertyName = $true)]
[string]$DestNetName,
[parameter(Mandatory, ValueFromPipelineByPropertyName = $true)]
[string]$SourceVC,
[parameter(Mandatory, ValueFromPipelineByPropertyName = $true)]
[string]$TargetVC,
[parameter(Mandatory, ValueFromPipelineByPropertyName = $true)]
[string]$TargetHostGroup,
[parameter(Mandatory, ValueFromPipelineByPropertyName = $true)]
[string]$VMtoHostGroup,
[parameter(Mandatory)]
[pscredential]$Credential,
[parameter(Mandatory)]
[pscredential]$AdminCredential,
[switch]$NoMigrate
)
begin {
$pesterTests = Join-Path -Path $PSScriptRoot -ChildPath 'vm_move.tests.ps1'
$testReults = Join-Path -Path $PSScriptRoot -ChildPath 'test_results'
function Invoke-Tracert {
param([string]$RemoteHost)
tracert $RemoteHost |ForEach-Object{
if($_.Trim() -match "Tracing route to .*") {
Write-Debug -Message $_
} elseif ($_.Trim() -match "^\d{1,2}\s+") {
$n,$a1,$a2,$a3,$target,$null = $_.Trim()-split"\s{2,}"
$Properties = @{
Hop = $n;
First = $a1;
Second = $a2;
Third = $a3;
Node = $target
}
New-Object psobject -Property $Properties
}
}
}
function PesterTest {
[cmdletbinding()]
param(
[string]$item,
[switch]$pre,
[switch]$post
)
$pesterParams = @{
Script = @{
Path = $pesterTests
Parameters = @{
ComputerName = $item
Credential = $AdminCredential
vCenter = $SourceVC
vCenterCredential = $Credential
}
}
PassThru = $true
}
if ($PSBoundParameters.ContainsKey('pre')) {
Write-Verbose -Message 'Running pre-migration Pester tests...'
$script:preMigTestResults = Invoke-Pester @pesterParams
$script:preMigTestResults.TestResult | % { $_ | Add-Member -MemberType NoteProperty -Name Stage -Value 'pre'}
} elseIf ($PSBoundParameters.ContainsKey('post')) {
Write-Verbose -Message 'Running post-migration Pester tests...'
$pesterParams.Script.Parameters.vCenter = $TargetVC
$script:postMigTestResults = Invoke-Pester @pesterParams
$script:postMigTestResults.TestResult | % { $_ | Add-Member -MemberType NoteProperty -Name Stage -Value 'post'}
$pesterTestCsv = Join-Path -Path $testReults -ChildPath "$item`_tests.csv"
$script:preMigTestResults.TestResult + $script:postMigTestResults.TestResult | Export-Csv -Path $pesterTestCsv -NoTypeInformation
}
}
function PingTest {
[cmdletbinding()]
param(
[string]$item,
[switch]$stop
)
$pingResults = (Join-Path -Path $testReults -ChildPath "$item`_pings.txt")
if (-not $PSBoundParameters.ContainsKey('stop')) {
Write-Verbose -Message 'Starting continuous ping...'
$pingCmd = { ping $args[0] -t > $args[1] }
#$pingCmd = { Test-Connection -Count 5000 -ComputerName $args[0] >> $args[1] }
$pingParams = @{
ScriptBlock = $pingCmd
ComputerName = '.'
Credential = $AdminCredential
ArgumentList = @($item, $pingResults)
AsJob = $true
}
$script:pingJob = Invoke-Command @pingParams
} else {
Write-Verbose -Message "Saving ping tests to [$pingResults]"
Start-Sleep -Seconds 5
$pingResults = $script:pingJob | Stop-Job -PassThru | Receive-Job
$script:pingJob | Remove-Job
}
}
function TraceRoute {
[cmdletbinding()]
param(
[string]$item,
[switch]$pre,
[switch]$post
)
if ($PSBoundParameters.ContainsKey('pre')) {
Write-Verbose -Message 'Running pre-migration tracert...'
$script:trPre = Invoke-Tracert -RemoteHost $item
$script:trPre | % { $_ | Add-Member -MemberType NoteProperty -Name Stage -Value 'pre'}
} elseif ($PSBoundParameters.ContainsKey('post')) {
Write-Verbose -Message 'Running post-migration tracert...'
$script:trPost = Invoke-Tracert -RemoteHost $item
$script:trPost | % { $_ | Add-Member -MemberType NoteProperty -Name Stage -Value 'post'}
$traceRouteResultsCsv = Join-Path -Path $testReults -ChildPath "$item`_tr.csv"
$tr = @()
$tr += $script:trPre
$tr += $script:trPost
$tr | Export-Csv -Path $traceRouteResultsCsv -NoTypeInformation
}
}
function MoveVM {
[cmdletbinding()]
param(
[string]$item,
[switch]$MoveVC,
[switch]$local,
[pscredential]$credential
)
$result = $false
if ($global:DefaultVIServer) {
Disconnect-VIServer -Server * -Force -Verbose:$false -Confirm:$false -ErrorAction SilentlyContinue | out-null
}
Write-Verbose -Message "Connecting to vCenter [$SourceVC]"
$srcVC = Connect-VIServer -Server $SourceVC -Credential $Credential -WarningAction SilentlyContinue -Verbose:$false
if ($PSBoundParameters.ContainsKey('MoveVC')) {
$s = $SourceVC.Split('.')[0]
$t = $TargetVC.Split('.')[0]
Write-Verbose -Message "Preparing Cross vCenter vMotion for [$item] from vCenter [$s] to [$t]"
Write-Verbose -Message "Connecting to vCenter [$TargetVC]"
$destVC = Connect-VIServer -Server $TargetVC -Credential $Credential -WarningAction SilentlyContinue -Verbose:$false
# Validate target parameters
$destCluster = Get-Cluster -Name $DestClusterName -Server $destVC -ErrorAction SilentlyContinue -Verbose:$false
$destDatastore = Get-Datastore -Name $DestDatastoreName -Server $destVC -ErrorAction SilentlyContinue -Verbose:$false
$destPortGroup = Get-VDPortgroup -Name $DestNetName -Server $destVC -ErrorAction SilentlyContinue -Verbose:$false
$destHostGroup = Get-DrsVMHostGroup -Name $TargetHostGroup -ErrorAction SilentlyContinue -Verbose:$false
if ($destDatastore -and $destHostGroup -and $destPortGroup -and $destCluster) {
$tvm = Get-VM -Name $item -Verbose:$false -Server $srcVC -ErrorAction SilentlyContinue
if ($tvm) {
$vm = $tvm | Get-View -Verbose:$false -Property Config.Hardware.Device -ErrorAction SilentlyContinue
# Validate there are no existing snapshots
$snaps = $VM | Get-Snapshot -ErrorAction SilentlyContinue
if ($snaps) {
throw 'There are existing snapshots on this VM. Migration will not continue.'
}
# Move VM to random host in destination cluster
$destHost = $destHostGroup.VMHost | Get-Random | % { Get-VMHost -Name $_ -Verbose:$false }
Write-Verbose -Message "Chose destination host [$($destHost.Name)]"
# Find Ethernet Device on VM to change VM Networks
$devices = $vm.Config.Hardware.Device
foreach ($device in $devices) {
if($device -is [VMware.Vim.VirtualEthernetCard]) {
$vmNetworkAdapter = $device
}
}
# New Service Locator required for Destination vCenter Server when not part of same SSO Domain
$service = New-Object VMware.Vim.ServiceLocator
$serviceCred = New-Object VMware.Vim.ServiceLocatorNamePassword
$serviceCred.username = $Credential.UserName
$serviceCred.password = $Credential.GetNetworkCredential().Password
$service.credential = $serviceCred
$service.instanceUuid = $destVC.InstanceUuid
$service.sslThumbprint = $destVCThumbprint
$service.url = "https://$destVC"
# Relocate Spec for Migration
$spec = New-Object VMware.Vim.VirtualMachineRelocateSpec
$spec.datastore = $destDatastore.Id
$spec.host = $destHost.Id
$spec.pool = $destCluster.ExtensionData.ResourcePool
$spec.service = $service
# Modify VM Network Adapter to new VM Netework (assumption 1 vNIC, but can easily be modified)
$spec.deviceChange = New-Object VMware.Vim.VirtualDeviceConfigSpec
$spec.deviceChange[0] = New-Object VMware.Vim.VirtualDeviceConfigSpec
$spec.deviceChange[0].Operation = "edit"
$spec.deviceChange[0].Device = $vmNetworkAdapter
$esx = $destHost | Get-View -Verbose:$false
# Get DVS port group Ids
foreach($netMoRef in $esx.Network | ? {$_.Type -eq 'DistributedVirtualPortGroup'} ){
$net = Get-View -Id $netMoRef -Server $destVC -Verbose:$false
if($net.Name -eq $DestNetName){
$dvPgKey = $net.MoRef.Value
$dvSwitchUuid = (Get-View -Id $net.Config.DistributedVirtualSwitch -Verbose:$false).Summary.Uuid
}
}
if ($dvPgKey -and $dvSwitchUuid) {
$spec.deviceChange[0].device.backing = New-Object VMware.Vim.VirtualEthernetCardDistributedVirtualPortBackingInfo
$spec.deviceChange[0].device.backing.port = New-Object VMware.Vim.DistributedVirtualSwitchPortConnection
$spec.deviceChange[0].device.backing.port.switchUuid = $dvSwitchUuid
$spec.deviceChange[0].device.backing.port.portgroupKey = $dvPgKey
# Issue Cross VC-vMotion
Write-Verbose "`nMigrating [$item] from [$srcVC] to [$destVC]..."
$task = $vm.RelocateVM_Task($spec, 'defaultPriority')
$task = Get-Task -Id $task -Verbose:$false
try {
# Wait for task to complete
while ($task.State.ToString().ToLower() -eq 'running') {
Write-Verbose -Message "VM migration $($task.PercentComplete)% complete"
Start-Sleep -Seconds 5
$task = Get-Task -Id $task.Id -Verbose:$false
}
$task = Get-Task -Id $task.Id -Verbose:$false
$migrationResult = $task.State.ToString()
if ($migrationResult -ne 'Error') {
Write-Verbose -Message "Migration complete"
# Get VM to host rule and append VM to it
# Make sure vm to host rule is enabled
$vmToHostRule = Get-DrsVMToVMHostRule -Name $VMtoHostGroup
if (-Not $vmToHostRule.Enabled) {
Write-Verbose -Message "Enabling VM to Host rule [$($vmToHostRule.Name)]"
$vmToHostRule | Set-DrsVMToVMHostRule -Enabled
}
$vmGroup = Get-DrsVMGroup -Name $vmToHostRule.VMGroupName
$destVM = Get-VM -Name $VMName -Server $destVC
Write-Verbose -Message "Adding [$($destVM.Name)] to VM group [$($vmGroup.Name)]"
$vmGroup | Set-DrsVMGroup -Append -VM $destVM
$result = $true
} else {
Write-Error -Message 'Migration failed!'
Write-Error $task.Result
}
} catch {
Write-Error -Message "Failed to move $item"
write-Error -Exception $_
}
} else {
if (-Not $dvPgKey) { throw "Unable to resolve dvPgKey: $dvPgKey"}
if (-Not $dvSwitchUuid) { throw "Unable to resolve dvSwitchUuid: $dvSwitchUuid"}
}
} else {
Write-Warning -Message "$VMName not found on source vCenter. Skipping."
}
} else {
if (-Not $destDatastore) { throw "Unable to resolve datastore: $destDatastoreName"}
if (-Not $destCluster) { throw "Unable to resolve cluster: $destCluster"}
if (-Not $destPortGroup) { throw "Unable to resolve network: $des$destPortGrouptDatastoreName"}
}
} elseIf ($PSBoundParameters.ContainsKey('local')) {
$s = $SourceVC.Split('.')[0]
Write-Verbose -Message "Preparing local vCenter vMotion for [$item] on vCenter [$s]"
$vm = Get-VM -Name $item -Server $srcVC -Verbose:$false
if ($vm) {
# Validate there are no existing snapshots
$snaps = $VM | Get-Snapshot -ErrorAction SilentlyContinue
if ($snaps) {
Write-Warning 'There are existing snapshots on this VM. Migration will not continue.'
break
}
# Determine if VM is already in the target host group
$destHostGroup = Get-DrsVMHostGroup -Name $TargetHostGroup -ErrorAction SilentlyContinue -Verbose:$false
# Move VM to random host in destination cluster
$destHost = $destHostGroup.VMHost | Get-Random | % { Get-VMHost -Name $_ -Verbose:$false }
Write-Verbose -Message "Chose destination host [$($destHost.Name)]"
# Do the move
$srcHost = $VM | Get-VMHost -Verbose:$false
Write-Verbose "`nMigrating [$item] from [($srcHost.Name)] to [$($destHost.Name)]...`n"
$task = Move-VM -VM $vm -Destination $destHost -VMotionPriority High -Server $srcVC -Verbose:$false -RunAsync
while ($task.State.ToString() -eq 'running') {
Write-Verbose -Message "VM migration $($task.PercentComplete)% complete"
Start-Sleep -Seconds 5
}
if ($task.State.ToString() -ne 'Error') {
Write-Verbose -Message "Migration complete"
# Get VM to host rule and append VM to it
# Make sure vm to host rule is enabled
$vmToHostRule = Get-DrsVMToVMHostRule -Name $VMtoHostGroup
if (-Not $vmToHostRule.Enabled) {
Write-Verbose -Message "Enabling VM to Host rule [$($vmToHostRule.Name)]"
$vmToHostRule | Set-DrsVMToVMHostRule -Enabled
}
$vmGroup = Get-DrsVMGroup -Name $vmToHostRule.VMGroupName
Write-Verbose -Message "Adding [$($VM.Name)] to VM group [$($vmGroup.Name)]"
$vmGroup | Set-DrsVMGroup -Append -VM $VM
$result = $true
} else {
Write-Error -Message 'Migration failed!'
Write-Error $task
}
} else {
Write-Warning -Message "[$item] not found on source vCenter. Skipping."
}
}
Disconnect-VIServer -Server * -Confirm:$false -WhatIf:$false -Verbose:$false -ErrorAction SilentlyContinue
return $result
}
}
process {
foreach ($item in $VMName) {
if ($SourceVC -ne $TargetVC) {
$whatIfMsg = "Cross vCenter vMotion to $TargetVC"
} else{
$whatIfMsg = "Local vCenter vMotion on SourceVC"
}
if ($PSCmdlet.ShouldProcess($item, $whatIfMsg)) {
$migResult = $false
# Setup continuous ping in the background
PingTest -item $item
# Run pre-migration Pester tests
PesterTest -item $item -pre
# Run tracert
if (Test-Connection -ComputerName $item -Count 1 -ErrorAction SilentlyContinue) {
TraceRoute -item $item -pre
} else {
Write-Warning -Message "Unable to ping $item. Skipping tracert"
}
$sw = [diagnostics.stopwatch]::StartNew()
if (-Not $PSBoundParameters.ContainsKey('NoMigrate')) {
if ($SourceVC -ne $TargetVC) {
# Perform Cross vCenter move
$migResult = MoveVM -item $item -movevc -credential $Credential
} else {
# Perform local VC move
$migResult = MoveVM -item $item -local -credential $Credential
}
} else {
Write-verbose -Message 'Skipping migration'
$migResult = $true
}
PingTest -item $item -Stop
$sw.stop()
if ($migResult) {
Write-Host "`n`n"
Write-Verbose -Message "Migration finished in [$($sw.elapsed.TotalSeconds)] seconds"
TraceRoute -item $item -post
PesterTest -item $item -post
}
}
}
}
end { }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment