#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