[PowerShell Script] PowerDbg v2.3 - Using PowerShell to Control WinDbg

It has been a while since my previous blog post. I’ve been busy with other tasks, including the new PowerDbg version 2.3.

There’s a lot of new stuff on this new version and my estimate is that about 80% of the most used Windbg commands have been covered so far. J

Let me present the new cmdlets:

Parse-PowerDbgVERTARGET

Parses the vertarget command, extracting User mode time and Kernel mode time.

Parse-PowerDbgRUNAWAY

Parses the !runaway 1 or !runaway 2.

Convert-PowerDbgRUNAWAYtoArray

Converts the output from !runaway 1 or !runaway 2 to a two dimension array with the elements in the same order as the command output. This means that item[0,0] shows the thread consuming more CPU time and item[0,1] shows the CPU time.

Parse-PowerDbgK

Parses the output from the k command, including the k command variations. The exception is kPn because kPn breaks the arguments into different lines.

The hash table for this cmdlet contains the thread number and the complete call stack for each thread.

Parse-PowerDbgSymbolsFromK

Parses the output from the k command, including the k command variations. The exception is kPn because kPn breaks the arguments into different lines.

The hash table for this parser contains the thread number and the symbols for each stack.

Parse-PowerDbgLM1M

Parses the output of lm1m command. It’s useful for finding the modules that are part of the process.

Classify-PowerDbgThreads

This cmdlet analyzes all threads and returns an array in which each item has a constant value. The value indicates what the thread is doing according to these constants:

$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

. . .

. . .

. . .

Great news! It’s very easy to add more symbols and improve the power of this cmdlet!

It works for native and managed codes.

Analyze-PowerDbgThreads

This may be the PowerDbg cmdlet that you will use most of the time. This cmdlet uses some of the cmdlets above to analyze the kind of work each thread is doing, then it displays the results sorted by User time.

This cmdlet is very useful for hangs, crashes, and high cpu scenarios! At any point, during your live or post-mortem debugging, you can run this command and understand what each thread is doing and the corresponding User and Kernel time.

It works for native and managed code.

With all cmdlets the PowerDbg library has so far, it will be easier for me and for you to build the next script.

Look, for example, at how Analyze-PowerDbgThreads works:

Attention! It’s better to use .reload /f instead of .reload to avoid delays when executing the k command and its variations. This delay may cause errors because the script may continue the execution while the WinDbg command is still running.

 

 

 

 

 

 

 

 

 

This is the source code for PowerDbg v2.0 (save it into your $profile).

It doesn’t look formatted, but it is. After copying and pasting it you will see. J

########################################################################################################

# 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."

}

If you find a bug hidden in this new PowerDbg version, let me know.

I hope you enjoy using it to create your own scripts!