Private/Kinds/StabilityHardware.ps1
|
# What the logs say of the hardware: disk errors, hardware errors, live kernel events and # blue screens, with what their codes mean. A part of the Stability Kind, and no Kind. function Get-DiskErrorDevice { <# .SYNOPSIS Which device a disk or file system event is about, read out of its message: a disk by number, a volume by letter, or unknown. Pure. .DESCRIPTION By what does not change with the language of Windows: the device path (\Device\Harddisk1\DR3), the number before "(PDO", and a drive letter in quotation marks or before a colon. A controller event names a port and no disk, and is unknown. #> [CmdletBinding()] [OutputType([psobject])] param([AllowNull()][AllowEmptyString()][string]$Message) $text = "$Message" if ($text -match '\\Device\\Harddisk(\d+)\\') { return [pscustomobject]@{ Kind = 'Disk'; Id = $Matches[1] } } if ($text -match '"?(\d+)"?\s*\(PDO') { return [pscustomobject]@{ Kind = 'Disk'; Id = $Matches[1] } } if ($text -match '\\Device\\HarddiskVolume\d+') { return [pscustomobject]@{ Kind = 'Unknown'; Id = '' } } if ($text -match '(?<![A-Za-z])([A-Za-z]):(?!\\\\|//)') { return [pscustomobject]@{ Kind = 'Volume'; Id = ('{0}:' -f $Matches[1].ToUpperInvariant()) } } [pscustomobject]@{ Kind = 'Unknown'; Id = '' } } function Group-DiskError { <# .SYNOPSIS The disk errors by the device they are about: how many, from when to when, whether the machine still has that device, and where it belongs. Pure. .DESCRIPTION -Disks is what Get-StabilityCurrentDisk gathered. A disk whose number, or a volume whose letter, the machine does not have now is gone - unplugged. Data from before that was gathered knows no device to be gone. Place is where the errors are reported: Internal a drive the machine has, and that is not on USB or removable External a drive on USB or removable, and one that is gone: a built-in drive does not leave Unplaced the event names no device, or nothing says which drives there are. It may be a built-in drive, and is graded as one. #> [CmdletBinding()] [OutputType([psobject])] param([AllowNull()][AllowEmptyCollection()]$ErrorRow, [AllowNull()]$Disks) $known = [bool](Get-DataProperty $Disks 'Gathered') $numbers = @((Get-DataCollection $Disks 'Numbers') | ForEach-Object { "$_" }) $volumes = @((Get-DataCollection $Disks 'Volumes') | ForEach-Object { "$_".ToUpperInvariant() }) $externalNumbers = @((Get-DataCollection $Disks 'External') | ForEach-Object { "$_" }) $externalVolumes = @((Get-DataCollection $Disks 'ExternalVolumes') | ForEach-Object { "$_".ToUpperInvariant() }) $order = [System.Collections.Generic.List[string]]::new() $groups = @{} foreach ($row in @($ErrorRow | Where-Object { $_ })) { $device = Get-DiskErrorDevice -Message "$(Get-DataProperty $row 'Message')" $key = '{0}|{1}' -f $device.Kind, $device.Id if (-not $groups.ContainsKey($key)) { $gone = $false if ($known -and $device.Kind -eq 'Disk') { $gone = $numbers -notcontains $device.Id } if ($known -and $device.Kind -eq 'Volume') { $gone = $volumes -notcontains $device.Id } $place = 'Unplaced' if ($known -and $device.Kind -ne 'Unknown') { $place = 'Internal' if ($gone) { $place = 'External' } elseif ($device.Kind -eq 'Disk' -and $externalNumbers -contains $device.Id) { $place = 'External' } elseif ($device.Kind -eq 'Volume' -and $externalVolumes -contains $device.Id) { $place = 'External' } } $order.Add($key) $groups[$key] = [pscustomobject]@{ Kind = $device.Kind; Id = $device.Id; Count = 0; First = $null; Last = $null; Gone = [bool]$gone; Place = $place } } $group = $groups[$key] $group.Count++ $time = ConvertTo-DataTime (Get-DataProperty $row 'Time') if ($null -ne $time) { if ($null -eq $group.First -or $time -lt $group.First) { $group.First = $time } if ($null -eq $group.Last -or $time -gt $group.Last) { $group.Last = $time } } } foreach ($key in $order) { $groups[$key] } } function Get-DiskErrorModel { <# .SYNOPSIS What the drive with a disk number was when the errors happened: model and serial number, from what the Elevated Part read. Pure; an empty string when nothing says. .DESCRIPTION A disk number is given to whatever is plugged in next. So of the drives seen under that number, the one seen last before the errors ended is taken, and where none was seen before, the first after. #> [CmdletBinding()] [OutputType([string])] param([AllowNull()][AllowEmptyCollection()]$History, [string]$DiskNumber, [AllowNull()]$Until) $mine = @(@($History | Where-Object { $_ }) | Where-Object { "$(Get-DataProperty $_ 'DiskNumber')" -eq "$DiskNumber" } | ForEach-Object { [pscustomobject]@{ Row = $_; Time = ConvertTo-DataTime (Get-DataProperty $_ 'Time') } } | Where-Object { $null -ne $_.Time }) if (-not $mine.Count) { return '' } $picked = $null foreach ($entry in $mine) { if ($null -ne $Until -and $entry.Time -gt ([datetime]$Until).AddMinutes(1)) { continue } if ($null -eq $picked -or $entry.Time -gt $picked.Time) { $picked = $entry } } if ($null -eq $picked) { foreach ($entry in $mine) { if ($null -eq $picked -or $entry.Time -lt $picked.Time) { $picked = $entry } } } $name = ('{0} {1}' -f (Get-DataProperty $picked.Row 'Manufacturer'), (Get-DataProperty $picked.Row 'Model')).Trim() $serial = "$(Get-DataProperty $picked.Row 'SerialNumber')".Trim() if ($name -and $serial) { return (Get-Text 'Value.Stability.DiskError.ModelSerial') -f $name, $serial } $name } function Get-DiskErrorGroupName { <# .SYNOPSIS What a group of disk errors is about, as a Finding and the table name it: the disk or volume, with model and serial number where the Elevated Part saw the drive attached, and that it is gone where it is. Pure. #> [CmdletBinding()] [OutputType([string])] param([Parameter(Mandatory)]$Group, [AllowNull()][AllowEmptyCollection()]$History) $name = switch ($Group.Kind) { 'Disk' { (Get-Text 'Value.Stability.DiskError.Disk') -f $Group.Id } 'Volume' { (Get-Text 'Value.Stability.DiskError.Volume') -f $Group.Id } default { Get-Text 'Value.Stability.DiskError.Unknown' } } if ($Group.Kind -eq 'Disk') { $model = Get-DiskErrorModel -History $History -DiskNumber $Group.Id -Until $Group.Last if ($model) { $name = (Get-Text 'Value.Stability.DiskError.Named') -f $name, $model } } if ($Group.Gone) { $name = (Get-Text 'Value.Stability.DiskError.Gone') -f $name } $name } function Get-DiskErrorValue { <# .SYNOPSIS The Value of a disk error Finding: how many events in the period, and per device how many and when. Pure. #> [CmdletBinding()] [OutputType([string])] param([AllowNull()]$Data, [AllowNull()][AllowEmptyCollection()]$Group, [AllowNull()][AllowEmptyCollection()]$History) $groups = @($Group | Where-Object { $_ }) $total = 0 foreach ($one in $groups) { $total += [int]$one.Count } $lines = @(Get-EventCountText -Count $total -Data $Data) foreach ($one in $groups) { $when = '' if ($null -ne $one.First -and $null -ne $one.Last) { if ($one.First.Date -eq $one.Last.Date) { $when = (Get-Text 'Value.Stability.DiskError.On') -f $one.First } else { $when = (Get-Text 'Value.Stability.DiskError.Between') -f $one.First, $one.Last } } $lines += ((Get-Text 'Value.Stability.DiskError.Group') -f (Get-DiskErrorGroupName -Group $one -History $History), $one.Count, $when).Trim().TrimEnd(',') } $lines -join $script:StabilityLineSeparator } function New-DiskErrorFinding { <# .SYNOPSIS The disk, controller and file system errors, by where the device belongs: the built-in drives, the external ones, and what names no drive. Pure. .DESCRIPTION Seen on three machines in one afternoon: "Datentraeger-, Controller- oder NTFS-Fehler: 11", FAIL, with the advice to check the SSD - for errors on a disk 1 that none of the machines had. Each had one disk, number 0, in good health; the errors were a drive somebody had plugged in for a morning. One line for both made the worse of the two the answer about the machine, and left the reader of "11" to find out that the SSD was meant by none of them. So there are three Findings, in the area of the drives: built-in always. "None" there is the sentence a reader looks for, and what it is graded on is the machine. external where there were any. A note for a drive that is gone; a warning, and no more, for one that is attached now - a backup disk with read errors is worth one. unplaced where there were any: an event that names no drive may be about a built-in one, and is graded as one. With admin rights a drive is named by model and serial number. #> [CmdletBinding()] [OutputType([psobject])] param([AllowNull()]$Data, [hashtable]$Parameters = @{}) # Read with Get-Parameter and a literal name: that is what the published reference finds. $warn = ConvertTo-Number (Get-Parameter $Parameters 'DiskErrorWarnAbove' 0) $fail = ConvertTo-Number (Get-Parameter $Parameters 'DiskErrorFailAbove' 5) if ($null -eq $warn) { $warn = 0 } if ($null -eq $fail) { $fail = 5 } $rows = Get-DataCollection $Data 'DiskErrors' $groups = @(Group-DiskError -ErrorRow $rows -Disks (Get-DataProperty $Data 'Disks')) $history = Get-DataCollection $Data 'DiskHistory' $table = @() if ($rows.Count) { $table = @(New-Reference -Section 'Title.Stability.DiskErrors') } $internal = @($groups | Where-Object { $_.Place -eq 'Internal' }) $external = @($groups | Where-Object { $_.Place -eq 'External' }) $unplaced = @($groups | Where-Object { $_.Place -eq 'Unplaced' }) # ---- the built-in drives $count = 0 foreach ($group in $internal) { $count += [int]$group.Count } $check = Get-Text 'Check.Stability.DiskErrors.Internal' if (-not $count) { # Cleared only where nothing is left that might be a built-in drive after all. $cleared = @() if (-not $unplaced.Count) { $cleared = @('disk-errors') } New-Finding -Category Storage -Check $check -Severity OK -Value (Get-EventCountText -Count 0 -Data $Data) ` -Cleared $cleared -Reference @(New-Reference -Signal 'drive-health-warning') } else { New-Finding -Category Storage -Check $check -Severity (Get-Severity $count $warn $fail) ` -Value (Get-DiskErrorValue -Data $Data -Group $internal -History $history) ` -Meaning (Get-Text 'Meaning.Stability.DiskErrors') -Hint (Get-Text 'Hint.Stability.DiskResetsBadBlocksOr') ` -Signal 'disk-errors' -Reference (@(New-Reference -Signal 'drive-health-warning') + $table) } # ---- what names no drive if ($unplaced.Count) { $count = 0 foreach ($group in $unplaced) { $count += [int]$group.Count } New-Finding -Category Storage -Check (Get-Text 'Check.Stability.DiskErrors.Unplaced') -Severity (Get-Severity $count $warn $fail) ` -Value (Get-DiskErrorValue -Data $Data -Group $unplaced -History $history) ` -Meaning (Get-Text 'Meaning.Stability.DiskErrors.Unplaced') ` -Hint ((Get-Text 'Hint.Stability.DiskResetsBadBlocksOr'), (Get-Text 'Hint.Stability.DiskErrors.Unplaced') -join $script:StabilityLineSeparator) ` -Signal 'disk-errors' -Reference (@(New-Reference -Signal 'drive-health-warning') + $table) } # ---- the external drives if ($external.Count) { $attached = 0 foreach ($group in @($external | Where-Object { -not $_.Gone })) { $attached += [int]$group.Count } $gone = @($external | Where-Object { $_.Gone }) $severity = Get-Severity $attached $warn ([double]::MaxValue) if ($severity -eq 'OK') { $severity = 'INFO' } $means = @() $hints = @() if ($attached) { $means += Get-Text 'Meaning.Stability.DiskErrors.ExternalAttached' $hints += Get-Text 'Hint.Stability.DiskErrors.ExternalAttached' } if ($gone.Count) { $means += Get-Text 'Meaning.Stability.DiskErrorGone' $hints += Get-Text 'Hint.Stability.DiskErrorGone' # A drive that is gone and that nobody saw attached has no name but a number. $unnamed = @($gone | Where-Object { $_.Kind -eq 'Disk' -and -not (Get-DiskErrorModel -History $history -DiskNumber $_.Id -Until $_.Last) }) if ($unnamed.Count) { $hints += Get-Text 'Hint.Stability.DiskErrorWhich' } } New-Finding -Category Storage -Check (Get-Text 'Check.Stability.DiskErrors.External') -Severity $severity ` -Value (Get-DiskErrorValue -Data $Data -Group $external -History $history) ` -Meaning ($means -join $script:StabilityLineSeparator) -Hint ($hints -join $script:StabilityLineSeparator) ` -Reference $table } } function New-DiskErrorSection { <# .SYNOPSIS The disk, controller and file system errors one by one, each with the drive it was about and where that belongs. Nothing where there were none. Pure. #> [CmdletBinding()] [OutputType([psobject])] param([AllowNull()]$Data) $rows = @((Get-DataCollection $Data 'DiskErrors') | Sort-Object { Get-DataProperty $_ 'Time' } -Descending) if (-not $rows.Count) { return } $history = Get-DataCollection $Data 'DiskHistory' $names = @{} foreach ($group in @(Group-DiskError -ErrorRow $rows -Disks (Get-DataProperty $Data 'Disks'))) { $place = switch ($group.Place) { 'Internal' { Get-Text 'Value.Stability.DiskError.Place.Internal' } 'External' { Get-Text 'Value.Stability.DiskError.Place.External' } default { Get-Text 'Value.Stability.DiskError.Place.Unplaced' } } $names['{0}|{1}' -f $group.Kind, $group.Id] = [pscustomobject]@{ Name = (Get-DiskErrorGroupName -Group $group -History $history); Place = $place } } New-Section -Title (Get-Text 'Title.Stability.DiskErrors') -Help (Get-Text 'Help.Stability.DiskErrors') -Row @( $rows | Select-Object -First 200 | ForEach-Object { $device = Get-DiskErrorDevice -Message "$(Get-DataProperty $_ 'Message')" $known = $names['{0}|{1}' -f $device.Kind, $device.Id] $row = [ordered]@{} $row[(Get-Text 'Column.NetworkDisruption.Time')] = Get-DataProperty $_ 'Time' $row[(Get-Text 'Column.Stability.DiskError.Drive')] = "$($known.Name)" $row[(Get-Text 'Column.Stability.DiskError.Place')] = "$($known.Place)" $row[(Get-Text 'Column.NetworkDisruption.Provider')] = Get-DataProperty $_ 'Provider' $row[(Get-Text 'Column.NetworkDisruption.Id')] = Get-DataProperty $_ 'Id' $row[(Get-Text 'Column.NetworkDisruption.Message')] = Get-DataProperty $_ 'Message' [pscustomobject]$row } ) } # Which component a WHEA event is of, by its id. Read off the provider's own list of # events (Get-WinEvent -ListProvider Microsoft-Windows-WHEA-Logger, German Windows 11 # 26300, 2026-10-11), whose texts name the component: memory, the processor core or the # AMD northbridge, a PCI bus or device. 16 and 17 name it in their payload and are of PCI # Express by what else they carry (bus, device, function). 1, 2, 40 and 41 name it in # their payload only, and are not told apart here. $script:HardwareErrorComponents = @{ 22 = 'Memory'; 23 = 'Memory'; 46 = 'Memory'; 47 = 'Memory'; 48 = 'Memory'; 49 = 'Memory' 18 = 'Processor'; 19 = 'Processor'; 20 = 'Processor'; 21 = 'Processor'; 28 = 'Processor'; 29 = 'Processor' 16 = 'Bus'; 17 = 'Bus'; 24 = 'Bus'; 25 = 'Bus'; 26 = 'Bus'; 27 = 'Bus' 42 = 'Bus'; 43 = 'Bus'; 44 = 'Bus'; 45 = 'Bus' } function Get-HardwareErrorComponent { <# .SYNOPSIS Which component a hardware error Windows logged is of: Memory, Processor, Bus, or Other where the event does not say by its id. Pure. #> [CmdletBinding()] [OutputType([string])] param([AllowNull()]$Id) $number = ConvertTo-Number $Id if ($null -ne $number -and $script:HardwareErrorComponents.ContainsKey([int]$number)) { return $script:HardwareErrorComponents[[int]$number] } 'Other' } function New-HardwareErrorFinding { <# .SYNOPSIS The hardware errors Windows logged, counted, and by the component they are of. .DESCRIPTION Graded on how many there are, as it was. The Value says of which component, and an error of the memory is declared as that beside the hardware error it is: what reads Findings together takes only those for a sign of the memory. #> [CmdletBinding()] param([AllowNull()]$Data, [hashtable]$Parameters) $warn = Get-NumberParameter $Parameters 'HardwareErrorWarnAbove' 0 $fail = Get-NumberParameter $Parameters 'HardwareErrorFailAbove' 10 $rows = @((Get-DataCollection $Data 'HardwareErrors') | ForEach-Object { $_ }) $of = @{ Memory = 0; Processor = 0; Bus = 0; Other = 0 } foreach ($row in $rows) { $of[(Get-HardwareErrorComponent -Id (Get-DataProperty $row 'Id'))]++ } $value = Get-EventCountText -Count $rows.Count -Data $Data $signal = @() if ($rows.Count) { $parts = @( if ($of.Memory) { (Get-Text 'Value.Stability.Hardware.Memory') -f $of.Memory } if ($of.Processor) { (Get-Text 'Value.Stability.Hardware.Processor') -f $of.Processor } if ($of.Bus) { (Get-Text 'Value.Stability.Hardware.Bus') -f $of.Bus } if ($of.Other) { (Get-Text 'Value.Stability.Hardware.Other') -f $of.Other } ) $value = (Get-Text 'Value.Stability.Hardware.Counted') -f $value, ($parts -join ', ') $signal = @('hardware-errors') if ($of.Memory) { $signal += 'memory-errors' } } New-Finding -Category Stability -Check (Get-Text 'Check.Stability.WHEAHardwareErrors') ` -Severity (Get-Severity $rows.Count $warn $fail) -Value $value ` -Meaning (Get-Text 'Meaning.Stability.HardwareErrors') ` -Hint (Get-Text 'Hint.Stability.CPURAMPCIeErrorsReported') -Signal $signal } function Get-LiveKernelCodeMeaning { <# .SYNOPSIS What a live kernel event's code means, in a Technician's words. Pure. .DESCRIPTION Windows writes a live dump and carries on where it could have stopped with a blue screen; the code is the bug check it would have been. Only the ones a Customer's PC produces are named - a display driver, the window system, standby, USB. A code that is not known here gets no meaning and is shown as it is. #> [CmdletBinding()] [OutputType([string])] param([AllowNull()][AllowEmptyString()][string]$Code) switch ("$Code" -replace '^0x0*', '') { '117' { return Get-Text 'Value.Stability.LiveKernel.117' } '141' { return Get-Text 'Value.Stability.LiveKernel.141' } '193' { return Get-Text 'Value.Stability.LiveKernel.193' } '1B0' { return Get-Text 'Value.Stability.LiveKernel.1B0' } '1B8' { return Get-Text 'Value.Stability.LiveKernel.1B8' } '190' { return Get-Text 'Value.Stability.LiveKernel.190' } '1A1' { return Get-Text 'Value.Stability.LiveKernel.1A1' } '19C' { return Get-Text 'Value.Stability.LiveKernel.19C' } '15F' { return Get-Text 'Value.Stability.LiveKernel.15F' } '144' { return Get-Text 'Value.Stability.LiveKernel.144' } '124' { return Get-Text 'Value.Stability.LiveKernel.124' } '1D4' { return Get-Text 'Value.Stability.LiveKernel.1D4' } } '' } function Get-StabilityCodeAdvice { <# .SYNOPSIS What to do about a stop code, or about a live kernel event's code. Pure. .DESCRIPTION Empty for a code nothing particular is known about. The same number can call for something else as a live kernel event than as a blue screen: Windows carried on, so it is rarely the hardware and usually a driver. #> [CmdletBinding()] [OutputType([string])] param([AllowNull()][AllowEmptyString()][string]$Code, [switch]$LiveKernel) $number = ("$Code" -replace '^0x0*', '').ToUpperInvariant() if ($LiveKernel) { switch ($number) { { $_ -in '117', '141', '193', '1B0', '1B8' } { return Get-Text 'Hint.Stability.Advice.LiveGraphics' } { $_ -in '190', '1A1' } { return Get-Text 'Hint.Stability.Advice.WindowSystem' } { $_ -in '19C', '15F' } { return Get-Text 'Hint.Stability.Advice.Standby' } { $_ -in '144', '1D4' } { return Get-Text 'Hint.Stability.Advice.Usb' } '124' { return Get-Text 'Hint.Stability.Advice.Hardware' } } return '' } switch ($number) { { $_ -in 'A', 'D1', '1E', '3B', '7E' } { return Get-Text 'Hint.Stability.Advice.Driver' } '133' { return Get-Text 'Hint.Stability.Advice.Dpc' } { $_ -in '19', '1A', '4E', '50', '139' } { return Get-Text 'Hint.Stability.Advice.Memory' } { $_ -in '24', '7A', '154' } { return Get-Text 'Hint.Stability.Advice.Disk' } '7B' { return Get-Text 'Hint.Stability.Advice.BootDisk' } 'EF' { return Get-Text 'Hint.Stability.Advice.SystemFiles' } { $_ -in '7F', '101', '124' } { return Get-Text 'Hint.Stability.Advice.Hardware' } '9F' { return Get-Text 'Hint.Stability.Advice.Power' } '116' { return Get-Text 'Hint.Stability.Advice.Graphics' } } '' } function Join-StabilityCodeMeaning { <# .SYNOPSIS What the codes a Finding names mean, as its Meaning. Pure. .DESCRIPTION One code: what it means. Several: each behind its code. A code nothing is known about is not mentioned - the Value names it, and that is all there is to say. .PARAMETER Meaning Code to what it means, for the codes that are known. #> [CmdletBinding()] [OutputType([string])] param([AllowNull()][AllowEmptyCollection()][string[]]$Code, [Parameter(Mandatory)][hashtable]$Meaning) $known = @($Code | Where-Object { $_ -and $Meaning[$_] }) if (-not $known.Count) { return '' } if ($known.Count -eq 1 -and @($Code | Where-Object { $_ }).Count -eq 1) { return "$($Meaning[$known[0]])" } @($known | ForEach-Object { (Get-Text 'Value.Stability.Code.Means') -f $_, $Meaning[$_] }) -join $script:StabilityLineSeparator } function Join-StabilityCodeAdvice { <# .SYNOPSIS One Hint for the codes a Finding names: what to do about each, said once. Pure. .DESCRIPTION Codes that call for the same thing share a sentence. Where they all do, the Hint is that and nothing else; where they differ, each part says which codes it is about. A code nothing is known about gets what the Finding always said. #> [CmdletBinding()] [OutputType([string])] param( # The codes, the most frequent first. [AllowNull()][AllowEmptyCollection()][string[]]$Code, [Parameter(Mandatory)][string]$General, [switch]$LiveKernel ) $parts = [ordered]@{} foreach ($one in @($Code | Where-Object { $_ })) { $advice = Get-StabilityCodeAdvice -Code $one -LiveKernel:$LiveKernel if (-not $advice) { $advice = $General } if (-not $parts.Contains($advice)) { $parts[$advice] = New-Object System.Collections.Generic.List[string] } $parts[$advice].Add($one) } if ($parts.Count -eq 0) { return $General } if ($parts.Count -eq 1) { return @($parts.Keys)[0] } @(foreach ($advice in $parts.Keys) { (Get-Text 'Hint.Stability.Advice.ForCodes') -f ($parts[$advice] -join ', '), $advice }) -join '. ' } function New-LiveKernelFinding { [CmdletBinding()] param([AllowNull()]$Data, [hashtable]$Parameters) $warn = Get-NumberParameter $Parameters 'LiveKernelEventWarnAbove' 0 $fail = Get-NumberParameter $Parameters 'LiveKernelEventFailAbove' ([double]::MaxValue) $incidents = (Get-DataCollection $Data 'LiveKernelEvents') $value = $incidents.Count # "2: 0x193 x2" told a Technician nothing, and "2x Code 0x193" not much more. The code # is still named - it is what to look up - but behind what it means, where that is # known, as a blue screen is. $groups = @($incidents | Where-Object { Get-DataProperty $_ 'Code' } | Group-Object Code | Sort-Object @{ Expression = 'Count'; Descending = $true }, Name) $codes = @($groups | ForEach-Object { $known = Get-LiveKernelCodeMeaning -Code $_.Name if ($known) { (Get-Text 'Value.Stability.LiveKernel.Named') -f $_.Count, $known, $_.Name } else { (Get-Text 'Value.Stability.LiveKernel.Unknown') -f $_.Count, $_.Name } }) if ($codes.Count) { $value = if ($incidents.Count -eq 1) { (Get-Text 'Value.Stability.LiveKernel.One') -f ($codes -join ', ') } else { (Get-Text 'Value.Stability.LiveKernel.Many') -f $incidents.Count, ($codes -join ', ') } } # What each code means stands beside it; the Meaning says what a live kernel event is. $meaning = '' if ($incidents.Count) { $meaning = Get-Text 'Meaning.Stability.LiveKernelGeneral' } # The graphics driver or the graphics system of Windows: 0x117, 0x141, 0x193, 0x1B0, 0x1B8. $signal = @() $graphics = @($groups | Where-Object { ("$($_.Name)" -replace '^0x0*', '').ToUpperInvariant() -in '117', '141', '193', '1B0', '1B8' }) if ($graphics.Count) { $signal = @('live-kernel-graphics') } New-Finding -Category Stability -Check (Get-Text 'Check.Stability.LiveKernelEventsDriver') ` -Severity (Get-Severity $incidents.Count $warn $fail) -Value $value ` -Meaning $meaning ` -Hint (Join-StabilityCodeAdvice -LiveKernel -Code @($groups | ForEach-Object { $_.Name }) ` -General (Get-Text 'Hint.Stability.LiveKernelGeneral')) ` -Signal $signal } function Get-BlueScreenCodeMeaning { <# .SYNOPSIS What a stop code means, and the name Windows gives it. Pure. .DESCRIPTION The ones a Customer's PC shows. A stop code that is not known here gets nothing, and the Finding shows the number as it is. #> [CmdletBinding()] param([AllowNull()][AllowEmptyString()][string]$Code) $name, $meaning = switch ("$Code" -replace '^0x0*', '') { 'A' { 'IRQL_NOT_LESS_OR_EQUAL', (Get-Text 'Value.Stability.BlueScreen.A') } '19' { 'BAD_POOL_HEADER', (Get-Text 'Value.Stability.BlueScreen.19') } '1A' { 'MEMORY_MANAGEMENT', (Get-Text 'Value.Stability.BlueScreen.1A') } '1E' { 'KMODE_EXCEPTION_NOT_HANDLED', (Get-Text 'Value.Stability.BlueScreen.1E') } '24' { 'NTFS_FILE_SYSTEM', (Get-Text 'Value.Stability.BlueScreen.24') } '3B' { 'SYSTEM_SERVICE_EXCEPTION', (Get-Text 'Value.Stability.BlueScreen.3B') } '4E' { 'PFN_LIST_CORRUPT', (Get-Text 'Value.Stability.BlueScreen.4E') } '50' { 'PAGE_FAULT_IN_NONPAGED_AREA', (Get-Text 'Value.Stability.BlueScreen.50') } '7A' { 'KERNEL_DATA_INPAGE_ERROR', (Get-Text 'Value.Stability.BlueScreen.7A') } '7B' { 'INACCESSIBLE_BOOT_DEVICE', (Get-Text 'Value.Stability.BlueScreen.7B') } '7E' { 'SYSTEM_THREAD_EXCEPTION_NOT_HANDLED', (Get-Text 'Value.Stability.BlueScreen.7E') } '7F' { 'UNEXPECTED_KERNEL_MODE_TRAP', (Get-Text 'Value.Stability.BlueScreen.7F') } '9F' { 'DRIVER_POWER_STATE_FAILURE', (Get-Text 'Value.Stability.BlueScreen.9F') } 'D1' { 'DRIVER_IRQL_NOT_LESS_OR_EQUAL', (Get-Text 'Value.Stability.BlueScreen.D1') } 'EF' { 'CRITICAL_PROCESS_DIED', (Get-Text 'Value.Stability.BlueScreen.EF') } '101' { 'CLOCK_WATCHDOG_TIMEOUT', (Get-Text 'Value.Stability.BlueScreen.101') } '116' { 'VIDEO_TDR_FAILURE', (Get-Text 'Value.Stability.BlueScreen.116') } '124' { 'WHEA_UNCORRECTABLE_ERROR', (Get-Text 'Value.Stability.BlueScreen.124') } '133' { 'DPC_WATCHDOG_VIOLATION', (Get-Text 'Value.Stability.BlueScreen.133') } '139' { 'KERNEL_SECURITY_CHECK_FAILURE', (Get-Text 'Value.Stability.BlueScreen.139') } '154' { 'UNEXPECTED_STORE_EXCEPTION', (Get-Text 'Value.Stability.BlueScreen.154') } } if ($name) { [pscustomobject]@{ Name = $name; Meaning = $meaning } } } function Get-BlueScreenSignal { <# .SYNOPSIS What the stop codes of a machine's blue screens observed, as Signals. Pure. .DESCRIPTION Not every stop code says what failed. The ones named here do: 0x7A, 0x24 and 0x154 are a drive or its file system that could not be read. #> [CmdletBinding()] [OutputType([string])] param([AllowNull()][AllowEmptyCollection()][string[]]$Code) $numbers = @($Code | ForEach-Object { ("$_" -replace '^0x0*', '').ToUpperInvariant() }) if (@($numbers | Where-Object { $_ -in '7A', '24', '154' }).Count) { 'stop-code-disk' } # Corrupted memory or its bookkeeping. A driver can do that as well as a module can, # which is why one of these alone is no Lead. if (@($numbers | Where-Object { $_ -in '1A', '4E', '50', '19', '139' }).Count) { 'stop-code-memory' } if (@($numbers | Where-Object { $_ -eq '116' }).Count) { 'stop-code-graphics' } } function New-BlueScreenFinding { [CmdletBinding()] param([AllowNull()]$Data, [hashtable]$Parameters) # Any blue screen at all is a FAIL, and always has been: one is a machine that stopped. $warn = Get-NumberParameter $Parameters 'BlueScreenWarnAbove' 0 $fail = Get-NumberParameter $Parameters 'BlueScreenFailAbove' 0 $screens = (Get-DataCollection $Data 'BlueScreens') $value = $screens.Count # The stop code is what a Technician looks up, so it is named: by what it means, by # the name Windows gives it, which is what a search finds, and by its number. $groups = @($screens | Where-Object { Get-DataProperty $_ 'StopCode' } | Group-Object { '0x{0}' -f (("$($_.StopCode)" -replace '^0x0*', '').ToUpperInvariant()) } | Sort-Object @{ Expression = 'Count'; Descending = $true }, Name) $means = @{} $codes = @($groups | ForEach-Object { $known = Get-BlueScreenCodeMeaning -Code $_.Name if ($known) { $means[$_.Name] = $known.Meaning (Get-Text 'Value.Stability.BlueScreen.Named') -f $_.Count, $known.Name, $_.Name } else { (Get-Text 'Value.Stability.BlueScreen.Unknown') -f $_.Count, $_.Name } }) if ($codes.Count) { $value = if ($screens.Count -eq 1) { (Get-Text 'Value.Stability.BlueScreen.One') -f ($codes -join ', ') } else { (Get-Text 'Value.Stability.BlueScreen.Many') -f $screens.Count, ($codes -join ', ') } } # Where the blue screens with one stop code began shortly after a Change, a line more # says so, for each such stop code. See Get-CrashChangeTie. $days = Get-DataProperty $Data 'Days' if ($null -eq $days) { $days = $script:StabilityDefaultDays } $ties = @() $lines = @() foreach ($group in $groups) { $tie = Get-CrashChangeTie -Time @($group.Group | ForEach-Object { Get-DataProperty $_ 'Time' }) ` -Change (Get-DataProperty $Data 'Change') -Log 'System' -Days $days -Parameters $Parameters if ($null -eq $tie) { continue } $ties += $tie # The stop code as the Value names it: by the name Windows gives it, and its number. $code = Get-Text 'Value.Stability.Change.Code' $group.Name $known = Get-BlueScreenCodeMeaning -Code $group.Name if ($known) { $code = Get-Text 'Value.Stability.Change.CodeNamed' $known.Name $group.Name } $lines += Get-Text 'Value.Stability.Change.FirstBlueScreen' $code $tie.First (Get-ChangeTieText -Tie $tie) } if ($lines.Count) { $value = (@("$value") + $lines) -join $script:StabilityLineSeparator } $meaning = @(Join-StabilityCodeMeaning -Code @($groups | ForEach-Object { $_.Name }) -Meaning $means) + @(Get-ChangeTieMeaning -Tie $ties) $hint = @(Join-StabilityCodeAdvice -Code @($groups | ForEach-Object { $_.Name }) ` -General (Get-Text 'Hint.Stability.LookUpTheStopCodes')) + @(Get-ChangeTieHint -Tie $ties) New-Finding -Category Stability -Check (Get-Text 'Check.Stability.BlueScreensBugCheck') ` -Severity (Get-Severity $screens.Count $warn $fail) -Value $value ` -Meaning (@($meaning | Where-Object { $_ }) -join $script:StabilityLineSeparator) ` -Hint (@($hint | Where-Object { $_ }) -join $script:StabilityLineSeparator) ` -Signal @(Get-BlueScreenSignal -Code @($groups | ForEach-Object { $_.Name })) ` -Reference @(Get-ChangeTieReference -Tie $ties) } |