The essentials

Quick reference

One focused task per row. Jump to the related section for complete, working examples.

UseSyntaxExamples
List counter setsGet-Counter -ListSet '*' | Sort-Object CounterSetName | Select-Object CounterSetName, CounterSetTypeView examples
Inspect processor pathsGet-Counter -ListSet 'Processor Information' | Select-Object -ExpandProperty PathsWithInstancesView examples
Sample total CPUGet-Counter -Counter ` '\Processor Information(_Total)\% Processor Utility' ` -SampleInterval 2 -MaxSamples 15View examples
Sample memory pressureGet-Counter -Counter ` '\Memory\Available MBytes','\Memory\Pages/sec' ` -SampleInterval 2 -MaxSamples 15View examples
Sample physical disk latencyGet-Counter -Counter ` '\PhysicalDisk(*)\Avg. Disk sec/Read','\PhysicalDisk(*)\Avg. Disk sec/Write' ` -MaxSamples 5View examples
Sample disk queuesGet-Counter -Counter ` '\PhysicalDisk(*)\Current Disk Queue Length' ` -SampleInterval 2 -MaxSamples 15View examples
Sample network throughputGet-Counter -Counter ` '\Network Interface(*)\Bytes Total/sec' ` -SampleInterval 2 -MaxSamples 15View examples
Extract cooked values$samples.CounterSamples | Select-Object Timestamp, Path, InstanceName, CookedValue, StatusView 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 processesGet-Process | Sort-Object CPU -Descending | Select-Object -First 15 Id, ProcessName, CPU, WorkingSet64, PrivateMemorySize64View examples
Sample process CPU ratesGet-Counter -Counter '\Process(*)\% Processor Time' ` -SampleInterval 2 -MaxSamples 5View examples
Sample a remote hostGet-Counter -ComputerName 'server01.contoso.com' ` -Counter '\Memory\Available MBytes' -MaxSamples 5View examples
Write a BLG export$samples | Export-Counter -Path './perf-sample.blg' -FileFormat blgView examples
Read a BLG captureImport-Counter -Path './perf-sample.blg' | Select-Object -ExpandProperty CounterSamplesView examples
List collector setslogman.exe queryView examples
Correlate System eventsGet-WinEvent -FilterHashtable @{ LogName='System'; ` StartTime=(Get-Date).AddHours(-1) } -MaxEvents 100View 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

01

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.

Inspect available processor and memory paths
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
Back to quick reference ↑
02

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.

Capture a bounded host baseline
$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
Back to quick reference ↑
03

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.

Collect related disk observations
$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
Back to quick reference ↑
04

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.

Summarize valid values without discarding provenance
$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 }
}
Back to quick reference ↑
05

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.

Capture process and event context
$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
Back to quick reference ↑
06

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.

Verify and bound a remote memory sample
$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
Back to quick reference ↑
07

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.

Prepare a collision-resistant BLG artifact
$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.
Back to quick reference ↑

Sources and further reading

References

Authoritative documentation used to verify and expand this cheat sheet.

  1. MicrosoftGet-Counterlearn.microsoft.com
  2. MicrosoftExport-Counterlearn.microsoft.com
  3. MicrosoftImport-Counterlearn.microsoft.com
  4. MicrosoftPerformance Monitor overviewlearn.microsoft.com
  5. Microsoftlogmanlearn.microsoft.com
  6. MicrosoftGet-Processlearn.microsoft.com

Help us improve

Found a typo or missing example?

Tell us what would make this cheat sheet clearer, more complete, or more useful.

Share feedback