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

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

Rate This
  • Comments 5

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"


  • ...as usual, great stuff Robert ;-)

  • 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

  • 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:


  • Hi,

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

    gwmi : Invalid class "MSVM_Processor"

    At C:\scripts\vm_balance_vms_across_all_nodes.ps1:47 char:16

    +                 $load = (@(gwmi -ns root\virtualization 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.

  • Change the load line to this -

    $load = (@(gwmi -namespace root\virtualization\v2 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.

Page 1 of 1 (5 items)
Leave a Comment
  • Please add 5 and 8 and type the answer here:
  • Post