Last year i attend the Dutch VMUG (NLVMUG) i followed session from
Michael Wilmsen that was: Migrate your datacenter without downtime.
I must also move al lot of VM’s from different datacenters to other datacenters.
I use the script from Michael Wilmsen to move the VM’s. But along the way I counter some problems with this script. So I begon tweaking and tweaking and tweaking this script to create for me the ultimate Cross vCenter PowerCLI Script.
Coolfeatures:
– Info through Whattsapp (Default not enabled)
– Dryrun (Test Run)
– Logging
– Selection through GUI
– Multiple Nic support maximum of 4.
– Datastore en Host selection based on Free space en Free Memory
– Check of Destination Host or Datastore in Maintance
– Destination Store exist in Destination Cluster
MoveVM.ps1:
#Filename: MoveVM.ps1
#Author: M. Wilmsen / W. Vissers
#Source: http://virtual-hike.com/nlvmug-2018/
#Version: 2.0
#Date: 21-10-2018
#ChangeLog:
# V0.9 – M. Wilmsen First Version
# V1.0 – Fixed Multiple Nics to maximium of 4 nics
# – Logfile name VM name
# V1.1 – Destination Cluster not the first Host
# V1.2 – Selected Destination host based on memory used
# V1.3 – Fixed folder location and VirtualPortGroup
# V1.4 – Fixed Datastore in Maintance
# V1.5 – Using Get-VICredentialStoreItem + Logpath Fixt
# V1.6 – Fixed Log in Hours in 24 uurs
# V1.7 – Fixed Using DatastoreCluster name based on Cluster name!
# V1.8 – Check if Destination has the same datastore
# – Ask know for input
# – VM selection with VMhost
# – Fixed Ping Check
# v1.9 – Added Destination Store exist in Destination Cluster
# v2.0 – Fixed Destination Store exist in Destination Cluster
<#
.SYNOPSIS
Script to migrate a virtual machine
.DESCRIPTION
Script to migrate compute and storage from cluster to cluster. Log will be in current dir [VM]-[-timestamp].log
.EXAMPLE
MoveVM.ps1
#>
################################## INIT #################################################
#Set WebOperation timeout
# set-PowerCLIConfiguration -WebOperationTimeoutSeconds 3600
#Define Global variables
$location = “D:\xmovewhattsapp”
$LogPath = “.\”
$DataStoreClusterPrefix = “SAN-“
$SourceVC = Read-Host “Give Source vCenter”
$DestinationVC = Read-Host “Give Destination vCenter”
$DRSRecommendation = $true
$Dryrun = $false
$SendWhatsApp = $false
$WhatsAppNumbers = “0123456789”
$WhatsAppGroup = “Namehireyourwhattsgroup”
$instanceId = “23” #chang this line
$clientId = “demo@demo.nl” #change this line
$clientSecret = “Puthiersecretid” #change this line
################################## PASSWORD STORE ##############################################
#Username
# Check if credentials exist in credential store if not ask for credentials and put them in credential store
If ((Get-VICredentialStoreItem).host -notcontains $SourceVC) {New-VICredentialStoreItem -Host $SourceVC -User $env:USERNAME -Password ((get-credential).GetNetworkCredential().Password)}
If ((Get-VICredentialStoreItem).host -notcontains $DestinationVC) {New-VICredentialStoreItem -Host $DestinationVC -User $env:USERNAME -Password ((get-credential).GetNetworkCredential().Password)}
# Remove-VICredentialStoreItem * -Confirm:$false
################################## END INIT #################################################
################################## FUNCTIONS #################################################
#Define log function
Function LogWrite
{
Param ([string]$logstring)
#Add logtime to entry
$LogTime = Get-Date -Format “MM-dd-yyyy_HH-mm-ss”
$logstring = $LogTime + ” : ” + $logstring
#Write logstring
Add-content $LogFile -value $logstring
Write-Host $logstring
}
#Define SendWhatsApp function
Function SendWhatsApp
{
Param ([string] $message)
if ( $SendWhatsApp ) {
$LogTime = Get-Date -Format “MM-dd-yyyy_hh-mm-ss”
$message = $logtime + ” : ” + $message
foreach ( $number in $WhatsAppNumbers )
{
$jsonObj = @{‘group_admin’=$number;
‘group_name’=$WhatsAppGroup;
‘message’=$message;}
Try {
$res = Invoke-WebRequest -Uri “http://api.whatsmate.net/v2/whatsapp/group/message/$instanceId” `
-Method Post `
-Headers @{“X-WM-CLIENT-ID”=$clientId; “X-WM-CLIENT-SECRET”=$clientSecret;} `
-Body (ConvertTo-Json $jsonObj)
LogWrite “WhatsMate Status Code: ” $res.StatusCode
LogWrite $res.Content
}
Catch {
$result = $_.Exception.Response.GetResponseStream()
$reader = New-Object System.IO.StreamReader($result)
$reader.BaseStream.Position = 0
$reader.DiscardBufferedData()
$responseBody = $reader.ReadToEnd();
Write-host “Status Code: ” $_.Exception.Response.StatusCode
Write-host $message
}
}
}
}
function Get-VmSize($vm)
{
#Initialize variables
$VmDirs =@()
$VmSize = 0
$searchSpec = New-Object VMware.Vim.HostDatastoreBrowserSearchSpec
$searchSpec.details = New-Object VMware.Vim.FileQueryFlags
$searchSpec.details.fileSize = $TRUE
Get-View -VIObject $vm | % {
#Create an array with the vm’s directories
$VmDirs += $_.Config.Files.VmPathName.split(“/”)[0]
$VmDirs += $_.Config.Files.SnapshotDirectory.split(“/”)[0]
$VmDirs += $_.Config.Files.SuspendDirectory.split(“/”)[0]
$VmDirs += $_.Config.Files.LogDirectory.split(“/”)[0]
#Add directories of the vm’s virtual disk files
foreach ($disk in $_.Layout.Disk) {
foreach ($diskfile in $disk.diskfile){
$VmDirs += $diskfile.split(“/”)[0]
}
}
#Only take unique array items
$VmDirs = $VmDirs | Sort | Get-Unique
foreach ($dir in $VmDirs){
$ds = Get-Datastore ($dir.split(“[“)[1]).split(“]”)[0]
$dsb = Get-View (($ds | get-view).Browser)
$taskMoRef = $dsb.SearchDatastoreSubFolders_Task($dir,$searchSpec)
$task = Get-View $taskMoRef
while($task.Info.State -eq “running” -or $task.Info.State -eq “queued”){$task = Get-View $taskMoRef }
foreach ($result in $task.Info.Result){
foreach ($file in $result.File){
$VmSize += $file.FileSize
}
}
}
}
return $VmSize
}
################################## END FUNCTIONS #################################################
#Login to vCenter servers
if (($global:DefaultVIServers).Name -notcontains $SourceVC -or $DestinationVC) {
#SourceVC
$ConnectVC = Connect-VIServer $SourceVC
$Message = “Connecting to ” + $ConnectVC + ” as ” + $env:USERNAME
#Logwrite $Message
#DestionationVC
$ConnectVC = Connect-VIServer $DestinationVC
$Message = “Connecting ” + $ConnectVC + ” as ” + $env:USERNAME
#Logwrite $Message
# Disconnect-VIServer * -Confirm:$false
}
Set-Location $location
$cluster=Get-Cluster -Server $SourceVC | Out-GridView -OutputMode Single -Title “Select Source Cluster”
$vmtomigrate =Get-Cluster $cluster -Server $SourceVC | Get-VM | Out-GridView -OutputMode Single -Title “Select VM”
$DestinationCluster = Get-Cluster -Server $DestinationVC | Out-GridView -OutputMode Single -Title “Select Destination Cluster”
$vmfolder=Get-folder -Server $DestinationVC | Out-GridView -OutputMode Single -Title “Select Folder”
#Main Script
#Set $MigError to false befor migration
$MigError = $false
#Get VM variables
$vm = get-vm $vmtomigrate
#Define LogFile with time stamp
$LogTime = Get-Date -Format “MM-dd-yyyy_hh-mm-ss”
if([IO.Directory]::Exists($LogPath))
{
#Do Nothing!!
}
else
{
New-Item -ItemType directory -Path $LogPath
}
$LogFile = $LogPath+$VM+”-“+$LogTime+”.log”
# LogWrite Gebruiker
Logwrite $env:USERNAME
# Get-VM Info
$VMHDDSize = Get-VmSize($vm)
$VMHDDSize = [Math]::Round(($VMHDDSize / 1GB),2)
Logwrite “Start Virtual Machine Move”
#If WhatsApp make notice
if ( $SendWhatsApp ) { LogWrite “Notifications will be send using WhatsApp to WhatsApp Group: $WhatsAppGroup” }
#If DryRun make Notice
if ( $Dryrun ) {
Logwrite “Start move virtual machines $vm Disksize $VMHDDSize GB (DryRun)”
SendWhatsApp “Start move virtual machines $vm Disksize $VMHDDSize GB(DryRun)”
}
else {
Logwrite “Start move virtual machines $vm Disksize $VMHDDSize GB”
SendWhatsApp “Start move virtual machines $vm Disksize $VMHDDSize GB”
}
$SourceCluster = get-vm $vm | Get-Cluster | select name
$vmip = $vm | Select @{N=”IP Address”;E={@($_.guest.IPAddress[0])}}
$vmip = $vmip.”ip address”
$VMHDDSize = Get-VmSize($vm)
$VMHDDSize = [Math]::Round(($VMHDDSize / 1GB),2)
$NetworkAdapter = Get-NetworkAdapter -VM $vm -Server $SourceVC
$SourceVMPortGroup = Get-NetworkAdapter -vm $vm | Select NetworkName
$switchname = $DestinationCluster
$Datastore = Get-VM $vm | Get-DataStore -Server $sourceVC | Select @{N=”Name”;E={@($_.Name)}}
$Datastore = $Datastore.Name
$DatastoreExistinOthervCenter = Get-Cluster $DestinationCluster | Get-DataStore -Server $DestinationVC | ? {$_.Name -like “*$Datastore*”}
if ($DatastoreExistinOthervCenter )
{
LogWrite “Datastore exsist $DestinationCluster in destination vCenter $DestinationVC “
$destinationDatastore = $DatastoreExistinOthervCenter }
Else
{
LogWrite “Datastore does not exsist in $DestinationCluster destination vCenter $DestinationVC”
# Select DataStore with the most free space and not in maintance
$DatastoreCluster = “$DataStoreClusterPrefix”+”$DestinationCluster”
$destinationDatastore = Get-DatastoreCluster $DatastoreCluster | Get-Datastore | Where {$_.State -ne “Maintenance”} | Sort-Object -Property FreeSpaceGB -Descending | Select-Object -First 1
}
$destinationDatastoreFreeSpace = $destinationDatastore | Select Name,@{N=”FreeSpace”;E={$_.ExtensionData.Summary.FreeSpace}}
$destinationDatastoreFreeSpace = [Math]::Round(($destinationDatastoreFreeSpace.”FreeSpace” / 1GB),2)
# Select the host with the less used memory
$DestinationHost = Get-Cluster –Name $DestinationCluster –Server $DestinationVC | Get-VMhost -State Connected | Sort-Object -Property MemoryUsageGB | Select-Object -First 1
# Change Here if you have a vm with multiple Network Cards (Remove the # for the multiple nics)
if ($NetworkAdapter.Count-eq 1) {
$DestinationVMPortgroup =@()
$DestinationVMPortgroup += Get-VirtualPortGroup -Server $DestinationVC -Vmhost $DestinationHost | Out-GridView -OutputMode Single -Title “Select Nic1”
}
elseif ($NetworkAdapter.Count-eq 2) {
$DestinationVMPortgroup =@()
$DestinationVMPortgroup += Get-VirtualPortGroup -Server $DestinationVC -Vmhost $DestinationHost | Out-GridView -OutputMode Single -Title “Select Nic1”
$DestinationVMPortgroup += Get-VirtualPortGroup -Server $DestinationVC -Vmhost $DestinationHost | Out-GridView -OutputMode Single -Title “Select Nic2”
}
elseif ($NetworkAdapter.Count-eq 3) {
$DestinationVMPortgroup =@()
$DestinationVMPortgroup += Get-VirtualPortGroup -Server $DestinationVC -Vmhost $DestinationHost | Out-GridView -OutputMode Single -Title “Select Nic1”
$DestinationVMPortgroup += Get-VirtualPortGroup -Server $DestinationVC -Vmhost $DestinationHost | Out-GridView -OutputMode Single -Title “Select Nic2”
$DestinationVMPortgroup += Get-VirtualPortGroup -Server $DestinationVC -Vmhost $DestinationHost | Out-GridView -OutputMode Single -Title “Select Nic3”
}
elseif ($NetworkAdapter.Count-eq 4) {
$DestinationVMPortgroup =@()
$DestinationVMPortgroup += Get-VirtualPortGroup -Server $DestinationVC -Vmhost $DestinationHost | Out-GridView -OutputMode Single -Title “Select Nic1”
$DestinationVMPortgroup += Get-VirtualPortGroup -Server $DestinationVC -Vmhost $DestinationHost | Out-GridView -OutputMode Single -Title “Select Nic2”
$DestinationVMPortgroup += Get-VirtualPortGroup -Server $DestinationVC -Vmhost $DestinationHost | Out-GridView -OutputMode Single -Title “Select Nic3”
$DestinationVMPortgroup += Get-VirtualPortGroup -Server $DestinationVC -Vmhost $DestinationHost | Out-GridView -OutputMode Single -Title “Select Nic4”
}
LogWrite “Start move: $vm”
Logwrite “VM IP: $vmip”
Logwrite “VM Disk Used (GB): $VMHDDSize”
Logwrite “VM Folder: $vmfolder”
Logwrite “Source vCenter: $SourceVC”
Logwrite “VM Source Cluster: $SourceCluster”
Logwrite “Destination vCenter: $DestinationVC”
Logwrite “VM Destination Cluster: $DestinationCluster”
Logwrite “Destination host: $DestinationHost”
LogWrite “VM Source PortGroup: $SourceVMPortGroup”
LogWrite “VM Destination Portgroup: $DestinationVMPortgroup”
Logwrite “VM Destination Datastore: $destinationDatastore”
LogWrite “Destination Datastore FreeSpace GB: $destinationDatastoreFreeSpace “
if ( $Dryrun ) {
$FreespaceAfterMigration = $destinationDatastoreFreeSpace – $VMHDDSize
if ( $FreespaceAfterMigration -lt 0 ) { Logwrite “ERROR: Datastore $destinationDatastore does not have sufficient freespace! Virtual Machine needs $VMHDDSize. Only $destinationDatastoreFreeSpace available.” }
else { Logwrite “Virtual Machine will fit on datastore $destinationDatastore. Freespace after migration is: $FreespaceAfterMigration GB” }
}
#Test if VM responsed to ping
if ($vmip -eq $null) {
LogWrite “Virtual Machine ip address not known”
Logwrite “No ping check will be performed after moving the Virtual Machine”
}
else {
Test-Connection -comp $vmip -quiet
LogWrite “Virtual Machine $vm response to ping before being moved. Virtual machine will be checked after being moved”
$PingVM = $true
}
#if ( $VMHDDSize -eq
if ( -NOT $Dryrun) {
#Migrate VM to cluster
LogWrite “Move $vm to vCenter $DestinationVC and datastore $DestinationDatastore”
Try {
$Result = Move-VM -VM $vm `
-Destination $DestinationHost `
-Datastore $DestinationDatastore `
-NetworkAdapter $NetworkAdapter `
-PortGroup $DestinationVMPortgroup `
-ErrorAction Stop
}
Catch {
$ErrorMessage = $_.Exception.Message
LogWrite “ERROR: Move of $vm to cluster $DestinationHost failed!!!”
Logwrite “ERROR: Move Status Code: $ErrorMessage”
SendWhatsApp “ERROR: Move of $vm failed!!! $ErrorMessage”
$MigError = $true
}
#Migrate VM to folder
LogWrite “Move $vm to vCenter $vmfolder”
Try {
$VMtemp = get-vm $vm
$Result = Move-VM -VM $vmtemp -InventoryLocation $vmfolder -ErrorAction Stop
}
Catch {
$ErrorMessage = $_.Exception.Message
LogWrite “ERROR: Move of $vm to folder $vmfolder failed!!!”
Logwrite “ERROR: Move Status Code: $ErrorMessage”
SendWhatsApp “ERROR: Move of $vm failed!!! $ErrorMessage”
$MigError = $true
}
}
$MigError = $false
#Test if VM is running on destination cluster
if ( -NOT $MigError -AND -NOT $Dryrun ) {
LogWrite “Check $vm is registered in $DestinationVC”
try {
$CheckVM = get-vm -name $vm -server $DestinationVC -ErrorAction Stop
if ( $CheckVM ) {
Logwrite “$vm registered in $DestinationVC”
}
else {
Logwrite “ERROR: $vm not found in $DestinationVC”
}
}
catch {
$ErrorMessage = $_.Exception.Message
Logwrite “ERROR: $vm not found in $DestinationVC”
Logwrite “ERROR: $ErrorMessage”
SendWhatsApp “ERROR move: $vm not found in $DestinationVC”
}
}
#Test is VM response to ping, if $PingVM = $True
if ($PingVM) {
if (Test-Connection -comp $vmip -quiet) {
LogWrite “Virtual Machine $vm response to ping after move”
SendWhatsApp “Virtual Machine $vm response to ping after move”
}
}
sleep 1
SendWhatsApp “Finished move action: $vm from $SourceVC to $DestinationVC”
Logwrite “Finished move action: $vm from $SourceVC to $DestinationVC”
if ($DRSRecommendation)
{
Get-DrsRecommendation -Cluster $DestinationCluster -Server $DestinationVC | Apply-DrsRecommendation
Logwrite “DRS Recommendatation applyed”
}
Else
{
Logwrite “No DRS Recommendatation applyed”
Write-Host “No DRS Recommendatation applyed”
}
#Disconnect from vCenter servers
Logwrite “Disconnect from vCenter servers $SourceVC $DestinationVC”
Disconnect-viserver $SourceVC -Confirm:$false
Disconnect-viserver $DestinationVC -Confirm:$false
Logwrite “Finished moving virtual machines, exiting…..”
SendWhatsApp “Finished moving virtual machines, exiting…..”

vSphere 6.7 delivers an exceptional experience for the user with an enhancedvCenter Server Appliance (vCSA). It introduces several new APIs that improve the efficiency and experience to deploy vCenter, to deploy multiple vCenters based on a template, to make management of vCenter Server Appliance significantly easier, as well as for backup and restore. It also significantly simplifies the vCenter Server topology through vCenter with embedded platform services controller in enhanced linked mode, enabling customers to link multiple vCenters and have seamless visibility across the environment without the need for an external platform services controller or load balancers.
Another key component that allows vSphere 6.7 to deliver a simplified and efficient experience is the graphical user interface itself. The HTML5-based vSphere Client provides a modern user interface experience that is both responsive and easy to use. With vSphere 6.7, it includes added functionality to support not only the typical workflows customers need but also other key functionality like managing NSX, vSAN, VUM as well as third-party components.
vSphere 6.7 adds support for Trusted Platform Module (TPM) 2.0 hardware devices and also introduces Virtual TPM 2.0, significantly enhancing protection and assuring integrity for both the hypervisor and the guest operating system. This capability helps prevent VMs and hosts from being tampered with, prevents the loading of unauthorized components and enables guest operating system security features security teams are asking for.
vSphere 6.7 continues to showcase VMware’s technological leadership and fruitful collaboration with our key partners by adding support for a key industry innovation poised to have a dramatic impact on the landscape, which is persistent memory. With vSphere Persistent Memory, customers using supported hardware modules, such as those available from Dell-EMC and HPE, can leverage them either as super-fast storage with high IOPS, or expose them to the guest operating system as non-volatile memory. This will significantly enhance performance of the OS as well as applications across a variety of use cases, making existing applications faster and more performant and enabling customers to create new high-performance applications that can leverage vSphere Persistent Memory.
vSphere 6.7 also introduces Cross-Cloud Cold and Hot Migration, further enhancing the ease of management across and enabling a seamless and non-disruptive hybrid cloud experience for customers.
