A powershell script which balances the load of Hyper-V VMs across all cluster nodes

Hi everyone,

I run a 2 node cluster with Hyper-V. While debugging and testing, i often have to reboot a node and end up with all my VM's on one node, and find myself moving them arround manually.

So i came up with the below script to automate this.

Let me know how it works for you!



 P.S. I'm happy to see that this found it's way as a feature into SCVMM 2012. It's called Dynamic Optimization (DO)

# VmBalancer.ps1 - A powershell script which balances the load of Hyper-V VMs across all cluster nodes
# Environment: Windows Server 2008 R2, Hyper-V, Failover cluster
# How it works:
#  All cluster nodes are checked for their virtual to logical processor ratio.
#  Then the average of this values is value is calculated for the entire cluster
#  Then the 2 nodes with the maximum and minimum distance to this average are calculated
#  If the minimum distance is greater then the maximum, the least loaded host is very likely capable
#  to take a VM from the maximum loaded host. So the script does LiveMigrate -one- VM per run 
# Notes:
#  The script does run standalone, if you set Set-ExecutionPolicy correctly. I run it as a Scheduled Task
#  every 30 minutes on each cluster node. The script checks if it runs on the one node currently owning the
#  Cluster Group, to ensure it is not run simulateneously on multiple nodes.
#  on Each run, only one VM might be moved, to allow for one LiveMigration at a time
#  When a VM is moved, an event from VM Balancer is written to the Application Event log.
# Thoughts:
#  Once you reboot a cluster node, by default, the running VM's are migrated to other nodes. When the node comes
#  back online, VM's are not moved back. You may set Prefered Owners and define a failback policy, but this is
#  rather static. In future, the script might make use of VMM's star rating for best placement, but for now it should also run without VMM
#  The recommended virtual to logical processer ratio is 8:1. Courtesy of algorithm
#  See:
#  It might happen that the destination node is not able to take the VM as it has not enough memory. This should be
#  covered by LiveMigration checks and the VM is then not moved.

# Warning:
#  This script had limited testing, on my 2 identical node cluster. Use at own risk after your own testing.
# robertvi at microsoft.com
# v1.0 101007

Import-Module FailoverClusters  # load cluster cmdlet

$maxload = 0
$minload = 1000
$maxloadedhost = 'undefined'
$minloadedhost = 'undefined'

#ensure we run on one node only
$master = Get-ClusterGroup |  ?{ $_.Name -like "Cluster Group" }
$thishost = Get-WmiObject -class win32_computersystem

$master = $master.OwnerNode.Name.ToLower()
$thishost = $thishost.Name.ToLower()

Write-Host "Cluster Group owner:" $master "Script Host:" $thishost "<"

if ( $master -ne $thishost)
 Write-Host "Exiting Script as it is not run on the node that owns the Cluster Group"


# Get all nodes
$nodes = get-clusternode

foreach ($node in $nodes)

 $Hostinfo = Get-WmiObject -class win32_computersystem -computername $node
 Write-Host "Host " $Hostinfo.Name
 $load = (@(gwmi -ns root\virtualization MSVM_Processor -computername $node).count / (@(gwmi Win32_Processor -computername $node) | measure -p NumberOfLogicalProcessors -sum).Sum)  
 Write-Host "Load " $load

 if ($load -ge $maxload)
  $maxload = $load
  $maxloadedhost = $node

 if ($load -le $minload)
  $minload = $load
  $minloadedhost = $node


 $averageload += $load
 $numberofnodes += 1

$averageload /= $numberofnodes
Write-Host "Average Load " $averageload
Write-Host "Max Load " $maxload
Write-Host "Min Load " $minload

# Now if the maximum loaded host - minimum loaded host is still above average, push a VM from maximum to minimum.

if (($maxload - $averageload) -gt $minload) #is maxload distance to average greater then minloads distance?
 Write-Host "Push a VM from" $maxloadedhost "to " $minloadedhost

 #find a running VM on $maxloadedhost and move to $minloadedhost

 $VMGroups = Get-ClusterNode $maxloadedhost.Name | Get-ClusterGroup | ?{ $_ | Get-ClusterResource | ?{ $_.ResourceType -like "Virtual Machine" } }

 foreach ($vm in $VMGroups)

    if ($vm.State -eq 'Online')
         Write-Host "VM Group to mmigrate" $vm.name

  # This is our best candidate. May still not possible to move when destination memory not sufficent.
  # LiveMigration will determine this and abort the migrate.
  # if Quick Migration should be used: Move-ClusterGroup $vm -Node $minloadedhost

  $evtinfo = "Moving " + $vm + " to Node " + $minloadedhost + " Avg load " +$averageload + " maxload " + $maxload + " minload " + $minload
  $evt=new-object System.Diagnostics.EventLog("Application")
  $evt.Source="VM Balancer"

          Move-ClusterVirtualMachineRole $vm -Node $minloadedhost -Wait 0



  Write-Host "No Balancing needed"


Comments (5)

  1. Ramazan Can [MVP] says:

    …as usual, great stuff Robert 😉

  2. Misha says:

    What if I have 4 nodes in the cluster? What current script does, is taking ALL VMs from the most loaded and move ALL them to the less loaded, and after that i am getting the less loaded as a most loaded :)…. Is it possible to add some extra logic and calculate how many VMs should be loaded from the most loaded node to to other nodes, members of the cluster? Thanks, Misha

  3. drumbsd says:

    I created a project on github with a modified version of this scripts to balance in the proper way the virtual machine across all hosts in the cluster to not have much more of difference > 1 ratio between the high and low host. The code is here:


  4. jabadm says:


    I'm having an issue with the WMI class MSVM_Processor. Getting error below:

    gwmi : Invalid class "MSVM_Processor"

    At C:scriptsvm_balance_vms_across_all_nodes.ps1:47 char:16

    +                 $load = (@(gwmi -ns rootvirtualization MSVM_Processor -computername $node). …

    +                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

       + CategoryInfo          : InvalidType: (:) [Get-WmiObject], ManagementException

       + FullyQualifiedErrorId : GetWMIManagementException,Microsoft.PowerShell.Commands.GetWmiObjectCommand

    Server 2012r2, Powershell V4, any assistance with why that class is an error for me would be appreciated.

  5. CPippin says:

    Change the load line to this –

    $load = (@(gwmi -namespace rootvirtualizationv2 MSVM_Processor -computername $node).count / (@(gwmi Win32_Processor -computername $node) | measure -property NumberOfLogicalProcessors -sum).Sum)

    The namespace in 2012R2 has been changed from what I read and found.

Skip to main content