Querying performance counters from PowerShell

Whether you want to know the CPU load of a system or how busy your network is, querying performance counters provides reliable and fast answers. In fact, PowerShell’s Get-Counter cmdlet makes this task almost trivial. To find out the current CPU load in the past 3 seconds (average), for example, this is all you need:

# Sample interval 3 seconds
$load = (Get-Counter "\Processor(_total)\% Processor Time" -SampleInterval 3).CounterSamples.CookedValue

"Average CPU load in the past 3 seconds was $load%"

All you need to know is the name of the performance counter, and we’ll cover that in a second.

Challenge: Non-US systems

The sample script above will either work beautifully or fail miserably. It simply won’t run on non-US systems. That’s right: performance counter names are localized, so they are differently named, depending on the language your computer uses. Crab but reality.

This is a huge strain. A German script will not run in New York, and a French script fails anywhere outside France.

Replacing performance counter names with ID numbers

There is a clever workaround, though: you can turn language-specific performance counter names into language-neutral ID numbers, then convert these numbers on the target system back into names. This way, you always get the names in the correct language.

Here’s a  function called Get-PerformanceCounterLocalName. When you submit the correct ID number, it returns the localized performance counter name for your system. While the sample code above worked only on US systems, the next sample code will work on any system, regardless of locale:

Function Get-PerformanceCounterLocalName

    $ComputerName = $env:COMPUTERNAME

  $code = '[DllImport("pdh.dll", SetLastError=true, CharSet=CharSet.Unicode)] public static extern UInt32 PdhLookupPerfNameByIndex(string szMachineName, uint dwNameIndex, System.Text.StringBuilder szNameBuffer, ref uint pcchNameBufferSize);'

  $Buffer = New-Object System.Text.StringBuilder(1024)
  [UInt32]$BufferSize = $Buffer.Capacity

  $t = Add-Type -MemberDefinition $code -PassThru -Name PerfCounter -Namespace Utility
  $rv = $t::PdhLookupPerfNameByIndex($ComputerName, $id, $Buffer, [Ref]$BufferSize)

  if ($rv -eq 0)
    $Buffer.ToString().Substring(0, $BufferSize-1)
    Throw 'Get-PerformanceCounterLocalName : Unable to retrieve localized name. Check computer name and performance counter ID.'

$processor = Get-PerformanceCounterLocalName 238
$percentProcessorTime = Get-PerformanceCounterLocalName 6

(Get-Counter "\$processor(_total)\$percentProcessorTime" -SampleInterval 1).CounterSamples.CookedValue

As you can see, instead of hard-coding localized performance counter names, the code uses Get-PerformanceCounterLocalName with ID numbers 238 and 6 to get the localized names, then uses those to get the performance data.

Which raises the question: Where do these numbers come from? And what other performance counters of interest are there?

Finding other performance counter names

Let’s first see how you can find all the other performance counter names, and then, how you can turn them into ID numbers. To see all the performance counters available, you can use this line:

Get-Counter -ListSet * | Select-Object -ExpandProperty Counter
\TBS counters\CurrentResources
\TBS counters\CurrentContexts
\WSMan Quota Statistics(*)\Process ID
\WSMan Quota Statistics(*)\Active Users
\WSMan Quota Statistics(*)\Active Operations
\WSMan Quota Statistics(*)\Active Shells

But beware: it is a huge list (and it may look different on your side, again, depending on your language settings). A better way may be to filter the general category. This would list all performance counters related to “processor”:

Get-Counter -ListSet *processor* | Select-Object -ExpandProperty Counter
\Processor Information(*)\Processor State Flags
\Processor Information(*)\% of Maximum Frequency
\Processor Information(*)\Processor Frequency
\Processor Information(*)\Parking Status
\Processor Information(*)\% Priority Time
\Processor Information(*)\C3 Transitions/sec
\Processor Information(*)\C2 Transitions/sec
\Processor Information(*)\C1 Transitions/sec
\Processor Information(*)\% C3 Time
\Processor Information(*)\% C2 Time
\Processor Information(*)\% C1 Time
\Processor Information(*)\% Idle Time
\Processor Information(*)\DPC Rate
\Processor Information(*)\DPCs Queued/sec
\Processor Information(*)\% Interrupt Time
\Processor Information(*)\% DPC Time
\Processor Information(*)\Interrupts/sec
\Processor Information(*)\% Privileged Time
\Processor Information(*)\% User Time

Let’s pick \Processor Information(*)\Processor Frequency counter: on a US system, you could easily query the processor frequency like this:

PS> Get-Counter "\Processor Information(*)\Processor Frequency" -SampleInterval 1

Timestamp                 CounterSamples
---------                 --------------
17.07.2013 22:35:00       \\tobiasair1\processor information(_total)\processor frequency

                          \\tobiasair1\processor information(0,_total)\processor
                          frequency :

                          \\tobiasair1\processor information(0,3)\processor frequency :

                          \\tobiasair1\processor information(0,2)\processor frequency :

                          \\tobiasair1\processor information(0,1)\processor frequency :

                          \\tobiasair1\processor information(0,0)\processor frequency :

So in your counter name, “(*)” stands for “all counter instances”, and as you can see, there are two total counts and four additional counts for my four-core-machine, representing each core. If I wanted to monitor a specific core, I could have written:

# Sample interval 1 seconds
$freq = (Get-Counter "\Processor Information(0,0)\Processor Frequency" -SampleInterval 1).CounterSamples.CookedValue

"Average CPU frequency in CPU Core 0 in the past second was $freq MHz"

Note how I replaced “(*)” with “(0,0)” in the counter name to monitor my first core CPU.

Translating performance counter names

In order for the script code to run on any machine, not just US systems, we now need to translate the performance counter names to their corresponding ID numbers. Here’s how:

function Get-PerformanceCounterID

    if ($script:perfHash -eq $null)
        Write-Progress -Activity 'Retrieving PerfIDs' -Status 'Working'

        $key = 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\CurrentLanguage'
        $counters = (Get-ItemProperty -Path $key -Name Counter).Counter
        $script:perfHash = @{}
        $all = $counters.Count

        for($i = 0; $i -lt $all; $i+=2)
           Write-Progress -Activity 'Retrieving PerfIDs' -Status 'Working' -PercentComplete ($i*100/$all)
           $script:perfHash.$($counters[$i+1]) = $counters[$i]


Get-PerformanceCounterID -Name 'Processor Information'
Get-PerformanceCounterID -Name 'Processor Frequency'

The function Get-PerformanceCounterID can translate the localized name part to a generic ID number. So the two localized strings correspond to ID numbers 1848 and 1884:

PS> Get-PerformanceCounterLocalName 1848
Processor Information

PS> Get-PerformanceCounterLocalName 1884
Processor Frequency

Now, you can turn the script into a globalized version that runs on any locale:

$ProcessorInformation = Get-PerformanceCounterLocalName 1848
$ProcessorFrequency = Get-PerformanceCounterLocalName 1884

$freq = (Get-Counter "\$ProcessorInformation(0,0)\$ProcessorFrequency" -SampleInterval 1).CounterSamples.CookedValue
"Average CPU frequency in CPU Core 0 in the past second was $freq MHz"


By turning localized performance counter names into ID numbers, working with performance counters finally is possible across different cultures, producing truly language-independent scripts. The trick is to convert the performance counter name into a culture-neutral ID number, then to use this number on the target system to get the correct performance counter name.

Filed in: Articles, Online Only Tags: , , ,

10 Responses to "Querying performance counters from PowerShell"

  1. Peter Kriegel says:

    Hi Tobias !
    many thanks to you for this awesome article!
    This comes right in handy, because we have discussed this issue in the german PowerShell forum! (are you read along there ? 😉 )

    see you on the next german PowerShell conference! The Last one was very exiting!


    Peter Kriegel

  2. Klaus says:

    Very handy PowerShell functions! Thank you very much for sharing.

    I face one problem with Get-PerformanceCounterID. For example “Aktuelle Warteschlangenlänge” exists with 3 different IDs, but only the last is returned.

  3. Klaus says:

    In the function Get-PerformanceCounterID I have changed the content of the for-loop with this:
    if ($counters[$i+1] -ne $null) {
    if ($script:perfHash.$($counters[$i+1]) -eq $null) {
    $script:perfHash.$($counters[$i+1]) = $counters[$i]
    } else {
    $script:perfHash.$($counters[$i+1]) += “, ” + $counters[$i]

    This will return a comma separated list of all IDs for the given -Name if that name exists more than once. You still need to try which ID is the one you look for, but at least you know which IDs to try.

    • Pit says:

      @Klaus, good point. I adopted your approach as follows:

      for($i = 0; $i -lt $all; $i+=2)
      $ctrId = $Counters[$i]
      $ctrName = $Counters[$i+1] # can be empty, at least on my test system :-/
      if ($ctrName)
      Write-Progress -Activity ‘Retrieving PerfIDs’ -Status ‘Working’ -PercentComplete ($i*100/$all)
      if ($script:perfHash.ContainsKey($ctrName))
      $script:perfHash.$ctrName += $ctrId
      $script:perfHash.$ctrName = @($ctrId)

  4. DarkTerror says:

    Thank you for contribution, i remake the Get-Id for better speed and performance

    function Get-PerformanceCounterID
    $key = ‘Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\CurrentLanguage’
    $counters = (Get-ItemProperty -Path $key -Name Counter).Counter
    $CounterID = $counters[$([array]::indexof($counters,$Name)-1)]
    return $CounterID

    Get-PerformanceCounterID -Name ‘Processor Information’
    Get-PerformanceCounterID -Name ‘Processor Frequency’

    Best Regards!

  5. Yusuf Ozturk says:

    This API does not work with some counter names if they are part of another feature or module. It seems, only native counter ID’s are unique, but if you are getting additional counters by adding extra role/feature from Add Roles and Features, then this API is completely useless. So it’s impossible to rely on this API 🙁


    On English OS:
    PS C:\Users\Administrator> Get-PerformanceCounterID “Hyper-V Dynamic Memory VM”

    On French OS:
    PS C:\Users\Administrateur> Get-PerformanceCounterLocalName 7696
    Physical Pages Allocated


  6. Namak says:


    Very nice posting and community.

    I am trying to collect some performance statistics for when a batch job is running. So I am polling counters like CPU and Memory. I write this out to file. The problem I am having is that the data is written to 2 rows. When I then try to import into MS Excel for some reporting it gets confused because 1 observation is spread over 2 rows.

    Data example:

    12/03/2015 16:17:53 \\machinename\memory\available mbytes :

    The PowerShell code I used was:

    get-counter -counter “\memory\committed bytes” -sampleinterval 1 -maxsamples 10
    Out-file C:\Temp\result.txt -append

    Any hint would be appreciated


  7. Peter says:

    Nice post,

    Why is the ID returned on every Windows OS version different ? and how to build a list to get an all round language and OS independent script?

    Get-PerformanceCounterID outputs another value on Server 2008 than 2012.


  8. Peter says:


    looks like it’s only at the % User Time, % Processor Time and’% Privileged Time , the other counters like eg Working Set – Private, ID Process etc have the same ID on every Windows OS version.


  9. Philippe Gervaix says:

    On Windows2012 R2 with Hyper-v only , i dont have this command Get-PerformanceCounterID
    Which module i need to install for use this command
    Thx in advance

Leave a Reply

Submit Comment

© 8322 PowerShell Magazine. All rights reserved. XHTML / CSS Valid.
Proudly designed by Theme Junkie.
%d bloggers like this: