This new version has one more parser for !PrintException and a killer feature that my colleagues and myself have wanted since the beginning: PowerDbg, more specifically Send-PowerDbgCommand, which now has the ability to wait until a command finished its execution.  It means no more delays to wait for a command to be processed and no more exceptions when the command takes longer than the delay to finish its execution.

 

To know how it’s a great improvement over the previous version, let me explain the PowerDbg concept.

 

If you are new to PowerDbg, here’s a great explanation about it.

Basically it’s a library that interacts with WinDbg using the Shell object from Windows Script Host.

With PowerDbg it’s possible to send commands to WinDbg and receive data from WinDbg.

The basic flow from a script using PowerDbg is below. As you can see, it’s very easy to interact with WinDbg and to create more parsers or scripts that use PowerDbg:

 

1-    Change (.wtitle PowerDbg) WinDbg title to PowerDbg, so the cmdlets can find the WinDbg instance.

2-    Send-PowerDbgCommand “command”  ß Sends command to the debugger.

3-    Parse-PowerDbg<command>      ß Parses the output and sends it to a CSV file.

4-    Convert-PowerDbgCSVToHashTable  ß Converts the CSV to a Hash Table.

 

Then you can use the hash table in the way you want.

It works fine, however, when Send-PowerDbgCommand sends a command to WinDbg; it returns, doing an asynchronous call. However, when the cmdlet returns the WinDbg could be still struggling to process the command. At this point, if a Parse cmdlet is called from a script, an exception will happen. To work around this timing issue, the scripts using PowerDbg and even some cmdlets used a sleep call to give WinDbg time to process the command.

The problems with this approach are:

a)    If the command is quick and returns the result it called upon, the scripts don’t know that, and they need to use an idle time just in case, thus, impacting the performance.

b)    If the developer using PowerDbg forgets to add Start-Sleep after using Send-PowerDbgCommand, it is almost sure an error will happen when another PowerDbg command is called. It happens because of the timing issue.

c)    If you use a long timeout, it may not be enough for specific cases, so your script will break, throwing an exception. For example, try to use it from PowerShell, using the previous PowerDbg library:

 

Send-PowerDbgCommand "~* kpn 1000"; Parse-PowerDbgK; $ht = @{}; $ht = Convert-PowerDbgCSVToHashTable


Or test using:

 

Analyze-PowerDbgThreads

 

If you have a dump or process with too many threads or if the symbols are still being solved, chances are you’ll get an exception. The reason is related to timing. After sending the command to the debugger, PowerShell will try to parse and create a Hash Table from an empty csv file while WinDbg is still processing the command.

To solve it you can use something like Start-Sleep 8, for example. However, what if the command took just 3 seconds? Or what if it took 1 minute?

 

 

SOLVING THE TIMING ISSUE

 

To solve the timing situation exemplified above, the latest PowerDbg version only returns from Send-PowerDbgCommand when WinDbg has finished processing the command!

It means no more idle time, better performance, and no more exceptions after a forced delay.

To do that I considered several approaches and ended up using the approach below:

Send-PowerDbgCommand uses “.pcmd” to send a message to the debugger when any command that affects execution runs (for more, see the WinDbg documentation). So, the user command is sent to the debugger and, just after it, the “r” command is sent to the debugger, too. Because the first command may be still be processing, the second command doesn’t run and stays in the queue.

The “r” command is considered a command the affects the execution, so the message configured with “.pcmd” is triggered when “r” is used. Got it? After executing the user command, the “r” command that is queued to be processed, is processed. Then our custom message is sent to the debugger. In the meantime Send-PowerDbgCommand is monitoring the log file that has the raw output and looking for the message that signals the end of command execution. The default timeout is 3 minutes and can be easily changed. When it finds the message, it removes the garbage from the output file so that the parsed file won’t have any differences when compared to previous versions of PowerDbg. (in other words, it won’t break your scripts J)

The only drawback is a message and registers that appear in the WinDbg window whenever you send a command, causing a bit of visual pollution, like:

 

#### IGNORE REGISTERS BELOW ####

eax=7ff24000 ebx=00000000 ecx=00000000 edx=76e7f06d esi=00000000 edi=00000000

eip=76e32ea8 esp=0b9fff74 ebp=0b9fffa0 iopl=0         nv up ei pl zr na pe nc

cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246

ntdll!DbgBreakPoint:

76e32ea8 cc              int     3

#### IGNORE REGISTERS ABOVE ####

 

Anyway, for the benefit of this approach it’s just a minor drawback, and it doesn’t affect the Parsers from PowerShell either in the way you use the library to create your own cmdlets or in scripts.

Note: The command .pcmd is not going to be in the article series Special Commands because, although it’s a special command, it’s very unlikely you end up using it during your debugging. At the same time, it’s very simple to use it.

 

Below, are examples of using PowerDbg, so you can use it as a reference when building scripts.

Verifying if a specified Module is in the process/dump:

a)    Send Command, parse, and convert CSV file to Hash Table:

Send-PowerDbgCommand "lm1m"; Parse-PowerDbgLM1M; $ht = @{}; $ht = Convert-PowerDbgCSVToHashTable  

 

b)    Now we have the Hash Table. You can play the way you want! J For instance, let’s see if a specific module exists:

$ht[“Test”]   ß Doesn’t return anything, so the module is not there.

$ht[“mscorwks”]  ß Returns the module, so it’s there.

 

 

 

Playing with the call stack:

a)    Again the same sequence: command -> parser -> convert csv to Hash table:

Send-PowerDbgCommand "~* kpn 1000"; Parse-PowerDbgK; $ht = @{}; $ht = Convert-PowerDbgCSVToHashTable

b)    This is the hash table:

$ht

Pay attention to this: Because we have a csv file, we have to use another way to put the stack as the value and the thread number as the key. To do that the cmdlet removed the newline and replaced it with a special character sequence that you can get from:

$global:g_frameDelimiter

It’s documented in the header file from Parse-PowerDbgK in case  were wondering. J

c)    Now, let’s see the call stack for thread 20. Note how I replace the $global:g_frameDelimiter for new line:

write-Host $ht["20"].Replace($global:g_frameDelimiter, "`n") -foreground Green -background Black

 

 It’s a piece of cake to see what each thread is doing, sorted by CPU time. Imagine the time you would spend by hand to understand what each thread is doing.

Here it is:

 

Analyze-PowerDbgThreads

 

 

 

 

 

By the way it’s super easy to improve the accuracy of Analyze-PowerDbgThreads, for example, adding symbols for your specific application!

 

OK, now, let’s suppose you want to use DML. Just one small detail: my WinDbg has customized colors, so it may look different on your screen:

 

a)    Let’s get the symbol to look for from the keyboard:

 

$symbol = Read-Host

 

b)    Sends a comment to WinDbg:

 

Send-PowerDbgComment "Click the hyperlink below"

 

c)    Builds the DML command:

 

Send-PowerDbgDML "Find Stacks that Match Symbol" "!findstack $symbol 2"

 

 

 

 

 

To learn more see the PowerShell Scripts that use PowerDbg and read the headers from each cmdlet.

This is the source code for PowerDbg v3.1. I like to save it into my $profile (Microsoft.PowerShell_profile.ps1), so I can use it from anywhere, but it's up to you.

 

 

 

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

# PowerDbg v 3.1

#

# New Features:

#

# - Parse-PowerDbgPRINTEXCEPTION()

# - No more Sleep calls to wait until a command is finished. Now, whenenever a command is sent to the debugger

#   the cmdlet Send-PowerDbgCommand waits until the command finish. Using this approach there won't happen

#   errors when you send a command that may take a long time to execute. At the same time the scripts using

#   PowerDbg don't need to implement delays anymore. It's a big performance improvement.

# - The script were revised and changed to be compatible with this new PowerDbg. To compatibilize I've

#   removed start-sleep and the regular "r" command (without arguments). Get the newest version.

# - Fixed bug that appeared when using WinDbg from the default path or when using a path that has

#   spaces, like: c:\Program Files

#

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

#

# Attention!   The timeout before raising an error while waiting for a command to be executed is 3 minutes (180 seconds)

#              If you want to increase this number, change the $timeoutInSeconds local variable.

#              Also, DO NOT use the "r" command, but you can use something like "r @$tid". Using the regular "r"

#              will break the execution if you have something like: "~* r;kL"

#

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

#

#                  03/28/2008 - No more delays while waiting for a command to be executed. Now whenever a

#                               command is sent to the debugger, the debugger sends a message and close

#                               the log file when it's done. The cmdlet verifies the log to see if the command

#                               finished its execution. If yes it returns, otherwise it keeps waiting.

#

#                  04/06/2008 - .logopen was being used without ", so failing when the Debugger was installed

#                               in a folder that has spaces, like: : c:\Program Files.

#

# 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

        {

            # If the log is open it may cause problems if another command is sent from PowerDbg.

            $output = $global:g_instance.SendKeys(".logclose")

            $output = $global:g_instance.SendKeys("{ENTER}")

            "Error message: $_"

        }

   

    # If 180 seconds is not enough for you, just increase this number.

    $timeoutInSeconds = 180

   

    # 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

    }

   

    $message = "#### IGNORE REGISTERS ABOVE ####"

   

    # Makes the debugger close the file and send a message when the command finishes its execution.

    # NOTE: Commands that are related to execution, like p, t, r, will close the log and trigger the message.

    $output = $global:g_instance.SendKeys(".pcmd -s `" .echo $message; .logclose `" ")   

    $output = $global:g_instance.SendKeys("{ENTER}")

        

    # 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}")

 

    $garbageMessage = "#### IGNORE REGISTERS BELOW ####"

   

    # To force the WinDbg to close the log and display the message when the command is done,

    # we have to use a command that is affects the execution without changing the execution.

    # The best command is "r" that display the registers.

    $output = $global:g_instance.SendKeys(".echo $garbageMessage;r")

    $output = $global:g_instance.SendKeys("{ENTER}")

   

    [System.Int32] $count = 0

 

    # Verify if command finished its execution. If it's done we should find the specific message,

    # otherwise the variable will be null.

    # If trying for more than 3 minutes, raise an exception.

    for(;;)

    {

        Start-Sleep 1   # Wait one second then try again.   

       

        $messageFound = (get-content "$aux\POWERDBG.LOG" | where-Object {$_ -like $message})

              

        # If null the command is still executing.

        if($messageFound -eq $null)

        {

            $count++;       

           

            write-host "." -Foreground Green -Background Black -nonewline

                                 

            # Is it trying for more than our threshold?

            if($count -gt $timeoutInSeconds)

            {

                throw "Timeout error! A command did not finish its execution after 3 minutes!"

            }

        }

        else

        {

            break # We can exit the loop. The command was executed.

        }

    }

   

    write-host "" -Foreground Green -Background Black  # Just a new empty line.

   

    # A delay is required here, just to be sure the file was flushed and closed.

    start-sleep 4

 

    # Extract output removing commands.

    $builder = New-Object System.Text.StringBuilder     

    $isDone  = $false

   

    # Remove commands and content that is specific for PowerDbg, include garbage created by the "r" command.

    get-content "$aux\POWERDBG.LOG"  | `

                foreach-object {  

                                   if($_ -imatch $garbageMessage)

                                   {

                                       # After a while debugging the script I found the break instruction doesn't work with foreach-object. Use a break here makes the script

                                       # finish the execution. As a workaround I'm using a flag.

                                       $isDone = $true

                                   }

                                   elseif($isDone -eq $false -and (($_ -notmatch "^.:...>") -and ($_ -notmatch "^Opened log file") -and ($_ -notmatch "^$message") -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).