Here is a collection of tips and tricks to debug PowerShell
Read UpThere 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 codeThe best route, is to make sure code is clean, and common errors are caught. To do so, runSet-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.aspxA 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 errorsTry/catch and trap can be used to process errors
TracingWrite-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 DebuggerThe 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 DebuggerPowerShell.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 areTo 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 BreakpointsSet-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 doSet-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 ModulesIf 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 JobsJobs 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 EventsDebugging eventing is kind of like a mix of debugging jobs and modules and moreTracing works with Write-Host but not with Write-Object in events.
$fsw = New-Object io.filesystemwatcher $env:tmpRegister-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).ModuleFor 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 RemotingFor 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 doInvoke-Command localhost {ping example.com; $LASTEXITCODE}
Or even better, create a persistent session$session = New-PSSession localhostInvoke-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 sessionInvoke-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.