Public/system/Get-CrashDump.ps1

#Requires -Version 5.1
function Get-CrashDump {
    <#
    .SYNOPSIS
        Inventory Windows crash memory dumps with size, type and BugCheck code
 
    .DESCRIPTION
        Enumerates minidumps and the full/kernel MEMORY.DMP on one or more machines in a single
        pass, reporting size, creation time and configured dump type. The BugCheck code and
        symbol are correlated from WER System Error Reporting event 1001 by temporal proximity,
        so no binary dump parsing is required. Local and remote targets are dispatched through
        Invoke-RemoteOrLocal.
 
    .PARAMETER ComputerName
        One or more computer names to target. Defaults to the local computer.
        Accepts pipeline input by value and by property name.
 
    .PARAMETER Credential
        Optional PSCredential for authenticating to remote machines. Ignored for
        local machine queries.
 
    .PARAMETER Path
        Override dump directory/glob. Default is '%SystemRoot%\Minidump\*.dmp' plus
        '%SystemRoot%\MEMORY.DMP'. When supplied, this path or glob is searched instead
        of the two defaults.
 
    .PARAMETER Newest
        Return only the N most recent dumps per machine, sorted by CreationTime
        descending. When omitted, all discovered dumps are returned.
 
    .EXAMPLE
        Get-CrashDump
 
        Returns every crash dump found on the local computer.
 
    .EXAMPLE
        Get-CrashDump -ComputerName 'SRV01' -Newest 5
 
        Returns the 5 most recent crash dumps on SRV01 via WinRM.
 
    .EXAMPLE
        'SRV01', 'SRV02' | Get-CrashDump -Credential $cred
 
        Returns crash dumps for SRV01 and SRV02 via pipeline, using alternate credentials.
 
    .OUTPUTS
        PSWinOps.CrashDump
        One object per discovered dump file, with size, creation time, configured dump
        type, and the correlated BugCheck code/symbol when resolvable.
 
    .NOTES
        Author: Franck SALLET
        Version: 1.0.0
        Last Modified: 2026-07-05
        Requires: PowerShell 5.1+ / Windows only
        Requires: Read access to %SystemRoot%\Minidump and %SystemRoot%\MEMORY.DMP;
        WinRM enabled on target machines for remote queries
 
    .LINK
        https://github.com/k9fr4n/PSWinOps
 
    .LINK
        https://github.com/k9fr4n/PSWinOps/issues/66
    #>

    [CmdletBinding()]
    [OutputType('PSWinOps.CrashDump')]
    param(
        [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [Alias('CN', 'Name', 'DNSHostName')]
        [string[]]$ComputerName = $env:COMPUTERNAME,

        [Parameter(Mandatory = $false)]
        [System.Management.Automation.PSCredential]$Credential,

        [Parameter(Mandatory = $false)]
        [string]$Path,

        [Parameter(Mandatory = $false)]
        [ValidateRange(1, [int]::MaxValue)]
        [int]$Newest
    )

    begin {
        Write-Verbose "[$($MyInvocation.MyCommand)] Starting crash dump inventory"

        $hasNewest = $PSBoundParameters.ContainsKey('Newest')
        $newestVal = if ($hasNewest) { $Newest } else { 0 }
        $hasPath   = $PSBoundParameters.ContainsKey('Path')
        $pathVal   = if ($hasPath) { $Path } else { '' }

        $scriptBlock = {
            param(
                [bool]$HasPath,
                [string]$OverridePath,
                [bool]$HasNewest,
                [int]$NewestCount
            )

            # Bugcheck code -> human readable symbol lookup (common codes only)
            $bugCheckSymbols = @{
                '0X00000001' = 'APC_INDEX_MISMATCH'
                '0X0000000A' = 'IRQL_NOT_LESS_OR_EQUAL'
                '0X0000001A' = 'MEMORY_MANAGEMENT'
                '0X0000001E' = 'KMODE_EXCEPTION_NOT_HANDLED'
                '0X00000024' = 'NTFS_FILE_SYSTEM'
                '0X0000003B' = 'SYSTEM_SERVICE_EXCEPTION'
                '0X0000004E' = 'PFN_LIST_CORRUPT'
                '0X00000050' = 'PAGE_FAULT_IN_NONPAGED_AREA'
                '0X0000007A' = 'KERNEL_DATA_INPAGE_ERROR'
                '0X0000007E' = 'SYSTEM_THREAD_EXCEPTION_NOT_HANDLED_M'
                '0X0000007F' = 'UNEXPECTED_KERNEL_MODE_TRAP'
                '0X0000008E' = 'KERNEL_MODE_EXCEPTION_NOT_HANDLED'
                '0X0000009F' = 'DRIVER_POWER_STATE_FAILURE'
                '0X000000C2' = 'BAD_POOL_CALLER'
                '0X000000CE' = 'DRIVER_UNLOADED_WITHOUT_CANCELLING_PENDING_OPERATIONS'
                '0X000000D1' = 'DRIVER_IRQL_NOT_LESS_OR_EQUAL'
                '0X000000EF' = 'CRITICAL_PROCESS_DIED'
                '0X000000F4' = 'CRITICAL_OBJECT_TERMINATION'
                '0X00000109' = 'CRITICAL_STRUCTURE_CORRUPTION'
                '0X00000119' = 'VIDEO_SCHEDULER_INTERNAL_ERROR'
                '0X00000124' = 'WHEA_UNCORRECTABLE_ERROR'
                '0X00000133' = 'DPC_WATCHDOG_VIOLATION'
                '0X00000139' = 'KERNEL_SECURITY_CHECK_FAILURE'
            }

            # Rule 5: CIM over WMI for the Windows directory
            $systemRoot = (Get-CimInstance -ClassName 'Win32_OperatingSystem' -ErrorAction Stop).WindowsDirectory

            $dumpFiles = [System.Collections.Generic.List[System.IO.FileInfo]]::new()

            if ($HasPath -and -not [string]::IsNullOrWhiteSpace($OverridePath)) {
                $resolvedOverride = $OverridePath
                if (Test-Path -Path $resolvedOverride -PathType Container -ErrorAction SilentlyContinue) {
                    $resolvedOverride = Join-Path -Path $resolvedOverride -ChildPath '*.dmp'
                }
                $found = @(Get-ChildItem -Path $resolvedOverride -File -ErrorAction SilentlyContinue)
                foreach ($item in $found) { $dumpFiles.Add($item) }
            } else {
                $miniDumpGlob = Join-Path -Path $systemRoot -ChildPath 'Minidump\*.dmp'
                $memoryDump   = Join-Path -Path $systemRoot -ChildPath 'MEMORY.DMP'

                $miniFound = @(Get-ChildItem -Path $miniDumpGlob -File -ErrorAction SilentlyContinue)
                foreach ($item in $miniFound) { $dumpFiles.Add($item) }

                $memoryFound = @(Get-Item -Path $memoryDump -ErrorAction SilentlyContinue)
                foreach ($item in $memoryFound) { $dumpFiles.Add($item) }
            }

            if ($dumpFiles.Count -eq 0) {
                return @()
            }

            # Configured dump type, read once, used to classify MEMORY.DMP
            $crashControlPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\CrashControl'
            $crashDumpEnabled = (Get-ItemProperty -Path $crashControlPath -Name 'CrashDumpEnabled' -ErrorAction SilentlyContinue).CrashDumpEnabled

            # WER System Error Reporting 1001 events, fetched once and correlated by proximity
            $werEvents = @(Get-WinEvent -FilterHashtable @{
                    LogName      = 'Application'
                    ProviderName = 'Microsoft-Windows-WER-SystemErrorReporting'
                    Id           = 1001
                } -ErrorAction SilentlyContinue)

            $results = [System.Collections.Generic.List[psobject]]::new()

            foreach ($dump in $dumpFiles) {
                $dumpType = if ($dump.Name -like 'Mini*.dmp') {
                    'Mini'
                } elseif ($crashDumpEnabled -eq 2) {
                    'Kernel'
                } else {
                    'Full'
                }

                $bugCheckCode   = $null
                $bugCheckSymbol = $null

                $closestEvent = $werEvents | Where-Object {
                    [math]::Abs(($_.TimeCreated - $dump.CreationTime).TotalMinutes) -le 10
                } | Sort-Object -Property { [math]::Abs(($_.TimeCreated - $dump.CreationTime).TotalMinutes) } | Select-Object -First 1

                if ($null -ne $closestEvent) {
                    $matched = [regex]::Match($closestEvent.Message, '0x[0-9A-Fa-f]{8}')
                    if ($matched.Success) {
                        $bugCheckCode = $matched.Value.ToUpperInvariant() -replace '^0X', '0x'
                        $lookupKey = $matched.Value.ToUpperInvariant()
                        if ($bugCheckSymbols.ContainsKey($lookupKey)) {
                            $bugCheckSymbol = $bugCheckSymbols[$lookupKey]
                        }
                    }
                }

                $results.Add([PSCustomObject]@{
                        PSTypeName      = 'PSWinOps.CrashDump'
                        ComputerName    = $env:COMPUTERNAME
                        DumpFile        = $dump.FullName
                        DumpType        = $dumpType
                        SizeMB          = [math]::Round($dump.Length / 1MB, 2)
                        CreationTime    = $dump.CreationTime.ToString('yyyy-MM-dd HH:mm:ss')
                        BugCheckCode    = $bugCheckCode
                        BugCheckSymbol  = $bugCheckSymbol
                        Timestamp       = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
                    })
            }

            $sorted = @($results | Sort-Object -Property CreationTime -Descending)

            if ($HasNewest -and $NewestCount -gt 0) {
                $sorted = @($sorted | Select-Object -First $NewestCount)
            }

            $sorted
        }
    }

    process {
        foreach ($targetComputer in $ComputerName) {
            try {
                Write-Verbose "[$($MyInvocation.MyCommand)] Querying crash dumps on '$targetComputer'"
                Invoke-RemoteOrLocal -ComputerName $targetComputer -Credential $Credential `
                    -ScriptBlock $scriptBlock `
                    -ArgumentList @($hasPath, $pathVal, $hasNewest, $newestVal)
            } catch {
                Write-Error "[$($MyInvocation.MyCommand)] Failed on '$targetComputer': $_"
            }
        }
    }

    end {
        Write-Verbose "[$($MyInvocation.MyCommand)] Completed crash dump inventory"
    }
}