Debugging Methodology


Debugging can be tricky. The best way to debug is to have a thorough understanding of what your script is doing, and how PowerShell is executing it. Let's work with an example: Without actually running this script, can you predict where the error will occur?

TrickyDebugging.ps1

image from book
 $foo = "this is the original text" function f1($str) {   "Calling f1..."   $str.toUpper() } function f2($value) {   "Calling f2... what is value?"   $value | get-member   ""   "Before f1 value is: " + $value   "Before f1 foo is: " + $script:foo   $script:foo = f1 $value   "after f1 value is: " + $value   "after f1 foo is: " + $script:foo } "" "BEFORE PASS 1, WHAT IS FOO?" $foo | get-member "" "PASS 1" f2 $foo "" "AFTER PASS 1, WHAT IS FOO?" $foo | get-member "" "" "PASS 2" f2 $foo "" "global value" $foo 
image from book

After reading through this script, try running it in PowerShell. Then, load the script into an editor like PrimalScript that offers line numbering, and let's walk through exactly what's happening. This is the process you'll have to do anytime you want to debug something.

First the variable $foo is assigned a value. This occurs in the script's scope, which is a child of the shell's global scope. Then, two functions, f1 and f2, are defined but not yet executed.

The action starts on line 21, where two literal strings are output and $foo is piped to the Get-Member cmdlet. This displays the type of object $foo is. If you've run the script you'll see that it's a System.String. Remember this piece of information.

Next, on line 24, a blank line is output. On line 27, $foo is passed to the fg2 function. That function is defined on line 9; you can see it's accepting input-whatever was in $foo-into the variable $value. Line 12 passes $value to Get-Member. Notice anything? The $value variable isn't recognized as a string since it's a generic System.Object. On line 14, $value is output, as is the script-level $foo variable. You'll notice in the script's output that these two match. This is exactly what should occur at this point.

The f1 function is called, passing $value as its input argument. The function places that input into the $str variable. It outputs "Calling f1" and then outputs the result of $str.toUpper. We didn't check, but we can expect that $str was received as a System.Object, but that PowerShell was able to coerce it into being a System.String so the toUpper() method would work. Everything output by function f1 becomes its "return value," which is placed into the script-level $foo object back on line 16. Lines 17 and 18 confirm that $value and $foo are now different since $foo has been replaced with an uppercase version of $value.

So far so good. However, on line 31, the output indicates that $foo isn't a System.String anymore-it's now a System.Object. Line 36 repeats the whole process, but gets an error on line 6 when f1 is called a second time, because $str is now an Object that can't be coerced into a String. As such, it has no toUpper() method to call, which is what the error indicates if you run the script.

So, the problem is that at some point PowerShell stopped coercing $str into a String and left it as an Object. Why? The answer is on the first call to f1. Remember, f1's output was "Calling f1" followed by a carriage return, followed by the result of $str.toUpper. That carriage return is the culprit since it prevents PowerShell from recognizing a string. Instead the carriage return causes it to recognize an array of two strings. This means the second time f1 is called, $str appears to contain an array, which doesn't have a toUpper() method.

The proper way to have a function output text without having that text become part of the function's return value is to use Write-Host. Modify line 5 as follows:

 Write-Host "Calling f1..." 

When you run the script again, you'll see it works fine! That's because the output of f1 never contains a carriage return, which allows the string to be recognized as a System.String by PowerShell.

Another fix would be to modify line 1 as follows:

 [string]$foo = "this is the original text" 

Again, just this change makes the script work because the decision on whether or not the string is a String or an Object is no longer PowerShell's choice. In fact, $foo is explicitly declared as a String, and stays that way throughout. Strings can contain carriage returns. However, keep in mind when something is not specifically declared as a string that contains a carriage return, it will be interpreted as an Object.

This is just one example of how nitty-gritty you need to get when you're debugging. Walk through every line of code. As in this example, add extra code to help you figure out what's what such as when we used Get-Member to see how data type variables were being treated. This isn't to say that debugging is simple. However, the best way to debug is by following your script one line at a time and seeing what PowerShell is doing.



Windows PowerShell. TFM
Internet Forensics
ISBN: 982131445
EAN: 2147483647
Year: 2004
Pages: 289

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net