PowerShell Monitor CPU and Memory Usage in Real Time

PowerShell Monitor CPU and Memory Usage in Real Time

PowerShell Tips Editor 3 min read
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 Time uses English names. On a non-English Windows, the path may differ. Use Get-Counter -ListSet * to discover available counter sets, or use Get-CimInstance Win32_Processor for a locale-independent alternative.
  • CIM vs WMI: use Get-CimInstance for modern PowerShell: Get-WmiObject is deprecated and removed in PowerShell 6+. Always use Get-CimInstance in new scripts. The syntax is nearly identical: replace Get-WmiObject Win32_OperatingSystem with Get-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.

Send-Item -To