The essentials
Quick reference
One focused task per row. Jump to the related section for complete, working examples.
| Use | Syntax | Examples |
|---|---|---|
| List counter sets | Get-Counter -ListSet '*' |
Sort-Object CounterSetName |
Select-Object CounterSetName, CounterSetType | View examples |
| Inspect processor paths | Get-Counter -ListSet 'Processor Information' |
Select-Object -ExpandProperty PathsWithInstances | View examples |
| Sample total CPU | Get-Counter -Counter `
'\Processor Information(_Total)\% Processor Utility' `
-SampleInterval 2 -MaxSamples 15 | View examples |
| Sample memory pressure | Get-Counter -Counter `
'\Memory\Available MBytes','\Memory\Pages/sec' `
-SampleInterval 2 -MaxSamples 15 | View examples |
| Sample physical disk latency | Get-Counter -Counter `
'\PhysicalDisk(*)\Avg. Disk sec/Read','\PhysicalDisk(*)\Avg. Disk sec/Write' `
-MaxSamples 5 | View examples |
| Sample disk queues | Get-Counter -Counter `
'\PhysicalDisk(*)\Current Disk Queue Length' `
-SampleInterval 2 -MaxSamples 15 | View examples |
| Sample network throughput | Get-Counter -Counter `
'\Network Interface(*)\Bytes Total/sec' `
-SampleInterval 2 -MaxSamples 15 | View examples |
| Extract cooked values | $samples.CounterSamples |
Select-Object Timestamp, Path, InstanceName, CookedValue, Status | View examples |
| Average valid samples | $samples.CounterSamples |
Where-Object Status -eq 0 |
Group-Object Path |
ForEach-Object { $_.Group.CookedValue |
Measure-Object -Average } | View examples |
| Snapshot costly processes | Get-Process |
Sort-Object CPU -Descending |
Select-Object -First 15 Id, ProcessName, CPU, WorkingSet64, PrivateMemorySize64 | View examples |
| Sample process CPU rates | Get-Counter -Counter '\Process(*)\% Processor Time' `
-SampleInterval 2 -MaxSamples 5 | View examples |
| Sample a remote host | Get-Counter -ComputerName 'server01.contoso.com' `
-Counter '\Memory\Available MBytes' -MaxSamples 5 | View examples |
| Write a BLG export | $samples |
Export-Counter -Path './perf-sample.blg' -FileFormat blg | View examples |
| Read a BLG capture | Import-Counter -Path './perf-sample.blg' |
Select-Object -ExpandProperty CounterSamples | View examples |
| List collector sets | logman.exe query | View examples |
| Correlate System events | Get-WinEvent -FilterHashtable @{ LogName='System'; `
StartTime=(Get-Date).AddHours(-1) } -MaxEvents 100 | View examples |
A useful performance investigation measures a bounded interval, preserves counter semantics, and correlates saturation with workload and time. One sample is rarely a baseline, a high percentage is not automatically a bottleneck, and counter names are localized. Get-Counter is Windows-only in Microsoft.PowerShell.Diagnostics and can query remote performance-counter infrastructure without PowerShell remoting when permissions and firewall policy allow.
Step by step
Detailed examples
Discover localized counter paths on the target
Performance object, counter, and instance names are localized, so English paths copied from documentation can fail on another display language. Use Get-Counter -ListSet on each target class, retain PathsWithInstances, and confirm the counter provider is enabled. Get-Counter is available only on Windows and was reintroduced in PowerShell 7; some protected sets require an elevated session or Performance Monitor Users membership. Discovery is read-only and has no WhatIf switch.
Get-Module Microsoft.PowerShell.Diagnostics | Select-Object Name, Version
Get-Counter -ListSet 'Processor*' | Select-Object CounterSetName, CounterSetType, Paths
Get-Counter -ListSet 'Memory' | Select-Object -ExpandProperty Paths Collect an interval instead of treating one point as a baseline
Counter values can be instantaneous, cumulative, or calculated from successive reads. Specify SampleInterval and MaxSamples so collection ends predictably and records a time window. Align CPU, memory, storage, and workload measurements in one call when practical. Continuous mode runs until interrupted and is unsuitable for unattended collection without explicit cancellation, storage, and retention controls. Sampling is read-only, requires no reboot, and does not support WhatIf.
$paths = @(
'\Processor Information(_Total)\% Processor Utility',
'\Memory\Available MBytes',
'\Memory\Pages/sec'
)
$samples = Get-Counter -Counter $paths -SampleInterval 2 -MaxSamples 15
$samples.CounterSamples | Select-Object Timestamp, Path, CookedValue, Status Interpret utilization together with demand, latency, and topology
A queue can be normal during a burst, low throughput can accompany high latency, and aggregate _Total values can hide a hot instance. Compare disk service time with queues and operations, network throughput with link speed and errors, and processor utility with run queues and workload expectations. Counter thresholds are workload- and hardware-specific; establish normal percentiles during comparable business periods before alerting.
$diskPaths = @(
'\PhysicalDisk(*)\Disk Reads/sec',
'\PhysicalDisk(*)\Disk Writes/sec',
'\PhysicalDisk(*)\Avg. Disk sec/Transfer',
'\PhysicalDisk(*)\Current Disk Queue Length'
)
Get-Counter -Counter $diskPaths -SampleInterval 2 -MaxSamples 15 Preserve timestamps, paths, instances, units, and status
CookedValue is the calculated value for a sample, while Path and InstanceName identify what it measured. Status zero indicates a successful sample; missing or recycled instances can produce invalid values. Keep raw sample sets for audit and calculate percentiles as well as averages when spikes matter. Do not round or convert units until presentation, and never merge similarly named process instances without an identity strategy.
$valid = $samples.CounterSamples | Where-Object Status -eq 0
$valid | Group-Object Path | ForEach-Object {
$measure = $_.Group.CookedValue | Measure-Object -Minimum -Maximum -Average
[pscustomobject]@{ Path = $_.Name; Samples = $measure.Count; Minimum = $measure.Minimum; Average = $measure.Average; Maximum = $measure.Maximum }
} Correlate system pressure with processes and events
Get-Process CPU is cumulative processor time since process start, not current utilization; WorkingSet64 is resident memory, not total private commitment. Process performance-counter instance names can be recycled and suffixes can change as processes exit. Capture PID-aware snapshots near the counter interval, record workload changes, and correlate a bounded event-log window. Reading another user's processes or protected logs may require elevation.
$now = Get-Date
Get-Process | Sort-Object WorkingSet64 -Descending | Select-Object -First 15 Id, ProcessName, StartTime, CPU, WorkingSet64, PrivateMemorySize64
Get-WinEvent -FilterHashtable @{ LogName='System'; StartTime=$now.AddMinutes(-30); EndTime=$now } -MaxEvents 100 | Select-Object TimeCreated, Id, LevelDisplayName, ProviderName, Message Separate remote counter transport from PowerShell remoting
Get-Counter -ComputerName uses Windows performance-counter remote infrastructure and does not depend on Enable-PSRemoting. The target still needs name resolution, firewall access, Remote Registry and performance services as applicable, and sufficient local or domain authorization. Prefer FQDNs and managed domain credentials; do not weaken firewall or TrustedHosts broadly to make collection work. Remote measurement changes no target state and normally requires no reboot.
$computer = 'server01.contoso.com'
Get-Counter -ComputerName $computer -ListSet 'Memory' | Select-Object CounterSetName, CounterSetType
Get-Counter -ComputerName $computer -Counter '\Memory\Available MBytes' -SampleInterval 2 -MaxSamples 5 Persist captures without silently replacing evidence
Export-Counter stores PerformanceCounterSampleSet objects as BLG, CSV, or TSV. BLG preserves native counter data efficiently; CSV is easier to inspect but can lose fidelity through downstream tools. File creation requires local path permissions and adequate space and can expose workload or user information. Generate a unique path, test for collisions, apply retention and ACLs, and hash the completed artifact. Export-Counter has no WhatIf preview, and logman state changes have separate operational impact; a preflight cannot prove capacity, final integrity, or consumer compatibility.
$path = Join-Path $PWD ("perf-{0:yyyyMMdd-HHmmss}.blg" -f (Get-Date))
if (Test-Path -LiteralPath $path) { throw "Refusing to overwrite $path" }
# Export-Counter has no WhatIf. After approval, export to $path and validate it with Import-Counter before deleting source evidence. Sources and further reading
References
Authoritative documentation used to verify and expand this cheat sheet.
Help us improve
Found a typo or missing example?
Tell us what would make this cheat sheet clearer, more complete, or more useful.



