Created
February 15, 2016 18:42
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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