PowerCLI: Discover Idle VMs

There are a number of operations management tools available which can allow you to discover virtual machines which can be categorised as idle, where they continue to run  with a low utilisation profile and can be part of the overall VM sprawl in your environment.

I was recently investigating how to retrieve Idle VMs based on their CPU, Disk and Network usage by querying statistical information from vCenter, this was all possible using PowerCLI and math functions within Powershell.

Firstly we will connect to the VI server and ¬†retrieve a collection of VMs and filter these to only return VMs where the Power State is equal to ‘PoweredOn’.

Connect-VIServer server1.domain.local
$VMs = Get-VM | Where-Object {$_.PowerState -eq "PoweredOn"} 

For each virtual machine retrieved in the collection we will want to retrieve statistical information for the following stats:

  • cpu.usagemhz.average
  • disk.usage.average
  • net.usage.average

For each stat retrieved we will want to use a date range where the start date is thirty days in the past and the finish date is the current date.  For Each metric retrieved we will count the number of objects in total and count the number of objects returned where the value is less or equal that specified  and then use the match function within Powershell to calculate this as a percentage.

In this example, the following thresholds were used:

Stat Unit Value
cpu.usagemhz.average Mhz 100
disk.usage.average KBps 20
net.usage.average KBps 1
$Output =ForEach ($VM in $VMs) 
     { 
     
    $CPUStat = Get-Stat -Entity $VM.Name -Stat cpu.usagemhz.average -Start (Get-Date).AddDays(-30) -Finish (Get-Date) 
    $CPUIdle = $CPUStat | Where-Object {$_.Value -le "100"} 
    $CPUDetection = ($CPUIdle.Count / $CPUStat.Count) *100 
    
    $DiskStat = Get-Stat -Entity $VM.Name -Stat disk.usage.average -Start (Get-Date).AddDays(-30) -Finish (Get-Date) 
    $DiskIdle = $DiskStat | Where-Object {$_.Value -le "20"} 
    $DiskDetection = ($DiskIdle.Count / $DiskStat.Count) * 100

    $NetworkStat = Get-Stat-Entity $VM.Name-Stat net.usage.average -Start (Get-Date).AddDays(-30) -Finish (Get-Date) 
    $NetworkIdle = $NetworkStat | Where-Object {$_.Value -le "1"} 
    $NetworkDetection = ($NetworkIdle.Count / $NetworkStat.Count) * 100

Now that we have retrieved the statistical information and calculated the percentage of samples that were less or equal to the  threshold value we will identity VMs that are believed to be idle by specifying that 90% of the returned samples were below the threashold limit for each stat retrieved and output this information to include the VM Name and the average value of each metric for the date range.

If ($CPUDetection -ge "90"-and $DiskDetection -ge "90"-and $NetworkDetection -ge "90")
        { 
        "" | Select @{N="Name";E={$VM.Name}},
        @{N="CPU Usage (Mhz)";E={[Math]::Truncate(($CPUStat.Value | Measure-Object-Average).Average)}},
        @{N="Disk I/O Usage (KBps)";E={[Math]::Truncate(($DiskStat.Value | Measure-Object-Average).Average)}},
        @{N="Network I/O Usage (KBps)";E={[Math]::Truncate(($NetworkStat.Value | Measure-Object-Average).Average)}}
        }     
    }
$Output | Export-Csv -Path D:\Output\IdleVMS.csv -NoTypeInformation

The above can be downloaded in full from the below, where the values can be modified to meet your requirements, the default is to use the above values as above.

https://app.box.com/s/4lxyezl55blfnsf1kk9m

The script can be run as below:

 ./Get-IdleVMs.ps1 -CpuMhz 200 -DiskIO 15 -NetworkIO 2 -Percentage 85 -Days 10 -vCenter server1.domain.local

Using Annotations to Terminate VMs based on Date (Part 2)

I recently talked about using termination dates on VMs in order to control VM sprawl in the datacenter with the help of using virtual machine type annotations (http://wp.me/p15Mdc-n1).

Now that we have identified the termination date and notified the user who requested the VM that it is approaching the end of its lifetime, how do we manage VMs that can now be marked as expired based on their termination date and which actions do we wish to invoke agaisnt them?

There are number of actions I will be looking at based on the below:

  • Shut Down Guest
  • Move the VM to a Resource Folder
  • Remove the VM and delete from the datastore.

As I will be invoking the script as a scheduled task I will add the PowerCLI snap-ins to the current powershell and then connect to my vcenter server and  build a collection of our VMs and loop through each VM in the collection to return virtual machine annotation types required to filter which VMs have exceeded their termination dates:

If (-not (Get-PSSnapin VMware.VimAutomation.Core -ErrorAction SilentlyContinue)) 
    {
    Add-PSSnapin VMware.VimAutomation.Core
    }

Connect-VIServer server1.domain.local 
$VMs=Get-VM
ForEach ($VM in $VMs)
    { 
    $Termination = (Get-VM $VM | Get-Annotation | Where-Object {$_.Name -eq "Termination Date"}).Value 
    $RequestedBy = (Get-VM $VM | Get-Annotation | Where-Object {$_.Name -eq "Requested By"}).Value
    If ($Termination -gt (Get-Date))
        { 

Shut Down Guest 

Firstly, we will look at simply shutting down the VM, as before we will build a collection of our VMs and loop through each VM in the collection to return virtual machine annotation types required to filter which VMs have exceeded their termination dates, we will simplify invoke the Shutdown-VMGuest cmdlet agasint the filtered VM and specify that the request does not require user confirmation.

Get-VM $VM | Shutdown-VMGuest -Confirm:$False
         } 
    } 

 Move the VM to a Resource Folder

Now we look to moving a VM to a resource folder by invoking the Move-VM cmdlet agaisnt all VMs in the collection where the termination date was greater than the current date. In this example the VM is being moved to the resource folder ‘Archive’.

Move-VM-VM $VM -Destination "Archive" 
        } 
    }

Remove the VM

A slightly more risky and permanent action would be to completely remove the VM once the termination date was greater than the current using the Remove-VM cmdlet and specifying the ‘DeletePermanently’ parameter to remove the VM not only from the inventory but from the datastore as well.

Remove-VM $VM -DeletePermanently -Confirm:$false 
        } 
    } 

Using Annotations to Terminate VMs based on Date (Part 1)

When users request a number of VMs which are not going to persistent in your datacenter this can lead to a large VM sprawl if not managed correctly. One method to do this would be to create a number of virtual machine type annotations based on the creation of the VM to include tags to  filter VMs that are now longer required.

In this example, I will be using the following virtual machine type annotations to filter VMs:

  • Termination Date
  • Requested By
  • Service Request

When creating VMs the above annotations will be require the value to be populated in the following format:

Annotation Type Value
Termination Date Date dd/MM/yyyy
Requested By Email user@domain.com
Service Request URL 1234

The requirements will be to alert via email those users who have requested VMs that are to expire in the next three days and provide the URL to the original service request if they require to extend the lifetime.

This process can run on a daily schedule by invoking the powershell script where the VMware PowerCLI cmdlets are loaded into the session.

So lets get started and load the cmdlets and connect to the a vcenter server (in this example, server1.domain.local).

if (-not (Get-PSSnapin VMware.VimAutomation.Core -ErrorAction SilentlyContinue)) 
   { 
   Add-PSSnapin VMware.VimAutomation.Core > $null
   } 

Connect-VIServer server1.domain.local

Once we have the cmdlets loaded in our powershell session and connected to the vcenter server, we will build a collection of VMs using the Get-VM cmdlet and loop through each VM returned in the collection:

$VMs = Get-VM 

ForEach ($VM in $VMs)
{

We will now return the value of the virtual machine type annotations listed above;

$Termination = (Get-VM $VM | Get-Annotation | Where-Object {$_.Name -eq "Termination Date"}).Value 
$RequestedBy = (Get-VM $VM | Get-Annotation | Where-Object {$_.Name -eq "Requested By"}).Value
$ServiceRequest = (Get-VM $VM | Get-Annotation | Where-Object {$_.Name -eq "Service Request"}).Value

In order to compate the termination date we will use conditional logic to determine if the termination date is greater than three days in the past

If ($Termination -gt (((Get-Date).AddDays(-3)).ToString("dd/MM/yyyy")))

If the statement is true will generate the URL of the service request by appending the $ServiceRequest variable to the link structure and send an email to the user who requested the VM to include the termination date and the link to extend the lifetime of the VM.

{
$URL = ("http://Server/Departments/Helpdesk/Lists/Service%20Requests/DispForm.aspx?ID=$ServiceRequest")
Send-MailMessage -From admin@domain.com -To $RequestedBy -Subject "VM Termination Notice" -Body "The virtual machine $VM you requested is due for termination on $Termination.`n`nIn order to extend the lifetime of this virtual machine, please update $URL." -SmtpServer server2.domain.local
} 

}

The user whom made the request will now receive an email to notify that the VM is nearing the end of its lifetime and is due for termination, as below:

TerminationCapture