Created
June 17, 2026 08:13
-
-
Save asw456/b274bd8fcc582c91eec5d8e64bf3b038 to your computer and use it in GitHub Desktop.
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
| # MemoryHealthReport.ps1 | |
| # Read-only memory / ECC / WHEA health report for Windows. | |
| # Safe to run: does not change settings, install software, or reboot. | |
| param( | |
| [int]$DaysBack = 30, | |
| [int]$MaxWheaEventsToRead = 20000 | |
| ) | |
| $ErrorActionPreference = "Continue" | |
| $Report = New-Object System.Collections.Generic.List[string] | |
| $Findings = New-Object System.Collections.Generic.List[string] | |
| function Add-Line { | |
| param([string]$Text = "") | |
| $script:Report.Add($Text) | |
| Write-Host $Text | |
| } | |
| function Add-Block { | |
| param([string]$Text) | |
| if ($null -eq $Text) { return } | |
| foreach ($line in ($Text -split "`r?`n")) { | |
| Add-Line $line | |
| } | |
| } | |
| function Section { | |
| param([string]$Title) | |
| Add-Line "" | |
| Add-Line ("=" * 78) | |
| Add-Line $Title | |
| Add-Line ("=" * 78) | |
| } | |
| function Safe-Run { | |
| param( | |
| [string]$Name, | |
| [scriptblock]$Script | |
| ) | |
| try { | |
| & $Script | |
| } | |
| catch { | |
| Add-Line "WARNING: Could not collect $Name" | |
| Add-Line ("Reason: " + $_.Exception.Message) | |
| $script:Findings.Add("Could not collect $Name: $($_.Exception.Message)") | |
| return $null | |
| } | |
| } | |
| function Bytes-ToGB { | |
| param($Bytes) | |
| if ($null -eq $Bytes -or $Bytes -eq 0) { return "Unknown" } | |
| return ("{0:N2} GB" -f ([double]$Bytes / 1GB)) | |
| } | |
| function KB-ToGB { | |
| param($KB) | |
| if ($null -eq $KB -or $KB -eq 0) { return "Unknown" } | |
| return ("{0:N2} GB" -f ([double]$KB / 1MB)) | |
| } | |
| function Trim-Value { | |
| param($Value) | |
| if ($null -eq $Value) { return "" } | |
| return (($Value | Out-String).Trim()) | |
| } | |
| function Get-Prop { | |
| param($Object, [string]$Name) | |
| if ($null -eq $Object) { return $null } | |
| if ($Object.PSObject.Properties.Name -contains $Name) { | |
| return $Object.$Name | |
| } | |
| return $null | |
| } | |
| function Add-Table { | |
| param($Objects) | |
| if ($null -eq $Objects) { | |
| Add-Line "(none)" | |
| return | |
| } | |
| $array = @($Objects) | |
| if ($array.Count -eq 0) { | |
| Add-Line "(none)" | |
| return | |
| } | |
| Add-Block ($array | Format-Table -AutoSize | Out-String -Width 240) | |
| } | |
| function Add-List { | |
| param($Object) | |
| if ($null -eq $Object) { | |
| Add-Line "(none)" | |
| return | |
| } | |
| Add-Block ($Object | Format-List | Out-String -Width 240) | |
| } | |
| function Get-WheaSummary { | |
| param([string]$Message) | |
| if ([string]::IsNullOrWhiteSpace($Message)) { | |
| return "(no message text)" | |
| } | |
| $lines = $Message -split "`r?`n" | | |
| ForEach-Object { $_.Trim() } | | |
| Where-Object { $_ -ne "" } | |
| $important = $lines | Where-Object { | |
| $_ -match "(?i)^(A .*hardware.*error|A .*memory.*error|Component:|Error Source:|Error Type:|Processor APIC ID:|Bank:|Device:|Physical Address:|Node:|Card:|Module:|Channel:|DIMM:|Rank:|Row:|Column:|FRU)" | |
| } | Select-Object -First 10 | |
| if (@($important).Count -eq 0) { | |
| $important = $lines | Select-Object -First 4 | |
| } | |
| return (($important -join " | ") -replace "\s+", " ") | |
| } | |
| function Is-MemoryWheaMessage { | |
| param([string]$Message) | |
| if ([string]::IsNullOrWhiteSpace($Message)) { | |
| return $false | |
| } | |
| return ($Message -match "(?i)\b(Component:\s*Memory|Memory Hierarchy|DIMM|DRAM|Channel|Rank|Bank|Row|Column|Physical Address)\b") | |
| } | |
| function Is-CorrectedMessage { | |
| param([string]$Message) | |
| if ([string]::IsNullOrWhiteSpace($Message)) { | |
| return $false | |
| } | |
| return ($Message -match "(?i)\bcorrected hardware error\b|\bCorrected Machine Check\b") | |
| } | |
| function Is-FatalOrUncorrectedMessage { | |
| param([string]$Message) | |
| if ([string]::IsNullOrWhiteSpace($Message)) { | |
| return $false | |
| } | |
| return ($Message -match "(?i)\bfatal\b|\buncorrected\b|\bnot corrected\b") | |
| } | |
| $ECCCorrectionMap = @{ | |
| 0 = "Reserved" | |
| 1 = "Other" | |
| 2 = "Unknown" | |
| 3 = "None" | |
| 4 = "Parity" | |
| 5 = "Single-bit ECC" | |
| 6 = "Multi-bit ECC" | |
| 7 = "CRC" | |
| } | |
| $ArrayUseMap = @{ | |
| 0 = "Reserved" | |
| 1 = "Other" | |
| 2 = "Unknown" | |
| 3 = "System memory" | |
| 4 = "Video memory" | |
| 5 = "Flash memory" | |
| 6 = "Non-volatile RAM" | |
| 7 = "Cache memory" | |
| } | |
| $FormFactorMap = @{ | |
| 0 = "Unknown" | |
| 1 = "Other" | |
| 2 = "SIP" | |
| 3 = "DIP" | |
| 4 = "ZIP" | |
| 5 = "SOJ" | |
| 6 = "Proprietary" | |
| 7 = "SIMM" | |
| 8 = "DIMM" | |
| 9 = "TSOP" | |
| 10 = "PGA" | |
| 11 = "RIMM" | |
| 12 = "SODIMM" | |
| 13 = "SRIMM" | |
| 14 = "SMD" | |
| 15 = "SSMP" | |
| 16 = "QFP" | |
| 17 = "TQFP" | |
| 18 = "SOIC" | |
| 19 = "LCC" | |
| 20 = "PLCC" | |
| 21 = "BGA" | |
| 22 = "FPBGA" | |
| 23 = "LGA" | |
| } | |
| $MemoryTypeMap = @{ | |
| 0 = "Unknown" | |
| 1 = "Other" | |
| 2 = "DRAM" | |
| 3 = "Synchronous DRAM" | |
| 4 = "Cache DRAM" | |
| 5 = "EDO" | |
| 6 = "EDRAM" | |
| 7 = "VRAM" | |
| 8 = "SRAM" | |
| 9 = "RAM" | |
| 10 = "ROM" | |
| 11 = "Flash" | |
| 12 = "EEPROM" | |
| 13 = "FEPROM" | |
| 14 = "EPROM" | |
| 15 = "CDRAM" | |
| 16 = "3DRAM" | |
| 17 = "SDRAM" | |
| 18 = "SGRAM" | |
| 19 = "RDRAM" | |
| 20 = "DDR" | |
| 21 = "DDR2" | |
| 22 = "DDR2 FB-DIMM" | |
| 24 = "DDR3" | |
| 25 = "FBD2" | |
| 26 = "DDR4" | |
| 27 = "LPDDR" | |
| 28 = "LPDDR2" | |
| 29 = "LPDDR3" | |
| 30 = "LPDDR4" | |
| 31 = "Logical non-volatile device" | |
| 34 = "DDR5" | |
| 35 = "LPDDR5" | |
| } | |
| $StartTime = (Get-Date).AddDays(-1 * $DaysBack) | |
| $Now = Get-Date | |
| $ComputerName = $env:COMPUTERNAME | |
| $Timestamp = Get-Date -Format "yyyyMMdd_HHmmss" | |
| $Desktop = [Environment]::GetFolderPath("Desktop") | |
| if ([string]::IsNullOrWhiteSpace($Desktop) -or -not (Test-Path $Desktop)) { | |
| $Desktop = $env:TEMP | |
| } | |
| $OutputFile = Join-Path $Desktop ("MemoryHealthReport_{0}_{1}.txt" -f $ComputerName, $Timestamp) | |
| Section "Memory Health Report - Summary" | |
| Add-Line ("Generated: {0}" -f (Get-Date -Format "yyyy-MM-dd HH:mm:ss zzz")) | |
| Add-Line ("Computer: {0}" -f $ComputerName) | |
| Add-Line ("User: {0}\{1}" -f $env:USERDOMAIN, $env:USERNAME) | |
| Add-Line ("Lookback: Last {0} day(s) for recent event counts" -f $DaysBack) | |
| Add-Line ("Note: This report is read-only.") | |
| $Identity = [Security.Principal.WindowsIdentity]::GetCurrent() | |
| $Principal = New-Object Security.Principal.WindowsPrincipal($Identity) | |
| $IsAdmin = $Principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) | |
| Add-Line ("Running as Administrator: {0}" -f $IsAdmin) | |
| if (-not $IsAdmin) { | |
| $Findings.Add("PowerShell was not run as Administrator. Some event log details may be missing.") | |
| } | |
| Section "System Information" | |
| $CS = Safe-Run "Computer system information" { Get-CimInstance Win32_ComputerSystem } | |
| $OS = Safe-Run "Operating system information" { Get-CimInstance Win32_OperatingSystem } | |
| $BIOS = Safe-Run "BIOS information" { Get-CimInstance Win32_BIOS } | |
| $BaseBoard = Safe-Run "Baseboard information" { Get-CimInstance Win32_BaseBoard } | |
| $CPU = Safe-Run "CPU information" { Get-CimInstance Win32_Processor | Select-Object -First 1 } | |
| if ($OS) { | |
| $Boot = $OS.LastBootUpTime | |
| $Uptime = $Now - $Boot | |
| } | |
| else { | |
| $Boot = $null | |
| $Uptime = $null | |
| } | |
| $SystemInfo = [PSCustomObject]@{ | |
| Manufacturer = Trim-Value $CS.Manufacturer | |
| Model = Trim-Value $CS.Model | |
| "System Type" = Trim-Value $CS.SystemType | |
| "Total RAM Visible" = Bytes-ToGB $CS.TotalPhysicalMemory | |
| OS = (Trim-Value $OS.Caption) | |
| "OS Version" = (Trim-Value $OS.Version) | |
| "OS Build" = (Trim-Value $OS.BuildNumber) | |
| "Last Boot" = $Boot | |
| Uptime = if ($Uptime) { "{0} days, {1} hours, {2} minutes" -f $Uptime.Days, $Uptime.Hours, $Uptime.Minutes } else { "Unknown" } | |
| BIOS = (Trim-Value $BIOS.SMBIOSBIOSVersion) | |
| "BIOS Release Date" = $BIOS.ReleaseDate | |
| Baseboard = ("{0} {1}" -f (Trim-Value $BaseBoard.Manufacturer), (Trim-Value $BaseBoard.Product)).Trim() | |
| CPU = (Trim-Value $CPU.Name) | |
| } | |
| Add-List $SystemInfo | |
| Section "Memory Array / ECC Capability From SMBIOS" | |
| $Arrays = Safe-Run "Physical memory array information" { Get-CimInstance Win32_PhysicalMemoryArray } | |
| $ArrayRows = @() | |
| foreach ($a in @($Arrays)) { | |
| $memErrCode = [int](Get-Prop $a "MemoryErrorCorrection") | |
| $useCode = [int](Get-Prop $a "Use") | |
| $memErrText = if ($ECCCorrectionMap.ContainsKey($memErrCode)) { $ECCCorrectionMap[$memErrCode] } else { "Unknown code $memErrCode" } | |
| $useText = if ($ArrayUseMap.ContainsKey($useCode)) { $ArrayUseMap[$useCode] } else { "Unknown code $useCode" } | |
| $maxCapExKB = Get-Prop $a "MaxCapacityEx" | |
| $maxCapKB = Get-Prop $a "MaxCapacity" | |
| $maxText = "Unknown" | |
| if ($maxCapExKB -and $maxCapExKB -gt 0) { | |
| $maxText = KB-ToGB $maxCapExKB | |
| } | |
| elseif ($maxCapKB -and $maxCapKB -gt 0) { | |
| $maxText = KB-ToGB $maxCapKB | |
| } | |
| $ArrayRows += [PSCustomObject]@{ | |
| Location = Trim-Value $a.Location | |
| Use = $useText | |
| "Memory Error Correction"= $memErrText | |
| "Slots/Sockets" = $a.MemoryDevices | |
| "Max Capacity" = $maxText | |
| } | |
| } | |
| Add-Table $ArrayRows | |
| $ECCArrayText = ($ArrayRows | Select-Object -ExpandProperty "Memory Error Correction" -ErrorAction SilentlyContinue) -join ", " | |
| if ($ECCArrayText -match "Single-bit ECC|Multi-bit ECC") { | |
| $Findings.Add("SMBIOS reports ECC-capable memory array: $ECCArrayText") | |
| } | |
| elseif ($ECCArrayText -match "None") { | |
| $Findings.Add("SMBIOS reports no memory error correction on at least one memory array.") | |
| } | |
| elseif ($ECCArrayText -match "Unknown|Other|Reserved" -or [string]::IsNullOrWhiteSpace($ECCArrayText)) { | |
| $Findings.Add("SMBIOS memory error correction status is unknown or not clearly reported.") | |
| } | |
| Section "Installed DIMMs / Memory Modules" | |
| $Modules = Safe-Run "Installed physical memory modules" { | |
| Get-CimInstance Win32_PhysicalMemory | Sort-Object BankLabel, DeviceLocator | |
| } | |
| $ModuleRows = @() | |
| foreach ($m in @($Modules)) { | |
| $dataWidth = Get-Prop $m "DataWidth" | |
| $totalWidth = Get-Prop $m "TotalWidth" | |
| $extraBits = $null | |
| $eccVisible = "Unknown" | |
| if ($null -ne $dataWidth -and $null -ne $totalWidth -and $dataWidth -gt 0 -and $totalWidth -gt 0) { | |
| $extraBits = [int]$totalWidth - [int]$dataWidth | |
| if ($extraBits -gt 0) { | |
| $eccVisible = "Yes - $extraBits extra check bit(s)" | |
| } | |
| elseif ($extraBits -eq 0) { | |
| $eccVisible = "No extra check bits visible" | |
| } | |
| else { | |
| $eccVisible = "Unexpected width values" | |
| } | |
| } | |
| $ffCode = [int](Get-Prop $m "FormFactor") | |
| $memTypeCode = [int](Get-Prop $m "SMBIOSMemoryType") | |
| if (-not $memTypeCode) { | |
| $memTypeCode = [int](Get-Prop $m "MemoryType") | |
| } | |
| $ffText = if ($FormFactorMap.ContainsKey($ffCode)) { $FormFactorMap[$ffCode] } else { "Unknown code $ffCode" } | |
| $memTypeText = if ($MemoryTypeMap.ContainsKey($memTypeCode)) { $MemoryTypeMap[$memTypeCode] } else { "Unknown code $memTypeCode" } | |
| $speed = Get-Prop $m "ConfiguredClockSpeed" | |
| if (-not $speed) { $speed = Get-Prop $m "Speed" } | |
| $voltage = Get-Prop $m "ConfiguredVoltage" | |
| if ($voltage) { | |
| $voltageText = "$voltage mV" | |
| } | |
| else { | |
| $voltageText = "" | |
| } | |
| $ModuleRows += [PSCustomObject]@{ | |
| Slot = Trim-Value $m.DeviceLocator | |
| Bank = Trim-Value $m.BankLabel | |
| Capacity = Bytes-ToGB $m.Capacity | |
| Manufacturer = Trim-Value $m.Manufacturer | |
| PartNumber = Trim-Value $m.PartNumber | |
| SerialNumber = Trim-Value $m.SerialNumber | |
| Type = $memTypeText | |
| FormFactor = $ffText | |
| Speed = if ($speed) { "$speed MT/s" } else { "" } | |
| Voltage = $voltageText | |
| DataWidth = $dataWidth | |
| TotalWidth = $totalWidth | |
| "ECC Bits Visible"= $eccVisible | |
| } | |
| } | |
| Add-Table $ModuleRows | |
| $InstalledCount = @($Modules).Count | |
| $SlotCount = 0 | |
| foreach ($a in @($Arrays)) { | |
| if ($a.MemoryDevices) { | |
| $SlotCount += [int]$a.MemoryDevices | |
| } | |
| } | |
| Add-Line ("Installed memory module count: {0}" -f $InstalledCount) | |
| if ($SlotCount -gt 0) { | |
| Add-Line ("Reported physical memory slots/sockets: {0}" -f $SlotCount) | |
| } | |
| $AnyExtraBits = @($ModuleRows | Where-Object { $_."ECC Bits Visible" -match "^Yes" }).Count | |
| $AnyNoExtraBits = @($ModuleRows | Where-Object { $_."ECC Bits Visible" -match "^No extra" }).Count | |
| if ($AnyExtraBits -gt 0) { | |
| $Findings.Add("At least one DIMM reports extra check bits through TotalWidth > DataWidth.") | |
| } | |
| elseif ($InstalledCount -gt 0 -and $AnyNoExtraBits -eq $InstalledCount) { | |
| $Findings.Add("Installed DIMMs do not show extra ECC/check bits through TotalWidth/DataWidth.") | |
| } | |
| else { | |
| $Findings.Add("DIMM width data was inconclusive for ECC bit visibility.") | |
| } | |
| Section "WHEA Hardware Error Log Summary" | |
| Add-Line ("Reading Microsoft-Windows-WHEA-Logger events from the System log.") | |
| Add-Line ("Maximum WHEA events read: {0}. If there are more than this, counts may be capped." -f $MaxWheaEventsToRead) | |
| $WheaEvents = Safe-Run "WHEA-Logger events" { | |
| Get-WinEvent -FilterHashtable @{ | |
| LogName = "System" | |
| ProviderName = "Microsoft-Windows-WHEA-Logger" | |
| } -MaxEvents $MaxWheaEventsToRead -ErrorAction Stop | |
| } | |
| $WheaEvents = @($WheaEvents) | |
| $WheaRecent = @($WheaEvents | Where-Object { $_.TimeCreated -ge $StartTime }) | |
| $WheaMemory = @($WheaEvents | Where-Object { Is-MemoryWheaMessage $_.Message }) | |
| $WheaMemoryRecent = @($WheaMemory | Where-Object { $_.TimeCreated -ge $StartTime }) | |
| $WheaMemory24h = @($WheaMemory | Where-Object { $_.TimeCreated -ge $Now.AddHours(-24) }) | |
| $WheaMemory7d = @($WheaMemory | Where-Object { $_.TimeCreated -ge $Now.AddDays(-7) }) | |
| $WheaMemoryCorrected = @($WheaMemory | Where-Object { Is-CorrectedMessage $_.Message }) | |
| $WheaMemoryCorrectedRecent = @($WheaMemoryRecent | Where-Object { Is-CorrectedMessage $_.Message }) | |
| $WheaMemoryFatal = @($WheaMemory | Where-Object { Is-FatalOrUncorrectedMessage $_.Message }) | |
| $WheaMemoryFatalRecent = @($WheaMemoryRecent | Where-Object { Is-FatalOrUncorrectedMessage $_.Message }) | |
| if ($WheaEvents.Count -eq 0) { | |
| Add-Line "No WHEA-Logger events found in the current System event log." | |
| } | |
| else { | |
| $OldestWhea = $WheaEvents | Sort-Object TimeCreated | Select-Object -First 1 | |
| $NewestWhea = $WheaEvents | Sort-Object TimeCreated -Descending | Select-Object -First 1 | |
| Add-Line ("Total WHEA-Logger events read: {0}" -f $WheaEvents.Count) | |
| Add-Line ("WHEA events within last {0} day(s): {1}" -f $DaysBack, $WheaRecent.Count) | |
| Add-Line ("Oldest WHEA event read: {0}" -f $OldestWhea.TimeCreated) | |
| Add-Line ("Newest WHEA event read: {0}" -f $NewestWhea.TimeCreated) | |
| Add-Line "" | |
| Add-Line "WHEA events by Event ID:" | |
| Add-Table ( | |
| $WheaEvents | | |
| Group-Object Id | | |
| Sort-Object Count -Descending | | |
| Select-Object @{Name="EventID";Expression={$_.Name}}, Count | |
| ) | |
| Add-Line "" | |
| Add-Line ("Memory-keyword WHEA events read: {0}" -f $WheaMemory.Count) | |
| Add-Line ("Memory-keyword WHEA events in last 24 hours: {0}" -f $WheaMemory24h.Count) | |
| Add-Line ("Memory-keyword WHEA events in last 7 days: {0}" -f $WheaMemory7d.Count) | |
| Add-Line ("Memory-keyword WHEA events in last {0} days: {1}" -f $DaysBack, $WheaMemoryRecent.Count) | |
| Add-Line ("Corrected memory-keyword WHEA events read: {0}" -f $WheaMemoryCorrected.Count) | |
| Add-Line ("Corrected memory-keyword WHEA events in last {0} days: {1}" -f $DaysBack, $WheaMemoryCorrectedRecent.Count) | |
| Add-Line ("Fatal/uncorrected memory-keyword WHEA events read: {0}" -f $WheaMemoryFatal.Count) | |
| Add-Line ("Fatal/uncorrected memory-keyword WHEA events in last {0} days: {1}" -f $DaysBack, $WheaMemoryFatalRecent.Count) | |
| if ($WheaMemory.Count -gt 0) { | |
| Add-Line "" | |
| Add-Line "Memory-keyword WHEA events by Event ID:" | |
| Add-Table ( | |
| $WheaMemory | | |
| Group-Object Id | | |
| Sort-Object Count -Descending | | |
| Select-Object @{Name="EventID";Expression={$_.Name}}, Count | |
| ) | |
| Add-Line "" | |
| Add-Line "Most recent 20 memory-keyword WHEA events:" | |
| Add-Table ( | |
| $WheaMemory | | |
| Sort-Object TimeCreated -Descending | | |
| Select-Object -First 20 ` | |
| @{Name="Time";Expression={$_.TimeCreated}}, | |
| @{Name="EventID";Expression={$_.Id}}, | |
| @{Name="Level";Expression={$_.LevelDisplayName}}, | |
| @{Name="Summary";Expression={Get-WheaSummary $_.Message}} | |
| ) | |
| Add-Line "" | |
| Add-Line "Full message text for most recent 5 memory-keyword WHEA events:" | |
| $i = 0 | |
| foreach ($evt in ($WheaMemory | Sort-Object TimeCreated -Descending | Select-Object -First 5)) { | |
| $i++ | |
| Add-Line "" | |
| Add-Line ("--- Memory WHEA Event #{0}: {1}, Event ID {2}, Level {3} ---" -f $i, $evt.TimeCreated, $evt.Id, $evt.LevelDisplayName) | |
| Add-Block $evt.Message | |
| } | |
| } | |
| } | |
| if ($WheaMemoryFatalRecent.Count -gt 0) { | |
| $Findings.Add("FATAL/UNCORRECTED memory-related WHEA events were found in the recent lookback window.") | |
| } | |
| elseif ($WheaMemoryCorrectedRecent.Count -gt 0) { | |
| $Findings.Add("Corrected memory-related WHEA events were found in the recent lookback window.") | |
| } | |
| elseif ($WheaMemory.Count -gt 0) { | |
| $Findings.Add("Memory-related WHEA events exist in the current System log, but not within the recent lookback window.") | |
| } | |
| else { | |
| $Findings.Add("No memory-keyword WHEA events were found in the WHEA events read from the current System log.") | |
| } | |
| Section "Windows Memory Diagnostic Results" | |
| $MemDiagEvents = Safe-Run "Windows Memory Diagnostic results" { | |
| Get-WinEvent -FilterHashtable @{ | |
| LogName = "System" | |
| ProviderName = "Microsoft-Windows-MemoryDiagnostics-Results" | |
| } -MaxEvents 20 -ErrorAction Stop | |
| } | |
| $MemDiagEvents = @($MemDiagEvents) | |
| if ($MemDiagEvents.Count -eq 0) { | |
| Add-Line "No Windows Memory Diagnostic result events found in the current System log." | |
| } | |
| else { | |
| Add-Table ( | |
| $MemDiagEvents | | |
| Sort-Object TimeCreated -Descending | | |
| Select-Object -First 20 ` | |
| @{Name="Time";Expression={$_.TimeCreated}}, | |
| @{Name="EventID";Expression={$_.Id}}, | |
| @{Name="Level";Expression={$_.LevelDisplayName}}, | |
| @{Name="Summary";Expression={($_.Message -split "`r?`n" | Select-Object -First 4) -join " "}} | |
| ) | |
| Add-Line "" | |
| Add-Line "Full message text for most recent 3 Windows Memory Diagnostic results:" | |
| $i = 0 | |
| foreach ($evt in ($MemDiagEvents | Sort-Object TimeCreated -Descending | Select-Object -First 3)) { | |
| $i++ | |
| Add-Line "" | |
| Add-Line ("--- Memory Diagnostic Event #{0}: {1}, Event ID {2} ---" -f $i, $evt.TimeCreated, $evt.Id) | |
| Add-Block $evt.Message | |
| } | |
| $BadDiag = @($MemDiagEvents | Where-Object { $_.Message -match "(?i)hardware problems were detected|memory problems were detected|errors were detected" }) | |
| if ($BadDiag.Count -gt 0) { | |
| $Findings.Add("Windows Memory Diagnostic has reported possible memory errors.") | |
| } | |
| } | |
| Section "Crash / Unexpected Restart Context" | |
| Add-Line "These are not proof of bad RAM, but they help correlate memory errors with crashes or power-loss symptoms." | |
| $CrashEvents = Safe-Run "Crash and unexpected restart events" { | |
| Get-WinEvent -FilterHashtable @{ | |
| LogName = "System" | |
| Id = 41, 1001, 6008 | |
| } -MaxEvents 200 -ErrorAction Stop | |
| } | |
| $CrashEvents = @($CrashEvents) | |
| $CrashRecent = @($CrashEvents | Where-Object { $_.TimeCreated -ge $StartTime }) | |
| Add-Line ("Crash/unexpected restart events read: {0}" -f $CrashEvents.Count) | |
| Add-Line ("Crash/unexpected restart events in last {0} day(s): {1}" -f $DaysBack, $CrashRecent.Count) | |
| if ($CrashEvents.Count -gt 0) { | |
| Add-Table ( | |
| $CrashEvents | | |
| Sort-Object TimeCreated -Descending | | |
| Select-Object -First 30 ` | |
| @{Name="Time";Expression={$_.TimeCreated}}, | |
| @{Name="EventID";Expression={$_.Id}}, | |
| @{Name="Provider";Expression={$_.ProviderName}}, | |
| @{Name="Summary";Expression={($_.Message -split "`r?`n" | Select-Object -First 2) -join " "}} | |
| ) | |
| } | |
| if ($CrashRecent.Count -gt 0) { | |
| $Findings.Add("Crash/unexpected restart events exist within the recent lookback window.") | |
| } | |
| Section "Preliminary Assessment" | |
| Add-Line "This is a first-pass assessment based only on what Windows can see." | |
| $Severity = "GREEN / NO WINDOWS-REPORTED MEMORY ERRORS FOUND" | |
| if ($WheaMemoryFatalRecent.Count -gt 0) { | |
| $Severity = "RED / FATAL OR UNCORRECTED MEMORY-RELATED HARDWARE ERRORS FOUND" | |
| } | |
| elseif ($WheaMemoryCorrectedRecent.Count -gt 0) { | |
| $Severity = "AMBER / CORRECTED MEMORY-RELATED HARDWARE ERRORS FOUND" | |
| } | |
| elseif ($BadDiag.Count -gt 0) { | |
| $Severity = "RED / WINDOWS MEMORY DIAGNOSTIC REPORTED MEMORY PROBLEMS" | |
| } | |
| elseif ($WheaMemory.Count -gt 0) { | |
| $Severity = "AMBER / HISTORICAL MEMORY-RELATED WHEA EVENTS FOUND" | |
| } | |
| Add-Line ("Assessment: {0}" -f $Severity) | |
| Add-Line "" | |
| if ($Findings.Count -eq 0) { | |
| Add-Line "No notable findings were collected." | |
| } | |
| else { | |
| foreach ($f in $Findings) { | |
| Add-Line ("- " + $f) | |
| } | |
| } | |
| Add-Line "" | |
| Add-Line "Interpretation guide:" | |
| Add-Line "- Any fatal or uncorrected memory-related WHEA event is serious." | |
| Add-Line "- Repeated corrected ECC/WHEA memory events usually justify replacing or reseating DIMMs, checking BIOS/firmware, disabling overclock/XMP/EXPO, and checking motherboard memory QVL support." | |
| Add-Line "- Zero WHEA memory events does not prove the RAM is healthy; it only means Windows did not log such errors in the current retained event log." | |
| Add-Line "- Consumer/non-ECC systems may have bad RAM without any ECC correction counts because there is no ECC telemetry to count." | |
| Add-Line "- Server/workstation systems may also have more authoritative memory logs in BMC tools such as iDRAC, iLO, XClarity, IPMI, or vendor diagnostics." | |
| Section "Report File" | |
| Add-Line ("Saving report to: {0}" -f $OutputFile) | |
| try { | |
| $Utf8NoBom = New-Object System.Text.UTF8Encoding($false) | |
| [System.IO.File]::WriteAllLines($OutputFile, $Report, $Utf8NoBom) | |
| Add-Line "Saved successfully." | |
| } | |
| catch { | |
| Add-Line ("WARNING: Could not save report file: " + $_.Exception.Message) | |
| } | |
| Add-Line "" | |
| Add-Line "DONE. Please send the report file listed above." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment