Tag-Based vMotion with PowerCLI to place VMs on preferred hosts

VMware customers with a license that includes the Distributed Resource Scheduler (DRS) can work with affinity and anti-affinity rules for virtual machines and hosts to keep virtual machines on preferred hosts. Customers without DRS do not have such a built-in feature. After a server failure or after maintenance virtual machines may be on different hosts and not running on the host where you would want them to run.

With a simple script you can start a vMotion process to migrate machines to their preferred hosts. You could for example run such a script to migrate virtual machines back to the host you have selected after host maintenance.

The script explained in this article will use virtual machine tags to accomplish this. In another article I have used a script that will read a csv file as input. You can find it here.

To migrate virtual machines to certain hosts first create tags to assign to virtual machines. Those tags must then match with the ESXi hostnames. First create a category. In my example script below I have used a category named host. Next create tags in that category with your host-names and assign those tags to virtual machines selecting on which you want them to run. The next image shows an example of such tags.

Next create the PowerCLI script that will read the file and perform the migrations.

# For this script create a tag category named host
# Next create tags that correspond to the host names in your environment
# Assign those tags to VMs and use the script to migrate VMs to the corresponding host
# Get al VMs with a tag assigned in the host category
$taglist = Get-TagAssignment -category host

#Randomize the list of machines so not all VMs are migrated to the same host first.
#This does not randomize the virtual machine placement on hosts but only the order in which
#migrations are performed so hosts will not be overloaded with too many VMs

$taglist | sort-object {get-random} 
 
foreach ( $item in $taglist ) {
     #place the needed information in these variables for better readability in the code later 
    $vmname = $item.entity 
    $hostname = $item.tag.ToString().substring(5) 
    Move-VM -VM $vmname -Destination $hostname 
}

That's all. For better readability of your output you could consider to add a pipeline with formatting:

Move-VM -VM $ivmname -Destination $hostname | format-table name, vmhost, powerstate -HideTableHeaders

It is also possible to add the -RunAsync parameter to the Move-VM command to invoke all migrations at the same time but that might add undesirable load on your hosts so if speed if not of the essence have the migrations run synchronously.

This script does not check if a virtual machine is already on the preferred host. That could be done but on the other hand if the vm is already on that host just nothing happens.

What you might want to add is some error handling. You could add the -ErrorAction parameter to the Move-VM command. You can use Stop to stop processing the for-each loop in the script. But it\'s also possible to use the Inquire option which will pause and ask what to do. These two options would look like this in the script:

Move-VM -VM $vmname -Destination $hostname -ErrorAction Stop

or

Move-VM -VM $vmname -Destination $hostname -ErrorAction Inquire