Use PowerShell to Simplify Collecting Performance Information

 Summary: Learn how to use Windows PowerShell to simplify collecting performance information from your servers.

Hey, Scripting Guy! Question

Hey, Scripting Guy! We are having performance problems with several of our servers. It is really strange, because if you were to look at the specifications, you would think they are awesome boxes. What I need to do is to collect performance counter information on these things, but I would like to be able to use a script because there are several boxes that need to be analyzed. I really need to find the performance bottleneck, because I do not think the servers are underpowered; I think there is something amiss instead. The thing is that my boss wants to add memory because he says, “With Windows you always need more RAM.” However, if he buys a bunch of expensive RAM and it does not solve the problem, he will blame me. Help, the situation is getting serious.


Hey, Scripting Guy! Answer Hello AC,

Microsoft Scripting Guy, Ed Wilson, here. Back when I was in consulting I used to do a lot of performance testing on servers, and I thought it was a lot of fun. What is cool is when you find evidence of a particular problem and you solve a serious issue. One thing I found out is that people really want to see the evidence before they will make changes—especially changes that cost money. Therefore, in your case, your boss is a little strange in this regard. Oh well. In the past, the solution for automating performance counters was to use the WMI classes. With Windows PowerShell 2.0, the WMI classes are not required, because there are some excellent cmdlets.

One of the problems with automating performance counters is knowing which counter to add. Windows PowerShell solves that problem by allowing you to use counter sets. To see the available performance counter sets, use the Get-Counter cmdlet with the listset parameter. In the following command I list the names of all the counter sets on my Windows 7 laptop.

PS C:\> Get-Counter -ListSet * | Sort-Object CounterSetName | Select-Object CounterSetName




.NET CLR Networking

.NET CLR Networking

.NET Data Provider for Oracle

.NET Data Provider for SqlServer

.NET Memory Cache 4.0


ASP.NET Applications

ASP.NET Apps v4.0.30319

ASP.NET State Service

ASP.NET v4.0.30319

Authorization Manager Applications




Client Side Caching

Distributed Routing Table

Event Tracing for Windows

Event Tracing for Windows Session

Fax Service

Generic IKEv1, AuthIP, and IKEv2

HTTP Service

HTTP Service Request Queues

HTTP Service URL Groups



Internet Information Services Global



IPsec AuthIP IPv4

IPsec AuthIP IPv6

IPsec Driver

IPsec IKEv1 IPv4

IPsec IKEv1 IPv6

IPsec IKEv2 IPv4

IPsec IKEv2 IPv6



Job Object

Job Object Details



MSDTC Bridge

MSDTC Bridge

NBT Connection


Network Inspection System

Network Interface


Offline Files

OpsMgr Connector

Pacer Flow

Pacer Pipe

Paging File

Peer Name Resolution Protocol

Per Processor Network Activity Cycles

Per Processor Network Interface Card Activity


Power Meter

Print Queue



Processor Information

RAS Port

RAS Total

ReadyBoost Cache


Search Indexer


Server Work Queues











TBS counters




Teredo Client

Teredo Relay

Teredo Server

Terminal Services

Terminal Services Session





WF (System.Workflow)




Windows Media Player Metadata

Windows Workflow Foundation

WSMan Quota Statistics

Suppose I am interested in monitoring memory-related performance counters (something that you should probably consider anyway). I need to supply the path to the specific counter to the Get-Counter Windows PowerShell cmdlet in order to monitor the counter. The nice thing is that I do not have to know the path to the counter, nor do I need to type all of those paths. The reason is that I can retrieve the paths from the memory counter set. To do this I use dotted notation and retrieve the paths. Here is the command to do this.

PS C:\> (get-Counter -ListSet memory).paths

\Memory\Page Faults/sec

\Memory\Available Bytes

\Memory\Committed Bytes

\Memory\Commit Limit

\Memory\Write Copies/sec

\Memory\Transition Faults/sec

\Memory\Cache Faults/sec

\Memory\Demand Zero Faults/sec


\Memory\Pages Input/sec

\Memory\Page Reads/sec

\Memory\Pages Output/sec

\Memory\Pool Paged Bytes

\Memory\Pool Nonpaged Bytes

\Memory\Page Writes/sec

\Memory\Pool Paged Allocs

\Memory\Pool Nonpaged Allocs

\Memory\Free System Page Table Entries

\Memory\Cache Bytes

\Memory\Cache Bytes Peak

\Memory\Pool Paged Resident Bytes

\Memory\System Code Total Bytes

\Memory\System Code Resident Bytes

\Memory\System Driver Total Bytes

\Memory\System Driver Resident Bytes

\Memory\System Cache Resident Bytes

\Memory\% Committed Bytes In Use

\Memory\Available KBytes

\Memory\Available MBytes

\Memory\Transition Pages RePurposed/sec

\Memory\Free & Zero Page List Bytes

\Memory\Modified Page List Bytes

\Memory\Standby Cache Reserve Bytes

\Memory\Standby Cache Normal Priority Bytes

\Memory\Standby Cache Core Bytes

So how do I use all these performance counter paths? The first thing I like to do is to store them into a variable, but that is not completely necessary because I can use the counter paths directly as shown here:

Get-Counter -Counter (get-Counter -ListSet memory).paths

The reason for storing the counter paths in a variable is so I can group them later. For now, I store the memory counter paths in a variable named $memory. I then use the Get-Counter cmdlet to retrieve an instance of the performance information for each of the counters in the variable. Truncated output of the process is shown here:

PS C:\> $memory=(get-Counter -ListSet memory).paths

PS C:\> Get-Counter -Counter $memory

Timestamp CounterSamples

——— ————–

1/22/2011 7:43:18 PM \\edwils1\memory\page faults/sec :


\\edwils1\memory\available bytes :


\\edwils1\memory\committed bytes :


<output truncated>

By default, the Get-Counter Windows PowerShell cmdlet retrieves a single instance of each of the counter’s values. I can tell the cmdlet to retrieve multiple samples, and I can specify the interval between performance snapshots. The minimum interval between samples is one second. The maximum number of samples is 9223372036854775807 because MaxSamples is a datatype of int64. Using the MaxValue static property of the int64 class, I found out the maximum value. The code to do this is shown here:

PS C:\> [int64]::MaxValue


Of course, if I really want to monitor a counter on a continuous basis, I use the Continuous parameter. When I monitor a set of counters continuously, I use the Control+C keyboard shortcut to halt performance monitor collection and to return to the Windows PowerShell console. The command to do this is shown here:

PS C:\> Get-Counter -Counter $memory -SampleInterval 1 -Continuous

Timestamp CounterSamples

——— ————–

1/22/2011 8:04:04 PM \\edwils1\memory\page faults/sec :



If I want to parse the data I collect, I store the results into a variable. The command to take five samples at one-second intervals, using the memory performance counters, is shown here (remember that earlier I stored all the memory performance counter paths in the $memory variable):

PS C:\> $counters = Get-Counter -Counter $memory -SampleInterval 1 -MaxSamples 5

After I have the performance values stored in the $counters variable, I can parse the data by using standard Windows PowerShell techniques. The first thing to do is to find out which type of object I have stored in the variable, and to see which properties it exposes. To do this, I use the Get-Member cmdlet in the manner that appears here:

PS C:\> $counters | Get-Member

TypeName: Microsoft.PowerShell.Commands.GetCounter.PerformanceCounterSampleSet

Name MemberType Definition

—- ———- ———-

Equals Method bool Equals(System.Object obj)

GetHashCode Method int GetHashCode()

GetType Method type GetType()

ToString Method string ToString()

CounterSamples Property Microsoft.PowerShell.Commands.GetCounter.PerformanceCounterSample[…

Timestamp Property System.DateTime Timestamp {get;set;}

Readings ScriptProperty System.Object Readings {get=$strPaths = “”…

This information tells me two things. The first is that I am dealing with a collection of data (I kind of figured that because I took five different readings at one-second intervals). The second thing I see is more significant, and that is that there is a property called CounterSamples, and it is a rich object as indicated by the Microsoft.PowerShell.Commands.GetCounter.PerformanceCounterSample[… property type information. Because I have a collection to deal with, it means that I will need to select an individual performance sample to work with. I can pipe the $Counters variable to the Foreach-Object cmdlet, or I can use the Foreach statement and walk through the data, or I can index into the collection. While I am still exploring, I will index into the collection and explore the CounterSamples property as illustrated here:

PS C:\> $counters[0].CounterSamples | Get-Member

TypeName: Microsoft.PowerShell.Commands.GetCounter.PerformanceCounterSample

Name MemberType Definition

—- ———- ———-

Equals Method bool Equals(System.Object obj)

GetHashCode Method int GetHashCode()

GetType Method type GetType()

ToString Method string ToString()

CookedValue Property System.Double CookedValue {get;set;}

CounterType Property System.Diagnostics.PerformanceCounterType CounterType {get;set;}

DefaultScale Property System.UInt32 DefaultScale {get;set;}

InstanceName Property System.String InstanceName {get;set;}

MultipleCount Property System.UInt32 MultipleCount {get;set;}

Path Property System.String Path {get;set;}

RawValue Property System.UInt64 RawValue {get;set;}

SecondValue Property System.UInt64 SecondValue {get;set;}

Status Property System.UInt32 Status {get;set;}

TimeBase Property System.UInt64 TimeBase {get;set;}

Timestamp Property System.DateTime Timestamp {get;set;}

Timestamp100NSec Property System.UInt64 Timestamp100NSec {get;set;}

The information that appears is standard performance counter type of information. The displayed properties bear a strong resemblance to the properties of the WMI performance counter classes. I like the cooked values, the path, and the time stamp. I sort by the path to group my counters together, and then wrap the table in case any paths are too long. The percentage sign (%) is an alias for the Foreach-Object cmdlet, sort is an alias for the Sort-Object cmdlet, and ft is an alias for the Format-Table cmdlet. The command to select the data I am interested in examining appears here.

$counters | % { $_.counterSamples } | sort path | ft timestamp, path, cookedvalue -Wrap –AutoSize

When the previous command runs, the output shown in the following image appears.

Image of script output

AC, that is all there is to getting started with using the Get-Counter Windows PowerShell cmdlet. Performance Week will continue tomorrow when I will talk about additional ways of using the Get-Counter cmdlet.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

Comments (6)

  1. AHMAD says:

    Does running the above solution affects the performance of the production server?

  2. Cholo says:


    How come when I run

    Get-Counter -counter "ASP.NETRequests Queued" -computername server1

    it works. But when I tried to run it to a list of servers say,

    Get-Counter -counter "ASP.NETRequests Queued" -computername (get-content D:ServerList.csv)

    Let’s say ServerList.csv file contains the following:


    some servers it’s ok and some servers are throwing an error saying

    "Get-Counter : The specified object was not found on the computer.
    At line:1 char:12
    + get-counter <<<< -counter ‘ASP.NETRequests Queued’ -computername (get-content D:ServerList.csv)
    + CategoryInfo : InvalidResult: (:) [Get-Counter], Exception
    + FullyQualifiedErrorId : CounterApiError,Microsoft.PowerShell.Commands.GetCounterCommand"

  3. Cholo says:

    I have mistyped it the line: Get-Counter -counter "ASP.NETRequests Queued" -computername (get-content D:ServerList.csv) it should be Get-Counter -counter "ASP.NETRequests Queued" -computername (get-content D:ServerList.csv)

  4. Bob De Lyser says:

    These counters work on a SQLServer 2008R2 box:
    "MemoryAvailable MBytes",
    "Paging File% Usage",
    "PhysicalDisk(*)Avg. Disk sec/Read",
    "PhysicalDisk(*)Avg. Disk sec/Write",
    "PhysicalDisk(*)Disk Reads/sec",
    "PhysicalDisk(*)Disk Writes/sec",
    "Processor(*)% Processor Time",
    "SystemProcessor Queue Length"
    But these do NOT:
    "MSSQL$SQL2012:Buffer ManagerPage life expectancy",
    "MSSQL$SQL2012:General StatisticsUser Connections",
    "MSSQL$SQL2012:Memory ManagerMemory Grants Pending",
    "MSSQL$SQL2012:SQL StatisticsSQL Compilations/sec",
    "MSSQL$SQL2012:SQL StatisticsSQL Re-Compilations/sec",
    "MSSQL$SQL2012:SQL StatisticsBatch Requests/sec",
    Here is the counter string when created direct in PerfMon:
    MSSQL$SQL2012:Buffer ManagerPage life expectancy
    MSSQL$SQL2012:General StatisticsUser Connections
    MSSQL$SQL2012:Memory ManagerMemory Grants Pending
    MSSQL$SQL2012:SQL StatisticsSQL Compilations/sec
    MSSQL$SQL2012:SQL StatisticsSQL Re-Compilations/sec
    MSSQL$SQL2012:SQL StatisticsBatch Requests/sec

    I would like to be able to use:
    $Ver = "$SQL2012"
    # $Ver = "$SQL2008R2"
    "MSSQL" + $Ver + ":Buffer ManagerPage life expectancy",
    so it works on different SQL Server versions, but the string concatenation doesn’t work either.


  5. Bob De Lyser says:

    Previous post was tried on SQL2012 box and SQL2008R2 box, which is why post mixes them server version.

  6. Stephen Weeks says:

    Recently I have started to follow your blogs and I have become a real fan of your. Thanks for showing me your tricks and lessons. It has really helped me and I most certainly will stay tuned for the next episode.

    Cheers Ed and thanks again!