VMware Cloud Community
DanTourangeau
Contributor
Contributor
Jump to solution

Move 2 VMs at the on 2 or more different Hosts

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

Reply
0 Kudos
1 Solution

Accepted Solutions
LucD
Leadership
Leadership
Jump to solution

Ok.
Try something like this.

 

$vmhost1 = "hostname1"
$vmhost2 = "hostname2"
$maxParallel = 2

$groups = Import-CSV -Path 'c:\Temp\migration\VMs_List-123.csv' -UseCulture |
    Group-Object -Property vmhost
    
$i = $j = 0

$eof = $false
while(-not $eof){
    if($i -lt $groups[0].Group.Count){
        $vm = Get-VM -Name $groups[0].Group[$i].name 
        Move-VM -VM $vm -Datastore $ds -RunAsync
        $i++
    }
    if($j -lt $groups[1].Group.Count){
        $vm = Get-VM -Name $groups[1].Group[$j].name 
        Move-VM -VM $vm -Datastore $ds -RunAsync
        $j++
    }

    while((Get-Task -Status Running | where{$_.Name -eq 'ApplyStorageDrsRecommendation_Task'}).Count -ge $maxParallel){
        sleep 5
    }

    if($i -eq $groups[0].Group.Count -and $j -lt $groups[1].Group.Count){
        $eof = $true
    }
}

 


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

View solution in original post

70 Replies
LucD
Leadership
Leadership
Jump to solution

Is the number of VMs per VMHost in that CSV the same?


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

Reply
0 Kudos
DanTourangeau
Contributor
Contributor
Jump to solution

Thanks for the reply LucD!

For this one it is but it will probably won't be the same for other migrations.

Reply
0 Kudos
LucD
Leadership
Leadership
Jump to solution

Ok.
Try something like this.

 

$vmhost1 = "hostname1"
$vmhost2 = "hostname2"
$maxParallel = 2

$groups = Import-CSV -Path 'c:\Temp\migration\VMs_List-123.csv' -UseCulture |
    Group-Object -Property vmhost
    
$i = $j = 0

$eof = $false
while(-not $eof){
    if($i -lt $groups[0].Group.Count){
        $vm = Get-VM -Name $groups[0].Group[$i].name 
        Move-VM -VM $vm -Datastore $ds -RunAsync
        $i++
    }
    if($j -lt $groups[1].Group.Count){
        $vm = Get-VM -Name $groups[1].Group[$j].name 
        Move-VM -VM $vm -Datastore $ds -RunAsync
        $j++
    }

    while((Get-Task -Status Running | where{$_.Name -eq 'ApplyStorageDrsRecommendation_Task'}).Count -ge $maxParallel){
        sleep 5
    }

    if($i -eq $groups[0].Group.Count -and $j -lt $groups[1].Group.Count){
        $eof = $true
    }
}

 


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

DanTourangeau
Contributor
Contributor
Jump to solution

Thanks!

It did work like I wanted.

So let say I have more Hosts I can only add more groups?
Group[2], Group[3] and ect...

if($j -lt $groups[2].Group.Count){
$vm = Get-VM -Name $groups[2].Group[$i].name
Move-VM -VM $vm -Datastore $ds -RunAsync
$j++

Reply
0 Kudos
LucD
Leadership
Leadership
Jump to solution

You can, but you will have to add

sections per additional host.

And the maximum in parallel should preferably be the same as the number of hosts


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

Reply
0 Kudos
DanTourangeau
Contributor
Contributor
Jump to solution

Luc, sorry for the late reply.

I have an error message when reaching the eof.

At 1st I thought it was one of my VM that was at fault but it seems it every time it reach the it loops on the same VM on the file.
I added a write-host to see more what going on.

 

Currently moving VM VM-xLarge-01 from group 1 to the DatastoreCluster xxx-xxx
ApplyStorageDrsRecommendati... Running 0 03:38:56 PM
Currently moving VM Test-2 from group 2 to the DatastoreCluster xxx-xxx
ApplyStorageDrsRecommendati... Running 0 03:38:56 PM
Currently moving VM Test-2 from group 2 to the DatastoreCluster xxx-xxx
Move-VM : 2021-06-15 3:46:30 PM Move-VM A specified parameter was not correct: Must specify datastore on which to place disk already in pod
At C:\Users\xxx\Desktop\9_Move_VMs_To_Cluster.ps1:116 char:9
+ Move-VM -VM $vm -Datastore $ds -RunAsync
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Move-VM], InvalidArgument
+ FullyQualifiedErrorId : Client20_VmServiceImpl_RelocateVmThroughSdrs_ViError,VMware.VimAutomation.ViCore.Cmdlets.Commands.MoveVM

Currently moving VM Test-2 from group 2 to the DatastoreCluster xxx-xxx
Move-VM : 2021-06-15 3:46:31 PM Move-VM A specified parameter was not correct: Must specify datastore on which to place disk already in pod
At C:\Users\xxx\Desktop\9_Move_VMs_To_Cluster.ps1:116 char:9
+ Move-VM -VM $vm -Datastore $ds -RunAsync
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Move-VM], InvalidArgument
+ FullyQualifiedErrorId : Client20_VmServiceImpl_RelocateVmThroughSdrs_ViError,VMware.VimAutomation.ViCore.Cmdlets.Commands.MoveVM

Currently moving VM Test-2 from group 2 to the DatastoreCluster xxx-xxx
Move-VM : 2021-06-15 3:46:31 PM Move-VM A specified parameter was not correct: Must specify datastore on which to place disk already in pod
At C:\Users\xxx\Desktop\9_Move_VMs_To_Cluster.ps1:116 char:9
+ Move-VM -VM $vm -Datastore $ds -RunAsync
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [Move-VM], InvalidArgument
+ FullyQualifiedErrorId : Client20_VmServiceImpl_RelocateVmThroughSdrs_ViError,VMware.VimAutomation.ViCore.Cmdlets.Commands.MoveVM


Reply
0 Kudos
LucD
Leadership
Leadership
Jump to solution

Is that target datastore part of a datastorecluster?


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

Reply
0 Kudos
DanTourangeau
Contributor
Contributor
Jump to solution

Hi Luc,

 

Sorry for the delay, I've been testing it and found the issue.


while(-not $eof){
if($i -lt $groups[0].Group.Count){
$vm = Get-VM -Name $groups[0].Group[$i].name
write-host "Currently moving VM" $vm.name "from group 1 to the DatastoreCluster " $ds -ForegroundColor Green
Move-VM -VM $vm -Datastore $ds -RunAsync
$i++
}
if($j -lt $groups[1].Group.Count){
$vm = Get-VM -Name $groups[1].Group[$i].name
write-host "Currently moving VM" $vm.name "from group 2 to the DatastoreCluster " $ds -ForegroundColor Green
Move-VM -VM $vm -Datastore $ds -RunAsync
$j++
}

 

Group[0] and Group[1] had the same counter letter so that's why it kept looping on the same VM for group[1].

Thanks again for your help! Really appreciate it!

Cheers!

Reply
0 Kudos
LucD
Leadership
Leadership
Jump to solution

Well spotted, I corrected the code above.


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

Reply
0 Kudos
DanTourangeau
Contributor
Contributor
Jump to solution

Not sure if I need to open a new treat but...

If I want to shutdown the guest O/S before the migration and once the move is completed, power on the vm.
Is it possible to do in this script?

I can find out for the shutdown but to power it on after the task is completed I have no idea...

Thanks again!

Reply
0 Kudos
LucD
Leadership
Leadership
Jump to solution

That is possible, but it will make the script a bit more complex.
I'll use an ArrayList to keep track of the background jobs, and at the end of the script, it will show the vMotions that failed, if any.

$vmhost1 = "hostname1"
$vmhost2 = "hostname2"
$maxParallel = 2

[System.Collections.ArrayList]$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) {
  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.Name.PowerState -ne 'PoweredOff'){
      sleep 5
    }
    $t = Move-VM -VM $vm -Datastore $ds -RunAsync
    $TaskArr.Add($t.Id) | Out-Null
    $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.Name.PowerState -ne 'PoweredOff') {
      Start-Sleep 5
      }
    $t = Move-VM -VM $vm -Datastore $ds -RunAsync
    $TaskArr.Add($t.Id) | Out-Null
    $j++
  }

  while ((Get-Task -Status Running | Where-Object { $_.Name -eq 'ApplyStorageDrsRecommendation_Task' }).Count -ge $maxParallel) {
    Start-Sleep 5
  }

  Get-Task -Id $TaskArr.Id -Status Success -PipelineVariable tCompleted |
  ForEach-Object -Process {
    Get-VM -Name $tCompleted.ExtensionData.EntityName | Start-VM -Confirm:$false
    $TaskArr.Remove($tCompleted)
  }

  if ($i -eq $groups[0].Group.Count -and $j -lt $groups[1].Group.Count) {
    $eof = $true
  }
}

while($TaskArr.Count -gt 0){
    Get-Task -Id $TaskArr.Id -Status Success -PipelineVariable tCompleted |
    ForEach-Object -Process {
      Get-VM -Name $tCompleted.ExtensionData.EntityName | Start-VM -Confirm:$false
      $TaskArr.Remove($tCompleted)
    }
    $erroredOut = @()
    Get-Task -Id $TaskArr.Id -Status Error -PipelineVariable tCompleted |
    ForEach-Object -Process {
      $erroredOut += $tCompleted.ExtensionData.EntityName
      $TaskArr.Remove($tCompleted)
    }
}
if($erroredOut.Count -gt 0){
  Write-Error "vMotion failed for VMs $($erroredOut -join ',')"
}


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

Reply
0 Kudos
DanTourangeau
Contributor
Contributor
Jump to solution

Thanks Luc for this nice script.

I had to make a change to the while loop as it was looping forever.

Can you help me with this error, I get this when it is at the get-task:

Get-Task : Cannot validate argument on parameter 'Id'. The argument is null or empty. Provide an argument that is not null or empty, and then try the command again.
At K:\DIMTPS 9\DIMTPS 9-2\Infrastructure\Software\Scripts\Migration\Work In Progress\Move_VMs_To_Cluster_PowerOn_PowerOff_RAW.ps1:133 char:16
+ Get-Task -Id $TaskArr.Id -Status Success -PipelineVariable tComplet ...
+ ~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Get-Task], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,VMware.VimAutomation.Common.Commands.Cmdlets.GetTask

 

Thank you again!

Reply
0 Kudos
LucD
Leadership
Leadership
Jump to solution

It could be that one or more Move-VM commands fail, then there is of course no TaskId.

You could build in a test (in both locations)

 

$t = Move-VM -VM $vm -Datastore $ds -RunAsync
if($t){
   $TaskArr.Add($t.Id) | Out-Null
}
$i++

Did you check in the Web Client if there is a vMotion initiated for each VM?

 

 


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

Reply
0 Kudos
DanTourangeau
Contributor
Contributor
Jump to solution

Thanks for your quick reply.

I still have the same error with this new block of codes.
I can see on the web client that the Storage DRS has started.

I can see 4 running as I changed the max to 4, 2 for each hosts.

Reply
0 Kudos
LucD
Leadership
Leadership
Jump to solution

What exactly did you change in the While-loop?

I would need to run the script in a debugger, step by step, to further analyse what goes wrong.


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

Reply
0 Kudos
DanTourangeau
Contributor
Contributor
Jump to solution

the following as before it couldn't continue and the $vm variable was staying at "poweredON" like it was not updating the new value.

$eof = $false
while (-not $eof) {
if ($i -lt $groups[0].Group.Count) {
$vm = Get-VM -Name $groups[0].Group[$i].name
Shutdown-VMGuest -VM $vm -Confirm:$false

while($vm.PowerState -ne 'PoweredOff'){
write-host "$vm.name from Group 1" $vm.PowerState
$vm = Get-VM -Name $groups[0].Group[$i].name
sleep 15
}

 

If it can help, when I ran only the variable $t to see what going on I had this :

PS Q:\> $t

Name                                                State           % Complete         Start         Time Finish Time
----                                                       -----         ----------                 ----------           -----------
ApplyStorageDrsRecommendati... Running                0                  12:45:18 PM

Reply
0 Kudos
LucD
Leadership
Leadership
Jump to solution

What was in the Id property of $t?

Do a $t.Id

 

When does this $eof change?

 

The VM powerstate should change, and that is why the script contains the line

$vm = Get-VM -Name $groups[0].Group[$i].name


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

Reply
0 Kudos
DanTourangeau
Contributor
Contributor
Jump to solution

What was in the Id property of $t?

Do a $t.Id

PS Q:\> $t.id
Task-task-7069

When does this $eof change?
Not sure I understand?

The VM powerstate should change, and that is why the script contains the line
$vm = Get-VM -Name $groups[0].Group[$i].name

Yes, but it seems it was just staying in the loop and not refreshing the variable.
I added some write-host to see if the script was looping but it seems to be stock in the while without going trough the $vm = Get-VM -Name $groups[0].Group[$i].name again to get it updated.

I'm still new to PS so I hope it doesn't sound to stupid.. :S

Thanks again for you quick replies!

Reply
0 Kudos
LucD
Leadership
Leadership
Jump to solution

Do all the Task object have a value for Id?
Check with 

$TaskArr.Id


To exit the loop, the value of $eof must change at some point.
Where does that happen?


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

Reply
0 Kudos