How To
Summary
This article provides a comprehensive framework for diagnosing Windows performance issues using PowerShell, built‑in system utilities, and Sysinternals tools. It includes step‑by‑step instructions to capture CPU, memory, disk, and network metrics; start and stop performance transcripts; and collect detailed performance logs using logman and Microsoft TSS.
An optional "all‑in‑one live overview" script offers fast, real‑time visibility into system health.
For deeper investigations, the article includes an Advanced Troubleshooting section covering ProcDump, Process Explorer, Process Monitor, and Task Manager, with clear guidance on when to use each tool and command-line examples for capturing hangs, crashes, thread activity, I/O bottlenecks, and access‑related failures.
Together, these resources provide a complete, production‑safe process for gathering actionable diagnostics and preparing data for support escalation.
Objective
Environment
Supported Operating Systems
- All Microsoft‑supported Windows Server operating systems
- All Microsoft‑supported Windows Client operating systems
Deployment Types
- Physical servers and workstations
- Virtual machines (Hyper‑V, VMware, Azure, AWS, IBM Cloud, etc.)
- On‑premises, cloud, and hybrid infrastructures
Workloads / Use Cases
- Application servers (IIS, .NET, Java, middleware, APIs)
- Infrastructure servers (file/print, identity services, RDS/terminal services)
- Systems showing intermittent or sustained CPU, memory, disk, or network performance anomalies
- Production workloads that require safe, non-intrusive monitoring tools
Intended Audience
- Windows system administrators
- Support and escalation engineers
- Performance engineering teams
- Operations and incident response teams
Steps
Contents
- Prerequisites
- Start/Stop Transcript
- CPU
- Memory
- Disk
- Network
- Optional: quick all-in-one live overview
- Gathering performance data using Logman
- Gathering additional data and logs (TSS)
- Advanced Troubleshooting
Prerequisites
• Recommended to use PowerShell ISE or Visual Studio Code with the PowerShell extension.
• Run as Administrator when collecting system-wide counters or Sysinternals traces.
• Ensure you have permission to write to C:\Temp\ and C:\PerfLogs\.
Start/Stop Transcript
Start a PowerShell transcript to capture all output to a text file for support upload:
$stamp = Get-Date -Format 'yyyyMMdd_HHmmss'
$log = "C:\Temp\HighCPU_$stamp.txt"
New-Item -ItemType Directory -Force -Path (Split-Path $log) | Out-Null
Start-Transcript -Path $log -Force
Stop when finished:
Stop-Transcript
CPU
Overall CPU
Get-CimInstance Win32_Processor | Select-Object LoadPercentage
Top 10 CPU-consuming processes (cumulative CPU time)
Get-Process | Sort-Object CPU -Descending | Select-Object -First 10
Note: CPU is cumulative CPU seconds since process start, not a live percent.
REAL-TIME CPU % (instant) for all running processes (name-based aggregation)
# Sample twice to compute % Processor Time
$sample = Get-Counter '\Process(*)\% Processor Time' -SampleInterval 1 -MaxSamples 2
$last = $sample.Readings[-1] # or $sample.CounterSamples from the last sample
# Get last counter samples cleanly
$lastSamples = $sample[-1].CounterSamples
# Logical processors (for normalization)
$lp = (Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors
# Aggregate by base process name (strip trailing #n), exclude _Total
$cpuByProc =
$lastSamples |
Where-Object { $_.InstanceName -ne '_Total' } |
Group-Object { $_.InstanceName -replace '#\d+$','' } |
ForEach-Object {
[pscustomobject]@{
Name = $_.Name
CPU_Total = ($_.Group | Measure-Object -Property CookedValue -Sum).Sum
'CPU%' = [math]::Round( (($_.Group | Measure-Object CookedValue -Sum).Sum) / $lp, 2 )
}
} |
Sort-Object 'CPU%' -Descending
# Join back to live process list (for PIDs, etc.)
$lookup = @{}
foreach ($r in $cpuByProc) { $lookup[$r.Name] = $r.'CPU%' }
Get-Process |
Select-Object Name, Id, @{
Name='CPU%'; Expression = {
if ($lookup.ContainsKey($_.Name)) { $lookup[$_.Name] } else { 0 }
}
} |
Sort-Object 'CPU%' -Descending
Tip (alternative, PID-accurate): For precise mapping across duplicate names (svchost/chrome), also sample \Process(*)\ID Process and join by instance to PID.
Memory
Overall memory usage
Get-CimInstance Win32_OperatingSystem |
Select-Object TotalVisibleMemorySize, FreePhysicalMemory
Top memory consumers
Get-Process | Sort-Object WorkingSet -Descending | Select-Object -First 10
Check available memory and paging
Get-Counter '\Memory\Available MBytes','\Paging File(_Total)\% Usage'
Disk
Available disk space
Get-PSDrive -PSProvider FileSystem
Disk queue length
Get-Counter '\PhysicalDisk(_Total)\Avg. Disk Queue Length'
Note: On modern/NVMe storage, queue length may be less indicative; consider latency counters like \PhysicalDisk(*)\Avg. Disk sec/Read and \PhysicalDisk(*)\Avg. Disk sec/Write.
Check SMART status
Get-PhysicalDisk | Select FriendlyName,HealthStatus,OperationalStatus
Network
Current network utilization
Get-Counter '\Network Interface(*)\Bytes Total/sec'
Active network connections
Get-NetTCPConnection | Group-Object State
Top processes using network ports
Get-NetTCPConnection | Select-Object LocalAddress,LocalPort,RemoteAddress,OwningProcess
Include the command line (great for spotting odd paths) and save to txt
$procs = Get-CimInstance Win32_Process |
Select-Object ProcessId, Name, CommandLine
$procByPid = $procs | Group-Object ProcessId -AsHashTable -AsString
Get-NetTCPConnection |
Select-Object LocalAddress, LocalPort, RemoteAddress, RemotePort, State, OwningProcess,
@{N='ProcessName'; E={ $procByPid[[string]$_.OwningProcess].Name }},
@{N='CommandLine'; E={ $procByPid[[string]$_.OwningProcess].CommandLine }}
Optional: quick all-in-one live overview
Run this to get a concise, live snapshot across CPU, memory, disk, network, and TCP states:
Write-Host "== CPU =="
Get-CimInstance Win32_Processor |
Select-Object LoadPercentage |
Format-Table -Auto
Write-Host "`n== Top CPU (cumulative) =="
Get-Process |
Sort-Object CPU -Descending |
Select-Object -First 10 Name, Id, CPU |
Format-Table -Auto
Write-Host "`n== Memory =="
Get-CimInstance Win32_OperatingSystem |
Select-Object TotalVisibleMemorySize, FreePhysicalMemory |
Format-Table -Auto
Write-Host "`n== Top Memory (WS) =="
Get-Process |
Sort-Object WorkingSet -Descending |
Select-Object -First 10 Name, Id, `
@{n='WS(MB)';e={[math]::Round($_.WorkingSet/1MB,1)}}, `
@{n='PM(MB)';e={[math]::Round($_.PrivateMemorySize/1MB,1)}} |
Format-Table -Auto
Write-Host "`n== Disk Latency & Queue =="
Get-Counter '\\PhysicalDisk(*)\\Avg. Disk sec/Read',
'\\PhysicalDisk(*)\\Avg. Disk sec/Write',
'\\PhysicalDisk(*)\\Current Disk Queue Length'
Write-Host "`n== Network Throughput =="
Get-Counter '\\Network Interface(*)\\Bytes Total/sec'
Write-Host "`n== TCP States =="
Get-NetTCPConnection |
Group-Object State |
Select-Object Name, Count |
Format-Table -Auto
Gathering performance data using Logman
Copy and paste into an elevated CMD prompt
Logman.exe create counter PerfLog-Short -o "c:\perflogs\PerfLog-Short.blg" -f bincirc -v mmddhhmm -max 300 -c "\LogicalDisk(*)\*" "\Memory\*" "\.NET CLR Memory(*)\*" "Thread\*" "\Cache\*" "\Network Interface(*)\*" "\Netlogon(*)\*" "\Paging File(*)\*" "\PhysicalDisk(*)\*" "\Print Queue(*)\*" "\Processor(*)\*" "\Processor Information*)\*" "\Process(*)\*" "\Thread(*)\*" "\Redirector\*" "\Server\*" "\System\*" "\Server Work Queues*)\*" "\Terminal Services\*" -si 00:00:03
logman perflog-Short start
--- Reproduce the issue ---
logman perflog-Short stop
Output location: C:\PerfLogs\PerfLog-Short.blg (circular up to 300 MB, sampling every 3 seconds). Adjust -si for longer reproductions (e.g., 00:00:10) to keep the same size cap.
Gathering additional data and logs (TSS)
Set-ExecutionPolicy -scope Process -ExecutionPolicy RemoteSigned -Force
Download Microsoft TSS utility: Search for Microsoft getTSS download
.\nTSS.ps1 -CollectLog DND_SETUPReport -AcceptEula
Results compress to C:\MS_Data\SDP. Upload to https://www.ecurep.ibm.com/app/upload_sf
Advanced Troubleshooting
1) ProcDump – Capture spikes, hangs, crashes, memory leaks
Best for: High CPU spikes (intermittent/sustained), hung apps, memory leaks, crash/exception capture for deep analysis.
CPU spike > 80% (auto-capture):
procdump -c 80 -s 5 -n 3 -ma <PID or ProcessName>
Example:
procdump -c 80 -s 3 -n 5 -ma w3wp.exe
Hang capture:
procdump -h -ma <PID>
Crash / first-chance exceptions:
procdump -e 1 -g -ma <PID>
Monitor respawning process:
procdump -w myapp.exe -ma
Memory threshold (private bytes > 2 GB):
procdump -pm 2000 -ma <PID>
2) Process Explorer – Live thread, memory & handle insights
Best for: Identifying CPU-consuming threads, file/registry handle lookups (locked files), service analysis inside svchost.exe, verifying signatures and DLL load issues.
Launch (regular/elevated):
procexp.exe
procexp.exe /e
Find locking handle:
procexp.exe /findhandle <filename>
Create process dump:
procexp.exe /dump <PID> <outputpath.dmp>
Thread CPU analysis (UI): Select process → Ctrl+H → Double-click → Threads tab → sort by CPU.
3) Process Monitor (ProcMon) – File/Registry/Network and process trace
Best for: ACCESS DENIED errors, installer/config failures, missing DLLs/paths, slow launch/logon diagnostics.
Launch:
procmon.exe
Start quietly with process filter:
procmon.exe /quiet /acceptEula /filter ProcessName myapp.exe include
Common filters (UI): Process Name is myapp.exe → Include; Result is ACCESS DENIED → Include; Result is NAME NOT FOUND → Include
Command-line filters:
procmon.exe /filter ProcessName myapp.exe include
procmon.exe /filter Result "ACCESS DENIED" include
Save capture / Export CSV:
procmon.exe /saveas C:\Temp\trace.pml
procmon.exe /saveas C:\Temp\trace.csv /noconfig
Workflow (slow startup): Start paused → Add filters → Enable Duration column → Capture → Stop → Sort by Duration.
4) Task Manager – Rapid triage
Best for: Quick identification of resource hotspots (CPU, memory, disk, network, GPU), mapping svchost.exe to services, power usage trends and fast process kills.
Launch / Open Performance tab:
taskmgr.exe
taskmgr.exe /7
svchost → service mapping (CLI alternative):
tasklist /svc /fi "imagename eq svchost.exe"
Show command lines (CLI):
wmic process get processid,commandline,name
Kill runaway process:
taskkill /PID <pid> /F
List I/O activity (perf counters):
typeperf "\Process(*)\IO Read Bytes/sec" "\Process(*)\IO Write Bytes/sec"
Tool Selection Cheat Sheet:
- CPU spike observed live → Process Explorer (inspect top thread & module)
- CPU spike intermittent → ProcDump -c (auto-collect dump on threshold)
- App hang → ProcDump -h -ma (ideal data for hang analysis)
- Crash/exception → ProcDump -e 1 -g -ma (first-chance dump for root cause)
- Installer fails → ProcMon (reveals missing files/permissions)
- ACCESS DENIED → ProcMon (identifies exact blocked object)
- Slow launch/startup → ProcMon (Duration pinpoints bottlenecks)
- Locked file → Process Explorer (find handle owner fast)
- Quick triage → Task Manager (built-in visibility)
- Memory leak → ProcDump -ma + ProcExp (Private Bytes)
Post-Capture: What to Send to Support
• PowerShell transcript: C:\Temp\HighCPU_<timestamp>.txt
• Perf counters: C:\PerfLogs\PerfLog-Short.blg
• ProcDump dumps: .dmp files (compress if large)
• ProcMon trace: .pml (and CSV export if requested)
• TSS bundle: C:\MS_Data\SDP\<bundle>.zip|.cab (upload to eCuRep)
Document Location
Worldwide
Was this topic helpful?
Document Information
Modified date:
13 February 2026
UID
ibm17260866