remove-psbreakpoint

Powershell – Debugging Techniques

This blog discuses some high level Debugging Techniques while working with Powershell. The contents of this blog are taken from the book – “Learn WINDOWS POWERSHELL, In a month of Lunches” By, Don Jones (All the credit goes to Don)

I am writing this blog, because we do not have enuf content on the web that discusses about the “Debugging” or “Troubleshooting” approaches while working with Powershell. And Don’s book helped me a lot in my personal endeavour. Now it is my turn to give it back to the Powershell Community.

First Computer Bug

There are two broad categories of bugs in the Powershell world. Firstly, the syntax errors and secondly, logic errors.

Syntax Errors

A Syntax Error means you typed something wrong. It might be a command name that you misspelled (Gte-Content instead of Get-Content) or it might be that you got the actual syntax of the command wrong (forgetting the hyphen between the verb and noun of a cmdlet name, or using a colon instead of a space to separate a parameter’s name and value). Whatever the cause, correcting your typing will solve the problem.

Powershell will usually tell you, in explicit detail, where the problem is. Powershell might not know what the problem is, but it will usually get pretty close to the location of the error. Example, here is a syntactically incorrect command and the resulting error message:

get-content

That is pretty clear: We used a parameter named -file, and Powershell could not find one. The error occurred on the line 1 of my  command and at character position 18.

Here is a checklist that will help you in overcoming the Syntax Errors:

  •  Make sure you typed the cmdlet name correctly. If you used an alias, make sure it is typed correctly, and that it points to the cmdlet you think it does.
  • Make sure parameter names are preceded by a dash and are followed by a space. Make sure you are using the correct parameter name (read the help!) and if you are abbreviating the parameter name, make sure you are providing enough characters to uniquely identify that parameter.
  • Most of the Powershell’s punctuation comes in pairs: single quotes, double quotes, square brackets, curly braces, and parenthesis are all good examples. Improper nesting, like ({this)}, means you are ending a pair before ending the pair it encloses.
  • Watch your spaces. In Powershell, spaces are special characters that indicate a separation between command elements. Powershell is not that case-sensitive, but it is very space-sensitive. There is a space in between parameter names and values. There is a space after a cmdlet name and before any parameter or values. There is a space after one parameter and before the next. Do not forget those.

Red Alert

The RED Text usually freaks us out. You can change the colour of the error message text to green.

(Get-Host).PrivateData.ErrorForegroundColor = 'green'

That only lasts for the duration of the shell session, so we can either put that in a profile script or we can do it before start working in the shell.

Logic Errors

Logic errors mean that your script or command is not doing what you want it to do, but it is not necessarily generating an error.

Some logic error will provide straightforward errors. You should know what to do with a  “file not found” error, or an “access denied” message, but sometimes errors are not always so clear, Get-WmiObject, for example, can produce an “RPC server not found” error if it is not able to locate a remote computer, etc.,

But the most vexing logic errors are the ones that do not produce any error at all – they just prevent your script or command from working properly. Check the below code snippet:

$name = Read-Host "Enter computer name"
if (Test-Connection $name) {
    Get-WmiObject Win32_BIOS -ComputerName $nmae
}

Logic errors, like syntax errors, can come from typos and one of such errors is listed in the above code snippet.

Debugging causes a lot of frustration, and we need to know the below things before we debug any script or command:

  • You cannot debug a script or command unless you have a clear expectation of what it is going to do.
  • You must execute your script an examine its reality (what  it actually does), and compare that reality to your expectations. When reality and your expectations differ, you have found the bug.
  • While executing the script and examining it, you need to read very, very carefully, so that you can spot typos. Sometimes using a different font can help.

Identifying your expectations

If you do not know what a command or a script should do, then you can never debug it. We are using the same code snippet as above to see the process involved.

$name = Read-Host "Enter computer name"

I expect that this will display “Enter Computer name:” on the screen, and allow me to type something. What ever I type will be stored in the variable $name.

if (Test-Connection $name) {

My expectation is that this will run the Test-Connection command, passing it the computer name in $name. I expect that this will ping the computer name I typed. I see that this is enclosed in an IF construct, so I expect that Test-Connection must return a True or False value. So, if the computer can be pinged, it will return True, and whatever is inside the IF construct will execute next.

Get-WmiObject Win32_BIOS -ComputerName $name

I expect that this will return Get-WmiObject and retrieve the Win32_BIOS class. We know that -class is the first parameter, so the Win32_BIOS will be fed to the -class parameter. I see that the -computername parameter is also specified, and it is being passed the computer name from the $name variable. Oh, wait – there is a typo. See, just a careful read-through of the script found a problem. I am going to leave the typo in there, though, and pretend that I was not being so careful with it (for now). I want to show you some other ways that you might have found it.

}

That closes the IF construct. With my expectations written down, it is time to start seeing where they differ from reality.

Adding Trace Code

The first trick is to add trace code to the script. It all starts with a helpful command called Write-Debug, which simply takes a message that you want it to display.

Write-Debug "Test Message

If you are following along, you will notice that Write-Debug will not produce any output.

Write-Debug actually sends your message to an alternate pipeline called Debug pipeline. Powershell has several of these alternate pipelines: Error, Warning, Debug and so forth. By default, the Debug pipeline’s switch  is set to SilentlyContinue, which is the same as OFF. The result is that all Write-Debug messages are suppressed.

Lets go ahead and change the setting in the script by adding this to the top:

$DebugPreference = "Continue"

Now we are free to add Write-Host statements to our script. Below is the revised script:

$DebugPreference = "Continue"

$name = Read-Host "Enter computer name"
Write-Debug "`$name contains $name"

if (Test-Connection $name) {
Write-Debug "Test-Connection was True"
    Get-WmiObject Win32_BIOS -ComputerName $nmae
} else {
    Write-Debug "Test-Connection was False"
}
You can see that I have added a Write-Debug to the inside of the IF construct, I even added an Else potion to the construct, containing a third Write-Debug message. That way, no matter which way the IF construct’s logic goes, I’ll see some output and know what is happening inside the script.
If you run the command, you will get the below error:
typo
That is where you will realize that there is a typo in that $name variable. Powershell is clearly telling us that there is a problem with the -computername parameter; if we look carefully at just that portion of the script, the $nmae typo is more obvious.
After fixing that and running  the script again:
notypo
That looks like what we want.
Now, we need to test the opposite situation, what happens when we provide a computer name that is not valid?
testfailed
That is certainly not what we are looking for. That is a logic error: the Test-Connection cmdlet clearly is not doing what I expected, which was to return a simple True or False value.
Stepping out of the script and just work with the Test-Connection from the command line:
tesst-connection
failagainOkay, that is definitely not what I expected. When I used the command with a valid computer name, I get back a table of results, not a True or False value. When I use it with an invalid computer name, I still get an error.
Reading the help, Help Test-Connection -full. I see that the command “returns the echo response replies”. The help also says that, “unlike the traditional ‘ping’ command, Test-Connection returns a Win32_PingStatus Object… bot you can use the -quiet parameter to force it to return only a Boolean value.” Yes, Please !
Looking at the -quiet parameter, I see it “supresses all errors and return $True if any pings succeed and $False if all failed.” That is what I want, so I will modify the script accordingly.
$DebugPreference = "Continue"

$name = Read-Host "Enter computer name"
Write-Debug "`$name contains $name"

if (Test-Connection $name -Quiet) {
Write-Debug "Test-Connection was True"
    Get-WmiObject Win32_BIOS -ComputerName $name
} else {
    Write-Debug "Test-Connection was False"
}
Running the script again, testing it with both a valid and invalid computer name. Below is the output:
works
In short:
  • Whenever I change the contents of a variable, I use Write-Debug to output the variable, just to I can check those contents.
  • Whenever I am going to read the value of a property or a variable, I use Write-Debug to output that property or variable, so that I can see what is going on inside the script.
  • Any time I have a loop or logic construct, I build it in such a way that I get a Write-Debug message no matter how the loop or logic works out.

Working with break points

Having to wade through a lot of debug messages is cumbersome in lengthy /complex scripts. So we fall to use Breakpoints, to make the debugging process a bit easy.

A breakpoint is a defined area where a script will pause its execution, allowing you to examine the environment that the script is running within. Powershell can be configured to break when:

  • Your script reaches a certain line
  • A variable is read and/or changed
  • A specific command is executed

In the first instance, you must specify the script that you are referring to. In the second and third situations, you can choose to specify a script, and the breaking point will only be active for that script. If you don’t, the breakpoint will occur globally through out the shell when that variable is read or written, or that command is executed.

Example, suppose I want to have the script stop immediately after Line 1 finishes executing, meaning that I want to break before the Line 2. Then I will run the command:

Set-PSBreakpoint -Script E:\Work\Powershell\scripts\demo.ps1 -Line 2

breakpoint

I also want to be notified whenever the $name variable is accessed, so I will run the below command:

Set-PSBreakpoint -Script E:\Work\Powershell\scripts\demo.ps1 -Variable name -mode Read

anotherbreakpoint

Notice that the variable name is just “name” and not “$name”. Variable names do not  include the dollar sign; $ is just a cue to the shell telling it that you wish to use the contents of a variable.

With those two breakpoints set, I will run the script. After entering the computer name, the script will break. The shell modifies the command-line prompt, indicating that I am in a suspended mode. Here I can examine the contents of variables, execute commands and so on. When I am done, I can run Exit to resume script execution. Here is how it all works:

breakpointfinal

This gives me the chance to test commands, see what is inside variables or properties, and so forth, without having to add a  lot of write-debug commands.

When I am done debugging, I can remove the breakpoints:

Get-PSBreakpoint | Remove-PSBreakpoint

Breakpoints are also supported in Powershell ISE. To set a line breakpoint, move your cursor to the desired line and press F9.The ISE will visually indicate where line breakpoints occur, using a red highlight. If you run the script within the ISE, breakpoints lines will be highlighted in yellow when the script reaches one of those lines. At that time, you can hover your cursor over any variable to see a tooltip with the contents of that variable.

 

Advertisement