Here is a collection of tips and tricks to debug PowerShell

Read Up
There is a 7-part series of “Debugging Monad Scripts” that Jon Newman wrote a few years ago that covers a lot of tips, including error handling, traps, tracing, and covers a lot of V1 stuff.

Clean code
The best route, is to make sure code is clean, and common errors are caught. To do so, run
Set-StrictMode -Version 2

Note, there’s a lot of normal PowerShell (v1 and V2) that StrictMode barfs on, so its recommend to only use Set-StrictMode when trying to debug their own scripts.

You can also change your error, and warning preference to make PowerShell stop at non-terminating errors or warning.
For example, $ErrorActionPreference = 'stop' or $WarningPreference = 'stop'

This makes sure that if any errors are raised, whether they are terminating or non-terminating, code will stop. Calling .net methods that throw exceptions are an example of a non-terminating error. In C#, they would be terminating.

Error information
$error holds the error information from commands. To get more details information, try
$error | select -ExpandProperty Exception | Format-List -f *
$error is explained here https://blogs.msdn.com/powershell/archive/2006/04/25/583229.aspx
A better Resolve-Error function from Jeffery can be found here http://blogs.msdn.com/powershell/archive/2006/12/07/resolve-error.aspx

$LASTEXITCODE tells you if a native command completed successfully or not. For example “ping example.com” gives $LASTEXITCODE of 1

$? Tells you if the last command had any error in it. This includes if you tried to execute a command that does not exist. Pping will not set $LASTEXITCODE, but it will set $? To False

Trapping and Catching errors
Try/catch and trap can be used to process errors

Tracing
Write-Debug will write messages to the screen. You need to set, $DebugPreference = "continue", so they actually appear. Setting $DebugPreference and $VerbosePreference can sometimes give you clues on what other commands are doing.
Write-Host is a more normal/boring way of getting output. Out-Host is sometimes better because it preserves format and output

Write-Output is usually a better alternative... Write-Host can’t be captured into log files.

More info on Write-Host and Write-Output is here http://blogs.msdn.com/monad/archive/2005/11/09/490625.aspx

Use the ISE Debugger
The ISE comes with a debugger that helps you to see the callstack, set and hit breakpoints, step over, into and out of your code. Find more information here - http://blogs.msdn.com/powershell/archive/2009/01/19/debugging-powershell-script-using-the-ise-editor.aspx

Use the Console Debugger
PowerShell.exe also comes with a  debugger. It’s not as easy to use as the ISE Debugger, but it can stop at all breakpoints set using Set-PSBreakpoint. It can step-over, step-into, and step-out of code. It can even display lines from the current script.
More information is available in about_debuggers http://technet.microsoft.com/en-us/library/dd347652.aspx

Finding out where you are
To find out where you are in the code, you can use “Set-PSDebug -Trace 2” this is especially useful if you get into an infinite loop, or if have little or no output from your scripts.

Advanced Breakpoints
Set-PSBreakpoint can do more than just line breakpoints. You can use Set-PSBreakpoint to break whenever a variable is set (-Variable) or when a command is ran (-Command)

Set-PSBreakpoint –Command Out-Default is an interesting trick from James Brundage, where you can break anytime output is about to be displayed

If you only want to know when something is hit, you can do
Set-PSBreakpoint -Command dir -Action {write-host "hit dir"}

If you want a conditional breakpoint, you can do something like,
Set-PSBreakpoint -Line 10 -Script MyScript.ps1 -Action {if ($ctr -eq 0) { break }}

Debugging Modules
If you have a module, you can place breakpoints into them using the ISE. Command breakpoints can be set on private functions and variables. If you loaded a module, and you wanted to ‘peek inside’ it, you can use the call operator
$m = Get-Module test
& $m {$privateVariableValue}
This lets you modify the module, even after you have loaded it

Debugging Jobs
Jobs are commands that run in the background in PowerShell. They have their own errors and output and you don’t get access to them directly.

For example, Start-Job -ScriptBlock {1;throw "SomeError"; 2}

You can see the status of the job using Get-Job
Id              Name            State      HasMoreData     Location             Command                 
--              ----            -----      -----------     --------             -------                 
1               Job1            Failed     True            localhost            1;throw "SomeError"; 2  

To get the errors and output from the job, use "(Get-Job 1).ChildJobs[0].JobStateInfo.Reason" or  “Receive-Job 1 –Keep”
It will have the error and you can investigate that.

Debugging Events
Debugging eventing is kind of like a mix of debugging jobs and modules and more
Tracing works with Write-Host but not with Write-Object in events.

$fsw = New-Object io.filesystemwatcher $env:tmp
Register-ObjectEvent -InputObject $fsw -EventName Disposed -Action {write-host hi}
$fsw.Dispose()

The above will write Write-Host. Also useful in debugging eventing is using Out-GridView, in particular, “dir variable:\ | Out-GridView” gives a quick view of the special variables set in events

You can get access to the related jobs, using Get-EventSubscriber | %{Get-Job -Name $_.SourceIdentifier}

From the eventing jobs, you can get access to the using (Get-Job).Module
For example, you can run,
& (Get-Job).Module {$event, $eventSubscriber, $eventArgs}
which lets your work in the event’s scope even after it has completed.

Debugging Remoting
For connection issues, see get-help about_remote_troubleshooting

For debugging, instead on Invoke-Command, it might be better to use Enter-PSSession, so that you can have a shell on the remote machine to try things out

Not all error information propagates back from remote invocations. For example,
Invoke-Command my-server {ping example.com}
$? And $LASTEXITCODE are not set

To get $LASTEXITCODE, you should either do
Invoke-Command localhost {ping example.com; $LASTEXITCODE}

Or even better, create a persistent session
$session = New-PSSession localhost
Invoke-Command $session {ping example.com}
Invoke-Command $session {$LASTEXITCODE}

To do debugging using tracing, remember that you need to set the VerbosePreference, DebugPreference etc in the remote session
Invoke-Command $session {$VerbosePreference = 'continue'}
Invoke-Command $session {Write-Verbose hi}

Share your skills bug hunters, PowerShell still doesn't have a Fix-Script cmdlet.

Hope this helps,
Ibrahim Abdul Rahim [MSFT]
This posting is provided “AS IS” with no warranties.