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.


The script can be run as below:

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

Generate system uptime report of VMs using PowerCLI

It is possible to return the system uptime of your VMs by retrieving the last statistical information for the metric ‘sys.uptime.latest’ in realtime using PowerCLI. For Example, to return the metric for the virtual machine ‘VM1’ we could run the following command:

Get-Stat -Entity VM1 -Stat sys.uptime.latest -Realtime -MaxSamples 1

This will return the uptime value in seconds, we can convert this using the New-Timespan cmdlet to convert the value, by using the value property returned below and invoking the cmdlet agaisnt this value.

$Uptime = Get-Stat-Entity $VM.Name -Stat sys.uptime.latest -Realtime -MaxSamples 1
$Timespan = New-Timespan- Seconds $Uptime.Value

For Example, if the value returned was ‘4235900’, invoking the New-TimeSpan value would return the following:

Days : 49
Hours : 0
Minutes : 38
Seconds : 20
Milliseconds : 0
Ticks : 42359000000000
TotalDays : 49.0266203703704
TotalHours : 1176.63888888889
TotalMinutes : 70598.3333333333
TotalSeconds : 4235900
TotalMilliseconds : 4235900000

So based on the above we could provide a more detailed and understandable duration for the up time of the VM, For Example, if I wanted to include Days, Minutes and Seconds I could run the following :

Get-Stat -Entity VM1 -Stat sys.uptime.latest -Realtime -MaxSamples 1
$Timespan = New-Timespan -Seconds $Uptime.Value
"" + $Timespan.Days + " Days, " + $Timespan.Hours + " Hours, " + $Timespan.Minutes + " Minutes"

This would return the following output, based on the new timespan value returned.

49 Days, 0 Hours, 38 Minutes

We can¬†also include the Powered On time of the VM as well using the Get-VIEvent by filtering¬†the ‘FullFormattedMessage’ for the string ‘powered on’ and returning the created time for the last event. ¬†By default, the Get-VIEvent, will return only 100 events (‘MaxSamples’), depending how long the¬†VM has been powered on for the filter may not return the event to which we are looking for.

An option could be to set the MaxSamplesSize to the maximum value, one disadvantage of this is that it can depending on your environment take a while to return the value required.

From the above we can build a collection of VMs and run the script block agaisnt each VM in the collection and output to a file.

Connect-VIServer server1.domain.local 

$VMs = Get-VM | Where-Object {$_.PowerState -eq "PoweredOn"}
$Output = ForEach ($VM in $VMs)

    "" | Select @{N="Name";E={$VM.Name}},
    @{N="Powered On";E={$Event = Get-VM $VM.Name | Get-VIEvent -MaxSamples [int]::MaxValue | Where-Object {$_.FullFormattedMessage -like "*powered on*"} | Select-First 1 
    @{N="Up Time";E={$Timespan = New-Timespan -Seconds (Get-Stat -Entity $VM.Name -Stat sys.uptime.latest -Realtime -MaxSamples 1).Value
    "" + $Timespan.Days + " Days, "+ $Timespan.Hours + " Hours, " +$Timespan.Minutes + " Minutes"}}
$Output | Export-Csv -Path "D:\Output\VMUptimeReport.csv "-NoTypeInformation

Retrieve statistical information for all virtual machines in a object level hierarchy

So the challenge was to obtain stats from a collection of all virtual machines in a particular object hierarchy within vCenter, this being either the datacenter, cluster, resource pool or folder level.

The requirement was to capture all metrics that are for each virtual machine for a 24 hour period, using the closest available statistical interval (default value of ‘5’ minutes) and output to a CSV file.

In order to complete this task, I would require to download and install the powershell interface for vSphere; VMware vSphere PowerCLI ¬†–¬†communities.vmware.com/community/vmtn/server/vsphere/automationtools/powercli.

Firstly, I would need to specify a number of variables, in order to connect to vCenter server, the container object  and to specify the output file; these currently are manually entered into the script, however these could be passed as parameters from the scripting interface using arguments or using the read-host cmdlet for manual user input.

# Powershell variables required to be completed to store vCenter Server and output information
$VCenter = “” # Enter the vCenter Server hostname or IP address. For Example, SERVER1.domain.local
$ObjectName = “” #Enter the name of the vCenter Server container object. For Example, Production.
$CSVLocation = “” # Enter the output folder to export the CSV file. For Example, D:\Output\.
$CSVFileName = “” # Enter the filename for the exported CSV file. For Example, ProductionVM

As we are capturing the previous 24 hours, I will also require to to output the date and time sting to the CSV filename in the following format  DDMMYYYY_HHMM

# Powershell variable to add the timestamp as a suffix for the filename.
$Date = (get-date).toString(‘ddMMyyyy_HHmm’)

As the process will be required be automated, I will also need to the add the registered snap-in for PowerCLI to the current session to invoke the required cmdlets;

# Adds registered vSphere PowerCLI snap-ins to the current session
Add-PSsnapin VMware.VimAutomation.Core >$null

Now we will connect to the vCenter server passing the variable specified

# Connect to the vCenter Server
Connect-VIServer $VCenter >$null

Now that we are connected we will need obtain all the virtual machines that exist in the object container in the variable specified:

# Collects all virtual machines in the specified vCenter Server container object
$VM = Get-VM -Location ($ObjectName)

Once the virtual machines in the above object container have been obtained, I will need to loop through each virtual machine to firstly obtain all metrics being captured by running the Get-StatType cmdlet agaisnt the virtual machine and pipeing the output to a variable.

The virtual machine and metrics variable will then be passed to the Get-Stat cmdlet, as stated previously we will be required to obtain all metrics for a 24 hour period so the Start and Finish parameters will use the Date variable, with the substraction method to use the previous day as the start time for the entity.  The returned output is then filtered to only include Entity, Timestamp, MetricID, Value and Unit for each virtual machine , this is then exported to a CSV file.

ForEach ($i in $VM)
# Retrieves the statistical infromation for each virtual machine for the previous 24 hours using a 5 minute interval and exports to a CSV file.
$Metrics = Get-VM $i.Name | Get-StatType
$Stats = Get-Stat -Entity $i.Name -Stat $metrics -Start (Date).AddDays(-1) -Finish (Date)
$Stats | Select-Object Entity, Timestamp, MetricID, Value, Unit | Export-Csv ($CSVLocation + $CSVFileName + “_” + $Date + “.csv”) -NoTypeInformation

The script (Get-ObjectContainerStats.ps1) in its entirety may be downloaded from: