Last active
December 13, 2025 15:48
-
-
Save Neo23x0/f43e42c18fe93b77223c484680401cb0 to your computer and use it in GitHub Desktop.
Windows Error Eval
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| .SYNOPSIS | |
| Windows triage collector (best-effort) - builds a ZIP with event logs and key system snapshots. | |
| .DESCRIPTION | |
| Collects a focused set of Windows artefacts to help debug "sometimes slow boot / input lag / occasional black screen" | |
| issues. Designed to never hang indefinitely: | |
| - Self-elevates to Administrator (UAC) if needed | |
| - Adds hard timeouts for external tools (msinfo32, dxdiag, wevtutil, robocopy) | |
| - Skips WER ReportQueue (often huge/locked and causes infinite runs) | |
| - Produces a ZIP in C:\Users\Public\triage_YYYYMMDD_HHMMSS.zip | |
| Notes: | |
| - Event logs (EVTX) and WER archives can include hostnames, usernames, installed software, file paths, device IDs. | |
| - If you need a "privacy-strict" mode, add a switch and we can trim channels further. | |
| .EXAMPLE | |
| powershell -NoProfile -ExecutionPolicy Bypass -File .\win-eval.ps1 | |
| .EXAMPLE | |
| powershell -NoProfile -ExecutionPolicy Bypass -File .\win-eval.ps1 -TimeoutSec 240 -NoTranscript | |
| .EXAMPLE | |
| powershell -NoProfile -ExecutionPolicy Bypass -File .\win-eval.ps1 -OutDir C:\Temp | |
| .NOTES | |
| Author: Florian Roth with ChatGPT 5.2 (vibe-coded, but with timeouts) | |
| Version: 0.4 | |
| License: MIT-ish, do whatever, no warranty, if it breaks your day - that's on you ;) | |
| .COPYRIGHT | |
| (c) 2025 Florian Roth. All rights reserved (for the header vibes). Use freely. | |
| #> | |
| [CmdletBinding()] | |
| param( | |
| [int]$TimeoutSec = 180, | |
| [string]$OutDir = $env:PUBLIC, | |
| [switch]$NoTranscript, | |
| [switch]$NoElevate | |
| ) | |
| # ---------------- helpers ---------------- | |
| function Step([string]$msg) { | |
| $t = Get-Date -Format "HH:mm:ss" | |
| Write-Host "[$t] $msg" | |
| } | |
| function Test-IsAdmin { | |
| $id = [Security.Principal.WindowsIdentity]::GetCurrent() | |
| $p = New-Object Security.Principal.WindowsPrincipal($id) | |
| return $p.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) | |
| } | |
| function Ensure-Admin { | |
| if (-not $NoElevate -and -not (Test-IsAdmin)) { | |
| Step "Not running as admin - relaunching elevated (UAC prompt)..." | |
| $argList = @( | |
| '-NoProfile', | |
| '-ExecutionPolicy','Bypass', | |
| '-File', "`"$PSCommandPath`"", | |
| '-NoElevate', | |
| "-TimeoutSec",$TimeoutSec | |
| ) | |
| if ($OutDir) { $argList += @("-OutDir", "`"$OutDir`"") } | |
| if ($NoTranscript) { $argList += "-NoTranscript" } | |
| Start-Process -FilePath "powershell.exe" -Verb RunAs -ArgumentList $argList | |
| exit | |
| } | |
| } | |
| function Run-Exe { | |
| param( | |
| [Parameter(Mandatory=$true)][string]$File, | |
| [string]$Args = "", | |
| [int]$Timeout = 180, | |
| [switch]$HideWindow | |
| ) | |
| Step "RUN $File $Args" | |
| try { | |
| $ws = $(if ($HideWindow) { "Hidden" } else { "Normal" }) | |
| $p = Start-Process -FilePath $File -ArgumentList $Args -PassThru -WindowStyle $ws | |
| } catch { | |
| Step "FAIL start $File - $($_.Exception.Message)" | |
| return $false | |
| } | |
| $ok = $false | |
| try { $ok = Wait-Process -Id $p.Id -Timeout $Timeout -ErrorAction SilentlyContinue } catch {} | |
| if (-not $ok) { | |
| Step "TIMEOUT ($Timeout s) - killing $File (pid $($p.Id))" | |
| try { Stop-Process -Id $p.Id -Force } catch {} | |
| return $false | |
| } | |
| Step "DONE $File" | |
| return $true | |
| } | |
| function Safe-Run([scriptblock]$sb, [string]$label) { | |
| Step $label | |
| try { & $sb | Out-Null } catch { Step "WARN: $($_.Exception.Message)" } | |
| } | |
| # ---------------- main ---------------- | |
| $ErrorActionPreference = "SilentlyContinue" | |
| Step "win-eval.ps1 starting..." | |
| Step "TimeoutSec = $TimeoutSec" | |
| Step "OutDir = $OutDir" | |
| Step "Transcript = $(-not $NoTranscript)" | |
| Ensure-Admin | |
| $ts = Get-Date -Format "yyyyMMdd_HHmmss" | |
| $out = Join-Path $OutDir "triage_$ts" | |
| New-Item -ItemType Directory -Path $out -Force | Out-Null | |
| Step "Output folder: $out" | |
| if (-not $NoTranscript) { | |
| try { | |
| $tr = Join-Path $out "transcript.txt" | |
| Start-Transcript -Path $tr -Append | Out-Null | |
| Step "Transcript started: $tr" | |
| } catch { | |
| Step "WARN: transcript could not be started - $($_.Exception.Message)" | |
| } | |
| } | |
| Step "Stage 1/5 - system snapshots" | |
| # msinfo32 is helpful but sometimes gets stuck; hard timeout and continue | |
| Run-Exe "msinfo32.exe" "/report `"$out\msinfo32.txt`"" $TimeoutSec -HideWindow | Out-Null | |
| Run-Exe "msinfo32.exe" "/nfo `"$out\msinfo32.nfo`"" $TimeoutSec -HideWindow | Out-Null | |
| # dxdiag can also hang occasionally; timeout applies | |
| Run-Exe "dxdiag.exe" "/t `"$out\dxdiag.txt`"" $TimeoutSec -HideWindow | Out-Null | |
| # Non-GUI fallbacks (usually reliable) | |
| Safe-Run { Get-ComputerInfo | Out-File -Encoding UTF8 (Join-Path $out "Get-ComputerInfo.txt") } "Collecting Get-ComputerInfo" | |
| Safe-Run { systeminfo /fo csv > (Join-Path $out "systeminfo.csv") } "Collecting systeminfo" | |
| Safe-Run { driverquery /v /fo csv > (Join-Path $out "driverquery.csv") } "Collecting driver list (driverquery)" | |
| Safe-Run { pnputil /enum-drivers > (Join-Path $out "pnputil_enum-drivers.txt") } "Collecting driver store (pnputil /enum-drivers)" | |
| Safe-Run { powercfg /a > (Join-Path $out "powercfg_a.txt") } "Collecting supported sleep states (powercfg /a)" | |
| Safe-Run { powercfg /requests > (Join-Path $out "powercfg_requests.txt") } "Collecting power requests (powercfg /requests)" | |
| Step "Stage 2/5 - event log exports (EVTX)" | |
| $logs = @( | |
| "System", | |
| "Application", | |
| "Setup", | |
| "Microsoft-Windows-Diagnostics-Performance/Operational", | |
| "Microsoft-Windows-DriverFrameworks-UserMode/Operational", | |
| "Microsoft-Windows-Kernel-PnP/Configuration", | |
| "Microsoft-Windows-USB-USBHUB3/Operational", | |
| "Microsoft-Windows-USB-USBXHCI/Operational", | |
| "Microsoft-Windows-WHEA-Logger/Operational" | |
| ) | |
| $logTimeout = [Math]::Max(60, [Math]::Min(180, [int]($TimeoutSec * 0.66))) | |
| foreach ($l in $logs) { | |
| $safe = ($l -replace '[\\\/:]','_') | |
| $dst = Join-Path $out "$safe.evtx" | |
| Step "Exporting: $l" | |
| Run-Exe "wevtutil.exe" "epl `"$l`" `"$dst`" /ow:true" $logTimeout -HideWindow | Out-Null | |
| } | |
| Step "Stage 3/5 - crash and WER artefacts" | |
| # ReportQueue intentionally skipped; it’s the #1 "runs forever" folder | |
| $copyJobs = @( | |
| @{ src="$env:WINDIR\Minidump"; dst=(Join-Path $out "extra_Minidump"); timeout=120 }, | |
| @{ src="$env:WINDIR\LiveKernelReports"; dst=(Join-Path $out "extra_LiveKernelReports"); timeout=120 }, | |
| @{ src="$env:ProgramData\Microsoft\Windows\WER\ReportArchive"; dst=(Join-Path $out "extra_WER_ReportArchive"); timeout=180 } | |
| ) | |
| foreach ($j in $copyJobs) { | |
| if (Test-Path $j.src) { | |
| New-Item -ItemType Directory -Path $j.dst -Force | Out-Null | |
| Step "Copying: $($j.src)" | |
| Run-Exe "robocopy.exe" "`"$($j.src)`" `"$($j.dst)`" /E /R:0 /W:0 /NFL /NDL /NJH /NJS /NP" $j.timeout -HideWindow | Out-Null | |
| } else { | |
| Step "Skip missing: $($j.src)" | |
| } | |
| } | |
| Step "Stage 4/5 - packaging (ZIP)" | |
| $zip = "$out.zip" | |
| try { | |
| Compress-Archive -Path (Join-Path $out "*") -DestinationPath $zip -Force | |
| Step "ZIP created: $zip" | |
| } catch { | |
| Step "ZIP failed: $($_.Exception.Message)" | |
| } | |
| Step "Stage 5/5 - done" | |
| if (-not $NoTranscript) { | |
| try { Stop-Transcript | Out-Null } catch {} | |
| } | |
| Step "Finished." | |
| $zip |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment