VMware Cloud Community
MikeErter
Enthusiast
Enthusiast

Move VMs serially

Hi Community,

How can I get all the VMs in a datastore and then storage vMotion them, one at a time, to a different datastore?

Here's what I tried, but it kicked off 8 svMotions at once- not what I want

$VmsInDatastore = get-datastore -name datastorename | get-vm

foreach ($vm in $VmsInDatastore) {move-vm -vm $vm -datastore destinationDatastore}

Thanks!

8 Replies
LucD
Leadership
Leadership

That is indeed a known "issue", the better part of the actual svMotion is done by independent tasks in vSphere.

Running the Move-VM cmdlet with the RunAsync parameter and then monitoring the state of the background task, will not help either.

The only way that I know that will work is to launch the svMotion and then monitor the vCenter server for the VmMigratedEvent event.

And of course verify that you have the corresponding one with your svMotion.

Another, far fetched alternative imho, is to lower the vCenter settings for the number of parallel svMotion to 1.

Then you can fire off the svMotions like you did, but the system will handle them one by one.

See Frank's Limiting the number of Storage vMotions post.


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

Reply
0 Kudos
MikeErter
Enthusiast
Enthusiast

Thank you for the reply Luc.

Reply
0 Kudos
EKardinal
Enthusiast
Enthusiast

Hi there,

after some fiddling around, I got it to work with the powershell background jobs (available at PowerShell V2 or higher).

The background job is waiting until all vMotion activities are done (1 or 8, doesn't matter).

Just pass your credentials in line 04 to 06, set the target datastore in line 07 and modify the command in line 12 to get the correct VMs.

To test the script, just add the -WhatIf parameter on line 24 after Move-VM.

# Load PowerCLI cmdlets

Add-PSSnapin VMware.VimAutomation.Core -ErrorAction "SilentlyContinue"


# Define vCenter User and target Datastore

$vcHost = 'VC-HOSTNAME.DOMAIN.COM'

$vcUser = 'user@domain.com'

$vcPass = 'PASSWORD'

$svmTarget = 'TARGET-DATASTORE'


# Connect to vCenter

Connect-VIServer $vcHost -User $vcUser -Password $vcPass


# Get VMs (pass array of VMs to $VMs, for example 'get-datastore test | get-vm')

$VMs = Get-ResourcePool "Tests" | Get-VM


Foreach($VM in $VMs) {

     Write-Host ("Start Storage vMotion for VM '" + $VM.Name + "'")

    

     $Arguments = @{"vcHost" = $vcHost; "vcUser" = $vcUser; "vcPass" = $vcPass; "svmTarget" = $svmTarget; "VMId" = $VM.Id;}

     # Start Job

     $Job = Start-Job -ArgumentList $Arguments -ScriptBlock {

          param([Hashtable]$Data)

          # Load PowerCLI cmdlets

          Add-PSSnapin VMware.VimAutomation.Core -ErrorAction "SilentlyContinue"

          # Connect to vCenter

          Connect-VIServer $Data.vcHost -User $Data.vcUser -Password $Data.vcPass

          # Start SvMotion

          Get-VM -Id $Data.VMId | Move-VM -Datastore $Data.svmTarget

          return

     }

     Write-Host ("Waiting...")

     Wait-Job -Job $Job | Receive-Job

     Write-Host ("Storage vMotion for VM '" + $VM.Name + "' finished")

}

Regards,

Emanuel

Reply
0 Kudos
Pathay
Contributor
Contributor

Hello guys

I tried to write a small script based on the way you designed the jobs management in your example

Goal is to take a list of VM on a defined wave number (arg1) from the csv file, and launch in // move-vm in the limit of the second argument..

It seems that it's hanging in the line   $finished = Wait-Job -Job $jobs -Any

because the jobs are always running even after a controlC, I have :still the two jobs running if I check with the get-job command

Any ideas ?

Thanks in advance for any comments

Pat

script is :

$SelectedWave = $args[0]

$maxjobs = $args[1]

$direnv = "DIR TO CSV"

# Define vCenter User

$vcHost = 'SERVER NAME'

$vcUser = 'USER NAME'

$vcPass = 'PASSWORD'

$jobs = @()

Add-PSSnapin VMware.VimAutomation.Core -ErrorAction "SilentlyContinue"

#

# Read the CSV file and filter on the wave argument

Import-Csv "$direnv\test1.csv" | Where-Object {$_.Wave -eq $SelectedWave } | foreach {

  $VM = $_.VM

  $TgtDatastore = $_.TgtDatastore

  $d = Get-Date

  $MsgText = "$d - Starting migrating $VM to $TgtDatastore"

  Write-Host ($MsgText)

  $Arguments = @{"vcHost" = $vcHost; "vcUser" = $vcUser; "vcPass" = $vcPass; "TgtDatastore" = $TgtDatastore; "VM" = $VM;}

  # Start SubProcess

  $Jobs += Start-Job -ArgumentList $Arguments -ScriptBlock {

  param([Hashtable]$Data)

  # Load PowerCLI cmdlets

  Add-PSSnapin VMware.VimAutomation.Core -ErrorAction "SilentlyContinue"

  # Connect to vCenter

  Connect-VIServer $Data.vcHost -User $Data.vcUser -Password $Data.vcPass

  # Start SvMotion

  Get-VM -Name $Data.VM | Move-VM -Datastore $Data.TgtDatastore

  return

  }

  $running = @($jobs | ? {$_.State -eq 'Running'})

  # Throttle jobs.

  while ($running.Count -ge $maxJobs) {

        $finished = Wait-Job -Job $jobs -Any

        $running = @($jobs | ? {$_.State -eq 'Running'})

  }

  #

  #Launch the move VM to the target Datastore

  #Get-VM -Name $VM | Move-VM -Datastore ($TgtDatastore)

}

# Wait for remaining.

Wait-Job -Job $jobs > $null

$jobs | Receive-Job

Reply
0 Kudos
LucD
Leadership
Leadership

You are not updating the content of the jobs in the variable $jobs.

The content will not change automatically, so the State will stay the initial state ("Running")

You can do a Get-Job to refresh the contents of the variable $jobs.

2nd question, why do you use the Wait-Job in the While loop. The Wait-Job cmdlet will wait for the background jobs to finish.

But as I see it, that is the same thing you are doing with the While loop.


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

Reply
0 Kudos
jrr001
Enthusiast
Enthusiast

LeetCloud: Mass storage vMotion: Migrate VMs serially/sequentially/one at a time between datastores ...

$sourcedatastore = "datastore1"

$destinationdatastore = "datastore2"

$vms = Get-VM -Datastore $sourcedatastore

foreach($vm in $vms){

    Move-VM -VM (Get-VM -Name $vm) -Datastore $destinationdatastore

}

Reply
0 Kudos
Freep
Contributor
Contributor

I wrote a function to do just that.  Feel free to use and change or remove write-host and other features.

FIrst be connected to the appropriate Vcenter of course.

Simply execute with Storage-Vmotion -VMname $vmlist -Datastore <datastore name>

This will monitor the completed event for each vm in the list and continue to storage vmotion the next only upon completion of the first.  Truly migrating one VM at a time. Enjoy.

PS. One thing to note, this requires the cmdlet 'Get-VIeventPLus' which I believe LucD wrote.  You can search for it in the community if you don't already have it installed or I believe it's located here:

http://www.lucd.info/2013/03/31/get-the-vmotionsvmotion-history/

function Storage-Vmotion {

<#

.SYNOPSIS

    Cmdlet will Storage Vmotion one VM at a time

.DESCRIPTION

    You may send a list of vm names OR a variable with a multiple VMs and pass to VMname

.PARAMETER VMname

    Pass vmnames or variable containing multiple

.INPUTS

.OUTPUTS

.EXAMPLE

    Storage-Vmotion -VMname $vmlist -DesitinationDatastore 'Datastore_1'

    Set up the vairable vmlist for example like this:

        $vmlist = Get-Cluster 'Cluster 1' | get-vm

    This example will migrate all VMs in Cluster 1 to the new datastore Datastore_1

.LINK

#>

    [CmdletBinding()]

    param (

        [Parameter(Mandatory=$true)][string[]]$VMname,

        [Parameter(Mandatory=$true)][string]$DesitinationDatastore

    )

    BEGIN {

        $ds = get-datastore $DesitinationDatastore

        $date = get-date

    }

    PROCESS{

   

        if ($VMname -isnot [VMware.VimAutomation.ViCore.Impl.V1.Inventory.VirtualMachineImpl] ) {

            $vms = get-vm $VMname

        } else {$vms = $VMname}

        foreach ($vm in $vms ){

            Remove-Variable failedmigration -ErrorAction SilentlyContinue

            write-host "Migrating $vm to $ds..." -ForegroundColor Green

            if ( ($vm.ExtensionData.config.files.VmPathName -replace '\[|\].*') -ne $DesitinationDatastore )  {

                $vm | move-vm -Datastore $ds -ErrorVariable failedmigration| out-null

            } else {

                write-host "$vm already on $ds"

                $failedmigration = "Failed"

            }

                                         

            do {            

                sleep -s 120

            } until ( (Get-VIEventPlus -Entity $vm  -Start $date | ? {$_ -is [VMware.Vim.VmMigratedEvent]})  -or ($failedmigration -ne $null) )

        }

    }

    END {}

}

Reply
0 Kudos
Hadleys_Hope
Contributor
Contributor

Hey Mike.

I had the same question and ended up crafting my own solution.  All I did was add a While loop to check to see if the VM in question had moved to the destination datastore.  Seems to work just fine.  Hope this helps you too.

Full disclosure, this method of vmotion seems to generate an error in PowerCLI but the move works just fine.  Not sure why this happens, some people say closing PowerCLI and opening helps.

#Gets a list of all of the VMs in cluster "SomeCluster" but excludes any VMs that have a name that starts "Backup" useful if you have VMs you dont want to move.

$vms = Get-Cluster SomeCluster | Get-VM | ?{$_.Name -NotLike "Backup*"}

#A list of datastores you want to move to

$destinations = ("Datastore01","Datastore02","Datastore03")

#Works through the list of VMs collected

ForEach($vm in ($vms | Sort-Object ProvisionedSpaceGB))

     {

     #Checks VMs have not already moved.

     if(($vm | Get-Datastore).Name -NotLike "Datastore0*")

          {

          #Finds the datastore with the most free space

          $mostfree = Get-Datastore $destinations | Sort-Object FreeSpaceGB -Descending | Select-Object -First 1

          #Checks to see there is enough free space to move the VM leaving at least 10% free

          if($vm.ProvisionedSpaceGB -lt ($mostfree.FreeSpaceGB) * .9)

               {

               #Updates the screen

               Clear-Host

               Write-Host("Moving $vm to datastore $mostfree")

               #Moves the VM to the datastre witht he most free space

               $vm | Move-VM -Datastore $mostfree

               $current = $vm

               #Checks to see if the vMotion is complete, otherwise loops, wiating 5 seconds in between loops

               While (($current | Get-Datastore).Name -NotLike "Datastore0*")

                    {

                    Sleep 5

                    }

               }

          Else

               {

               Write-Host("Not enough room to move $vm to datastore $mostfree")

               }

          }

     }

Reply
0 Kudos