Migrate VMs From One Datastore to Another – PowerCLI

If you need to move VMs off a datastore for things like; array maintenance or to do non-in place upgrades from older VMFS versions, then a script like this can help. For example, upgrading VMFS3 to VMFS5 is not the same thing. You can read about the differences here VMFS5 VMFS-3, What’s the Deal? If you need to just simply move all VMs from one datastore to another, then this script will get the job done. Even though VMware has come out with storage DRS and datastore maintenance mode, which can be used as well, I still find this script useful.

This assumes that all hosts can see both datastores or a VM move may fail. I set this up to prevent migrations from filling a datastore to over 80% used, and to prevent from the destination datastore being more than 1.5x over provisioned. If a migration will exceed that than every VM is checked before moving serially and any move that violates the constraints will be skipped. You will get a report at the end of the script execution. By default it will attempt to move 3 VMs at a time, but you can specify how many simultaneous moves to do at a time.

NOTE: Requires PowerCLI and a connection to a vCenter server. I would also recommend you use the a minimum of PowerShell v3 ($PSVersionTable) or you may experience unexpected results.

Function Code:

Move-VMsToDatastore

function Move-VMsToDatastore {
<#
.SYNOPSIS
Evacuate all VMs off a datastore by moving them to another datastore
.DESCRIPTION
Move-VMsToDatastore will attempt to migrate VMs from one datastore to another
If there is enough space on the target datastore to move all VMs it will run multiple migrations at once
The default number of concurrent migrations is 3 but can be specified with -ConcurrentMigrations.  If 
free space will be less than 80% or the destination datastore will be overprovisioned more than 1.5x
then the script will default to moving only 1 VM at a time and any VMs that violate those constraints will
be skipped
.EXAMPLE
C:\>Move-VMsToDatastore -SourceDataStore "CLARiiON_LUN1" -DestDataStore "CLARiiON_LUN2"

DESCRIPTION
-----------
This command will try to move all VMs from the datastore named CLARiiON_LUN1 to the datastore named CLARiiON_LUN2
.EXAMPLE
C:\>Move-VMsToDatastore -SourceDataStore "CLARiiON_LUN1" -DestDataStore "CLARiiON_LUN2" -ConcurrentMigrations 5

DESCRIPTION
-----------
This command will try to move all VMs from the datastore named CX_LUN001 to the datastore named CX_LUN002
If there is enough free space on the target datastore to move all VMs, it will run 5 moves simultaneously
#>
[CmdletBinding()]
    param (
        [parameter(Mandatory=$true)]
        [string]$SourceDataStore,
        
        [parameter(Mandatory=$true)]
        [string]$DestDataStore,
        
        [parameter(Mandatory=$false)]
        [int]$ConcurrentMigrations=3,
        
        [parameter(Mandatory=$false)]
        [switch]$SmallestFirst
    )
    PROCESS {
    
        $MAX_OVER_PROV = 1.5    #maximum over provision allowed is 1.5x
        $MIN_FREE = .2            #minimum free space allowed is 20%

        #get src and dest datastores
        try {
            $srcDS = Get-Datastore -Name $SourceDataStore -ErrorAction Stop
            $destDS = Get-Datastore -Name $DestDataStore -ErrorAction Stop
        }
        catch {
            Write-Host "`n$_`n" -ForegroundColor Magenta
            return
        }

        if($srcDS.Length -ne 1) {
            Write-Host "ERROR: Source or Destination name returned many or no datastores" -ForegroundColor Magenta
            return
        }
    
        #environment info
        $destView = $destDS | Get-View
        $srcView = $srcDS | Get-View
        $srcView.RefreshDatastoreStorageInfo()
        $destView.RefreshDatastoreStorageInfo()
        $destFreeGB = $destView.Summary.FreeSpace / 1024 / 1024 / 1024
        $srcUsedGB = ($srcView.Summary.Capacity - $srcView.Summary.FreeSpace) / 1024 / 1024 / 1024
        $minFreeSpaceAllowed = ($destView.Summary.Capacity / 1024 / 1024 / 1024) * $MIN_FREE
        $maxOverprovisioned = ($destView.Summary.Capacity / 1024 / 1024 / 1024) * $MAX_OVER_PROV
        $srcDsProvSpaceGB = ($srcView.Summary.Capacity - $srcView.Summary.FreeSpace + $srcView.Summary.Uncommitted) / 1024 / 1024 / 1024
        $destDsProvSpaceGB = ($destView.Summary.Capacity - $destView.Summary.FreeSpace + $destView.Summary.Uncommitted) / 1024 / 1024 / 1024
        $oneStream = $false
        
        #sorting with the smallest first will move smaller VMs first based on provisioned space
        if($SmallestFirst) {
            try {
                $vmsToMove = Get-VM -Datastore $srcDS | Sort-Object ProvisionedSpaceGB
            }
            catch {
                Write-Host "`n$_`n" -ForegroundColor Magenta
            }
        }
        else {
            try {
                $vmsToMove = Get-VM -Datastore $srcDS | Sort-Object ProvisionedSpaceGB -Descending
            }
            catch {
                Write-Host "`n$_`n" -ForegroundColor Magenta
            }
        }
        
        #no VMs are on source datastore
        if($vmsToMove -eq $null) {
            Write-Host "`nNo VMs found on source datastore " -ForegroundColor Yellow -NoNewline
            Write-Host "$SourceDataStore`n`n" -ForegroundColor Cyan 
            return
        }

        #user specified 1 migration at a time
        if( $ConcurrentMigrations -lt 2 ) {
            $oneStream = $true
        }
        
        #if the free space could be less than 80% after moves, or if the provisioned space will be over 1.5 times capacity, then move 1 at a time
        elseif( (($destFreeGB - $srcUsedGB) -lt $minFreeSpaceAllowed) -or (($srcDsProvSpaceGB + $destDsProvSpaceGB) -gt $maxOverprovisioned) ) {

            $oneStream = $true
            
            Write-Host "`n`n******************WARNING******************" -ForegroundColor DarkYellow
            Write-Host "Not all VMs will safely fit on $DestDataStore" -ForegroundColor DarkYellow
            Write-Host "Only 1 migration will run at a time" -ForegroundColor DarkYellow
            if($SmallestFirst) {
                Write-Host "VMs are scheduled to move SMALLEST->LARGEST" -ForegroundColor DarkYellow
            }
            else {
                Write-Host "VMs are scheduled to move LARGEST->SMALLEST" -ForegroundColor DarkYellow
            }
            Write-Host "and moves that could exceed 80% full or exceed" -ForegroundColor DarkYellow 
            Write-Host "1.5x overprovisioned on the target datastore" -ForegroundColor DarkYellow
            Write-Host "will be skipped" -ForegroundColor DarkYellow
            Write-Host "*******************************************`n`n" -ForegroundColor DarkYellow
        }
        #otherwise move the specified number of systems concurrently
        else {
            Write-Host "`n`nNumber of concurrent moves is set to: "  -ForegroundColor DarkGray -NoNewline
            write-host "$ConcurrentMigrations`n`n" -ForegroundColor Cyan
        }
        
        #move 1 VM at a time checking space after each move
        if($oneStream) {
        
            #start moving VMs
            foreach ($vm in $vmsToMove) {

                $vmProvSpaceGB = $vm.ProvisionedSpaceGB
                $vmUsedSpaceGB = $vm.UsedSpaceGB
                
                #grab new free space and provisioned space and calculate what the destination datastore values will be after the vm is migrated
                $destView.RefreshDatastoreStorageInfo()
                $destDsProvSpaceGB = ($destView.Summary.Capacity - $destView.Summary.FreeSpace + $destView.Summary.Uncommitted) / 1024 / 1024 / 1024
                $destFreeGB = $destView.Summary.FreeSpace / 1024 / 1024 / 1024
                $freeSpaceAfterMoveGB = [System.Math]::Round( ($destFreeGB - $vmUsedSpaceGB), 2)
                $provSpaceAfterMoveGB = [System.Math]::Round( ($destDsProvSpaceGB + $vmProvSpaceGB), 2)
                
                #don't migrate if vm move will bring free space on datastore below 20%
                if( $freeSpaceAfterMoveGB -lt $minFreeSpaceAllowed ) {
                    Write-Host $vm.Name -ForegroundColor Yellow -NoNewline
                    Write-Host " skipped " -ForegroundColor DarkGray -NoNewline
                    Write-Host "$freeSpaceAfterMoveGB GB" -ForegroundColor Yellow -NoNewline
                    Write-Host " sree space is below the allowed " -ForegroundColor DarkGray -NoNewline
                    Write-Host "$minFreeSpaceAllowed GB" -ForegroundColor Yellow
                }
                #don't migrate if vm move will overprovision datastore over 1.5 times the capacity
                elseif( $provSpaceAfterMoveGB -gt $maxOverprovisioned ) {
                    Write-Host $vm.Name -ForegroundColor Yellow -NoNewline
                    Write-Host " skipped " -ForegroundColor DarkGray -NoNewline
                    Write-Host "$provSpaceAfterMoveGB GB" -ForegroundColor Yellow -NoNewline
                    Write-Host " Provisioned is above the allowed " -ForegroundColor DarkGray -NoNewline
                    Write-Host "$maxOverprovisioned GB" -ForegroundColor Yellow
                }
                #ok to migrate vm
                else {
                    Write-Host "Moving " -ForegroundColor DarkGray -NoNewline
                    Write-Host $vm.Name -ForegroundColor Cyan -NoNewline
                    Write-Host " to " -ForegroundColor DarkGray -NoNewline
                    Write-Host $DestDataStore -ForegroundColor Cyan -NoNewline
                    Write-Host " from "  -ForegroundColor DarkGray -NoNewline
                    Write-Host $SourceDataStore -ForegroundColor Cyan
                    
                    try {
                        move-vm -datastore $destDS -VM $vm -ErrorAction Stop | Out-Null
                    }
                    catch {
                        Write-Host "`n$_`n" -ForegroundColor Magenta
                    }
                }
            }
        }
        #if there is enough space then run multiple moves at once (NO DISK SPACE CHECKS AFTER THIS POINT)
        else {
        
            $arrayOfMigrationTasks = @()
        
            foreach ($vm in $vmsToMove) {
                Write-Host "Moving " -ForegroundColor DarkGray -NoNewline
                Write-Host $vm.Name -ForegroundColor Cyan -NoNewline
                Write-Host " to " -ForegroundColor DarkGray -NoNewline
                Write-Host $DestDataStore -ForegroundColor Cyan -NoNewline
                Write-Host " from "  -ForegroundColor DarkGray -NoNewline
                Write-Host $SourceDataStore -ForegroundColor Cyan
                
                #migrate datastore
                try {
                    $arrayOfMigrationTasks += move-vm -datastore $destDS -VM $vm -RunAsync -ErrorAction Stop
                }
                catch {
                    Write-Host "`n$_`n" -ForegroundColor Magenta
                }
                
                #wait for the number of currently migrating systems to fall under the allowed simultaneous moves
                while ( $arrayOfMigrationTasks.Length -gt ($ConcurrentMigrations - 1) ) {
                    Start-Sleep -Seconds 10
                    
                    #pull tasks that are no longer running out of the list of running tasks
                    foreach ($migrationTask in $arrayOfMigrationTasks) {
                        if( (Get-Task -Id $migrationTask.Id).State -ne "Running" ) {
                            [array]$arrayOfMigrationTasks = $arrayOfMigrationTasks | ?{$_.Id -ne $migrationTask.Id}
                        }
                    }
                    Write-Host "Number of migrations still running = " -NoNewline -ForegroundColor DarkGray
                    Write-Host $arrayOfMigrationTasks.Length -ForegroundColor Cyan
                }
            }
        }
        
        #wait for remaining moves to finish before moving on
        while($arrayOfMigrationTasks.Length -gt 0) {
            
            Start-Sleep -Seconds 10
                    
            #pull tasks that are no longer running out of the list of running tasks
            foreach ($migrationTask in $arrayOfMigrationTasks) {
                if( (Get-Task -Id $migrationTask.Id).State -ne "Running" ) {
                    $arrayOfMigrationTasks = $arrayOfMigrationTasks | ?{$_.Id -ne $migrationTask.Id}
                }
            }
            Write-Host "Number of migrations still running = " -NoNewline -ForegroundColor DarkGray
            Write-Host $arrayOfMigrationTasks.Length -ForegroundColor Cyan
        }
        
        #check source datastore for VMs that didn't move
        $vmsRemaining = Get-VM -Datastore $srcDS
        
        if($vmsRemaining.Length -eq 0) {
            Write-Host ""
            Write-Host "ALL VMS HAVE BEEN SUCCESSFULLY EVACTUATED FROM: " -ForegroundColor Gray -NoNewline
            Write-Host $SourceDataStore -ForegroundColor Cyan
            Write-Host ""
        }
        else {
            Write-Host ""
            Write-Host "THE FOLLOWING VMS WERE NOT REMOVED FROM: " -ForegroundColor Gray -NoNewline 
            Write-Host $SourceDataStore -ForegroundColor Cyan
            $vmsRemaining | %{Write-Host $_.name -ForegroundColor Yellow}
            Write-Host ""
        }
        
        return
    }
}

Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>