Your savior is the -RunAsync Parameter of Set-VMHost. The description tells us the following (looking at the developer portal):
Indicates that the command returns immediately without waiting for the task to complete. In this mode, the output of the cmdlet is a Task object. For more information about the RunAsync parameter, run "help About_RunAsync" in the VMware PowerCLI console.
In theory you would use a foreach loop for running through your hostlist and firing up the evacuation and maintenance mode setting into the background like this:
$global:myMaintTask = @()
foreach ($ESXiServer in $ESXiServers) {
Write-Host "Evacuating $($ESXiServer.Name) and entering Maintenance Mode..."
$global:myMaintTask += Set-VMhost $ESXiServer.Name -State maintenance -Evacuate -RunAsync
}
This would start the evacuation and maintenance mode setting of all your hosts in parallel. Then you would wait for all Tasks in $global:myMaintTask to finish. I prefer to use global vars here because it makes it easier to debug even if you are filling the var within a function. It will also work with a normal variable like $myMaintTask. Adding some comments this could look like:
# start evacuation and maintenance mode
$global:myMaintTask = @()
foreach ($ESXiServer in $ESXiServers) {
Write-Host "Evacuating $($ESXiServer.Name) and entering Maintenance Mode..."
$global:myMaintTask += Set-VMhost $ESXiServer.Name -State maintenance -Evacuate -RunAsync
}
# wait until all hosts are in maintenance mode
while (@(get-task -Id $($global:myMaintTask.Id | where {$_ -like "Task-task-*"}) -ErrorAction SilentlyContinue | where {$_.State -eq 'Running' -OR $_.State -eq 'Queued'}).Count -gt 0) {
Write-Host "Waiting..."
Start-Sleep -Seconds 10
}
Write-Host "All Tasks have finished!"
Dependent on your infrastructure this may trigger too many vMotions at the same time. Maybe you would want to add a limit for the parallelization. This then could look like this:
$parallelLimit = 3
$global:myMaintTask = @()
foreach ($ESXiServer in $ESXiServers) {
# are currently more or equal to $parallelLimit tasks running, then wait until the count of tasks falls
if ($($global:myMaintTask | Measure-Object).count -ge $parallelLimit) {
# wait until the count of evacuations in parallel fall under $parallelLimit
while (@(get-task -Id $($global:myMaintTask.Id | where {$_ -like "Task-task-*"}) -ErrorAction SilentlyContinue | where {$_.State -eq 'Running' -OR $_.State -eq 'Queued'}).Count -ge $parallelLimit) {
# wait
Start-Sleep -Seconds 10
}
Write-Host "Amount of parallel evacuations has fallen under $($parallelLimit)."
}
# after waiting start the next evacuation
Write-Host "Evacuating $($ESXiServer.Name) and entering Maintenance Mode..."
$global:myMaintTask += Set-VMhost $ESXiServer.Name -State maintenance -Evacuate -RunAsync
}
The variable $parallelLimit will steer how many evacuations you trigger in parallel.
Then I would check if really all hosts on my list have entered the maintenance mode and trigger the reboot only on them.
$hostsInMaint = @()
$hostsFail = @()
foreach ($ESXiServer in $ESXiServers) {
if ($(Get-VMHost -Name $ESXiServer.Name).ConnectionState -eq 'Maintenance') {
$hostsInMaint += $ESXiServer.Name
} else {
$hostsFail += $ESXiServer.Name
}
}
The Restart-VMhost command also has the -RunAsync parameter (see documentation). You can then build a reboot block from the above example for evacuation and entering maintenance mode. I would suggest to also include a parallelization limit to not overload your power infrastructure with 50 boots at once.