Public/Get-NSPMDECrashEvent.ps1

function Get-NSPMDECrashEvent {
    <#
    .SYNOPSIS
        Application crash and hang events (Application Error 1000, Windows Error Reporting 1001,
        Application Hang 1002, .NET Runtime 1026) as flat records: executable name and path,
        faulting module, exception code.
 
    .DESCRIPTION
        The first step towards the sharing-violation proof: the events name the EXECUTABLES that
        fail. The data-file path being fought over only shows up in the ProcMon capture.
 
        Event 1000 carries a stable property layout; for everything else (and other locales) the
        rendered message is parsed, which also fills anything the properties left blank.
 
    .PARAMETER ComputerName
        Hosts to read. The local machine is read directly; a remote one through the Remote Event
        Log service (RPC), which needs that firewall rule and rights on the host.
 
    .PARAMETER Days
        How far back to look. Default 14.
 
    .PARAMETER AdditionalProvider
        Extra Application-log providers to include (an app's own crash source); every event ID
        from them is read.
 
    .EXAMPLE
        Get-NSPMDECrashEvent -Days 30
 
    .EXAMPLE
        Get-NSPMDECrashEvent -ComputerName APP01 | Get-NSPMDECandidateApp
    #>

    [CmdletBinding()]
    param(
        [string[]]$ComputerName = @($env:COMPUTERNAME),
        [ValidateRange(1, 3650)][int]$Days = 14,
        [string[]]$AdditionalProvider = @()
    )

    $start = (Get-Date).AddDays(-$Days)
    $queries = @(@{ LogName = 'Application'; ProviderName = @('Application Error', 'Application Hang', 'Windows Error Reporting', '.NET Runtime'); Id = @(1000, 1001, 1002, 1026); StartTime = $start })
    if ($AdditionalProvider) { $queries += @{ LogName = 'Application'; ProviderName = @($AdditionalProvider); StartTime = $start } }

    foreach ($pc in $ComputerName) {
        $isLocal = (-not $pc) -or $pc -in @('.', 'localhost', $env:COMPUTERNAME)
        foreach ($query in $queries) {
            $read = @{ FilterHashtable = $query; ErrorAction = 'Stop' }
            if (-not $isLocal) { $read['ComputerName'] = $pc }
            $events = @()
            try { $events = @(Get-WinEvent @read) }
            catch {
                if ($_.FullyQualifiedErrorId -like 'NoMatchingEventsFound*' -or $_.Exception.Message -match 'No events were found') { continue }
                Write-Warning "Could not read the Application log on ${pc}: $($_.Exception.Message)"
                continue
            }

            foreach ($e in $events) {
                $msg = [string]$e.Message
                $props = @($e.Properties | ForEach-Object { $_.Value })
                $exeName = $null; $exePath = $null; $modPath = $null; $exCode = $null

                if ($e.Id -eq 1000) {
                    $exeName = $props[0]
                    if ($props.Count -gt 10) { $exePath = $props[10] }
                    if ($props.Count -gt 3) { $modPath = $props[3] }
                    if ($props.Count -gt 6) { $exCode = $props[6] }
                } elseif ($e.Id -eq 1002) {
                    $exeName = $props[0]
                }

                if ($msg) {
                    if ($msg -match 'Faulting application name:\s*([^,\r\n]+)') { $exeName = $Matches[1].Trim() }
                    if ($msg -match 'Faulting application path:\s*([^\r\n]+)') { $exePath = $Matches[1].Trim() }
                    if ($msg -match 'Faulting module path:\s*([^\r\n]+)') { $modPath = $Matches[1].Trim() }
                    if ($msg -match 'Exception code:\s*(0x[0-9A-Fa-f]+)') { $exCode = $Matches[1].Trim() }
                    if (-not $exeName -and $msg -match 'P1:\s*([^\r\n\s]+\.exe)') { $exeName = $Matches[1].Trim() }
                    if (-not $exeName -and $msg -match 'Application:\s*([^\r\n\s]+\.exe)') { $exeName = $Matches[1].Trim() }
                }
                if (-not $exeName) { continue }
                if ($exCode -is [int] -or $exCode -is [uint32] -or $exCode -is [long]) { $exCode = '0x{0:x8}' -f [uint32]([long]$exCode -band 0xFFFFFFFF) }

                [pscustomobject][ordered]@{
                    PSTypeName    = 'NSP.MDE.CrashEvent'
                    Computer      = $pc
                    TimeCreated   = $e.TimeCreated
                    EventId       = $e.Id
                    Provider      = $e.ProviderName
                    ExeName       = [string]$exeName
                    ExePath       = [string]$exePath
                    ModulePath    = [string]$modPath
                    ExceptionCode = [string]$exCode
                    Message       = ($msg -replace '\s+', ' ').Trim()
                }
            }
        }
    }
}