Scripting a DRS Replacement Using PowerCLI

One of the things that I LOVED about having an evaluation copy of vCenter (other than Storage vMotion) is the use of Distributed Resource Scheduler (DRS). I loved the fact that I could let my VMs live on their own and vCenter would intelligently balance the load across all the VM hosts (and shut them down if they weren’t needed). When I finally had to move from the eval mode to a licensed server I felt a little let down because some of my VM hosts would be at 30% memory utilization while others would be at 70% memory utilization. Maybe it’s just me but that seems like a real waste of resources. 🙂

I recently found out it’s really bad when you need to power off a host for maintenance and then need to move the VMs back to the original host. To make my life a lot easier I created the following script. It balances memory usage across all the hosts to about 10% of the average. I also make certain specific VMs always placed on certain hosts (my vCenter server is always on the first host and my SQL servers are always on different hosts). This feature is enabled if a CSV is located in the same directory as the script (named balanced.csv) with a Name column and a host column that have the name of the VM and the host it should be placed on respectively. The script assumes you already have a connection to your vCenter server (using Connect-VIServer).

Set-StrictMode -version 2

function GetPercentages(){
       # get all the hosts
       $objHosts = get-vmhost;

       # loop through each of the hosts and calculate the percentage of memory used
       foreach($objHost in $objHosts){
               $objHost | add-member NoteProperty PercentMemory ($objHost.MemoryUsageMB/$objHost.MemoryTotalMB *100)
               $objHost
       }
}

function GetVMToMove($strHost, $arrAllVMs){
       #get all the VMs that are powered on on this host
       $objVMs = get-vm | where {($_.Host.Name -eq $strHost) -and ($_.PowerState -eq "PoweredOn")}

       # check to see if there is a static file
       if(test-path "balanced.csv"){
               # read in the static file
               $arrStatic = import-csv "balanced.csv"

               # loop through all the static entries
               foreach($objStatic in $arrStatic){
                       #echo $objStatic.VM;
                       $objVM = $objVM | where {$_.Name -ne ($objStatic.Name)}
               }
       }

       # sort based on the amount of memory used
       $objVMs = $objVMs | sort "Memory*"

       # return the VM using the least amount of RAM
       $objVMs[0];
}

# get all the VMs
$arrAllVMs = get-vm | where {$_.PowerState -eq "PoweredOn"}

# check to see if there is a static file
if(test-path "balanced2.csv"){
       # read in the static file
       $arrStatic = import-csv "balanced.csv"

       # move the static VMs to the correct location
       echo "Moving static VMs to the correct host."

       # loop through all the VMs
       foreach($objStatic in $arrStatic){
               # get the VM
               $objVM = get-VM $objStatic.Name;

               # check to see if the VM is not on the correct host
               if($objVM.Host.Name -ne $objStatic.Host){
                       echo ("Moving " + $objStatic.Name);
                       #move the VM
                       $objVM | move-vm -destination (get-vmhost $objStatic.Host)
               }
       }
}

# a flag to indicate that we moved a VM
$bMoved = $true;

# keep looping through the list until no VMs have been moved
while($bMoved){
       # get the hosts with percentages
       $objHosts = GetPercentages | sort PercentMemory -descending | select Name, PercentMemory

       # calculate the average memory
       $intAverage = 0;
       $intCount = 0;
       foreach($objHost in $objHosts){
               $intAverage += $objHost.PercentMemory;
               $intCount++;
       }
       $intAverage /= $intCount;

       # display the average memory used
       echo ("Average is " + $intAverage);

       # display the hosts so it's easier to know what's going on
       $objHosts | select Name, PercentMemory;

       # determine the host with the least amount of RAM used
       $strDest = $objHosts[$objHosts.Count-1].Name;

       # this is a flag to see if anything has been moved
       $bMoved = $false;

       # loop through the hosts
       foreach($objHost in $objHosts){
               # check to see if this is more than 10% of the average
               if($objHost.PercentMemory -gt ($intAverage + 10)){
                       # get the name of the VM to move
                       $objVM = GetVMToMove $objHost.Name

                       # display some output to let the user know what is being moved
                       echo ('Moving ' + $objVM.Name + " from " + $objHost.Name);

                       #move the VM
                       $objVM | move-vm -destination (get-vmhost $strDest);

                       # flag that a VM has been moved
                       $bMoved = $true;
               }
       }
}

2 thoughts on “Scripting a DRS Replacement Using PowerCLI

  1. Hi. This is a great idea.
    Rather than relying on a csv file you could store the host affinity as a custom attribute against the guest.
    If you were to for instance call this HostAffinity, it’s value can be checked as follows:
    $objVM = get-vm -Name myguest
    if ($objVM.Host.Name -ne $objVM.CustomFields[“HostAffinity”])
    {
    write-host $objVM.name ” needs moving”
    }

  2. Pingback: VMware Host Cluster VM memory balancing script implementation | Ryan Mangan's IT Blog

Leave a Reply

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