PC running slow? Get a full health report with one PowerShell script

A free PowerShell script that shows what's slowing your PC down: CPU and memory hogs, startup programs, disk health, free space and recent errors, in one zip file.

✓ Safe for work PCs No admin rights needed Windows 10/11
PCHealthReport.ps1
⬇ Download
How to run it
  1. 1Download
  2. 2Right-click › Run with PowerShell
  3. 3Results open in Downloads
Didn't work? Do this instead
  1. 1Copy script
  2. 2Open PowerShell from Start
  3. 3Paste and press Enter
Safety & file details

Free to use, provided as-is. It doesn't install anything or send data anywhere, and it runs without admin rights. As with any script, read it before you run it — or ask us.

PowerShell · Version 2.0 · 330 lines

SHA-256 (to check the download wasn't altered: Get-FileHash .\PCHealthReport.ps1)
0d9cc1afbc8f120f73e46a52482210d49482617cb09adf3e57b92007554267d5

View the script
#Requires -Version 5.1
<#
.SYNOPSIS
    PC Health & Performance Report

.DESCRIPTION
    Collects read-only diagnostics that help work out why a Windows PC is
    slow, freezing, overheating or restarting: hardware and BIOS, what is
    using the CPU and memory right now, startup programs, drivers, disk
    health and free space, disk response time, recent system errors,
    unexpected shutdowns, battery health, recent Windows updates and uptime.
    Everything is saved to a folder in your Downloads folder and zipped,
    ready to send to whoever is helping you.

    The script does NOT change any settings, install anything or send data
    anywhere. The only things it writes are the report folder and zip file.

.NOTES
    Source   : https://techs4u.com.au/resources
    Version  : 2.0
    Requires : Windows 10/11, Windows PowerShell 5.1 or PowerShell 7+
    Licence  : MIT - free to use and modify. Provided as-is, without warranty.

    HOW TO RUN
      1. Download   - your browser saves it to your Downloads folder.
      2. Run it     - right-click PCHealthReport.ps1 > Run with PowerShell
                      (Windows 11: right-click > Show more options first).
      3. Results    - saved to your Downloads folder, which opens for you.

      IF RIGHT-CLICK DOESN'T WORK (the most reliable method):
      1. Copy the whole script (the "Copy script" button on the web page,
         or open the file in Notepad, Ctrl+A, Ctrl+C).
      2. Open Windows PowerShell from the Start menu.
      3. Paste it in (right-click inside the window, or Ctrl+V) and press
         Enter. If Windows warns about pasting multiple lines, choose
         "Paste anyway".
      Results are saved to your Downloads folder the same way.

    TIP: run it while the PC is being slow, so the "right now" sections
    show what is actually using it.

    PRIVACY: the report includes your computer name, Windows username, the
    PC's serial number (useful for warranty checks) and the names of running
    programs. Review it before sharing it with anyone.
#>

# ============================================================================
#  TECHS4U SCRIPT STANDARD - output to Downloads, zip, Explorer, wait for Enter
# ============================================================================
$ScriptTitle = 'PC Health & Performance Report'
$OutputName  = 'PCHealthReport'

function Get-DownloadsFolder {
    $path = $null
    try { $path = (New-Object -ComObject Shell.Application).Namespace('shell:Downloads').Self.Path } catch {}
    if (-not $path -or -not (Test-Path $path)) {
        try {
            $raw = (Get-ItemProperty 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders' -ErrorAction Stop).'{374DE290-123F-4565-9164-39C4925E467B}'
            if ($raw) { $path = [Environment]::ExpandEnvironmentVariables($raw) }
        } catch {}
    }
    if (-not $path -or -not (Test-Path $path)) { $path = Join-Path $env:USERPROFILE 'Downloads' }
    if (-not (Test-Path $path)) { $path = $env:TEMP }
    return $path
}

function Wait-BeforeClose {
    Write-Host ""
    Write-Host "Press Enter to close this window..." -ForegroundColor DarkGray
    [void](Read-Host)
}

Clear-Host
Write-Host "==============================================" -ForegroundColor Cyan
Write-Host "  $ScriptTitle"                                 -ForegroundColor Cyan
Write-Host "  techs4u.com.au"                               -ForegroundColor Cyan
Write-Host "==============================================" -ForegroundColor Cyan
Write-Host ""
Write-Host "This script only READS system information - it changes nothing." -ForegroundColor Gray
Write-Host "The report includes your computer name, username and PC serial number." -ForegroundColor Gray
Write-Host ""
Start-Sleep -Seconds 2

try {
    $Folder = Join-Path (Get-DownloadsFolder) ("{0}_{1}" -f $OutputName, (Get-Date -Format 'yyyyMMdd-HHmm'))
    New-Item -ItemType Directory -Path $Folder -Force -ErrorAction Stop | Out-Null
    $ReportPath = Join-Path $Folder "$OutputName.txt"
} catch {
    Write-Host "SETUP FAILED - could not create the report folder: $($_.Exception.Message)" -ForegroundColor Red
    Wait-BeforeClose
    exit
}

$Steps = @(
    "System Information","Device & BIOS","CPU","Memory","What is using the CPU right now",
    "What is using memory","Startup Programs","Drivers","Disk Health","Free Space",
    "Disk Response Time","System Errors","Unexpected Shutdowns","Battery","Windows Updates",
    "Power Plan","Uptime"
)
$Total   = $Steps.Count
$Current = 0

function Show-Progress {
    param([string]$Status)
    $script:Current++
    $pct = [math]::Round(($script:Current / $Total) * 100)
    Write-Host "[$pct%] $Status..." -ForegroundColor Yellow
}

function Add-Section {
    param([string]$Title, [scriptblock]$Content)
    Add-Content -Path $ReportPath -Value "`r`n===== $Title =====`r`n"
    try {
        $out = & $Content | Out-String -Width 300
        if ([string]::IsNullOrWhiteSpace($out)) { $out = "(nothing found)" }
        Add-Content -Path $ReportPath -Value $out
    } catch {
        Add-Content -Path $ReportPath -Value "ERROR collecting $Title : $($_.Exception.Message)"
    }
}

# First 2 non-empty lines of an event message
function Get-ShortMessage {
    param([string]$Message)
    (($Message -split "`r?`n") | Where-Object { $_.Trim() } | Select-Object -First 2) -join ' | '
}

Add-Content -Path $ReportPath -Value "PC HEALTH & PERFORMANCE REPORT  (techs4u.com.au)"
Add-Content -Path $ReportPath -Value "Generated: $(Get-Date)"
Add-Content -Path $ReportPath -Value "Computer: $env:COMPUTERNAME  |  User: $env:USERNAME"
Add-Content -Path $ReportPath -Value "Report Folder: $Folder"

Write-Host ""
Write-Host "Collecting the report - this takes about a minute..." -ForegroundColor Cyan
Write-Host ""

Show-Progress "System Information"
Add-Section "System Information" {
    $os = Get-CimInstance Win32_OperatingSystem
    $cs = Get-CimInstance Win32_ComputerSystem
    [PSCustomObject]@{
        OS             = $os.Caption
        Version        = $os.Version
        Build          = $os.BuildNumber
        Architecture   = $os.OSArchitecture
        InstallDate    = $os.InstallDate
        PartOfDomain   = $cs.PartOfDomain
        TotalRAMGB     = [math]::Round($cs.TotalPhysicalMemory/1GB,1)
    }
}

Show-Progress "Device & BIOS"
Add-Section "Device & BIOS" {
    $bios = Get-CimInstance Win32_BIOS
    $cs   = Get-CimInstance Win32_ComputerSystem
    [PSCustomObject]@{
        Manufacturer    = $cs.Manufacturer
        Model           = $cs.Model
        SerialNumber    = $bios.SerialNumber
        BIOSVersion     = $bios.SMBIOSBIOSVersion
        BIOSReleaseDate = $bios.ReleaseDate
    }
}

Show-Progress "CPU"
Add-Section "CPU" {
    Get-CimInstance Win32_Processor |
        Select-Object Name,NumberOfCores,NumberOfLogicalProcessors,MaxClockSpeed,CurrentClockSpeed,LoadPercentage
}

Show-Progress "Memory"
Add-Section "Memory" {
    $os = Get-CimInstance Win32_OperatingSystem
    [PSCustomObject]@{
        TotalGB     = [math]::Round($os.TotalVisibleMemorySize/1MB,2)
        FreeGB      = [math]::Round($os.FreePhysicalMemory/1MB,2)
        UsedPercent = [math]::Round((($os.TotalVisibleMemorySize-$os.FreePhysicalMemory)/$os.TotalVisibleMemorySize)*100,1)
    }
}

Show-Progress "What is using the CPU right now"
Add-Section "Top 15 Processes by CPU (measured over 3 seconds)" {
    # Two snapshots of each process's CPU time, 3 seconds apart. The
    # difference is what it used in that window - unlike the plain CPU
    # column, which is total CPU seconds since the process started.
    $cores = (Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors
    $before = @{}
    Get-Process | ForEach-Object { if ($_.CPU -ne $null) { $before[$_.Id] = $_.CPU } }
    Start-Sleep -Seconds 3
    Get-Process | Where-Object { $_.CPU -ne $null -and $before.ContainsKey($_.Id) } | ForEach-Object {
        [PSCustomObject]@{
            Process    = $_.ProcessName
            Id         = $_.Id
            CPUPercent = [math]::Round((($_.CPU - $before[$_.Id]) / 3 / $cores) * 100, 1)
            MemoryMB   = [math]::Round($_.WorkingSet64/1MB)
        }
    } | Sort-Object CPUPercent -Descending | Select-Object -First 15 | Format-Table -AutoSize
}

Show-Progress "What is using memory"
Add-Section "Top 15 Processes by Memory" {
    Get-Process | Sort-Object WorkingSet64 -Descending | Select-Object -First 15 |
        Select-Object @{N='Process';E={$_.ProcessName}},Id,
            @{N='MemoryMB';E={[math]::Round($_.WorkingSet64/1MB)}} | Format-Table -AutoSize
}

Show-Progress "Startup Programs"
Add-Section "Startup Programs" {
    Get-CimInstance Win32_StartupCommand | Select-Object Name,Command,Location | Format-Table -AutoSize -Wrap
}

Show-Progress "Drivers"
Add-Section "Drivers - non-Microsoft, oldest first" {
    Get-CimInstance Win32_PnPSignedDriver |
        Where-Object { $_.DriverProviderName -and $_.DriverProviderName -notlike '*Microsoft*' } |
        Sort-Object DriverDate |
        Select-Object DeviceName,DriverVersion,DriverDate,DriverProviderName | Format-Table -AutoSize
}

Show-Progress "Disk Health"
Add-Section "Disk Health" {
    Get-PhysicalDisk |
        Select-Object FriendlyName,MediaType,BusType,HealthStatus,OperationalStatus,
            @{N='SizeGB';E={[math]::Round($_.Size/1GB)}}
}

Show-Progress "Free Space"
Add-Section "Free Space (90%+ used = slow PC and failed updates)" {
    Get-Volume | Where-Object { $_.DriveLetter -and $_.Size } | ForEach-Object {
        $used = [math]::Round((1 - $_.SizeRemaining / $_.Size) * 100, 1)
        [PSCustomObject]@{
            Drive       = "$($_.DriveLetter):"
            Label       = $_.FileSystemLabel
            SizeGB      = [math]::Round($_.Size/1GB,1)
            FreeGB      = [math]::Round($_.SizeRemaining/1GB,1)
            UsedPercent = $used
            Status      = if ($used -ge 90) { "CRITICAL - very low space" } elseif ($used -ge 75) { "WARNING - getting full" } else { "OK" }
        }
    } | Format-Table -AutoSize
}

Show-Progress "Disk Response Time"
Add-Section "Disk Response Time (average of 5 seconds; over 0.050 sec = slow disk)" {
    $samples = Get-Counter '\PhysicalDisk(_Total)\Avg. Disk sec/Transfer' -SampleInterval 1 -MaxSamples 5 -ErrorAction SilentlyContinue
    if ($samples) {
        $vals = $samples.CounterSamples | ForEach-Object { $_.CookedValue }
        [PSCustomObject]@{
            AverageSec = [math]::Round(($vals | Measure-Object -Average).Average, 4)
            WorstSec   = [math]::Round(($vals | Measure-Object -Maximum).Maximum, 4)
        }
    } else {
        "Performance counter not available on this PC"
    }
}

Show-Progress "System Errors"
Add-Section "System Errors - last 7 days (newest 60)" {
    Get-WinEvent -FilterHashtable @{ LogName='System'; Level=1,2; StartTime=(Get-Date).AddDays(-7) } -MaxEvents 60 -ErrorAction SilentlyContinue |
        Select-Object TimeCreated,ProviderName,Id,LevelDisplayName,@{N='Message';E={ Get-ShortMessage $_.Message }} |
        Format-Table -AutoSize -Wrap
}

Show-Progress "Unexpected Shutdowns"
Add-Section "Unexpected Shutdowns / CPU Throttling - last 30 days" {
    # 41 = unexpected restart, 6008 = previous shutdown was unexpected,
    # 37 = CPU speed limited by firmware (often heat or power)
    Get-WinEvent -FilterHashtable @{ LogName='System'; Id=41,6008,37; StartTime=(Get-Date).AddDays(-30) } -MaxEvents 50 -ErrorAction SilentlyContinue |
        Select-Object TimeCreated,Id,ProviderName,@{N='Message';E={ Get-ShortMessage $_.Message }} |
        Format-Table -AutoSize -Wrap
}

Show-Progress "Battery"
Add-Section "Battery" {
    $bat = Get-CimInstance Win32_Battery -ErrorAction SilentlyContinue
    if ($bat) {
        $bat | Select-Object Name,EstimatedChargeRemaining,BatteryStatus
        "Full battery health report saved as BatteryReport.html in this folder."
    } else {
        "No battery found (desktop PC)."
    }
}
if (Get-CimInstance Win32_Battery -ErrorAction SilentlyContinue) {
    try { powercfg /batteryreport /output (Join-Path $Folder 'BatteryReport.html') 2>&1 | Out-Null } catch {}
}

Show-Progress "Windows Updates"
Add-Section "Most recent Windows updates" {
    Get-HotFix -ErrorAction SilentlyContinue | Where-Object InstalledOn |
        Sort-Object InstalledOn -Descending | Select-Object -First 8 HotFixID,Description,InstalledOn
}

Show-Progress "Power Plan"
Add-Section "Power Plan" { powercfg /getactivescheme }

Show-Progress "Uptime"
Add-Section "Uptime (weeks without a restart can slow a PC down)" {
    $os = Get-CimInstance Win32_OperatingSystem
    [PSCustomObject]@{
        LastBoot   = $os.LastBootUpTime
        UptimeDays = [math]::Round(((Get-Date)-$os.LastBootUpTime).TotalDays,1)
    }
}

# ============================================================================
#  FINISH - zip, show the result, wait for Enter
# ============================================================================
Write-Host ""
Write-Host "Zipping the report..." -ForegroundColor DarkGray
$ZipPath = "$Folder.zip"
try { Compress-Archive -Path (Join-Path $Folder '*') -DestinationPath $ZipPath -Force -ErrorAction Stop }
catch { $ZipPath = $null }

Write-Host ""
if (Test-Path $ReportPath) {
    Write-Host "========================================" -ForegroundColor Green
    Write-Host "DONE. The report is in your Downloads folder:" -ForegroundColor Green
    Write-Host "  $Folder" -ForegroundColor Green
    if ($ZipPath) {
        Write-Host "Send this one file to your IT support person:" -ForegroundColor Green
        Write-Host "  $ZipPath" -ForegroundColor Green
    }
    Write-Host "Check the report before sharing - it includes your computer name, username and serial number." -ForegroundColor Green
    Write-Host "========================================" -ForegroundColor Green
    Start-Process explorer.exe -ArgumentList "/select,`"$(if ($ZipPath) { $ZipPath } else { $ReportPath })`""
} else {
    Write-Host "SOMETHING WENT WRONG - the report file was not created." -ForegroundColor Red
    Write-Host "Expected location: $ReportPath" -ForegroundColor Red
}
# Kept on one line so a pasted run pauses here correctly.
Wait-BeforeClose; exit

A slow PC usually has one main cause: a program hogging the CPU, too little free space, a failing disk, or too many things starting with Windows. This script checks all of them in about a minute and zips the results into one file.

Tip: run it while the PC is being slow, so it captures what's actually using it.

When to use it

  • The PC is slow to start or open programs
  • It freezes, or the fan is loud all the time
  • It restarts or shuts down on its own

What it checks

  • What's using the CPU and memory right now
  • Programs that start with Windows
  • Disk health, free space and disk speed
  • System errors and unexpected shutdowns
  • Battery health (laptops), recent updates and uptime

It doesn't change anything on your PC, and nothing is sent anywhere.

Reading the report

Open PCHealthReport.txt and check these first:

  • Free Space: anything marked CRITICAL or WARNING. Free up space, or ask us.
  • Disk Health: anything other than Healthy means back up now.
  • Top Processes by CPU: one program using a lot is usually the cause.
  • Uptime: more than 7 days? Restart the PC.

Not sure what it means? Send it to us and we'll take a look.

Privacy

The report includes your computer name, Windows username and the PC's serial number. Only share it with people you trust.

Was this helpful?
🚩 Report a problem with this script
0 / 2000

Still stuck? We can fix it for you remotely, usually the same day. See our Remote computer support service.

Get help