Private/Invoke-InfraPulseStabilityCheck.ps1
|
function Invoke-InfraPulseStabilityCheck { [CmdletBinding()] param( [Parameter(Mandatory)] [psobject]$Context, [Parameter(Mandatory)] [System.Collections.IDictionary]$Settings ) $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() $scriptBlock = { param($CheckSettings) if ($env:OS -ne 'Windows_NT') { throw 'The Stability check requires a Windows target.' } $startTime = (Get-Date).AddDays(-[double]$CheckSettings.LookbackDays) $incidents = New-Object System.Collections.Generic.List[object] $queryErrors = New-Object System.Collections.Generic.List[string] $queriesAttempted = 0 # Each indicator is queried separately so one unreadable provider does # not hide the others. $indicatorQueries = @( @{ Kind = 'Bugcheck'; Filter = @{ LogName = 'System'; StartTime = $startTime; ProviderName = 'Microsoft-Windows-WER-SystemErrorReporting'; Id = 1001 } } @{ Kind = 'Kernel power loss'; Filter = @{ LogName = 'System'; StartTime = $startTime; ProviderName = 'Microsoft-Windows-Kernel-Power'; Id = 41 } } @{ Kind = 'Unexpected shutdown'; Filter = @{ LogName = 'System'; StartTime = $startTime; ProviderName = 'EventLog'; Id = 6008 } } @{ Kind = 'Hardware error (WHEA)'; Filter = @{ LogName = 'System'; StartTime = $startTime; ProviderName = 'Microsoft-Windows-WHEA-Logger'; Level = @(1, 2) } } ) foreach ($indicatorQuery in $indicatorQueries) { $queriesAttempted++ try { $matchedEvents = @(Get-WinEvent -FilterHashtable $indicatorQuery.Filter -MaxEvents 50 -ErrorAction Stop) foreach ($matchedEvent in $matchedEvents) { $incidentRecord = [pscustomobject]@{ Kind = [string]$indicatorQuery.Kind TimeCreated = $matchedEvent.TimeCreated Id = [int]$matchedEvent.Id Provider = [string]$matchedEvent.ProviderName } [void]$incidents.Add($incidentRecord) } } catch { if ($_.FullyQualifiedErrorId -notlike 'NoMatchingEventsFound*') { [void]$queryErrors.Add("$($indicatorQuery.Kind): $($_.Exception.Message)") } } } $minidumpCount = $null try { $minidumpPath = Join-Path -Path $env:SystemRoot -ChildPath 'Minidump' if (Test-Path -LiteralPath $minidumpPath) { $minidumpCount = @(Get-ChildItem -LiteralPath $minidumpPath -Filter '*.dmp' -File -ErrorAction Stop).Count } else { $minidumpCount = 0 } } catch { $minidumpCount = $null } [pscustomobject]@{ Incidents = @($incidents.ToArray()) MinidumpCount = $minidumpCount QueriesAttempted = $queriesAttempted QueryErrors = @($queryErrors.ToArray()) } } $stability = Invoke-InfraPulseCommand -Context $Context -ScriptBlock $scriptBlock -ArgumentList @($Settings) $stopwatch.Stop() $incidents = @($stability.Incidents | Sort-Object -Property TimeCreated -Descending) $queryErrors = @($stability.QueryErrors) $incidentCount = $incidents.Count if ($queryErrors.Count -ge [int]$stability.QueriesAttempted -and $incidentCount -eq 0) { return New-InfraPulseResult -Status 'Unknown' -CheckName 'Stability' -Category 'Reliability' -ComputerName $Context.ComputerName -Target 'Operating system' -Message 'Crash indicators could not be queried.' -Recommendation 'Validate System event log read access; every indicator query failed.' -Evidence ([ordered]@{ QueryErrors = $queryErrors }) -DurationMs $stopwatch.Elapsed.TotalMilliseconds -ErrorMessage ($queryErrors -join '; ') } if ($incidentCount -ge [int]$Settings.CriticalCount) { $status = 'Critical' $message = "$incidentCount crash indicator(s) in the last $($Settings.LookbackDays) day(s)." $recommendation = 'Correlate bugchecks, power-loss events, and hardware errors with driver, firmware, and hardware diagnostics before the host degrades further.' } elseif ($incidentCount -ge [int]$Settings.WarningCount) { $status = 'Warning' $message = "$incidentCount crash indicator(s) in the last $($Settings.LookbackDays) day(s)." $recommendation = 'Review the incident evidence and check minidumps, recent driver changes, and power events.' } else { $status = 'Healthy' $message = "No crash indicators in the last $($Settings.LookbackDays) day(s)." $recommendation = '' } $evidence = [ordered]@{ IncidentCount = $incidentCount LookbackDays = [double]$Settings.LookbackDays Incidents = @($incidents | Select-Object -First 25) MinidumpCount = $stability.MinidumpCount QueryErrors = $queryErrors } return New-InfraPulseResult -Status $status -CheckName 'Stability' -Category 'Reliability' -ComputerName $Context.ComputerName -Target 'Operating system' -Message $message -ObservedValue $incidentCount -WarningThreshold (">= {0} incident(s)" -f $Settings.WarningCount) -CriticalThreshold (">= {0} incident(s)" -f $Settings.CriticalCount) -Recommendation $recommendation -Evidence $evidence -DurationMs $stopwatch.Elapsed.TotalMilliseconds } |