Debug-DMPFiles.ps1

 

Debug-DMPFiles.ps1


#region Function Get-CrashDumpFiles
Function Get-CrashDumpFiles
    {
        [cmdletbinding()]
        Param
            (
                [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$True,ValueFromPipeline=$True)]
                    [string[]]$ComputerName = $env:COMPUTERNAME,

                [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$True,ValueFromPipeline=$True)]
                    [string]$StorageLocation = '\\path\to\CrashDumpStorage'
            )

        Begin
            {
                [System.Collections.ArrayList]$CrashDumpDetails = @()
            }

        Process
            {
                Try
                    {
                        [int]$Counter=0
                        foreach($Computer in $ComputerName)
                            {
                                $Counter++ | Out-Null

                                Write-Host "Processing computer $Counter of $($ComputerName.Count)"

                                Remove-Variable RemotePath,MoveDestination,MemoryDMP,MemoryDMPDate,MiniDMP,MiniDMPCount -ErrorAction SilentlyContinue

                                # path to remote machine
                                $RemotePath = '\\' + $Computer + '\C$'

                                # path to data storage location
                                $MoveDestination = Join-Path $StorageLocation $Computer

                                # is online
                                if(!(Test-Path -Path "$RemotePath\Windows" -PathType Container -ErrorAction Stop))
                                    {
                                        $CrashDumpDetails += [pscustomobject]@{
                                            ComputerName = $Computer;
                                            Status = 'Inaccessible';
                                            #DumpFileDir = $MoveDestination;
                                            DumpFileDir = '';
                                            MemoryDMP = '';
                                            MemoryDMPDate = '';
                                            MiniDMP = '';
                                            MiniDMPCount = '';
                                        }
                                        continue
                                    }

                                # check for memory.dmp
                                [bool]$MemoryDMP = $false
                                $MemoryDMPDate = ''
                                if(Test-Path -Path "$RemotePath\Windows\Memory.dmp" -PathType Leaf -ErrorAction Stop)
                                    {
                                        if(!(Test-Path -Path $MoveDestination -PathType Container -ErrorAction Stop))
                                            {
                                                New-Item -Path $MoveDestination -ItemType directory -ErrorAction Stop | Out-Null
                                            }

                                        [bool]$MemoryDMP = $true

                                        $MemoryDMPDate = (Get-ChildItem -Path "$RemotePath\Windows\Memory.dmp").LastWriteTime
                                        Move-Item -Path "$RemotePath\Windows\Memory.dmp" -Destination $MoveDestination -Verbose -Force -ErrorAction Stop
                                    }

                                # Check for mini dumps
                                [bool]$MiniDMP = $false
                                $MiniDMPCount = 0
                                if(Test-Path -Path "$RemotePath\Windows\Minidump" -PathType Container)
                                    {
                                        $MiniDMPCount = (Get-ChildItem -Path "$RemotePath\Windows\Minidump" -ErrorAction Stop | Measure-Object).Count
                                        if($MiniDMPCount -gt 0)
                                            {
                                                if(!(Test-Path -Path $MoveDestination -PathType Container -ErrorAction Stop))
                                                    {
                                                        New-Item -Path $MoveDestination -ItemType directory -ErrorAction Stop | Out-Null
                                                    }

                                                [bool]$MiniDMP = $true
                                                Get-ChildItem -Path "$RemotePath\Windows\Minidump" | Sort-Object -Property LastWriteTime | Select -First 5 | % { Move-Item -Path $_.FullName -Destination $MoveDestination -Verbose -Force -ErrorAction Stop }
                                            }
                                    }

                                # Check for dmp files generated via Task Manager > Details > Process > Create dump file
                                [bool]$OtherDMP = $false
                                $OtherDMPCount = 0
                                foreach($UserProfile in $(Get-ChildItem -Path "$RemotePath\Users"))
                                    {
                                        if(!(Test-Path -Path $($UserProfile.FullName + '\AppData\Local\Temp') -PathType Container)) { continue }
                                        $OtherDMPCount = (Get-ChildItem $($UserProfile.FullName + '\AppData\Local\Temp') -Filter *.dmp -ErrorAction Stop | Measure-Object).Count
                                        if($OtherDMPCount -gt 0)
                                            {
                                                if(!(Test-Path -Path $MoveDestination -PathType Container -ErrorAction Stop))
                                                    {
                                                        New-Item -Path $MoveDestination -ItemType directory -ErrorAction Stop | Out-Null
                                                    }

                                                [bool]$OtherDMP = $true
                                                Get-ChildItem $($UserProfile.FullName + '\AppData\Local\Temp') -Filter *.dmp | % { Move-Item -Path $_.FullName -Destination $MoveDestination -Verbose -Force -ErrorAction Stop }
                                            }
                                    }

                                # add details to arraylist
                                $CrashDumpDetails += [pscustomobject]@{
                                    ComputerName = $Computer;
                                    Status = 'OK';
                                    DumpFileDir = $MoveDestination;
                                    MemoryDMP = $MemoryDMP;
                                    MemoryDMPDate = $MemoryDMPDate;
                                    MiniDMP = $MiniDMP;
                                    MiniDMPCount = $MiniDMPCount;
                                    OtherDMP = $OtherDMP
                                    OtherDMPCount = $OtherDMPCount;
                                }

                                Remove-Variable RemotePath,MoveDestination,MemoryDMP,MemoryDMPDate,MiniDMP,MiniDMPCount -ErrorAction SilentlyContinue
                            }
                    }
                Catch
                    {
                        throw $_
                    }
            }

        End { return $CrashDumpDetails }
    }
#endregion Function Get-CrashDumpFiles

#region Function Analyze-DumpFile
Function Analyze-DumpFile
    {
        [cmdletbinding()]
        Param
            (
                # Path to either a specific dmp file or a directory containing multiple dmp files
                [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$True,ValueFromPipeline=$True)]
                [Alias('DumpFileDir','DumpFile')]
                    [string[]]$Dump,

                [Parameter(Mandatory=$false)]
                    [switch]$Recurse = $false,

                # Path to the DebuggingTools executables
                [Parameter(Mandatory=$false)]
                    [string]$DBGRoot = "path\to\the\debuggingtools",

                # Path to the CDB.EXE
                [Parameter(Mandatory=$false)]
                    [string]$CDBEXE = $(Join-Path $DBGRoot 'cdb.exe'),

                # This file contains the commands we'll run against the dump file
                [Parameter(Mandatory=$false)]
                    [string]$AnalysisCommandsFile = "path\to\the\runscriptfile"
            )

        Begin
            {
                [System.Collections.ArrayList]$AnalysisDetails = @()
            }

        Process
            {
                Foreach($DumpFile in $Dump)
                    {
                        if($DumpFile.ToString().ToLower().EndsWith(".dmp"))
                            {
                                $DumpFiles = Get-ChildItem -Path $DumpFile -Include *.dmp -Recurse:$Recurse | Sort-Object -Property LastWriteTime
                            }
                        elseif((Get-Item $DumpFile) -is [System.IO.DirectoryInfo])
                            {
                                $DumpFiles = Get-ChildItem -Path "$DumpFile\*" -Include *.dmp -Recurse:$Recurse | Sort-Object -Property LastWriteTime
                            }

                        # Counter for keeping track
                        [int]$DMPCounter = 0

                        if($DumpFiles.Count -gt 1) { Write-Host "Preparing to process [$($DumpFiles.Count)] files" }
                        elseif($DumpFiles.Count -eq 1) { Write-Host "Preparing to process [$($DumpFiles.Count)] file" }

                        foreach($DMPFile in $DumpFiles)
                            {
                                Remove-Variable Log,ServiceTiming,CDBExitCode,ProbablyCausedBy,ExtraData -ErrorAction SilentlyContinue

                                $DMPCounter++ | Out-null

                                if(-not $Log)
                                    {
                                        if($AnalysisCommandsFile)
                                            {
                                                $Log = (Split-Path -Path $DMPFile -Parent) + "\" + (Split-Path -Path $DMPFile -Leaf) + "_" + (Split-Path -Path $AnalysisCommandsFile -Leaf).ToString().Replace(".txt",".log")
                                            }
                                        else
                                            {
                                                $Log = (Split-Path -Path $DMPFile -Parent) + "\" + (Split-Path -Path $DMPFile -Leaf).ToString().Replace(".dmp",".log")
                                            }
                                    }

                                if(Test-Path -Path $Log -PathType Leaf)
                                    {
                                        Move-Item -Path $Log -Destination $($Log.Replace(".log","_" + $(get-date -Format yyyyMMdd_hhmmss) + "_.log")) -Verbose
                                    }

                                Write-Host ">>>>>> $($DMPCounter.ToString().PadLeft($DumpFiles.Count.ToString().Length,'0')) of $($DumpFiles.Count) :: Starting Analysis of Dump [$($DMPFile.FullName)] >>>>>>"
                                Write-Host "Log [$Log]"

                                $ServiceTiming = Measure-Command {
                                    # Run the CDB with our commands file if specified
                                    if($AnalysisCommandsFile)
                                        {
                                            &$CDBEXE -z "$($DMPFile.FullName)" -c "`$`$<$AnalysisCommandsFile;Q" | Tee-Object -FilePath $Log
                                        }
                                    else
                                        {
                                            &$CDBEXE -z "$($DMPFile.FullName)" | Tee-Object -FilePath $Log
                                        }
                                }

                                $CDBExitCode = $LASTEXITCODE

                                if($CDBExitCode -ne 0)
                                    {
                                        Write-Host "ERROR CODE [$CDBExitCode] ANALYZING [$($DMPFile.FullName)] SEE LOG [$Log]"

                                        $AnalysisDetails += [pscustomobject]@{
                                            DumpFile = $DMPFile.FullName;
                                            Date = $DMPFile.LastWriteTime;
                                            CDBStatus = $CDBExitCode;
                                            BSODCause = '';
                                            BSODExtraData = '';
                                            AnalysisDuration = $ServiceTiming;
                                        }

                                        Write-Host "<<<<<< $($DMPCounter.ToString().PadLeft($DumpFiles.Count.ToString().Length'0')) of $($DumpFiles.Count) :: Finished Analysis of Dump [$($DMPFile.FullName)] :: Duration $($ServiceTiming.Minutes)m $($ServiceTiming.Seconds)s <<<<<<`r`n"
                                        Remove-Variable Log,ServiceTiming,CDBExitCode,ProbablyCausedBy,ExtraData -ErrorAction SilentlyContinue
                                        continue
                                    }

                                $ProbablyCausedBy = (Get-ChildItem -Path $Log | Select-String -Pattern "Probably caused by").Line.ToString().Replace("Probably caused by : ",'')
                                if($ProbablyCausedBy -like "* ( *")
                                    {
                                        $ExtraData = $ProbablyCausedBy.Substring($ProbablyCausedBy.IndexOf(" ")+3,$ProbablyCausedBy.LastIndexOf(" ")-3-$ProbablyCausedBy.IndexOf(" "))
                                        $ProbablyCausedBy = $ProbablyCausedBy.Substring(0,$ProbablyCausedBy.IndexOf(" "))
                                    }
                                elseif($ProbablyCausedBy -like "*memory_corruption*")
                                    {
                                        $ExtraData = (Get-ChildItem -Path $Log | Select-String -Pattern "Module load completed but symbols could not be loaded for ").Line.ToString().Replace("*** ERROR: Module load completed but symbols could not be loaded for ",'')
                                    }

                                $AnalysisDetails += [pscustomobject]@{
                                    DumpFile = $DMPFile.FullName;
                                    Date = $DMPFile.LastWriteTime;
                                    CDBStatus = $CDBExitCode;
                                    BSODCause = $ProbablyCausedBy;
                                    BSODExtraData = $ExtraData;
                                    AnalysisDuration = $ServiceTiming;
                                }

                                Write-Host "<<<<<< $($DMPCounter.ToString().PadLeft($DumpFiles.Count.ToString().Length,'0')) of $($DumpFiles.Count) :: Finished Analysis of Dump [$($DMPFile.FullName)] :: Duration $($ServiceTiming.Minutes)m $($ServiceTiming.Seconds)s <<<<<<`r`n"
                                Remove-Variable Log,ServiceTiming,CDBExitCode,ProbablyCausedBy,ExtraData -ErrorAction SilentlyContinue
                            }
                    }
            }

        end { Return $AnalysisDetails }
    }
#endregion Function Analyze-DumpFile

# Grab the dumps from the asset to the temporary storage location
#  This will do the current machine
$GetCrashDumpFiles = Get-CrashDumpFiles

#  This will do a single remote machine
#$GetCrashDumpFiles = Get-CrashDumpFiles -ComputerName Computer1

#  This will handle multiple remote machines
#$GetCrashDumpFiles = Get-CrashDumpFiles -ComputerName Computer1,Computer2,Computer3,Computer4

# Show all results
#$GetCrashDumpFiles | Sort MemoryDMPDate,MiniDMPCount | Format-Table -AutoSize
# Show only machines that are online/accessible AND have either a memory dump OR at least one mini dump OR some other dump file
#$GetCrashDumpFiles | ? { ($_.Status -eq 'ok') -and (($_.MemoryDMP -eq $true) -or ($_.MiniDMP -eq $true) -or ($_.MiniDMP -eq $true)) } | Sort MemoryDMPDate,MiniDMPCount | Format-Table -AutoSize
# Analze the dumps of machines that have either a memory dump OR at least one mini dump OR some other dump file
$DumpFileAnalysis = Analyze-DumpFile -DumpFileDir $($GetCrashDumpFiles | ? { ($_.MemoryDMP -eq $true) -or ($_.MiniDMP -eq $true) -or ($_.OtherDMP -eq $true) } | Select -ExpandProperty DumpFileDir)

# Alternatively maybe you have a repository of .DMP files that you just want to analyze
#$DumpFileAnalysis = Analyze-DumpFile -DumpFileDir '\\path\do\dumpfiles'

# Review the results of the DMP file analysis
$DumpFileAnalysis | Sort -Descending -Property Date | Format-Table -AutoSize

.