PowerShell Monitor CPU and Memory Usage in Real Time

Before you order more RAM or upgrade your server, verify what’s actually consuming resources. PowerShell gives you real-time CPU and memory visibility through Get-Counter, Get-CimInstance, and Get-Process — without installing any monitoring agent. This guide shows you how to check CPU memory usage in PowerShell, identify the top resource consumers, log metrics over time, and build a simple real-time monitor loop.
Check Total and Free Memory
Use Get-CimInstance (Windows Management Instrumentation) to query total and available RAM:
# Get memory information
$os = Get-CimInstance Win32_OperatingSystem
$totalGB = [math]::Round($os.TotalVisibleMemorySize / 1MB, 2)
$freeGB = [math]::Round($os.FreePhysicalMemory / 1MB, 2)
$usedGB = $totalGB - $freeGB
$usedPct = [math]::Round(($usedGB / $totalGB) * 100, 1)
Write-Output "Total RAM: $totalGB GB"
Write-Output "Used: $usedGB GB ($usedPct%)"
Write-Output "Free: $freeGB GB"
Total RAM: 15.87 GB
Used: 12.34 GB (77.8%)
Free: 3.53 GB
Note: Get-CimInstance requires PowerShell 3+ and replaces the deprecated Get-WmiObject. Use Get-CimInstance in all new scripts — WMI cmdlets are removed in PowerShell 6+.
Get CPU Usage with Get-Counter
Real-time CPU percentage requires Performance Counters:
# Get current CPU usage percentage
$cpu = Get-Counter '\Processor(_Total)\% Processor Time' -SampleInterval 1 -MaxSamples 1
$cpuPercent = [math]::Round($cpu.CounterSamples.CookedValue, 1)
Write-Output "CPU Usage: $cpuPercent%"
# Sample every 2 seconds, 5 times
$samples = Get-Counter '\Processor(_Total)\% Processor Time' -SampleInterval 2 -MaxSamples 5
$samples.CounterSamples | Select-Object Timestamp,
@{ Name='CPU%'; Expression={ [math]::Round($_.CookedValue,1) } }
CPU Usage: 23.7%
Timestamp CPU%
--------- ----
5/4/2026 9:15:01 AM 23.7
5/4/2026 9:15:03 AM 18.2
5/4/2026 9:15:05 AM 31.4
5/4/2026 9:15:07 AM 25.8
5/4/2026 9:15:09 AM 20.1
The counter path \Processor(_Total)\% Processor Time may vary by locale. If it fails, run Get-Counter -ListSet Processor to see the available counter names on your system.
Top Processes by Memory
# Top 10 processes by working set memory
Get-Process | Sort-Object WorkingSet -Descending |
Select-Object -First 10 ProcessName, Id,
@{ Name='MemMB'; Expression={ [math]::Round($_.WorkingSet/1MB,1) } },
@{ Name='MemGB'; Expression={ [math]::Round($_.WorkingSet/1GB,3) } } |
Format-Table -AutoSize
ProcessName Id MemMB MemGB
----------- -- ----- -----
chrome 4892 523.2 0.511
outlook 7123 187.4 0.183
vscode 3456 157.3 0.154
powershell 1234 84.1 0.082
Top Processes by CPU
# Top 10 by CPU usage
# Note: CPU property is lifetime CPU seconds, not current %
Get-Process | Sort-Object CPU -Descending |
Select-Object -First 10 ProcessName, Id,
@{ Name='CPU(s)'; Expression={ [math]::Round($_.CPU,1) } },
@{ Name='MemMB'; Expression={ [math]::Round($_.WorkingSet/1MB,1) } }
# For current CPU %, use Get-Counter per process
$procName = 'chrome'
$counter = Get-Counter "\Process($procName)\% Processor Time" -SampleInterval 1 -MaxSamples 1
$pct = [math]::Round($counter.CounterSamples.CookedValue / [Environment]::ProcessorCount, 1)
Write-Output "$procName current CPU: $pct%"
Log Metrics to File
# Log CPU and memory every minute to CSV
$logPath = 'C:\Logs\perf-metrics.csv'
# Create header if file doesn't exist
if (-not (Test-Path $logPath)) {
'Timestamp,CPU%,TotalGB,FreeGB,UsedPct' | Set-Content $logPath
}
$os = Get-CimInstance Win32_OperatingSystem
$cpu = (Get-Counter '\Processor(_Total)\% Processor Time' -SampleInterval 1 -MaxSamples 1).CounterSamples.CookedValue
$totalGB = [math]::Round($os.TotalVisibleMemorySize/1MB, 2)
$freeGB = [math]::Round($os.FreePhysicalMemory/1MB, 2)
$usedPct = [math]::Round((($totalGB-$freeGB)/$totalGB)*100, 1)
$cpuPct = [math]::Round($cpu, 1)
"$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss'),$cpuPct,$totalGB,$freeGB,$usedPct" |
Add-Content $logPath
Build a Simple Loop Monitor
# Real-time monitor — runs until Ctrl+C
while ($true) {
$os = Get-CimInstance Win32_OperatingSystem
$cpu = (Get-Counter '\Processor(_Total)\% Processor Time' -SampleInterval 1 -MaxSamples 1).CounterSamples.CookedValue
$freeGB = [math]::Round($os.FreePhysicalMemory/1MB, 2)
$totalGB = [math]::Round($os.TotalVisibleMemorySize/1MB, 2)
$usedPct = [math]::Round((($totalGB-$freeGB)/$totalGB)*100,1)
$cpuPct = [math]::Round($cpu,1)
Clear-Host
Write-Output "$(Get-Date -Format 'HH:mm:ss') CPU: $cpuPct% RAM: $usedPct% used ($freeGB GB free of $totalGB GB)"
Write-Output ""
Get-Process | Sort-Object WorkingSet -Descending | Select-Object -First 5 ProcessName,
@{ Name='MemMB'; Expression={ [math]::Round($_.WorkingSet/1MB,1) } } |
Format-Table -AutoSize
Start-Sleep -Seconds 3
}
Common Errors and Fixes
-
Get-Counter path syntax is locale-dependent: The counter path
\Processor(_Total)\% Processor Timeuses English names. On a non-English Windows, the path may differ. UseGet-Counter -ListSet *to discover available counter sets, or useGet-CimInstance Win32_Processorfor a locale-independent alternative. -
CIM vs WMI: use Get-CimInstance for modern PowerShell:
Get-WmiObjectis deprecated and removed in PowerShell 6+. Always useGet-CimInstancein new scripts. The syntax is nearly identical: replaceGet-WmiObject Win32_OperatingSystemwithGet-CimInstance Win32_OperatingSystem.
Related Cmdlets / See Also
Wrapping Up
PowerShell gives you real-time CPU and memory data through Get-Counter for CPU percentages, Get-CimInstance for total/free RAM, and Get-Process for per-process resource breakdown. Use the loop monitor pattern for live visibility, and log metrics to CSV for trend analysis. Always use Get-CimInstance instead of the deprecated Get-WmiObject. Your next step: schedule the metric logging script to run every minute and build a week’s worth of baseline data before making hardware decisions.


