########################################################################################################
# PowerDbg v 2.3
#
# Roberto Alexis Farah
# All my functions are provided "AS IS" with no warranties, and confer no rights.
########################################################################################################
# Global variables.
########################################################################################################
$global:g_instance = $null
$global:g_fileParsedOutput = "POWERDBG-PARSED.LOG"
$global:g_fileCommandOutput = "POWERDBG-OUTPUT.LOG"
$global:g_CSVDelimiter = ","
$global:g_frameDelimiter = "#$#@"
# It's not possible to create an enum like C++/C# using a PowerShell keyword.
# The PowerShell blog has a solution for it, but here I'm going to use local variables.
$global:g_unknownSymbol = 0
$global:g_waitingForCriticalSection = 1
$global:g_doingIO = 2
$global:g_threadWaiting = 3
$global:g_GCthread = 4
$global:g_waitUntilGCComplete = 5
$global:g_suspendForGC = 6
$global:g_waitForFinalize = 7
$global:g_tryingGetLock = 8
$global:g_winSockReceivingData = 9
$global:g_finalizerThread = 10
$global:g_CLRDebuggerThread = 11
$global:g_w3wpMainThread = 12
$global:g_threadPool = 13
$global:g_CompressionThreaad = 14
$global:g_COMcall = 15
$global:g_CLRWorkerThread = 16
$global:g_completionPortIOThread = 17
$global:g_gateThread = 18
$global:g_timerThread = 19
$global:g_unloadAppDomainThread = 20
$global:g_RPCWorkerThread = 21
$global:g_LPRCWorkerThread = 22
$global:g_hostSTAThread = 23
# The array below has the meaning of each constant above. It can be used to display high level information
# to the user.
$global:g_whatThreadIsDoing =
@(
"Thread working and doing unknown activity.", # 0
"Thread waiting for Critical Section.", # 1
"Thread doing I/O operation.", # 2
"Thread in wait state.", # 3
"Thread from Garbage Collector.", # 4
"Thread waiting for the Garbage Collector to complete.", # 5
"Thread is being suspended to perform a Garbage Collector." # 6
"Thread waiting for the Finalizer event. The Finalizer thread might be blocked.", # 7
"Thread trying to get a managed lock.", # 8
"Thread receiving or trying to receive data. The data might be from the database.", # 9
"Thread is the Finalizer Thread.", # 10
"Thread is the CLR Debugger Thread.", # 11
"Thread is the W3WP.EXE main thread.", # 12
"Thread is from the W3WP.EXE pool of threads.", # 13
"Thread is a Compression Thread from W3WP.EXE.", # 14
"Thread doing a COM call.", # 15
"Thread is a CLR Worker Thread.", # 16
"Thread is a Completion Port I/O Thread.", # 17
"Thread is a CLR Gate Thread.", # 18
"Thread is a CLR Timer Thread.", # 19
"Thread is an Unload Application Domain Thread.", # 20
"Thread is a RPC Worker Thread.", # 21
"Thread is an LRPC Worker Thread.", # 22
"Thread is the Host STA Thread." # 23
)
########################################################################################################
# Function: Start-PowerDbgWinDbg
#
# Parameters: [string] <$debuggerPathExe>
# Path and executable where is located your WinDbg.
#
# [string] <$nameOfDumpOrProcess>
# Name of dump file or process to debug. The process must be running.$#
#
# [string] <$symbolPath>
# Specifies the symbol file search path. Separate multiple paths with a semicolon (;).
#
# Return: Global variable $debuggerInstance that has the debugger instance.
#
# Purpose: Start an WinDbg $g_instance and open a dump file or attach to a running process.
#
# Changes History:
#
# Roberto Alexis Farah
# All my functions are provided "AS IS" with no warranties, and confer no rights.
########################################################################################################
function Start-PowerDbgWinDbg(
[string] $debuggerPathExe = $(throw "Error! You must provide the Windbg path."),
[string] $nameOfDumpOrProcess = $(throw "Error! You must provide dump file or process name."),
[string] $symbolPath = $(throw "Error! You must provide the symbol path.")
)
{
set-psdebug -strict
$ErrorActionPreference = "stop"
trap {"Error message: $_"}
$debugger = $debuggerPathExe
$isExe = $false
# Check if the argument is a dump file or process name and use the corresponding WinDbg command.
if($nameOfDumpOrProcess -ilike "*.dmp")
{
$debugger += " -z " + $nameOfDumpOrProcess
}
elseif($nameOfDumpOrProcess -ilike "*.exe")
{
$isExe = $true
}
else
{
throw "Error! You must provide a valid dump file or executing process."
}
if($isExe)
{
$debugger += " -Q -QSY -QY -v -y " + "`"$symbolPath`"" + " -c `".symfix;.reload`"" + " -T PowerDbg" + " " + $nameOfDumpOrProcess
}
else
{
$debugger += " -Q -QSY -QY -v -y " + "`"$symbolPath`"" + " -c `".symfix;.reload`"" + " -T PowerDbg"
}
# Now we can start a new debugger instance.
$global:g_instance = new-object -comobject WScript.Shell
$output = $global:g_instance.Run($debugger, 3)
# Maximize window. It's not necessary to use the full name.
$output = $global:g_instance.AppActivate("PowerDbg")
}
########################################################################################################
# Function: Send-PowerDbgCommand
#
# Parameters: [string] <$command>
# Command from Windbg. Avoid mixing more than one command at the same line to be easier to parse the output.
#
# Return: Nothing.
#
# Purpose: Sends a command to the Windbg instance that has the PowerDbg title and saves the command and its output
# into a log file named POWERDBG.LOG.
# If there's no instance that has the PowerDbg title you need to use the .wtitle command from WinDbg
# and change the WinDbg window in order to start with the PowerDbg string.
# The command output will be into the POWERDBG-OUTPUT.LOG.
# Your parser functions should use POWERDBG-OUTPUT.LOG.
#
# Changes History: 12/31/2007 - In a few specific scenarios the .logopen command may not create the log file.
# Now the POWERDBG.LOG is tested. If it was not created, the function tries to
# create it for a limited number of times.
#
# Roberto Alexis Farah
# All my functions are provided "AS IS" with no warranties, and confer no rights.
########################################################################################################
function Send-PowerDbgCommand([string] $command = $(throw "Error! You must provide a Windbg command."))
{
set-psdebug -strict
$ErrorActionPreference = "stop"
trap {"Error message: $_"}
# First let's locate if Start-PowerDbgWinDbg had created an WinDbg instance.
# If not, let's try to use one running instance.
if($global:g_instance -eq $null)
{
$global:g_instance = new-object -comobject WScript.Shell
}
# Set focus to Windbg instance.
$return = $global:g_instance.AppActivate("PowerDbg")
start-sleep 1
# Set focus to Command window.
$return = $global:g_instance.AppActivate("Command")
start-sleep 1
# Get the directory where the log will be created.
$aux = get-Process windbg
# We may have several instances. That's why I use element 0.
if($aux.Count -gt 0)
{
$aux = $aux[0].MainModule.Filename
}
else
{
$aux = $aux.MainModule.Filename
}
$aux = [System.IO.Path]::GetDirectoryName($aux)
$logFile = "$aux\POWERDBG.LOG"
$fileExists = test-Path $logFile
# Remove log file, so we can know if it was created.
if($fileExists -eq $true)
{
remove-Item $logFile
}
# Create log.
$output = $global:g_instance.SendKeys(".logopen $logFile")
$output = $global:g_instance.SendKeys("{ENTER}")
# If POWERDBG.LOG was not created, try it again for 5 times.
for([System.Int32] $count = 0; $count -lt 5; $count++)
{
start-Sleep 1
$fileExists = test-Path $logFile
# If the log file was created leaves the loop.
if($fileExists -eq $true)
{
break
}
else # If not, tries to create it again, for 5 more times.
{
# Tries to create log again.
$output = $global:g_instance.SendKeys(".logopen $logFile")
$output = $global:g_instance.SendKeys("{ENTER}")
}
}
# Adjust specific commands.
$command = $command.Replace("~", "{~}")
$command = $command.Replace("%", "{%}")
$command = $command.Replace("+", "{+}")
# Send command.
$output = $global:g_instance.SendKeys($command)
$output = $global:g_instance.SendKeys("{ENTER}")
# Close log, saving last command and its output.
$output = $global:g_instance.SendKeys(".logclose")
$output = $global:g_instance.SendKeys("{ENTER}")
# A delay is required here.
start-sleep 3
# Extract output removing commands.
$builder = New-Object System.Text.StringBuilder
get-content "$aux\POWERDBG.LOG" | foreach-object {if(($_ -notmatch "^.:...>") -and ($_ -notmatch "^Opened log file") -and ($_ -notmatch "^Closing open log file")){$builder = $builder.AppendLine([string] $_)}}
# Save the output into a file. The location is the same you are executing PowerDbg.
out-file -filepath $global:g_fileCommandOutput -inputobject "$builder"
}
########################################################################################################
# Function: Parse-PowerDbgDT
#
# Parameters: [switch] [$useFieldNames]
# Switch flag. If $useFieldNames is present then the function saves the field
# names from struct/classes and their values. Otherwise, it creates saves the offsets
# and their values.
#
# Return: Nothing.
#
# Purpose: Maps the output from the "dt" command using a hash table. The output
# is saved into the file POWERDBG-PARSED.LOG
# All Parse functions should use the same outputfile.
# You can easily map the POWERDBG-PARSED.LOG to a hash table.
# Convert-PowerDbgCSVtoHashTable() does that.
#
# Changes History:
#
# Roberto Alexis Farah
# All my functions are provided "AS IS" with no warranties, and confer no rights.
########################################################################################################
function Parse-PowerDbgDT([switch] $useFieldNames)
{
set-psdebug -strict
$ErrorActionPreference = "stop"
trap {"Error message: $_"}
# Extract output removing commands.
$builder = New-Object System.Text.StringBuilder
# Title for the CSV fields.
$builder = $builder.AppendLine("key,value")
# \s+ --> Scans for one or more spaces.
# (\S+) --> Gets one or more chars/digits/numbers without spaces.
# \s+ --> Scans for one or more spaces.
# (\w+) --> Gets one or more chars.
# .+ --> Scans for one or more chars (any char except for new line).
# \: --> Scans for the ':' char.
# \s+ --> Scans for one or more spaces.
# (.+.) --> Gets the entire remainder string including the spaces.
if($useFieldNames)
{
foreach($line in $(get-content $global:g_fileCommandOutput))
{
if($line -match "0x\S+\s+(?<key>\w+).+\:\s+(?<value>.+)")
{
$builder = $builder.AppendLine($matches["key"] + $global:g_CSVDelimiter + $matches["value"])
}
}
}
else
{
foreach($line in $(get-content $global:g_fileCommandOutput))
{
if($line -match "(?<key>0x\S+).+\:\s+(?<value>.+)")
{
$builder = $builder.AppendLine($matches["key"] + $global:g_CSVDelimiter + $matches["value"])
}
}
}
# Send output to our default file.
out-file -filepath $global:g_fileParsedOutput -inputobject "$builder"
}
########################################################################################################
# Function: Convert-PowerDbgCSVtoHashTable
#
# Parameters: None.
#
# Return: Hash table.
#
# Purpose: Sometimes the Parse-PowerDbg#() functions return a CSV file. This function
# loads the data using a hash table.
# However, it works just when the CSV file has two fields: key and value.
#
# Changes History:
#
# Roberto Alexis Farah
# All my functions are provided "AS IS" with no warranties, and confer no rights.
########################################################################################################
function Convert-PowerDbgCSVtoHashTable()
{
set-psdebug -strict
$ErrorActionPreference = "stop"
trap {"Error message: $_"}
$hashTable = @{}
import-csv -path $global:g_fileParsedOutput | foreach {$hashTable[$_.key] = $_.value}
return $hashTable
}
########################################################################################################
# Function: Send-PowerDbgDML
#
# Parameters: [string] <$hyperlinkDML>
# Hyperlink for the DML command.
#
# [string] <$commandDML>
# Command to execute when the hyperlink is clicked.
#
# Return: Nothing.
#
# Purpose: Creates a DML command and send it to Windbg.
# DML stands for Debug Markup Language. Using DML you can create hyperlinks that
# run a command when the user click on them.
#
# Changes History:
#
# Roberto Alexis Farah
# All my functions are provided "AS IS" with no warranties, and confer no rights.
########################################################################################################
function Send-PowerDbgDML(
[string] $hyperlinkDML = $(throw "Error! You must provide the hyperlink for DML."),
[string] $commandDML = $(throw "Error! You must provide the command for DML.")
)
{
set-psdebug -strict
$ErrorActionPreference = "stop"
trap {"Error message: $_"}
Send-PowerDbgCommand ".printf /D `"<link cmd=\`"$commandDML\`"><b>$hyperlinkDML</b></link>\n\`"`""
}
########################################################################################################
# Function: Parse-PowerDbgNAME2EE
#
# Parameters: None.
#
# Return: Nothing.
#
# Purpose: Maps the output from the "!name2ee" command using a hash table. The output
# is saved into the file POWERDBG-PARSED.LOG
# All Parse functions should use the same outputfile.
# You can easily map the POWERDBG-PARSED.LOG to a hash table.
# Convert-PowerDbgCSVtoHashTable() does that.
#
# Changes History:
#
# Roberto Alexis Farah
# All my functions are provided "AS IS" with no warranties, and confer no rights.
########################################################################################################
function Parse-PowerDbgNAME2EE()
{
set-psdebug -strict
$ErrorActionPreference = "stop"
trap {"Error message: $_"}
# Extract output removing commands.
$builder = New-Object System.Text.StringBuilder
# Title for the CSV fields.
$builder = $builder.AppendLine("key,value")
# \s+ --> Scans for one or more spaces.
# (\S+) --> Gets one or more chars/digits/numbers without spaces.
# \s+ --> Scans for one or more spaces.
# (\w+) --> Gets one or more chars.
# .+ --> Scans for one or more chars (any char except for new line).
# \: --> Scans for the ':' char.
# \s+ --> Scans for one or more spaces.
# (.+.) --> Gets the entire remainder string including the spaces.
foreach($line in $(get-content $global:g_fileCommandOutput))
{
# Attention! The Name: doesn't map to the right value, however, it should be the same method name provide as argument.
if($line -match "(?<key>\w+\:)\s+(?<value>\S+)")
{
$builder = $builder.AppendLine($matches["key"] + $global:g_CSVDelimiter + $matches["value"])
}
}
# Send output to our default file.
out-file -filepath $global:g_fileParsedOutput -inputobject "$builder"
}
########################################################################################################
# Function: Parse-PowerDbgDUMPMD
#
# Parameters: None.
#
# Return: Nothing.
#
# Purpose: Maps the output from the "!dumpmd" command using a hash table. The output
# is saved into the file POWERDBG-PARSED.LOG
# All Parse functions should use the same outputfile.
# You can easily map the POWERDBG-PARSED.LOG to a hash table.
# Convert-PowerDbgCSVtoHashTable() does that.
#
# Changes History:
#
# Roberto Alexis Farah
# All my functions are provided "AS IS" with no warranties, and confer no rights.
########################################################################################################
function Parse-PowerDbgDUMPMD()
{
set-psdebug -strict
$ErrorActionPreference = "stop"
trap {"Error message: $_"}
# Extract output removing commands.
$builder = New-Object System.Text.StringBuilder
# Title for the CSV fields.
$builder = $builder.AppendLine("key,value")
# \s+ --> Scans for one or more spaces.
# (\S+) --> Gets one or more chars/digits/numbers without spaces.
# \s+ --> Scans for one or more spaces.
# (\w+) --> Gets one or more chars.
# .+ --> Scans for one or more chars (any char except for new line).
# \: --> Scans for the ':' char.
# \s+ --> Scans for one or more spaces.
# (.+.) --> Gets the entire remainder string including the spaces.
foreach($line in $(get-content $global:g_fileCommandOutput))
{
if($line -match "(?<key>((^Method Name :)|(^MethodTable)|(^Module:)|(^mdToken:)|(^Flags :)|(^Method VA :)))\s+(?<value>\S+)")
{
$builder = $builder.AppendLine($matches["key"] + $global:g_CSVDelimiter + $matches["value"])
}
}
# Send output to our default file.
out-file -filepath $global:g_fileParsedOutput -inputobject "$builder"
}
########################################################################################################
# Function: Parse-PowerDbgDUMPMODULE
#
# Parameters: None.
#
# Return: Nothing.
#
# Purpose: Maps the output from the "!dumpmodule" command using a hash table. The output
# is saved into the file POWERDBG-PARSED.LOG
# All Parse functions should use the same outputfile.
# You can easily map the POWERDBG-PARSED.LOG to a hash table.
# Convert-PowerDbgCSVtoHashTable() does that.
#
# Changes History:
#
# Roberto Alexis Farah
# All my functions are provided "AS IS" with no warranties, and confer no rights.
########################################################################################################
function Parse-PowerDbgDUMPMODULE()
{
set-psdebug -strict
$ErrorActionPreference = "stop"
trap {"Error message: $_"}
# Extract output removing commands.
$builder = New-Object System.Text.StringBuilder
# Title for the CSV fields.
$builder = $builder.AppendLine("key,value")
[int] $countFields = 0
# \s+ --> Scans for one or more spaces.
# (\S+) --> Gets one or more chars/digits/numbers without spaces.
# \s+ --> Scans for one or more spaces.
# (\w+) --> Gets one or more chars.
# .+ --> Scans for one or more chars (any char except for new line).
# \: --> Scans for the ':' char.
# \s+ --> Scans for one or more spaces.
# (.+.) --> Gets the entire remainder string including the spaces.
foreach($line in $(get-content $global:g_fileCommandOutput))
{
# Fields for .NET Framework 2.0
if($line -match "(?<key>((^dwFlags)|(^Assembly:)|(^LoaderHeap:)|(^TypeDefToMethodTableMap:)|(^TypeRefToMethodTableMap:)|(^MethodDefToDescMap:)|(^FieldDefToDescMap:)|(^MemberRefToDescMap:)|(^FileReferencesMap:)|(^AssemblyReferencesMap:)|(^MetaData start address:)))\s+(?<value>\S+)")
{
$builder = $builder.AppendLine($matches["key"] + $global:g_CSVDelimiter + $matches["value"])
$countFields++
}
}
# If nothing was found, let's try to use the .NET Framework 1.1 fields.
if($countFields -lt 3)
{
foreach($line in $(get-content $global:g_fileCommandOutput))
{
# Fields for .NET Framework 2.0
if($line -match "(?<key>((^dwFlags)|(^Assembly\*)|(^LoaderHeap\*)|(^TypeDefToMethodTableMap\*)|(^TypeRefToMethodTableMap\*)|(^MethodDefToDescMap\*)|(^FieldDefToDescMap\*)|(^MemberRefToDescMap\*)|(^FileReferencesMap\*)|(^AssemblyReferencesMap\*)|(^MetaData starts at)))\s+(?<value>\S+)")
{
$builder = $builder.AppendLine($matches["key"] + $global:g_CSVDelimiter + $matches["value"])
$hasFound = $true
}
}
}
# Send output to our default file.
out-file -filepath $global:g_fileParsedOutput -inputobject "$builder"
}
########################################################################################################
# Function: Parse-PowerDbgLMI
#
# Parameters: None.
#
# Return: Nothing.
#
# Purpose: Maps the output from the "!lmi" command using a hash table. The output
# is saved into the file POWERDBG-PARSED.LOG
# All Parse functions should use the same outputfile.
# You can easily map the POWERDBG-PARSED.LOG to a hash table.
# Convert-PowerDbgCSVtoHashTable() does that.
#
# Changes History:
#
# Roberto Alexis Farah
# All my functions are provided "AS IS" with no warranties, and confer no rights.
########################################################################################################
function Parse-PowerDbgLMI()
{
set-psdebug -strict
$ErrorActionPreference = "stop"
trap {"Error message: $_"}
# Extract output removing commands.
$builder = New-Object System.Text.StringBuilder
# Title for the CSV fields.
$builder = $builder.AppendLine("key,value")
# \s+ --> Scans for one or more spaces.
# (\S+) --> Gets one or more chars/digits/numbers without spaces.
# \s+ --> Scans for one or more spaces.
# (\w+) --> Gets one or more chars.
# .+ --> Scans for one or more chars (any char except for new line).
# \: --> Scans for the ':' char.
# \s+ --> Scans for one or more spaces.
# (.+.) --> Gets the entire remainder string including the spaces.
foreach($line in $(get-content $global:g_fileCommandOutput))
{
if($line -match "(?<key>((^.+\:)))\s+(?<value>\S+)")
{
$strNoLeftSpaces = $matches["key"]
$strNoLeftSpaces = $strNoLeftSpaces.TrimStart()
$builder = $builder.AppendLine($strNoLeftSpaces + $global:g_CSVDelimiter + $matches["value"])
}
}
# Send output to our default file.
out-file -filepath $global:g_fileParsedOutput -inputobject "$builder"
}
########################################################################################################
# Function: Has-PowerDbgCommandSucceeded
#
# Parameters: None.
#
# Return: Return $true if the last command succeeded or $false if not.
#
# Purpose: Return $true if the last command succeeded or $false if not.
#
# Changes History:
#
# Roberto Alexis Farah
# All my functions are provided "AS IS" with no warranties, and confer no rights.
########################################################################################################
function Has-PowerDbgCommandSucceeded
{
set-psdebug -strict
$ErrorActionPreference = "stop"
trap {"Error message: $_"}
# Extract output removing commands.
$builder = New-Object System.Text.StringBuilder
foreach($line in $(get-content $global:g_fileCommandOutput))
{
if($line -imatch "(Fail) | (Failed) | (Error) | (Invalid) | (Unable to get) | (Exception)")
{
return $false
}
}
return $true
}
########################################################################################################
# Function: Send-PowerDbgComment
#
# Parameters: [string] $comment
# Comment to be sent to the debugger.
#
# Return: Nothing.
#
# Purpose: Sends a bold comment to the debugger. Uses DML.
#
# Changes History:
#
# Roberto Alexis Farah
# All my functions are provided "AS IS" with no warranties, and confer no rights.
########################################################################################################
function Send-PowerDbgComment(
[string] $comment = $(throw "Error! You must provide a comment.")
)
{
set-psdebug -strict
$ErrorActionPreference = "stop"
trap {"Error message: $_"}
Send-PowerDbgCommand ".printf /D `"\n<b>$comment</b>\n\n\`"`""
}
########################################################################################################
# Function: Parse-PowerDbgVERTARGET
#
# Parameters: None.
#
# Return: Nothing.
#
# Purpose: Maps the output of Kernel time and User time from "vertarget" command, using a hash table. The output
# is saved into the file POWERDBG-PARSED.LOG
# The number of days is ignored in this version.
# All Parse functions should use the same outputfile.
# You can easily map the POWERDBG-PARSED.LOG to a hash table.
# Convert-PowerDbgCSVtoHashTable() does that.
#
# Changes History:
#
# Roberto Alexis Farah
# All my functions are provided "AS IS" with no warranties, and confer no rights.
########################################################################################################
function Parse-PowerDbgVERTARGET()
{
set-psdebug -strict
$ErrorActionPreference = "stop"
trap {"Error message: $_"}
# Extract output removing commands.
$builder = New-Object System.Text.StringBuilder
# Title for the CSV fields.
$builder = $builder.AppendLine("key,value")
# \s+ --> Scans for one or more spaces.
# (\S+) --> Gets one or more chars/digits/numbers without spaces.
# \s+ --> Scans for one or more spaces.
# (\w+) --> Gets one or more chars.
# .+ --> Scans for one or more chars (any char except for new line).
# \: --> Scans for the ':' char.
# \s+ --> Scans for one or more spaces.
# (.+.) --> Gets the entire remainder string including the spaces.
foreach($line in $(get-content $global:g_fileCommandOutput))
{
if($line -match "(?<key>((Kernel time:)|(User time:)))\s+\d+\s+\S+\s+(?<value>\d+\:\d+\:\d+\.\d+)")
{
$builder = $builder.AppendLine($matches["key"] + $global:g_CSVDelimiter + $matches["value"])
}
}
# Send output to our default file.
out-file -filepath $global:g_fileParsedOutput -inputobject "$builder"
}
########################################################################################################
# Function: Parse-PowerDbgRUNAWAY
#
# Parameters: None.
#
# Return: Nothing.
#
# Purpose: Maps the output of "!runaway 1" or "!runaway 2" command, using a hash table.
# For this version the number of days is not being considered.
# The output is saved into the file POWERDBG-PARSED.LOG
# All Parse functions should use the same outputfile.
# You can easily map the POWERDBG-PARSED.LOG to a hash table.
# Convert-PowerDbgCSVtoHashTable() does that.
#
# Attention! If you need to know the top threads consuming CPU time use the Convert-PowerDbgRUNAWAYtoArray
# instead of this command. With Convert-PowerDbgRUNAWAYtoArray, the array has the exact same order of the
# original command.
#
# Changes History:
#
# Roberto Alexis Farah
# All my functions are provided "AS IS" with no warranties, and confer no rights.
########################################################################################################
function Parse-PowerDbgRUNAWAY()
{
set-psdebug -strict
$ErrorActionPreference = "stop"
trap {"Error message: $_"}
# Extract output removing commands.
$builder = New-Object System.Text.StringBuilder
# Title for the CSV fields.
$builder = $builder.AppendLine("key,value")
# \s+ --> Scans for one or more spaces.
# (\S+) --> Gets one or more chars/digits/numbers without spaces.
# \s+ --> Scans for one or more spaces.
# (\w+) --> Gets one or more chars.
# .+ --> Scans for one or more chars (any char except for new line).
# \: --> Scans for the ':' char.
# \s+ --> Scans for one or more spaces.
# (.+.) --> Gets the entire remainder string including the spaces.
foreach($line in $(get-content $global:g_fileCommandOutput))
{
if($line -match "(?<key>(\d+))\:\S+\s+\d+\s+\S+\s+(?<value>\d+\:\d+\:\d+\.\d+)")
{
$builder = $builder.AppendLine($matches["key"] + $global:g_CSVDelimiter + $matches["value"])
}
}
# Send output to our default file.
out-file -filepath $global:g_fileParsedOutput -inputobject "$builder"
}
########################################################################################################
# Function: Convert-PowerDbgRUNAWAYtoArray
#
# Parameters: None.
#
# Return: Two dimensions array.
#
# Purpose: After executing the !runaway 1 or !runaway 2, use this command to put the information into
# an array.
#
# Changes History:
#
# Roberto Alexis Farah
# All my functions are provided "AS IS" with no warranties, and confer no rights.
########################################################################################################
function Convert-PowerDbgRUNAWAYtoArray()
{
set-psdebug -strict
$ErrorActionPreference = "stop"
trap {"Error message: $_"}
# We need to count line numbers to be able to create the array.
# This particular command extracts the information from the command output file.
$num = get-Content $global:g_fileCommandOutput
# Now, we create a multidimensional array.
# We need to discard 3 lines that corresponds to:
# User Mode Time
# Thread Time
# and one extra white line.
$arrayFromRUNAWAY = new-Object 'object[,]' ($num.Length - 3),2
[System.Int32] $i = 0 # Counter.
foreach($line in $(get-content $global:g_fileCommandOutput))
{
if($line -match "(?<key>(\d+))\:\S+\s+\d+\s+\S+\s+(?<value>\d+\:\d+\:\d+\.\d+)")
{
$arrayFromRUNAWAY[$i, 0] = $matches["key"]
$arrayFromRUNAWAY[$i, 1] = $matches["value"]
$i++
}
}
# The cmoma below is very important, otherwise the function will return a single dimension array.
return ,$arrayFromRUNAWAY
}
########################################################################################################
# Function: Parse-PowerDbgK
#
# Parameters: None.
#
# Return: Nothing.
#
# Purpose: Maps the output of "k" command and variations like "kv, kbn, kpn", etc..., using a hash table.
# It doesn't work with "kPn".
# The key is the thread number if you use something like "~* kbn" or the key is "#" if you use
# something like "kb" just to show the stack from the current thread.
# Frame are separated by '$global:g_frameDelimiter'. So, to display the frames using newline you need to
# replace before displaying.
#
# The output is saved into the file POWERDBG-PARSED.LOG
# All Parse functions should use the same outputfile.
# You can easily map the POWERDBG-PARSED.LOG to a hash table.
# Convert-PowerDbgCSVtoHashTable() does that.
#
# Attention!
# 1- It doesn't work with "kPn".
# 2- It replaces "," by ";" to avoid conflict with CSV delimiter.
#
# Changes History: 12/21/2007 - The number of threads couldn't exceed 2 digits. Now it works until 999 threads.
#
# Roberto Alexis Farah
# All my functions are provided "AS IS" with no warranties, and confer no rights.
########################################################################################################
function Parse-PowerDbgK()
{
set-psdebug -strict
$ErrorActionPreference = "stop"
trap {"Error message: $_"}
# Extract output removing commands.
$builder = New-Object System.Text.StringBuilder
# Title for the CSV fields.
$builder = $builder.AppendLine("key,value")
$key = ""
# [ #] --> One space or #
# \s --> Just one space.
# (d+) --> Returns the decimal digits.
# \s+ --> 1 or more spaces.
# | --> or
# \s --> One space.
# (#) --> Returns #.
# \s --> One space.
# | --> or
# (\w+\s\w+\s.+) --> Returns two blocks of words and the remaining string.
# | --> or
# (\d+\s\w+\s\w+\s.+) --> Returns one block of digits + two blocks of words + remaining string.
# [ #]\s(\d+)\s+|\s(#)\s|(\w+\s\w+\s.+)|(\d+\s\w+\s\w+\s.+) <-- The actual implementation differs a little bit.
foreach($line in $(get-content $global:g_fileCommandOutput))
{
# For each call stack we have the key that is the thread number or # and the value that are a set
# of lines, including all frames, etc...
# Here we first identify the thread number if it was not identified before.
if($line -match "([ #]\s*(?<key>(\d+))\s+Id)")
{
# If key changed append a new line. Do not consider one single thread, that is represented by "#"
if($key -ne $matches["key"])
{
# The string assignment is a small tricky to avoid displaying contents in PS window.
$builder = $builder.AppendLine("")
$key = $matches["key"]
}
# Just add the key. The stack is the value and it's going to be added below.
# The string assignment is a small tricky to avoid displaying contents in PS window.
$builder = $builder.Append($matches["key"] + $global:g_CSVDelimiter)
}
#elseif($line -match "(?<value>(\w+\s\w+\s.+))") # Gets the stack.
elseif($line -match "(?<value>(\w+\s\w+\s.+))") # Gets the stack.
{
# If there is just one thread the thread number doesn't appear. For this case the thread number
# will be "#".
if($key -eq "")
{
$key = "#"
# Just add the key. The stack is the value and it's going to be added below.
# The string assignment is a small tricky to avoid displaying contents in PS window.
$builder = $builder.Append($key + $global:g_CSVDelimiter)
}
# Append each frame replacing any commas by ";".
# The value part of the hash table is a long string with all frames. At the end of each frame there is
# a delimiter. When you want to show the stack you know you can replace the delimiter by `r`n.
# Using a delimiter is easy to do that.
$builder = $builder.Append($matches["value"].Replace(",",";") + $global:g_frameDelimiter)
}
}
# Send output to our default file.
out-file -filepath $global:g_fileParsedOutput -inputobject "$builder"
}
########################################################################################################
# Function: Parse-PowerDbgSymbolsFromK
#
# Parameters: None.
#
# Return: Nothing.
#
# Purpose: Maps just the symbols of "k" command and variations like "kv, kbn, kpn", etc..., using a hash table.
# It doesn't work with "kPn".
# The key is the thread number if you use something like "~* kbn" or the key is "#" if you use
# something like "kb" just to show the stack from the current thread.
# Frame are separated by '$global:g_frameDelimiter'. So, to display the frames using newline you need to
# replace before displaying.
#
# The output is saved into the file POWERDBG-PARSED.LOG
# All Parse functions should use the same outputfile.
# You can easily map the POWERDBG-PARSED.LOG to a hash table.
# Convert-PowerDbgCSVtoHashTable() does that.
#
# Attention!
# 1- It doesn't work with "kPn".
# 2- It replaces "," by ";" to avoid conflict with CSV delimiter.
#
# Changes History: 12/21/2007 - The number of threads couldn't exceed 2 digits. Now it works until 999 threads.
#
# Roberto Alexis Farah
# All my functions are provided "AS IS" with no warranties, and confer no rights.
########################################################################################################
function Parse-PowerDbgSymbolsFromK()
{
set-psdebug -strict
$ErrorActionPreference = "stop"
trap {"Error message: $_"}
# Extract output removing commands.
$builder = New-Object System.Text.StringBuilder
# Title for the CSV fields.
$builder = $builder.AppendLine("key,value")
$key = ""
# [ #] --> One space or #
# \s --> Just one space.
# (d+) --> Returns the decimal digits.
# \s+ --> 1 or more spaces.
# | --> or
# \s --> One space.
# (#) --> Returns #.
# \s --> One space.
# | --> or
# (\w+\s\w+\s.+) --> Returns two blocks of words and the remaining string.
# | --> or
# (\d+\s\w+\s\w+\s.+) --> Returns one block of digits + two blocks of words + remaining string.
# [ #]\s(\d+)\s+|\s(#)\s|(\w+\s\w+\s.+)|(\d+\s\w+\s\w+\s.+) <-- The actual implementation differs a little bit.
foreach($line in $(get-content $global:g_fileCommandOutput))
{
# For each call stack we have the key that is the thread number or # and the value that are a set
# of lines, including all frames, etc...
# Here we first identify the thread number if it was not identified before.
if($line -match "([ #]\s*(?<key>(\d+))\s+Id)")
{
# If key changed append a new line. Do not consider one single thread, that is represented by "#"
if($key -ne $matches["key"])
{
# The string assignment is a small tricky to avoid displaying contents in PS window.
$builder = $builder.AppendLine("")
$key = $matches["key"]
}
# Just add the key. The stack is the value and it's going to be added below.
# The string assignment is a small tricky to avoid displaying contents in PS window.
$builder = $builder.Append($matches["key"] + $global:g_CSVDelimiter)
}
elseif($line -match "\s(?<value>(\w+!\w+::\w+))|(?<value>(\w+!\w+)|(?<value>(\w+_ni)))") # Gets the symbols from the stack.
{
# \s(\w+!\w+::\w+)|(\w+!\w+)
# (\w+!\w+::\w+) <-- Find possible C++ methods.
# (\w+!\w+) <-- Find regular symbols.
# The order is important here because the "or" is not going to evaluate the second expression if the
# first expression returns true.
# If there is just one thread the thread number doesn't appear. For this case the thread number
# will be "#".
if($key -eq "")
{
$key = "#"
# Just add the key. The stack is the value and it's going to be added below.
# The string assignment is a small tricky to avoid displaying contents in PS window.
$builder = $builder.Append($key + $global:g_CSVDelimiter)
}
# Append each symbol frame found plus the delimiter.
$builder = $builder.Append($matches["value"] + $global:g_frameDelimiter)
}
}
# Send output to our default file.
out-file -filepath $global:g_fileParsedOutput -inputobject "$builder"
}
########################################################################################################
# Function: Parse-PowerDbgLM1M
#
# Parameters: None.
#
# Return: Nothing.
#
# Purpose: Maps the output of "lm1m".
#
# Changes History:
#
# Roberto Alexis Farah
# All my functions are provided "AS IS" with no warranties, and confer no rights.
########################################################################################################
function Parse-PowerDbgLM1M()
{
set-psdebug -strict
$ErrorActionPreference = "stop"
trap {"Error message: $_"}
# Extract output removing commands.
$builder = New-Object System.Text.StringBuilder
# Title for the CSV fields.
$builder = $builder.AppendLine("key,value")
foreach($line in $(get-content $global:g_fileCommandOutput))
{
if($line -match "(?<key>(\w+))")
{
# Value and key has the same value for this particular parser.
$builder = $builder.AppendLine($matches["key"] + $global:g_CSVDelimiter + $matches["key"])
}
}
# Send output to our default file.
out-file -filepath $global:g_fileParsedOutput -inputobject "$builder"
}
########################################################################################################
# Function: Classify-PowerDbgThreads
#
# Parameters: None.
#
# Return: Array where the index is the thread number and the element is one of these values:
# 0 UNKNOWN_SYMBOL
# 1 WAITING_FOR_CRITICAL_SECTION
# 2 DOING_IO
# 3 WAITING
# 4 GC_THREAD
# 5 WAIT_UNTIL_GC_COMPLETE
# 6 SUSPEND_FOR_GC
# 7 WAIT_FOR_FINALIZE
# 8 TRYING_MANAGED_LOCK
# 9 DATA_FROM_WINSOCK
#
# The constants above are stored in global variables.
#
# Purpose: Returns an array which the index corresponds to thread numbers and the content is a value represented
# by the constants above. This cmdlet gives you an idea of what the threads are doing.
# Notice that is very easy to add more symbols and more constants to get a more granular analysis.
#
# Changes History: 01/25/08 - More symbols added to the hash table. The analysis became more granular.
#
# Mike McIntyre
# Roberto Alexis Farah
# All my functions are provided "AS IS" with no warranties, and confer no rights.
########################################################################################################
function Classify-PowerDbgThreads()
{
set-psdebug -strict
$ErrorActionPreference = "stop"
trap {"Error message: $_"}
$symbolsForEachThread = @{}
# Let's save resource. We need kL with just 8 frames.
Send-PowerDbgCommand "~* kL 8"
# Forces a delay because it may take time to get the stacks.
start-sleep 8
$hasCommandSucceeded = Has-PowerDbgCommandSucceeded
# Check if the command was executed with success.
# It's unlikely to have a failure with the "k" command, but I'm being proactive.
if($false -eq $hasCommandSucceeded)
{
throw "Couldn't get call stacks!"
}
# Let's parse the output of the "k" command and variations, however, we want the symbols
# not the complete call stack.
Parse-PowerDbgSymbolsFromK
# Now let's get the symbols for each thread. We don't need the stack details here.
$symbolsForEachThread = Convert-PowerDbgCSVtoHashTable
# IMPORTANT!!!
# The symbols need to be written in uppercase, because the comparison is doing using uppercase letters to avoid
# mismatches.
$symbols = @{
"KERNEL32!COPYFILE" = $global:g_doingIO;
"KERNEL32!CREATEDIRECTORY" = $global:g_doingIO;
"KERNEL32!CREATEFILE" = $global:g_doingIO;
"KERNEL32!DELETEFILE" = $global:g_doingIO;
"KERNEL32!FILEIOCOMPLETIONROUTINE" = $global:g_doingIO;
"KERNEL32!FINDCLOSE" = $global:g_doingIO;
"KERNEL32!FINDCLOSECHANGENOTIFICATION" = $global:g_doingIO;
"KERNEL32!FINDFIRSTCHANGENOTIFICATION" = $global:g_doingIO;
"KERNEL32!FINDFIRSTFILE" = $global:g_doingIO;
"KERNEL32!FINDFIRSTFILEEX" = $global:g_doingIO;
"KERNEL32!FINDNEXTCHANGENOTIFICATION" = $global:g_doingIO;
"KERNEL32!FINDNEXTFILE" = $global:g_doingIO;
"KERNEL32!FLUSHFILEBUFFERS" = $global:g_doingIO;
"KERNEL32!GETBINARYTYPE" = $global:g_doingIO;
"KERNEL32!GETCURRENTDIRECTORY" = $global:g_doingIO;
"KERNEL32!GETDRIVETYPE" = $global:g_doingIO;
"KERNEL32!GETFILEATTRIBUTES" = $global:g_doingIO;
"KERNEL32!GETFILEATTRIBUTESEX" = $global:g_doingIO;
"KERNEL32!GETFILEINFORMATIONBYHANDLE" = $global:g_doingIO;
"KERNEL32!GETFILESIZE" = $global:g_doingIO;
"KERNEL32!GETFILESIZEEX" = $global:g_doingIO;
"KERNEL32!GETFULLPATHNAME" = $global:g_doingIO;
"KERNEL32!GETTEMPFILENAME" = $global:g_doingIO;
"KERNEL32!GETTEMPPATH" = $global:g_doingIO;
"KERNEL32!LOCKFILE" = $global:g_doingIO;
"KERNEL32!LOCKFILEEX" = $global:g_doingIO;
"KERNEL32!MOVEFILE" = $global:g_doingIO;
"KERNEL32!READDIRECTORYCHANGESW" = $global:g_doingIO;
"KERNEL32!READFILE" = $global:g_doingIO;
"KERNEL32!READFILEEX" = $global:g_doingIO;
"KERNEL32!REMOVEDIRECTORY" = $global:g_doingIO;
"KERNEL32!SEARCHPATH" = $global:g_doingIO;
"KERNEL32!SETCURRENTDIRECTORY" = $global:g_doingIO;
"KERNEL32!SETENDOFFILE" = $global:g_doingIO;
"KERNEL32!SETFILEATTRIBUTES" = $global:g_doingIO;
"KERNEL32!SETFILEPOINTER" = $global:g_doingIO;
"KERNEL32!SETFILEPOINTEREX" = $global:g_doingIO;
"KERNEL32!UNLOCKFILE" = $global:g_doingIO;
"KERNEL32!UNLOCKFILEEX" = $global:g_doingIO;
"KERNEL32!WRITEFILE" = $global:g_doingIO;
"KERNEL32!WRITEFILEEX" = $global:g_doingIO;
"NTDLL!ZWREMOVEIOCOMPLETION" = $global:g_doingIO;
"NTDLL!RTLPWAITFORCRITICALSECTION" = $global:g_waitingForCriticalSection;
"NTDLL!RTLENTERCRITICALSECTION" = $global:g_waitingForCriticalSection;
"KERNEL32!ENTERCRITICALSECTION" = $global:g_waitingForCriticalSection;
"KERNEL32!MSGWAITFORMULTIPLEOBJECTS" = $global:g_threadWaiting;
"KERNEL32!MSGWAITFORMULTIPLEOBJECTSEX" = $global:g_threadWaiting;
"KERNEL32!REGISTERWAITFORSINGLEOBJECT" = $global:g_threadWaiting;
"KERNEL32!SIGNALOBJECTANDWAIT" = $global:g_threadWaiting;
"KERNEL32!UNREGISTERWAIT" = $global:g_threadWaiting;
"KERNEL32!UNREGISTERWAITEX" = $global:g_threadWaiting;
"KERNEL32!WAITFORMULTIPLEOBJECTS" = $global:g_threadWaiting;
"KERNEL32!WAITFORMULTIPLEOBJECTSEX" = $global:g_threadWaiting;
"KERNEL32!WAITFORSINGLEOBJECT" = $global:g_threadWaiting;
"KERNEL32!WAITFORSINGLEOBJECTEX" = $global:g_threadWaiting;
"KERNEL32!WAITORTIMERCALLBACK" = $global:g_threadWaiting;
"USER32!NTUSERGETMESSAGE" = $global:g_threadWaiting;
"USER32!NTUSERMESSAGECALL" = $global:g_threadWaiting;
"USER32!NTUSERWAITMESSAGE" = $global:g_threadWaiting;
"NTDLL!DBGBREAKPOINT" = $global:g_threadWaiting;
"NTDLL!RTLPWAITTHREAD" = $global:g_threadWaiting;
"KERNEL32!SLEEPEX" = $global:g_threadWaiting;
"KERNEL32!SLEEP" = $global:g_threadWaiting;
"NTDLL!NTDELAYEXECUTION" = $global:g_threadWaiting;
"MFC80D!AFXINTERNALPUMPMESSAGE" = $global:g_threadWaiting;
"MFC80!AFXINTERNALPUMPMESSAGE" = $global:g_threadWaiting;
"MSCORWKS!SVR::GC_HEAP::GC_THREAD_STUB" = $global:g_GCthread;
"MSCORSVR!SVR::GC_HEAP::GC_THREAD_STUB" = $global:g_GCthread;
"MSCORSVR!GCHEAP::WAITUNTILGCCOMPLETE" = $global:g_waitUntilGCComplete;
"MSCORWKS!GCHEAP::WAITUNTILGCCOMPLETE" = $global:g_waitUntilGCComplete;
"MSCORWKS!THREAD::SYSSUSPENDFORGC" = $global:g_suspendForGC;
"MSCORSVR!THREAD::SYSSUSPENDFORGC" = $global:g_suspendForGC;
"MSCORWKS!WAITFORFINALIZEREVENT" = $global:g_waitForFinalize;
"MSCORSVR!WAITFORFINALIZEREVENT" = $global:g_waitForFinalize;
"MSCORWKS!SVR::WAITFORFINALIZEREVENT" = $global:g_waitForFinalize;
"MSCORSVR!SVR::WAITFORFINALIZEREVENT" = $global:g_waitForFinalize;
"MSCORWKS!WKS::WAITFORFINALIZEREVENT" = $global:g_waitForFinalize;
"MSCORSVR!WKS::WAITFORFINALIZEREVENT" = $global:g_waitForFinalize;
"MSCORWKS!JITUNTIL_MONCONTENTION" = $global:g_tryingGetLock;
"MSCORSVR!JITUNTIL_MONCONTENTION" = $global:g_tryingGetLock;
"MSWSOCK!WSPRECV" = $global:g_winSockReceivingData;
"WS2_32!RECV" = $global:g_winSockReceivingData;
"MSCORSVR!GCHeap::FINALIZETHREADSTART" = $global:g_finalizerThread;
"MSCORWKS!GCHeap::FINALIZETHREADSTART" = $global:g_finalizerThread;
"MSCORSVR!DEBUGGERRCTHREAD::MAINLOOP" = $global:g_CLRDebuggerThread;
"MSCORWKS!DEBUGGERRCTHREAD::MAINLOOP" = $global:g_CLRDebuggerThread;
"W3DT!WP_CONTEXT::RUNMAINTHREADLOOP" = $global:g_w3wpMainThread;
"W3TP!THREAD_POOL_DATA::THREADPOOLTHREAD" = $global:g_threadPool;
"W3CORE!HTTP_COMPRESSION::COMPRESSIONTHREAD" = $global:g_CompressionThreaad;
"OLE32!CRPCCHANNELBUFFER::SENDRECEIVE2" = $global:g_COMcall;
"MSCORWKS!CLREVENT::WAIT" = $global:g_CLRWorkerThread;
"MSCORSVR!CLREVENT::WAIT" = $global:g_CLRWorkerThread;
"MSCORWKS!THREADPOOLMGR::COMPLETIONPORTTHREADSTART" = $global:g_completionPortIOThread;
"MSCORSVR!THREADPOOLMGR::COMPLETIONPORTTHREADSTART" = $global:g_completionPortIOThread;
"MSCORWKS!THREADPOOLMGR::GATETHREADSTART" = $global:g_gateThread;
"MSCORSVR!THREADPOOLMGR::GATETHREADSTART" = $global:g_gateThread;
"MSCORWKS!THREADPOOLMGR::TIMERTHREADSTART" = $global:g_timerThread;
"MSCORSVR!THREADPOOLMGR::TIMERTHREADSTART" = $global:g_timerThread;
"MSCORWKS!APPDOMAIN::ADUNLOADTHREADSTART" = $global:g_unloadAppDomainThread;
"MSCORSVR!APPDOMAIN::ADUNLOADTHREADSTART" = $global:g_unloadAppDomainThread;
"RPCRT4!THREADSTARTROUTINE" = $global:g_RPCWorkerThread;
"RPCRT4!COMMON_PROCESSCALLS" = $global:g_RPCWorkerThread;
"RPCRT4!LRPC_ADDRESS::RECEIVELOTSACALLS" = $global:g_LPRCWorkerThread;
"OLE32!CDLLHOST::STAWORKERLOOP" = $global:g_hostSTAThread
}
# The array has the right size to fit all threads.
$array = 1..($symbolsForEachThread.count - 1)
# Now we scan all threads and for each threads we scan all frames until a symbols mapped to the hash table is found
# or there are no more frames.
# Below, we need to discount the hast table "key" and "value" strings because they are considered one element.
for([System.Int32] $i = 0; $i -lt ($symbolsForEachThread.count - 1); $i++)
{
# The delimiter is converted to new line. Now it's easy to process each line.
$stack = $symbolsForEachThread[$i.ToString()].Replace($global:g_FrameDelimiter, "`n")
# Sets the default value.
$array[$i] = $global:g_unknownSymbol
# This is to be able to read each line.
$stringReader = [System.IO.StringReader] $stack
# Scan the symbols for each thread, line by line.
while(($frame = $stringReader.ReadLine()) -ne $null)
{
# Now we try to locate the symbol in our hash table.
# Always using uppercase.
if($symbols[$frame.ToUpper()] -ne $null)
{
# If symbol not located we don't assign $null to the array.
if($symbols[$frame.ToUpper()] -ne $null)
{
# If found we assign the constant to the array element.
$array[$i] = $symbols[$frame.ToUpper()]
}
}
}
}
# Force resources to be freed from memory.
$symbols = $null
$symbolsForEachThread = $null
return $array
}
########################################################################################################
# Function: Analyze-PowerDbgThreads
#
# Parameters: None.
#
# Return: Nothing.
#
# Purpose: Analyzes and displays what each thread is doing and the CPU time, sorted by User time.
# This cmdlet is very useful for hangs, high CPU and crashes scenarios.
#
# Attention! If you have a mini-dump with no thread information you may want to create
# a simplified cmdlet that doesn't use the information from !runaway.
# To do that just remove all parts of this script that use User and Kernel time. :)
# This script tries to use the CPU time because it's unlikely to have a dump that not have it.
#
# Changes History: 01/25/08 - Threads with unknown symbol appear in red color.
#
# Mike McIntyre
# Roberto Alexis Farah
# All my functions are provided "AS IS" with no warranties, and confer no rights.
########################################################################################################
function Analyze-PowerDbgThreads()
{
set-psdebug -strict
$ErrorActionPreference = "stop"
trap {"Error message: $_"}
# Let's get User time.
Send-PowerDbgCommand "!runaway 1"
$hasCommandSucceeded = Has-PowerDbgCommandSucceeded
if($false -eq $hasCommandSucceeded)
{
throw "This dump has no threads information!"
}
# Gets the array of user mode time.
$arrayOfUserTime = Convert-PowerDbgRUNAWAYtoArray
# Let's get Kernel time.
# It's not necessary to validate the command output because we already did that.
Send-PowerDbgCommand "!runaway 2"
# Parses to get a hash table.
Parse-PowerDbgRUNAWAY
# Gets a hash table from the CSV file created with the cmdlet above.
$kernelTime = Convert-PowerDbgCSVtoHashTable
# Analyze the call stack for each thread to know what they are doing.
$arrayOfClassification = Classify-PowerDbgThreads
# Simple header.
write-Host "Threads sorted by User Time...`n" -foreground Green -background Black
write-Host "Thread Number`tUser Time`tKernel Time`t`tActivity" -foreground Green -background Black
# Displays information to user.
for([System.Int32] $i = 0; $i -lt ($arrayOfUserTime.Count / 2); $i++)
{
# If unknown activity, put it in red.
if($arrayOfClassification[$arrayOfUserTime[$i, 0]] -eq $global:g_unknownSymbol)
{
write-Host " " $arrayOfUserTime[$i, 0] "`t`t" $arrayOfUserTime[$i, 1] "`t" $kernelTime[$arrayOfUserTime[$i, 0]] "`t" $global:g_whatThreadIsDoing[$arrayOfClassification[$arrayOfUserTime[$i, 0]]] -foreground Red -background Black
}
else
{
write-Host " " $arrayOfUserTime[$i, 0] "`t`t" $arrayOfUserTime[$i, 1] "`t" $kernelTime[$arrayOfUserTime[$i, 0]] "`t" $global:g_whatThreadIsDoing[$arrayOfClassification[$arrayOfUserTime[$i, 0]]] -foreground Green -background Black
}
}
Send-PowerDbgComment "Analyze-PowerDbgThreads finished execution."
}