How to Create Automated Hyper-V Performance Reports

Save to My DOJO

How to Create Automated Hyper-V Performance Reports

Wouldn’t it be nice to periodically get an automatic performance review of your Hyper-V VMs? Well, this blog post shows you how to do exactly that.

Hyper-V Performance Counters & Past Material

Over the last few weeks, I’ve been working with Hyper-V performance counters and PowerShell, developing new reporting tools. I thought I’d write about Hyper-V Performance counters here until I realized I already have.
https://www.altaro.com/hyper-v/performance-counters-hyper-v-and-powershell-part-1/
https://www.altaro.com/hyper-v/hyper-v-performance-counters-and-powershell-part-2/
Even though I wrote these articles several years ago, nothing has really changed. If you aren’t familiar with Hyper-V performance counters I encourage you to take a few minutes and read these. Otherwise, some of the material in this article might not make sense.

Get-CimInstance

Normally, using Get-Counter is a better approach, especially if you want to watch performance over a given timespan. But sometimes you just want a quick point in time snapshot. Or you may have network challenges. As far as I can tell Get-Counter uses legacy networking protocols, i.e. RPC and DCOM. This does not make them very firewall friendly. You could use PowerShell Remoting and Invoke-Command to run Get-Counter on the remote server. Or you can use Get-CimInstance which is what I want to cover in this article.

When you run Get-Counter, you are actually querying performance counter classes in WMI. This means you can get the same information using Get-CimInstance, or Get-WmiObject. But because we want to leverage WSMan and PowerShell Remoting, we’ll stick with the former.

Building a Hyper-V Performance Report

First, we need to identify the counter classes. I’ll focus on the classes that have “cooked” or formatted data.

$computer = $env:computername
Get-CimClass -ClassName *perfformatted*hyper* -ComputerName $computer | Select-Object -ExpandProperty Cimclassname

I’m setting a variable for the computername so that you can easily re-use the code. I’m demonstrating this on a Windows 10 desktop running Hyper-V but you can just as easily point $Computer to a Hyper-V host.

It is pretty easy to leverage the PowerShell pipeline and create a report for all Hyper-V performance counters.

"$($computer.toupper()) Hyper-V Performance Report" | Out-file c:workperf.txt
Get-CimClass -ClassName *perfformatted*hyper* -ComputerName $computer | 
foreach-object  {
 Write-Host "Querying $($_.cimclassname)" -foreground green
 $_.CimClassName | Out-file c:workperf.txt -append
 Get-CimInstance -ClassName $_.CimClassName -ComputerName $computer | out-file c:workperf.txt -Append
}

The text file will list each performance counter class followed by all instances of that class. If you run this code, you’ll see there are a number of properties that won’t have any values. It might help to filter those out. Here’s a snippet of code that is a variation on the text file. This code creates an HTML report, skipping properties that likely will have no value.

Get-CimClass -ClassName *perfformatted*hyper* -ComputerName $computer | 
foreach-object -begin {$frags=@("<H1>Hyper-V Performance Report</H1>")} -process  {
    write-Host "Processing $($_.CimClassName)" -ForegroundColor Cyan
    $split = $_.CimClassName -split "_"
    #count how many properties
    if ($_.CimClassProperties.where({$_.name -notmatch "caption|description|frequency|timestamp"}).count -gt 7) {
        $as = "list"
    } else {
        $as = "table"
    }
    $frags+= "<h2>$($split[-1])</h2>"
    $frags+= Get-CimInstance -ClassName $_.CimClassName -ComputerName $computer | 
    Select-Object * -ExcludeProperty CimClass,Cim*Properties,PS*,Timestamp*,Frequency*,Caption,Description |
    ConvertTo-Html -Fragment -as $as
} -end {
 ConvertTo-HTML -CssUri C:scriptssample2.css -Body $frags -Title "Perf Report $computer" |
 Out-File c:workperf.html
}

This code creates an HTML report using fragments. I also am dynamically deciding to create a table or a list based on the number of properties.

HTML Performance Counter Report

Thus far I’ve been creating reports for all performance counters and all instances. But you might only be interested in a single virtual machine. This is a situation where you can take advantage of WMI filtering.

In looking at the output from all classes, I can see that the Name property on these classes can include the virtual machine name as part of the value. So I will go through every class and filter only for instances that contain the name of VM I want to monitor.

$VMName = "srv2"
#group each entry by its classname
$g = get-cimclass -ClassName *perfformatted*hyper* -ComputerName $computer |
foreach-object { 
   write-host "Querying $($_.cimclassname)" -foreground green
    Get-Ciminstance -ClassName $_.CimClassName -filter "Name Like '%$VMName%'" -ComputerName $computer |
Select-object * -ExcludeProperty Cim*Properties,Timestamp*,Frequency*,Caption,Description -ov s |
group-object -Property {$_.cimclass.CimClassname } 
 
}
#create the array of html fragments
$frags=@("<H1>Hyper-V Performance Report: $($VMName.ToUpper())</H1>")
$frags+="<H2 Hyper-V Host: $($computer.toUpper())"
foreach ($item in $g) {
    Write-Host "Creating fragment for $($item.name)" -ForegroundColor Cyan
    $frags+="<h2>$($item.name)</h2>"
    $frags+= $item.group | Select-object * -ExcludeProperty PS*Computername,CIMClass |
    ConvertTo-Html -Fragment -as List
}

$frags+="<h5><i>Report run $(Get-Date)</i></h5>"

ConvertTo-HTML -CssUri C:scriptssample2.css -Body $frags -Title "Perf Report $($vmname.toupper())" |
 Out-File "c:work$($vmname.tolower()).html"

This example also adds a footer to the report showing when it was created.

HTML Performance Report for a Single VM

It doesn’t take much more effort to create a report for each virtual machine. I turned my code into the beginning of a usable PowerShell function, designed to take pipeline input.

Function New-HTMLPerfReport {
[cmdletbinding()]
Param(
[Parameter(Position = 0, Mandatory, ValueFromPipelinebyPropertyName,ValueFromPipeline)]

[ValidateNotNullorEmpty()]
[string]$VMName,
[Parameter(Position = 0, Mandatory, ValueFromPipelinebyPropertyName)]
[ValidateNotNullorEmpty()]
[string]$Computername,
[string]$DestinationPath = ".",
[switch]$Passthru
)

Begin {}
Process {
    #this function lacks error handling
    Write-Host "Analyzing VM $($vmname.toupper()) on $($computername.toUpper())" -foreground magenta

    $file = Join-Path -Path $DestinationPath -ChildPath "$($vmname.tolower())-perf.html"
    #group each entry by its classname
    $g = get-cimclass -ClassName *perfformatted*hyper* -ComputerName $computername |
    foreach-object { 

       write-verbose "Querying $($_.cimclassname)" 
       Get-Ciminstance -ClassName $_.CimClassName -filter "Name Like '%$VMName%'" -ComputerName $computername |
        Select-object * -ExcludeProperty Cim*Properties,Timestamp*,Frequency*,Caption,Description -ov s |
        Group-Object -Property {$_.cimclass.CimClassname } 
   
    }

    #create the array of html fragments
    $frags=@("<H1>Hyper-V Performance Report: $($VMName.ToUpper())</H1>")
    $frags+="<H2 Hyper-V Host: $($computer.toUpper())"
    foreach ($item in $g) {
        Write-Verbose "Creating fragment for $($item.name)" 
                $frags+="<h2>$($item.name)</h2>"
        $frags+= $item.group | Select-object * -ExcludeProperty PS*Computername,CIMClass |
        ConvertTo-Html -Fragment -as List
    }

    $frags+="<h5><i>Report run $(Get-Date)</i></h5>"

    Write-Verbose "Creating HTML file $file"
    ConvertTo-HTML -CssUri C:scriptssample2.css -Body $frags -Title "Perf Report $($vmname.toupper())" |
    Out-File -FilePath $file
    
    if ($Passthru) {
        Get-Item -Path $file
    } 
}

End {}

}

With this function, I can query as many virtual machines and create a performance report for each.

Get-VM -computername <your VMHost> | where state -eq running | New-HTMLPerfReport -Passthru -DestinationPath c:reports

You can take this idea a step further and run this as a PowerShell scheduled job, perhaps saving the report files to an internal team web server.

I have at least one other intriguing PowerShell technique for working with Hyper-V performance counters, but I think I’ve given you enough to work with for today so I’ll save it until next time.

Wrap-Up

Did you find this useful? Have you done something similar and used a different method? Let us know in the comments section below!

Thanks for Reading!

Altaro Hyper-V Backup
Share this post

Not a DOJO Member yet?

Join thousands of other IT pros and receive a weekly roundup email with the latest content & updates!

3 thoughts on "How to Create Automated Hyper-V Performance Reports"

Leave a comment or ask a question

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

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

Notify me of follow-up replies via email

Yes, I would like to receive new blog posts by email

What is the color of grass?

Please note: If you’re not already a member on the Dojo Forums you will create a new account and receive an activation email.