########################################################################################################
# Global variables.
########################################################################################################
$global:g_instance = $null
$global:g_fileParsedOutput = "POWERDBG-PARSED.LOG"
$global:g_fileCommandOutput = "POWERDBG-OUTPUT.LOG"
########################################################################################################
# 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:
#
# 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)
# Create log.
$output = $global:g_instance.SendKeys(".logopen $aux\POWERDBG.LOG")
$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"] + "," + $matches["value"])
}
}
}
else
{
foreach($line in $(get-content $global:g_fileCommandOutput))
{
if($line -match "(?<key>0x\S+).+\:\s+(?<value>.+)")
{
$builder = $builder.AppendLine($matches["key"] + "," + $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"] + "," + $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"] + "," + $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"] + "," + $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"] + "," + $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 + "," + $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)")
{
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\`"`""
}