IBM Support

Windows Performance Troubleshooting Using PowerShell & Sysinternals

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

Provide administrators and support engineers with a comprehensive, production‑safe methodology for diagnosing and troubleshooting Windows performance issues. This article consolidates essential PowerShell commands, performance counter collection techniques, Sysinternals‑based deep‑dive tools, and Microsoft TSS procedures into a single, actionable guide. Its goal is to enable fast triage, consistent data collection, and effective escalation by equipping users with reliable steps to identify CPU, memory, disk, network, and application‑level bottlenecks.

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

[{"Type":"MASTER","Line of Business":{"code":"LOB66","label":"Technology Lifecycle Services"},"Business Unit":{"code":"BU070","label":"IBM Infrastructure"},"Product":{"code":"SSTIPK","label":"Microsoft Windows"},"ARM Category":[{"code":"a8mKe000000004NIAQ","label":"Windows"}],"ARM Case Number":"","Platform":[{"code":"PF025","label":"Platform Independent"}],"Version":""}]

Document Information

Modified date:
13 February 2026

UID

ibm17260866