I found this PowerCli script from LucD. It does work really well. 🙂
However I'm trying to find a way to run only 2 storage vMotions on 2 or more hosts reading a list of VMs for each host.
It does work but only 1 host at the time, no sure how to run to foreach at the same time or it will be an entire new way?
That is what I have so far, I've tried to add some if or else if but always ends up with the same results.
Any help or suggestion will be gladly appreciate. 🙂
Thanks,
$vmhost1 = "hostname1"
$vmhost2 = "hostname2"
$vmlist = Import-CSV c:\Temp\migration\VMs_List-123.csv
$vmlist1 = Import-CSV c:\Temp\migration\VMs_List-123.csv | where {$_.vmhost -eq $vmhost1}
$vmlist2 = Import-CSV c:\Temp\migration\VMs_List-123.csv | where {$_.vmhost -eq $vmhost2}
$maxParallel = 2
foreach ($VM1 in $VMlist1) {
write-host "Currently moving VM" $vm1.name "to the DatastoreCluster " $ds -ForegroundColor Green
Move-VM -VM (Get-VM -Name $vm1.name) -Datastore $ds -RunAsync
do {
sleep 5
}
while((Get-Task -Status Running | where{$_.Name -eq 'ApplyStorageDrsRecommendation_Task'}).Count -ge $maxParallel)
foreach ($VM2 in $VMlist2) {
write-host "Currently moving VM" $vm2.name "to the DatastoreCluster " $ds -ForegroundColor Green
Move-VM -VM (Get-VM -Name $vm2.name) -Datastore $ds -RunAsync
do {
sleep 5
}
while((Get-Task -Status Running | where{$_.Name -eq 'ApplyStorageDrsRecommendation_Task'}).Count -ge $maxParallel)
}
}
Disconnect-VIServer -Server * -Confirm:$false
Just enter a new line with only Get-Task on it.
Do that before the other instances of the Get-Task cmdlet
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
Ok, I did some more testing and it looks as if the Task event doesn not contain the VM's name in EntityName when the Datastore parameter on the Move-VM cmdlet has a DatastoreCluster.
When it is a Datastore, the EntityName does contain the VM's name.
Are you doing the Move-VM to a DatastoreCluster?
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
Yes for this migration all the VMs will be moved to a Datastores Cluster.
Thanks,
Ok, then we need to rethink the logic of the script
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
I don't think I will help you much with this as I am not really advance in PowerCli.
However, I found that this line "$vm.ExtensionData.RecentTask.value" return the task ID of this $vm.
Is it possible to match that info with the $TaskArr.Id to find the correct VM and then Power it on?
I'm trying to add that to the script but I'm struggling.
Thanks!
Ok, I tried to rewrite the code to use a Hash Table.
That way we can use a Key (Task.Id) and a Value (VmName).
That allows use to find back the name of the VM based on the TaskId.
$vmhost1 = "hostname1"
$vmhost2 = "hostname2"
$maxParallel = 2
#[System.Collections.ArrayList]$TaskArr = @()
$TaskArr = @{}
$groups = Import-Csv -Path 'c:\Temp\migration\VMs_List-123.csv' -UseCulture |
Group-Object -Property vmhost
$i = $j = 0
$eof = $false
while (-not $eof) {
Write-Verbose "EOF: $eof I: $i J:$j"
if ($i -lt $groups[0].Group.Count) {
$vm = Get-VM -Name $groups[0].Group[$i].name
Shutdown-VMGuest -VM $vm -Confirm:$false
while ((Get-VM -Name $vm).PowerState -ne 'PoweredOff') {
Start-Sleep 5
}
$t = Move-VM -VM $vm -Datastore $ds -RunAsync
Write-Verbose "Move $($vm.Name) to datastore $($ds.Name) "
if ($t) {
Write-Verbose "Task created: $($t.Id)"
# $TaskArr.Add($t) | Out-Null
$TaskArr.Add($t.Id,$vm.Name)
Write-Verbose "TaskArr Count : $($TaskArr.Count)"
} else {
Write-Verbose "Move Task is empty"
}
$i++
}
if ($j -lt $groups[1].Group.Count) {
$vm = Get-VM -Name $groups[1].Group[$j].name
Shutdown-VMGuest -VM $vm -Confirm:$false
while ((Get-VM -Name $vm).PowerState -ne 'PoweredOff') {
Start-Sleep 5
}
$t = Move-VM -VM $vm -Datastore $ds -RunAsync
Write-Verbose "Move $($vm.Name) to datastore $($ds.Name) "
if ($t) {
Write-Verbose "Task created: $($t.Id)"
# $TaskArr.Add($t) | Out-Null
$TaskArr.Add($t.Id, $vm.Name)
Write-Verbose "TaskArr Count : $($TaskArr.Count)"
} else {
Write-Verbose "Move Task is empty"
}
$j++
}
Write-Verbose "EOF: $eof I: $i J:$j"
Write-Verbose "TaskArr Count : $($TaskArr.Count)"
# Write-Verbose "IDs : $($TaskArr.Id -join '|')"
Write-Verbose "IDs : $($TaskArr.Keys -join '|')"
while ((Get-Task -PipelineVariable $tCompleted |
# Where-Object { $_.State -eq 'Running' -and $_.Name -eq 'ApplyStorageDrsRecommendation_Task' -and $TaskArr.Id -contains $_.Id }).Count -ge $maxParallel) {
Where-Object { $_.State -eq 'Running' -and $_.Name -eq 'ApplyStorageDrsRecommendation_Task' -and $TaskArr.Keys -contains $_.Id }).Count -ge $maxParallel) {
Write-Verbose "Still background tasks running - going to sleep"
Start-Sleep 5
}
Write-Verbose "Checking for VMs to start"
Write-Verbose "Tasks in hash table $($TaskArr.Count)"
Get-Task |
# Where-Object { $_.State -eq 'Success' -and $_.Name -eq 'ApplyStorageDrsRecommendation_Task' -and $TaskArr.Id -contains $_.Id } |
Where-Object { $_.State -eq 'Success' -and $_.Name -eq 'ApplyStorageDrsRecommendation_Task' -and $TaskArr.Keys -contains $_.Id } |
ForEach-Object -Process {
Write-Verbose "Start VM $($_.ExtensionData.Info.EntityName)"
# Get-VM -Name $_.ExtensionData.Info.EntityName | Start-VM -Confirm:$false
Get-VM -Name $TaskArr[$_.Id] | Start-VM -Confirm:$false
# $TaskArr.Remove($_)
$TaskArr.Remove($_.Id)
}
Write-Verbose "Task in hash table $($TaskArr.Count)"
if ($i -eq $groups[0].Group.Count -and $j -lt $groups[1].Group.Count) {
Write-Verbose "Reached the end of both VM lists"
$eof = $true
}
}
Write-Verbose "Wait till remaining Tasks are finished"
while ($TaskArr.Count -gt 0) {
Get-Task |
# Where-Object { $_.State -eq 'Success' -and $_.Name -eq 'ApplyStorageDrsRecommendation_Task' -and $TaskArr.Id -contains $_.Id } |
Where-Object { $_.State -eq 'Success' -and $_.Name -eq 'ApplyStorageDrsRecommendation_Task' -and $TaskArr.Keys -contains $_.Id } |
ForEach-Object -Process {
Write-Verbose "Start VM $($_.ExtensionData.Info.EntityName)"
# Get-VM -Name $_.ExtensionData.Info.EntityName | Start-VM -Confirm:$false
Get-VM -Name $TaskArr[$_.Id] | Start-VM -Confirm:$false
# $TaskArr.Remove($_)
$TaskArr.Remove($_.Id)
}
$erroredOut = @()
Get-Task |
# Where-Object { $_.State -eq 'Error' -and $_.Name -eq 'ApplyStorageDrsRecommendation_Task' -and $TaskArr.Id -contains $_.Id } |
Where-Object { $_.State -eq 'Error' -and $_.Name -eq 'ApplyStorageDrsRecommendation_Task' -and $TaskArr.Keys -contains $_.Id } |
ForEach-Object -Process {
# Write-Verbose "Error for VM $($_.ExtensionData.Info.EntityName)"
# $erroredOut += $_.ExtensionData.Info.EntityName
Write-Verbose "Error for VM $($TaskArr[$_.Id])"
$erroredOut += $TaskArr[$_.Id]
$TaskArr.Remove($_.Id)
}
}
if ($erroredOut.Count -gt 0) {
Write-Error "vMotion failed for VMs $($erroredOut -join ',')"
}
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
Wow Luc, that is great work!
The VMs are able to stop, migrate and start.
Quick question however, how many VMs are you testing the script with?
I tested it with 8 VMs with a "$maxParallel = 2" and on 8 VMs there is 1 or 2 that didn't transfer and the script doesn't give me any errors and doesn't reach the eof.
I tested it with 8 VMs with a "$maxParallel = 4" and on 8 there is 2 or 3 that didn't transfer and the script doesn't give me any errors and doesn't reach the eof. After the script start about 5 VM, I don't see it going to sleep anymore but looping in :
Write-Verbose "Checking for VMs to start" Write-Verbose "Tasks in hash table $($TaskArr.Count)" Get-Task | # Where-Object { $_.State -eq 'Success' -and $_.Name -eq 'ApplyStorageDrsRecommendation_Task' -and $TaskArr.Id -contains $_.Id } | Where-Object { $_.State -eq 'Success' -and $_.Name -eq 'ApplyStorageDrsRecommendation_Task' -and $TaskArr.Keys -contains $_.Id } | ForEach-Object -Process { Write-Verbose "Start VM $($_.ExtensionData.Info.EntityName)" # Get-VM -Name $_.ExtensionData.Info.EntityName | Start-VM -Confirm:$false Get-VM -Name $TaskArr[$_.Id] | Start-VM -Confirm:$false # $TaskArr.Remove($_) $TaskArr.Remove($_.Id) } Write-Verbose "Task in hash table $($TaskArr.Count)" if ($i -eq $groups[0].Group.Count -and $j -lt $groups[1].Group.Count) { Write-Verbose "Reached the end of both VM lists" $eof = $true }
Thanks again for all the hard works!
I tested of course in a limited environment, 4 VMs with MaxParallel set to 2.
I have no clue why some VMs failed to move.
Perhaps have a look at the events for those VMs.
The script is of course not handling cases when a Task fails (Error status).
That would require additional code.
Blog: lucd.info Twitter: @LucD22 Co-author PowerCLI Reference
I checked and there is no info, like there are been skipped, also it's different VMs each time.
I'll keep testing, Thanks again! 🙂
Hi LucD, just an FYI...
I added another check after the $maxParallel as when it reach close of the eof and there is only 2 VMs left but the $maxParallel is set to 4 it was skipping them. The added check will see if there is still some tasks running and if so it will continue sleeping/checking for VMs to start.
Thanks again for your help, I wouldn't be able to have accomplish this without you!
while ((Get-Task -PipelineVariable $tCompleted |
Where-Object { $_.State -eq 'Running' -and $_.Name -eq 'ApplyStorageDrsRecommendation_Task' -and $TaskArr.Keys -contains $_.Id }).Count -ge $maxParallel) {
Write-Host "Still background tasks running - going to sleep" -ForegroundColor Yellow
Start-Sleep 60
}
if ($i -eq $groups[0].Group.Count -and $j -eq $groups[1].Group.Count -and $TaskArr.Count -ge 1 ) {
Write-Host "Still some background tasks running - going to sleep" -ForegroundColor Yellow
Start-Sleep 30
Write-Verbose "Checking for VMs to start"
Write-Verbose "Tasks in hash table $($TaskArr.Count)"
Get-Task |
Where-Object { $_.State -eq 'Success' -and $_.Name -eq 'ApplyStorageDrsRecommendation_Task' -and $TaskArr.Keys -contains $_.Id } |
ForEach-Object -Process {
Write-Host "Start VM $($TaskArr[$_.Id])" -ForegroundColor Magenta
Get-VM -Name $TaskArr[$_.Id] | Start-VM -Confirm:$false
$TaskArr.Remove($_.Id)
}
}