Files
linux-scripts/server-forensics.ps1
T
chiefgeek a1a17e81a1 Sync all scripts from website downloads — 352 scripts total
Includes updated JS challenge scripts with Claude-User whitelist,
same-site referer bypass, Blackbox-Exporter allowed bot, and all
new exporters, cheat sheets, and automation scripts.
2026-05-25 03:31:08 +02:00

544 lines
22 KiB
PowerShell

#Requires -RunAsAdministrator
###############################################################################
# server-forensics.ps1 - Post-mortem forensics for crashed/locked Windows servers
#
# Collects system state, event logs, crash dumps, resource usage, and
# network info into a timestamped report for root-cause analysis.
#
# Author: Phil Connor
# Contact: contact@mylinux.work
# License: MIT
# Version 1.00
#
# Usage:
# .\server-forensics.ps1 # Full forensic collection
# .\server-forensics.ps1 -Quick # Quick summary only
# .\server-forensics.ps1 -Service "W3SVC" # Focus on a specific service
# .\server-forensics.ps1 -Since (Get-Date).AddHours(-2) # Logs since a time
# .\server-forensics.ps1 -OutputDir "C:\Temp" # Custom output directory
###############################################################################
[CmdletBinding()]
param(
[switch]$Quick,
[string]$Service,
[DateTime]$Since = (Get-Date).AddHours(-4),
[string]$OutputDir = "C:\Forensics"
)
$ErrorActionPreference = "Continue"
#------------------------------------------------------------------------------
# SETUP
#------------------------------------------------------------------------------
$Timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$CaseDir = Join-Path $OutputDir $Timestamp
New-Item -ItemType Directory -Path $CaseDir -Force | Out-Null
$ReportPath = Join-Path $CaseDir "forensics-report.txt"
function Write-Log { param($msg) Write-Host "[forensics] $msg" -ForegroundColor Green }
function Write-Warn { param($msg) Write-Host "[forensics] $msg" -ForegroundColor Yellow }
function Write-Err { param($msg) Write-Host "[forensics] $msg" -ForegroundColor Red }
function Collect {
param(
[string]$Label,
[scriptblock]$Command
)
$header = "=== $Label ==="
Write-Host $header -ForegroundColor Cyan
$header | Out-File -Append -FilePath $ReportPath
try {
$output = & $Command 2>&1 | Out-String
$output | Out-File -Append -FilePath $ReportPath
Write-Output $output
} catch {
$err = " [Error collecting: $_]"
$err | Out-File -Append -FilePath $ReportPath
Write-Output $err
}
"" | Out-File -Append -FilePath $ReportPath
}
Write-Log "Forensics case: $CaseDir"
Write-Log "Looking back since: $Since"
@"
Forensics Report - $(Get-Date)
Hostname: $env:COMPUTERNAME
OS: $((Get-CimInstance Win32_OperatingSystem).Caption)
=============================================
"@ | Out-File -FilePath $ReportPath
###############################################################################
# SECTION 1: SYSTEM STATE SNAPSHOT
###############################################################################
Write-Log "Collecting system state..."
Collect "Uptime & Boot Time" {
$os = Get-CimInstance Win32_OperatingSystem
[PSCustomObject]@{
LastBootTime = $os.LastBootUpTime
Uptime = (New-TimeSpan -Start $os.LastBootUpTime -End (Get-Date)).ToString()
} | Format-List
}
Collect "Last Unexpected Shutdowns" {
Get-WinEvent -FilterHashtable @{LogName='System'; Id=6008; StartTime=$Since} -MaxEvents 20 -ErrorAction SilentlyContinue |
Format-Table TimeCreated, Message -Wrap
}
Collect "System Startup Events" {
Get-WinEvent -FilterHashtable @{LogName='System'; Id=6005,6006,6009,6013; StartTime=$Since} -MaxEvents 20 -ErrorAction SilentlyContinue |
Format-Table TimeCreated, Id, Message -Wrap
}
Collect "BugCheck / Blue Screen Events" {
Get-WinEvent -FilterHashtable @{LogName='System'; Id=1001; ProviderName='Microsoft-Windows-WER-SystemErrorReporting'; StartTime=$Since} -MaxEvents 10 -ErrorAction SilentlyContinue |
Format-Table TimeCreated, Message -Wrap
if (Test-Path "$env:SystemRoot\Minidump") {
"`nMinidump files:"
Get-ChildItem "$env:SystemRoot\Minidump" -ErrorAction SilentlyContinue |
Sort-Object LastWriteTime -Descending | Select-Object -First 10 |
Format-Table Name, LastWriteTime, Length
}
if (Test-Path "$env:SystemRoot\MEMORY.DMP") {
$dmp = Get-Item "$env:SystemRoot\MEMORY.DMP"
"`nFull memory dump: $($dmp.FullName) ($('{0:N0} MB' -f ($dmp.Length / 1MB)), $($dmp.LastWriteTime))"
}
}
###############################################################################
# SECTION 2: RESOURCE EXHAUSTION CHECK
###############################################################################
Write-Log "Checking resource exhaustion..."
Collect "Memory Usage" {
$os = Get-CimInstance Win32_OperatingSystem
$totalGB = [math]::Round($os.TotalVisibleMemorySize / 1MB, 2)
$freeGB = [math]::Round($os.FreePhysicalMemory / 1MB, 2)
$usedGB = $totalGB - $freeGB
$pct = [math]::Round(($usedGB / $totalGB) * 100, 1)
[PSCustomObject]@{
TotalGB = $totalGB
UsedGB = $usedGB
FreeGB = $freeGB
UsedPct = "$pct%"
} | Format-List
"`nPage File:"
Get-CimInstance Win32_PageFileUsage | Format-Table Name, CurrentUsage, AllocatedBaseSize, PeakUsage
}
Collect "Disk Usage" {
Get-CimInstance Win32_LogicalDisk -Filter "DriveType=3" |
Select-Object DeviceID,
@{N='SizeGB'; E={[math]::Round($_.Size/1GB,2)}},
@{N='FreeGB'; E={[math]::Round($_.FreeSpace/1GB,2)}},
@{N='UsedPct'; E={[math]::Round((($_.Size - $_.FreeSpace) / $_.Size) * 100, 1)}} |
Format-Table -AutoSize
}
Collect "Handle Count (Top 15)" {
Get-Process | Sort-Object HandleCount -Descending | Select-Object -First 15 |
Format-Table Id, ProcessName, HandleCount, @{N='WorkingSetMB';E={[math]::Round($_.WorkingSet64/1MB,1)}} -AutoSize
}
###############################################################################
# SECTION 3: PROCESS STATE
###############################################################################
Write-Log "Collecting process info..."
Collect "Top Processes by CPU Time" {
Get-Process | Where-Object { $_.CPU } | Sort-Object CPU -Descending | Select-Object -First 20 |
Format-Table Id, ProcessName,
@{N='CPU_Sec';E={[math]::Round($_.CPU,1)}},
@{N='WorkingSetMB';E={[math]::Round($_.WorkingSet64/1MB,1)}},
@{N='Threads';E={$_.Threads.Count}},
@{N='Handles';E={$_.HandleCount}} -AutoSize
}
Collect "Top Processes by Memory" {
Get-Process | Sort-Object WorkingSet64 -Descending | Select-Object -First 20 |
Format-Table Id, ProcessName,
@{N='WorkingSetMB';E={[math]::Round($_.WorkingSet64/1MB,1)}},
@{N='PrivateMB';E={[math]::Round($_.PrivateMemorySize64/1MB,1)}},
@{N='VirtualMB';E={[math]::Round($_.VirtualMemorySize64/1MB,1)}} -AutoSize
}
Collect "Not Responding Processes" {
Get-Process | Where-Object { $_.Responding -eq $false } |
Format-Table Id, ProcessName, StartTime, @{N='WorkingSetMB';E={[math]::Round($_.WorkingSet64/1MB,1)}} -AutoSize
if (-not (Get-Process | Where-Object { $_.Responding -eq $false })) {
" None found"
}
}
###############################################################################
# SECTION 4: SERVICES
###############################################################################
Write-Log "Checking services..."
Collect "Stopped Auto-Start Services" {
Get-CimInstance Win32_Service |
Where-Object { $_.StartMode -eq 'Auto' -and $_.State -ne 'Running' } |
Format-Table Name, DisplayName, State, StartMode, ExitCode -AutoSize
}
Collect "Services with Non-Zero Exit Codes" {
Get-CimInstance Win32_Service |
Where-Object { $_.ExitCode -ne 0 -and $_.ExitCode -ne $null } |
Format-Table Name, State, ExitCode, @{N='Win32Exit';E={$_.Win32ExitCode}} -AutoSize
}
if ($Service) {
Write-Log "Focused forensics on: $Service"
Collect "Service Detail: $Service" {
Get-CimInstance Win32_Service -Filter "Name='$Service'" | Format-List *
}
Collect "Service Event Log: $Service" {
Get-WinEvent -FilterHashtable @{LogName='System'; StartTime=$Since} -MaxEvents 500 -ErrorAction SilentlyContinue |
Where-Object { $_.Message -match $Service } |
Select-Object -First 50 |
Format-Table TimeCreated, Id, LevelDisplayName, Message -Wrap
}
}
###############################################################################
# SECTION 5: EVENT LOGS (THE GOLD MINE)
###############################################################################
Write-Log "Mining event logs..."
Collect "Critical & Error Events - System Log" {
Get-WinEvent -FilterHashtable @{LogName='System'; Level=1,2; StartTime=$Since} -MaxEvents 50 -ErrorAction SilentlyContinue |
Format-Table TimeCreated, Id, ProviderName, @{N='Message';E={$_.Message.Substring(0, [Math]::Min(200, $_.Message.Length))}} -Wrap
}
Collect "Critical & Error Events - Application Log" {
Get-WinEvent -FilterHashtable @{LogName='Application'; Level=1,2; StartTime=$Since} -MaxEvents 50 -ErrorAction SilentlyContinue |
Format-Table TimeCreated, Id, ProviderName, @{N='Message';E={$_.Message.Substring(0, [Math]::Min(200, $_.Message.Length))}} -Wrap
}
Collect "Application Crashes (WER)" {
Get-WinEvent -FilterHashtable @{LogName='Application'; ProviderName='Windows Error Reporting'; StartTime=$Since} -MaxEvents 20 -ErrorAction SilentlyContinue |
Format-Table TimeCreated, Message -Wrap
}
Collect "Application Hangs (Event 1002)" {
Get-WinEvent -FilterHashtable @{LogName='Application'; Id=1002; StartTime=$Since} -MaxEvents 20 -ErrorAction SilentlyContinue |
Format-Table TimeCreated, Message -Wrap
}
if (-not $Quick) {
Collect "Kernel Power Events (unexpected shutdowns)" {
Get-WinEvent -FilterHashtable @{LogName='System'; ProviderName='Microsoft-Windows-Kernel-Power'; StartTime=$Since} -MaxEvents 20 -ErrorAction SilentlyContinue |
Format-Table TimeCreated, Id, Message -Wrap
}
Collect "Disk Errors" {
Get-WinEvent -FilterHashtable @{LogName='System'; ProviderName='disk','Ntfs','volmgr','vhdmp'; Level=1,2,3; StartTime=$Since} -MaxEvents 30 -ErrorAction SilentlyContinue |
Format-Table TimeCreated, ProviderName, Message -Wrap
}
}
###############################################################################
# SECTION 6: NETWORK STATE
###############################################################################
Write-Log "Checking network..."
Collect "Listening Ports" {
Get-NetTCPConnection -State Listen -ErrorAction SilentlyContinue |
Sort-Object LocalPort |
Select-Object LocalAddress, LocalPort, OwningProcess,
@{N='Process';E={(Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).ProcessName}} |
Format-Table -AutoSize
}
Collect "Connection Count by State" {
Get-NetTCPConnection -ErrorAction SilentlyContinue |
Group-Object State |
Sort-Object Count -Descending |
Format-Table Count, Name -AutoSize
}
Collect "Network Adapter Errors" {
Get-NetAdapterStatistics -ErrorAction SilentlyContinue |
Format-Table Name, ReceivedPacketErrors, ReceivedDiscards, OutboundPacketErrors, OutboundDiscards -AutoSize
}
if (-not $Quick) {
Collect "DNS Client Cache (last 20)" {
Get-DnsClientCache -ErrorAction SilentlyContinue |
Select-Object -First 20 |
Format-Table Entry, RecordName, Data -AutoSize
}
Collect "Firewall Profile Status" {
Get-NetFirewallProfile -ErrorAction SilentlyContinue |
Format-Table Name, Enabled, DefaultInboundAction, DefaultOutboundAction -AutoSize
}
Collect "Routing Table" {
Get-NetRoute -ErrorAction SilentlyContinue |
Where-Object { $_.DestinationPrefix -ne 'ff00::/8' } |
Select-Object -First 30 |
Format-Table DestinationPrefix, NextHop, InterfaceAlias, RouteMetric -AutoSize
}
}
###############################################################################
# SECTION 7: CRASH DUMPS & WER
###############################################################################
if (-not $Quick) {
Write-Log "Looking for crash dumps..."
Collect "Windows Error Reports" {
$werPaths = @(
"$env:ProgramData\Microsoft\Windows\WER\ReportArchive",
"$env:ProgramData\Microsoft\Windows\WER\ReportQueue",
"$env:LOCALAPPDATA\Microsoft\Windows\WER\ReportArchive"
)
foreach ($p in $werPaths) {
if (Test-Path $p) {
"`n--- $p ---"
Get-ChildItem $p -ErrorAction SilentlyContinue |
Sort-Object LastWriteTime -Descending |
Select-Object -First 10 |
Format-Table Name, LastWriteTime
}
}
}
Collect "Recent Dump Files" {
$dumpPaths = @(
"$env:SystemRoot\Minidump",
"$env:SystemRoot\LiveKernelReports",
"$env:LOCALAPPDATA\CrashDumps"
)
foreach ($p in $dumpPaths) {
if (Test-Path $p) {
"`n--- $p ---"
Get-ChildItem $p -Recurse -ErrorAction SilentlyContinue |
Sort-Object LastWriteTime -Descending |
Select-Object -First 10 |
Format-Table Name, LastWriteTime, @{N='SizeMB';E={[math]::Round($_.Length/1MB,2)}}
}
}
}
}
###############################################################################
# SECTION 8: HARDWARE / STORAGE HEALTH
###############################################################################
if (-not $Quick) {
Write-Log "Checking hardware health..."
Collect "Physical Disk Health" {
Get-PhysicalDisk -ErrorAction SilentlyContinue |
Format-Table FriendlyName, MediaType, HealthStatus, OperationalStatus, Size -AutoSize
}
Collect "Storage Reliability Counters" {
Get-PhysicalDisk -ErrorAction SilentlyContinue | ForEach-Object {
$disk = $_
$counters = Get-StorageReliabilityCounter -PhysicalDisk $disk -ErrorAction SilentlyContinue
if ($counters) {
"--- $($disk.FriendlyName) ---"
$counters | Format-List Temperature, Wear, ReadErrorsTotal, WriteErrorsTotal,
ReadErrorsCorrected, ReadErrorsUncorrected, PowerOnHours
}
}
}
Collect "WHEA Hardware Errors" {
Get-WinEvent -FilterHashtable @{LogName='System'; ProviderName='Microsoft-Windows-WHEA-Logger'; StartTime=$Since} -MaxEvents 20 -ErrorAction SilentlyContinue |
Format-Table TimeCreated, LevelDisplayName, Message -Wrap
}
Collect "Volume Status" {
Get-Volume -ErrorAction SilentlyContinue |
Where-Object { $_.DriveLetter } |
Format-Table DriveLetter, FileSystemLabel, HealthStatus,
@{N='SizeGB';E={[math]::Round($_.Size/1GB,2)}},
@{N='FreeGB';E={[math]::Round($_.SizeRemaining/1GB,2)}},
@{N='FreePct';E={[math]::Round(($_.SizeRemaining/$_.Size)*100,1)}} -AutoSize
}
}
###############################################################################
# SECTION 9: SECURITY TRIAGE
###############################################################################
if (-not $Quick) {
Write-Log "Security quick-check..."
Collect "Failed Login Attempts (last 24h)" {
$failedLogins = Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4625; StartTime=(Get-Date).AddHours(-24)} -MaxEvents 200 -ErrorAction SilentlyContinue
if ($failedLogins) {
"Total failed logins: $($failedLogins.Count)"
"`nBy account:"
$failedLogins | ForEach-Object {
$xml = [xml]$_.ToXml()
$ns = @{e='http://schemas.microsoft.com/win/2004/08/events/event'}
$target = ($xml | Select-Xml "//e:Data[@Name='TargetUserName']" -Namespace $ns).Node.'#text'
$ip = ($xml | Select-Xml "//e:Data[@Name='IpAddress']" -Namespace $ns).Node.'#text'
[PSCustomObject]@{Account=$target; SourceIP=$ip}
} | Group-Object Account | Sort-Object Count -Descending | Select-Object -First 10 |
Format-Table Count, Name -AutoSize
"`nBy source IP:"
$failedLogins | ForEach-Object {
$xml = [xml]$_.ToXml()
$ns = @{e='http://schemas.microsoft.com/win/2004/08/events/event'}
($xml | Select-Xml "//e:Data[@Name='IpAddress']" -Namespace $ns).Node.'#text'
} | Where-Object { $_ -and $_ -ne '-' } | Group-Object | Sort-Object Count -Descending | Select-Object -First 10 |
Format-Table Count, Name -AutoSize
} else {
" No failed logins found"
}
}
Collect "Account Lockouts" {
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4740; StartTime=$Since} -MaxEvents 20 -ErrorAction SilentlyContinue |
Format-Table TimeCreated, Message -Wrap
}
Collect "Privilege Escalation Events" {
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4672,4673; StartTime=$Since} -MaxEvents 20 -ErrorAction SilentlyContinue |
Format-Table TimeCreated, Id, @{N='Message';E={$_.Message.Substring(0, [Math]::Min(150, $_.Message.Length))}} -Wrap
}
Collect "New Services Installed" {
Get-WinEvent -FilterHashtable @{LogName='System'; Id=7045; StartTime=$Since} -MaxEvents 20 -ErrorAction SilentlyContinue |
Format-Table TimeCreated, Message -Wrap
}
Collect "Scheduled Tasks (non-Microsoft)" {
Get-ScheduledTask -ErrorAction SilentlyContinue |
Where-Object { $_.Author -notmatch 'Microsoft' -and $_.State -ne 'Disabled' } |
Select-Object -First 20 |
Format-Table TaskName, State, Author, @{N='Action';E={$_.Actions.Execute}} -AutoSize
}
}
###############################################################################
# SECTION 10: PERFORMANCE COUNTERS SNAPSHOT
###############################################################################
if (-not $Quick) {
Write-Log "Collecting performance counters..."
Collect "Performance Counter Snapshot" {
$counters = @(
'\Processor(_Total)\% Processor Time',
'\Memory\Available MBytes',
'\Memory\Pages/sec',
'\Memory\Committed Bytes',
'\PhysicalDisk(_Total)\% Disk Time',
'\PhysicalDisk(_Total)\Avg. Disk Queue Length',
'\PhysicalDisk(_Total)\Disk Reads/sec',
'\PhysicalDisk(_Total)\Disk Writes/sec',
'\Network Interface(*)\Bytes Total/sec',
'\TCPv4\Connections Established',
'\System\Processor Queue Length',
'\System\Context Switches/sec'
)
Get-Counter -Counter $counters -SampleInterval 2 -MaxSamples 3 -ErrorAction SilentlyContinue |
ForEach-Object {
"Sample: $($_.Timestamp)"
$_.CounterSamples | Format-Table Path, @{N='Value';E={[math]::Round($_.CookedValue,2)}} -AutoSize
}
}
}
###############################################################################
# SUMMARY
###############################################################################
Write-Log "Generating summary..."
$summary = @()
$summary += ""
$summary += "============================================="
$summary += "FORENSICS SUMMARY"
$summary += "============================================="
$summary += "Report generated: $(Get-Date)"
$summary += "Hostname: $env:COMPUTERNAME"
$os = Get-CimInstance Win32_OperatingSystem
$uptime = (New-TimeSpan -Start $os.LastBootUpTime -End (Get-Date)).ToString("d\.hh\:mm\:ss")
$summary += "Uptime: $uptime"
$summary += "OS: $($os.Caption)"
$summary += ""
$summary += "🔴 CRITICAL FINDINGS:"
# Unexpected shutdowns
$unexpectedShutdowns = (Get-WinEvent -FilterHashtable @{LogName='System'; Id=6008; StartTime=$Since} -MaxEvents 100 -ErrorAction SilentlyContinue | Measure-Object).Count
if ($unexpectedShutdowns -gt 0) {
$summary += " - $unexpectedShutdowns unexpected shutdown(s)"
}
# BSODs
$bsods = (Get-WinEvent -FilterHashtable @{LogName='System'; Id=1001; ProviderName='Microsoft-Windows-WER-SystemErrorReporting'; StartTime=$Since} -MaxEvents 100 -ErrorAction SilentlyContinue | Measure-Object).Count
if ($bsods -gt 0) {
$summary += " - $bsods Blue Screen / BugCheck event(s)"
}
# App crashes
$appCrashes = (Get-WinEvent -FilterHashtable @{LogName='Application'; Id=1000; StartTime=$Since} -MaxEvents 100 -ErrorAction SilentlyContinue | Measure-Object).Count
if ($appCrashes -gt 0) {
$summary += " - $appCrashes application crash(es)"
}
# App hangs
$appHangs = (Get-WinEvent -FilterHashtable @{LogName='Application'; Id=1002; StartTime=$Since} -MaxEvents 100 -ErrorAction SilentlyContinue | Measure-Object).Count
if ($appHangs -gt 0) {
$summary += " - $appHangs application hang(s)"
}
# Stopped auto-start services
$stoppedSvcs = (Get-CimInstance Win32_Service | Where-Object { $_.StartMode -eq 'Auto' -and $_.State -ne 'Running' } | Measure-Object).Count
if ($stoppedSvcs -gt 0) {
$summary += " - $stoppedSvcs auto-start service(s) not running"
}
# Disk space
Get-CimInstance Win32_LogicalDisk -Filter "DriveType=3" | ForEach-Object {
$usedPct = [math]::Round((($_.Size - $_.FreeSpace) / $_.Size) * 100, 1)
if ($usedPct -ge 90) {
$summary += " - Drive $($_.DeviceID) is $usedPct% full"
}
}
# Memory
$memPct = [math]::Round((($os.TotalVisibleMemorySize - $os.FreePhysicalMemory) / $os.TotalVisibleMemorySize) * 100, 1)
if ($memPct -ge 90) {
$summary += " - Memory usage at $memPct%"
}
# Hung processes
$hungProcs = (Get-Process | Where-Object { $_.Responding -eq $false } | Measure-Object).Count
if ($hungProcs -gt 0) {
$summary += " - $hungProcs not-responding process(es)"
}
# Disk health
Get-PhysicalDisk -ErrorAction SilentlyContinue | Where-Object { $_.HealthStatus -ne 'Healthy' } | ForEach-Object {
$summary += " - Disk '$($_.FriendlyName)' health: $($_.HealthStatus)"
}
$summary += ""
$summary += "📁 Full report: $ReportPath"
$summary += "📁 Case folder: $CaseDir"
$summaryText = $summary -join "`n"
$summaryText | Tee-Object -Append -FilePath $ReportPath
# Export key event logs for offline analysis
Write-Log "Exporting event logs..."
wevtutil epl System (Join-Path $CaseDir "System.evtx") 2>$null
wevtutil epl Application (Join-Path $CaseDir "Application.evtx") 2>$null
wevtutil epl Security (Join-Path $CaseDir "Security.evtx") 2>$null
Write-Log "✅ Forensics collection complete: $CaseDir"