Use PowerShell to Check Your Server’s Performance

Summary: Microsoft Scripting Guy, Ed Wilson, teaches you how to use Windows PowerShell to check your server’s performance.

Hey, Scripting Guy! Question

Hey, Scripting Guy! I need to use Windows PowerShell to collect performance information from my servers, but I also would like to parse the data after it is collected. Is that something I can do with Windows PowerShell?


Hey, Scripting Guy! AnswerHello BT,

Microsoft Scripting Guy, Ed Wilson, here. The more time I spend with Windows PowerShell, the more amazed I become at the power, and the ease-of-use of this incredible tool. I am not saying this because I am the Microsoft Scripting Guy; I was saying things like this before I assumed my current position. When I look at some of my VBScript scripts that I used in the past for performance monitoring and I compare them with Windows PowerShell, it becomes clear how far we have progressed. A good example is the GetProcessorPerformance.vbs script I wrote for my WMI book. In that script, I use the SwbemRefresher object to obtain fresh performance data. It was a cool technique (but then my old Osborne computer was cool too.) The GetProcessorPerformance.vbs script appears here.


strComputer = “.”

wmiNS = “\root\cimv2”

wmiQuery = “’_Total'”

Set objWMIService = GetObject(“winmgmts:\\” & strComputer & wmiNS)

Set objRefresher = CreateObject(“WbemScripting.SWbemRefresher”)

Set objItem = objRefresher.Add(objWMIService,wmiQuery).object


For i = 1 To 4


Wscript.echo “InterruptsPersec:” & objItem.InterruptsPersec

Wscript.echo “Name:” & objItem.Name

Wscript.echo “PercentIdleTime:” & objItem.PercentIdleTime

Wscript.echo “PercentInterruptTime:” & objItem.PercentInterruptTime

Wscript.echo “PercentPrivilegedTime:” & objItem.PercentPrivilegedTime

Wscript.echo “PercentProcessorTime:” & objItem.PercentProcessorTime

Wscript.echo “PercentUserTime:” & objItem.PercentUserTime & vbcrlf

WScript.sleep 3000


I can accomplish exactly the same thing in seven lines of code. The longest portion of the script creates an array of specific performance counter paths and stores the resulting array in a variable named $cpu. The actual command that gathers four performance counter sample sets at three-second intervals is a single line of code. Even if the number of lines of code were the same, the Windows PowerShell code is much easier to read. The complete Get-ProcessorPerformance.ps1 script appears here.


$cpu = “\processor(_total)\interrupts/sec”,

“\processor(_total)\% idle time”,

“\processor(_total)\% interrupt time”,

“\processor(_total)\% privileged time”,

“\processor(_total)\% processor time”,

“\processor(_total)\% user time”

Get-Counter -Counter $cpu -SampleInterval 3 -MaxSamples 4

When the Get-ProcessorPerformance.ps1 script runs, the output appears as shown in the following image.

Image of script output

If I do not need to select specific performance monitor counters from the list set, I can reduce the Get-ProcessorPerformance.ps1 script to a single line of code. This line of code appears here.

Get-Counter -Counter (Get-Counter -listset processor).paths -MaxSamples 4 -SampleInterval 3

There are generally four resources I like to monitor on a server: Memory, Disk, Processor, and Network. Combining these four groups of performance counters, is made easy by the fact that the Counter parameter accepts an array of strings that point to the counter paths. I can easily add arrays together.

The first thing I do is retrieve the processor performance counter paths and store them in a variable named processor. This is seen here.

PS C:\> $processor = (Get-Counter -listset processor).paths

PS C:\> $processor

\Processor(*)\% Processor Time

\Processor(*)\% User Time

\Processor(*)\% Privileged Time


\Processor(*)\% DPC Time

\Processor(*)\% Interrupt Time

\Processor(*)\DPCs Queued/sec

\Processor(*)\DPC Rate

\Processor(*)\% Idle Time

\Processor(*)\% C1 Time

\Processor(*)\% C2 Time

\Processor(*)\% C3 Time

\Processor(*)\C1 Transitions/sec

\Processor(*)\C2 Transitions/sec

\Processor(*)\C3 Transitions/sec

Next, I retrieve the physical disks counters. I use the physical disks because that is where the activity is, in relation to performance. Whereas it is possible to have several logical disks carved out of one physical disk, that one physical disk can only spin so fast, be in use so long, and the queue length can only be so great. I store the physical disk counter paths in the $physicalDisk variable. I like to check to see what is actually contained in the variable – an error is not generated if I type a non-existent list set, and therefore I always verify that I have the paths I seek. This is shown here.

PS C:\> $physicalDisk = (Get-Counter -listset physicaldisk).paths

PS C:\> $physicalDisk

\PhysicalDisk(*)\Current Disk Queue Length

\PhysicalDisk(*)\% Disk Time

\PhysicalDisk(*)\Avg. Disk Queue Length

\PhysicalDisk(*)\% Disk Read Time

\PhysicalDisk(*)\Avg. Disk Read Queue Length

\PhysicalDisk(*)\% Disk Write Time

\PhysicalDisk(*)\Avg. Disk Write Queue Length

\PhysicalDisk(*)\Avg. Disk sec/Transfer

\PhysicalDisk(*)\Avg. Disk sec/Read

\PhysicalDisk(*)\Avg. Disk sec/Write

\PhysicalDisk(*)\Disk Transfers/sec

\PhysicalDisk(*)\Disk Reads/sec

\PhysicalDisk(*)\Disk Writes/sec

\PhysicalDisk(*)\Disk Bytes/sec

\PhysicalDisk(*)\Disk Read Bytes/sec

\PhysicalDisk(*)\Disk Write Bytes/sec

\PhysicalDisk(*)\Avg. Disk Bytes/Transfer

\PhysicalDisk(*)\Avg. Disk Bytes/Read

\PhysicalDisk(*)\Avg. Disk Bytes/Write

\PhysicalDisk(*)\% Idle Time

\PhysicalDisk(*)\Split IO/Sec

After I have the path to the physical disk counters, I grab the memory counter paths, and store them in a variable named $memory. This is shown here.

PS C:\> $memory = (Get-Counter -listset memory).paths

PS C:\> $memory

\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

The last set of counters I need to obtain are the counters for my network adapter. That counter set is called Network Interface, and I store the paths in a variable called $network. This is seen here.

PS C:\> $network = (Get-Counter -listset ‘network interface’).paths

PS C:\> $network

\Network Interface(*)\Bytes Total/sec

\Network Interface(*)\Packets/sec

\Network Interface(*)\Packets Received/sec

\Network Interface(*)\Packets Sent/sec

\Network Interface(*)\Current Bandwidth

\Network Interface(*)\Bytes Received/sec

\Network Interface(*)\Packets Received Unicast/sec

\Network Interface(*)\Packets Received Non-Unicast/sec

\Network Interface(*)\Packets Received Discarded

\Network Interface(*)\Packets Received Errors

\Network Interface(*)\Packets Received Unknown

\Network Interface(*)\Bytes Sent/sec

\Network Interface(*)\Packets Sent Unicast/sec

\Network Interface(*)\Packets Sent Non-Unicast/sec

\Network Interface(*)\Packets Outbound Discarded

\Network Interface(*)\Packets Outbound Errors

\Network Interface(*)\Output Queue Length

\Network Interface(*)\Offloaded Connections

PS C:\>

After I have the four counter set paths working the way I want, I add the arrays together and store the results in a variable called $mycounters. This appears here:

$mycounters = $processor + $physicalDisk + $memory + $network

Once again I check the variable as shown in the following image.

Image of $mycounters variable

The command to collect all this performance information appears here.

$perf = Get-Counter -Counter $mycounters -SampleInterval 3 -MaxSamples 4

I can then parse the data that is stored in the $perf variable by using normal Windows PowerShell techniques.

For a quick review: The following commands are used to gather the processor, physical disk, memory, and network performance counters. I typed them during an interactive Windows PowerShell console session, but you might want to put them into a script, so you can easily run them on multiple servers.

$processor = (Get-Counter -listset processor).paths

$physicalDisk = (Get-Counter -listset physicaldisk).paths

$memory = (Get-Counter -listset memory).paths

$network = (Get-Counter -listset ‘network interface’).paths

$mycounters = $processor + $physicalDisk + $memory + $network

$perf = Get-Counter -Counter $mycounters -SampleInterval 3 -MaxSamples 4


BT, that is all there is to parsing performance data. Performance week will continue tomorrow when I will talk about importing counters.

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 (3)

  1. kaushik says:

    How can we continuously monitor the CPU performance (CPU Usage) and get an alert (email) whenever there is a breach in the specified threshold limit?

  2. Troy says:

    Post was very helpful for me.

    Helped me create a script a powershell memory usage script that monitors my servers.…/monitoring-memory-usage-with-powershell

Skip to main content