Private/Kinds/CrashDump.ps1

# The CrashDump Kind: the files Windows wrote down when it stopped.
#
# The Stability Kind counts blue screens from the event log. This finds what they left
# behind, which is the thing a second-level Technician can actually analyse: a minidump
# names the faulting driver, and the event does not.

$script:CrashDumpNeedsAdmin = $true

function Get-CrashDumpData {
    [CmdletBinding()]
    [OutputType([psobject])]
    param([hashtable]$Parameters = @{})

    $days  = [int](Get-Parameter $Parameters 'Days' 30)
    $since = (Get-Date).AddDays(-$days)

    $folder = Join-Path $env:SystemRoot 'Minidump'

    $denied = $false
    $dumps  = @()
    if (Test-Path -LiteralPath $folder) {
        $errors = $null
        $dumps = @(Get-ChildItem -LiteralPath $folder -Filter *.dmp -File -ErrorAction SilentlyContinue -ErrorVariable errors |
            Where-Object { $_.LastWriteTime -ge $since } |
            ForEach-Object {
                [pscustomobject]@{ Name = $_.Name; LastWriteTime = $_.LastWriteTime; SizeKB = [math]::Round($_.Length / 1KB) }
            })
        # A folder that exists and will not open is not a machine with no crash dumps.
        if ($errors -and @($errors | Where-Object { Test-AccessDenied -ErrorRecord $_ }).Count) { $denied = $true }
    }

    $full = Get-Item -LiteralPath (Join-Path $env:SystemRoot 'MEMORY.DMP') -ErrorAction SilentlyContinue

    [pscustomobject]@{
        PSTypeName        = 'Gutcheck.Data.CrashDump'
        Days              = $days
        Folder            = $folder
        FolderUnreadable  = $denied
        Minidumps         = $dumps
        FullDumpWrittenAt = $(if ($full -and $full.LastWriteTime -ge $since) { $full.LastWriteTime } else { $null })
    }
}

function ConvertTo-CrashDumpFinding {
    [CmdletBinding()]
    [OutputType([psobject])]
    param(
        [AllowNull()]$Data,
        [hashtable]$Parameters = @{}
    )

    $warnAbove = Get-Parameter $Parameters 'MinidumpWarnAbove' 0
    $failAbove = Get-Parameter $Parameters 'MinidumpFailAbove' ([double]::MaxValue)

    $days = Get-DataProperty $Data 'Days'

    if (Get-DataProperty $Data 'FolderUnreadable') {
        New-Finding -Category Stability -Check (Get-Text 'Check.CrashDump.Minidumps') -Severity INFO `
            -Value (Get-Text 'Value.CrashDump.FolderNeedsAdmin') `
            -Hint (Get-Text 'Hint.CrashDump.RerunAndAllowTheAdmin')
    }
    else {
        $dumps = Get-DataCollection $Data 'Minidumps'
        New-Finding -Category Stability -Check (Get-Text 'Check.CrashDump.Minidumps') `
            -Severity (Get-Severity $dumps.Count $warnAbove $failAbove) `
            -Value $(if ($dumps.Count) { (Get-Text 'Value.CrashDump.CountInPeriod') -f $dumps.Count, (Get-DataProperty $Data 'Folder'), $days }
                     else { 'none in period' }) `
            -Hint (Get-Text 'Hint.CrashDump.AnalyseWithWinDbgAnalyzeV')
    }

    $full = Get-DataProperty $Data 'FullDumpWrittenAt'
    if ($full) {
        New-Finding -Category Stability -Check (Get-Text 'Check.CrashDump.FullMemoryDump') -Severity WARN `
            -Value ('{0:yyyy-MM-dd HH:mm}' -f [datetime]$full) `
            -Hint (Get-Text 'Hint.CrashDump.WindowsWroteACompleteMemory')
    }
}

function ConvertTo-CrashDumpSection {
    [CmdletBinding()]
    [OutputType([psobject])]
    param([AllowNull()]$Data)

    New-Section -Title (Get-Text 'Title.CrashDump.Minidumps') -Row @(
        (Get-DataCollection $Data 'Minidumps') | Sort-Object LastWriteTime -Descending |
            Select-Object Name, LastWriteTime, SizeKB
    )
}