{"id":3751,"date":"2009-07-13T18:18:00","date_gmt":"2009-07-13T18:18:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/powershell\/2009\/07\/13\/advanced-debugging-in-powershell\/"},"modified":"2019-02-18T13:12:31","modified_gmt":"2019-02-18T20:12:31","slug":"advanced-debugging-in-powershell","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/powershell\/advanced-debugging-in-powershell\/","title":{"rendered":"Advanced Debugging in PowerShell"},"content":{"rendered":"<p>Here is a collection of tips and tricks to debug PowerShell<\/p>\n<p><strong>Read Up<br \/><\/strong>There is a 7-part series of \u201c<a href=\"http:\/\/www.bing.com\/search?q=site%3Ablogs.msdn.com%2Fpowershell+%22debugging+monad+scripts%22&amp;form=QBLH&amp;qs=n\">Debugging Monad Scripts<\/a>\u201d 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.<\/p>\n<p><strong>Clean code<br \/><\/strong>The best route, is to make sure code is clean, and common errors are caught. To do so, run<br \/>Set-StrictMode -Version 2<\/p>\n<p>Note, there\u2019s 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.<\/p>\n<p>You can also change your error, and warning preference to make PowerShell stop at non-terminating errors or warning.<br \/>For example, $ErrorActionPreference = &#8216;stop&#8217; or $WarningPreference = &#8216;stop&#8217;<\/p>\n<p>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.<\/p>\n<ul>\n<li>Terminating vs Non-terminating is discussed here <a href=\"http:\/\/blogs.msdn.com\/powershell\/archive\/2006\/04\/25\/583228.aspx\">http:\/\/blogs.msdn.com\/powershell\/archive\/2006\/04\/25\/583228.aspx<\/a><\/li>\n<li>Preference variables are covered here <a href=\"http:\/\/blogs.msdn.com\/powershell\/archive\/2006\/04\/25\/583232.aspx\">http:\/\/blogs.msdn.com\/powershell\/archive\/2006\/04\/25\/583232.aspx<\/a><\/li>\n<li>Set-PSDebug is discussed here <a href=\"http:\/\/blogs.msdn.com\/monad\/archive\/2005\/11\/09\/491035.aspx\">http:\/\/blogs.msdn.com\/monad\/archive\/2005\/11\/09\/491035.aspx<\/a><\/li>\n<\/ul>\n<p><strong>Error information<br \/><\/strong>$error holds the error information from commands. To get more details information, try<br \/>$error | select -ExpandProperty Exception | Format-List -f *<br \/>$error is explained here <a href=\"https:\/\/blogs.msdn.com\/powershell\/archive\/2006\/04\/25\/583229.aspx\">https:\/\/blogs.msdn.com\/powershell\/archive\/2006\/04\/25\/583229.aspx<\/a><br \/>A better Resolve-Error function from Jeffery can be found here <a href=\"http:\/\/blogs.msdn.com\/powershell\/archive\/2006\/12\/07\/resolve-error.aspx\">http:\/\/blogs.msdn.com\/powershell\/archive\/2006\/12\/07\/resolve-error.aspx<\/a><\/p>\n<p>$LASTEXITCODE tells you if a native command completed successfully or not. For example \u201cping example.com\u201d gives $LASTEXITCODE of 1<\/p>\n<p>$? 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<\/p>\n<p><strong>Trapping and Catching errors<br \/><\/strong>Try\/catch and trap can be used to process errors<\/p>\n<ul>\n<li>See more information here &#8211; <a href=\"http:\/\/blogs.msdn.com\/powershell\/archive\/2009\/06\/17\/traps-vs-try-catch.aspx\">http:\/\/blogs.msdn.com\/powershell\/archive\/2009\/06\/17\/traps-vs-try-catch.aspx<\/a><\/li>\n<li>On traps, you can see more here <a href=\"http:\/\/blogs.msdn.com\/powershell\/archive\/2006\/04\/25\/583234.aspx\">http:\/\/blogs.msdn.com\/powershell\/archive\/2006\/04\/25\/583234.aspx<\/a><\/li>\n<\/ul>\n<p><strong>Tracing<br \/><\/strong>Write-Debug will write messages to the screen. You need to set, $DebugPreference = &#8220;continue&#8221;, so they actually appear. Setting $DebugPreference and $VerbosePreference can sometimes give you clues on what other commands are doing.<br \/>Write-Host is a more normal\/boring way of getting output. Out-Host is sometimes better because it preserves format and output<\/p>\n<p>Write-Output is usually a better alternative&#8230; Write-Host can\u2019t be captured into log files.<\/p>\n<p>More info on Write-Host and Write-Output is here <a href=\"http:\/\/blogs.msdn.com\/monad\/archive\/2005\/11\/09\/490625.aspx\">http:\/\/blogs.msdn.com\/monad\/archive\/2005\/11\/09\/490625.aspx<\/a><\/p>\n<p><strong>Use the ISE Debugger<br \/><\/strong>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 &#8211; <a href=\"http:\/\/blogs.msdn.com\/powershell\/archive\/2009\/01\/19\/debugging-powershell-script-using-the-ise-editor.aspx\">http:\/\/blogs.msdn.com\/powershell\/archive\/2009\/01\/19\/debugging-powershell-script-using-the-ise-editor.aspx<\/a><\/p>\n<p><strong>Use the Console Debugger<br \/><\/strong>PowerShell.exe also comes with a&nbsp; debugger. It\u2019s 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.<br \/>More information is available in about_debuggers <a href=\"http:\/\/technet.microsoft.com\/en-us\/library\/dd347652.aspx\">http:\/\/technet.microsoft.com\/en-us\/library\/dd347652.aspx<\/a><\/p>\n<p><strong>Finding out where you are<br \/><\/strong>To find out where you are in the code, you can use \u201cSet-PSDebug -Trace 2\u201d this is especially useful if you get into an infinite loop, or if have little or no output from your scripts.<\/p>\n<p><strong>Advanced Breakpoints<br \/><\/strong>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)<\/p>\n<p>Set-PSBreakpoint \u2013Command Out-Default is an interesting trick from James Brundage, where you can break anytime output is about to be displayed<\/p>\n<p>If you only want to know when something is hit, you can do<br \/>Set-PSBreakpoint -Command dir -Action {write-host &#8220;hit dir&#8221;}<\/p>\n<p>If you want a conditional breakpoint, you can do something like,<br \/>Set-PSBreakpoint -Line 10 -Script MyScript.ps1 -Action {if ($ctr -eq 0) { break }}<\/p>\n<p><strong>Debugging Modules<br \/><\/strong>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 \u2018peek inside\u2019 it, you can use the call operator<br \/>$m = Get-Module test<br \/>&amp; $m {$privateVariableValue}<br \/>This lets you modify the module, even after you have loaded it<\/p>\n<p><strong>Debugging Jobs<br \/><\/strong>Jobs are commands that run in the background in PowerShell. They have their own errors and output and you don\u2019t get access to them directly.<\/p>\n<p>For example, Start-Job -ScriptBlock {1;throw &#8220;SomeError&#8221;; 2}<\/p>\n<p>You can see the status of the job using Get-Job <br \/>Id&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Name&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; State&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HasMoreData&nbsp;&nbsp;&nbsp;&nbsp; Location&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Command&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br \/>&#8212;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8212;-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8212;&#8211;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8212;&#8212;&#8212;&#8211;&nbsp;&nbsp;&nbsp;&nbsp; &#8212;&#8212;&#8211;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8212;&#8212;-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br \/>1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Job1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Failed&nbsp;&nbsp;&nbsp;&nbsp; True&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; localhost&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1;throw &#8220;SomeError&#8221;; 2&nbsp;&nbsp; <\/p>\n<p>To get the errors and output from the job, use &#8220;(Get-Job 1).ChildJobs[0].JobStateInfo.Reason&#8221; or&nbsp; \u201cReceive-Job 1 \u2013Keep\u201d<br \/>It will have the error and you can investigate that.<\/p>\n<p><strong>Debugging Events<br \/><\/strong>Debugging eventing is kind of like a mix of debugging jobs and modules and more<br \/>Tracing works with Write-Host but not with Write-Object in events.<\/p>\n<p>$fsw = New-Object io.filesystemwatcher $env:tmp<br \/>Register-ObjectEvent -InputObject $fsw -EventName Disposed -Action {write-host hi}<br \/>$fsw.Dispose()<\/p>\n<p>The above will write Write-Host. Also useful in debugging eventing is using Out-GridView, in particular, \u201cdir variable:\\ | Out-GridView\u201d gives a quick view of the special variables set in events<\/p>\n<p>You can get access to the related jobs, using Get-EventSubscriber | %{Get-Job -Name $_.SourceIdentifier}<\/p>\n<p>From the eventing jobs, you can get access to the using (Get-Job).Module<br \/>For example, you can run, <br \/>&amp; (Get-Job).Module {$event, $eventSubscriber, $eventArgs}<br \/>which lets your work in the event\u2019s scope even after it has completed.<\/p>\n<p><strong>Debugging Remoting<br \/><\/strong>For connection issues, see get-help about_remote_troubleshooting<\/p>\n<p>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<\/p>\n<p>Not all error information propagates back from remote invocations. For example,<br \/>Invoke-Command my-server {ping example.com}<br \/>$? And $LASTEXITCODE are not set<\/p>\n<p>To get $LASTEXITCODE, you should either do<br \/>Invoke-Command localhost {ping example.com; $LASTEXITCODE}<\/p>\n<p>Or even better, create a persistent session<br \/>$session = New-PSSession localhost<br \/>Invoke-Command $session {ping example.com}<br \/>Invoke-Command $session {$LASTEXITCODE}<\/p>\n<p>To do debugging using tracing, remember that you need to set the VerbosePreference, DebugPreference etc in the remote session<br \/>Invoke-Command $session {$VerbosePreference = &#8216;continue&#8217;}<br \/>Invoke-Command $session {Write-Verbose hi}<\/p>\n<p>Share your skills bug hunters, PowerShell still doesn&#8217;t&nbsp;have a Fix-Script cmdlet.<\/p>\n<p>Hope this helps,<br \/>Ibrahim Abdul Rahim [MSFT]<br \/>This posting is provided \u201cAS IS\u201d with no warranties.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Here is a collection of tips and tricks to debug PowerShell Read UpThere is a 7-part series of \u201cDebugging Monad Scripts\u201d 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 [&hellip;]<\/p>\n","protected":false},"author":600,"featured_media":13641,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[62,140,30,161,178,4,260,270,8],"class_list":["post-3751","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-powershell","tag-lastexitcode","tag-debugging","tag-error","tag-events","tag-get-module","tag-howto","tag-powershell-ise","tag-powershell-v2","tag-remoting"],"acf":[],"blog_post_summary":"<p>Here is a collection of tips and tricks to debug PowerShell Read UpThere is a 7-part series of \u201cDebugging Monad Scripts\u201d 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 [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/posts\/3751","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/users\/600"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/comments?post=3751"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/posts\/3751\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/media\/13641"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/media?parent=3751"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/categories?post=3751"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell\/wp-json\/wp\/v2\/tags?post=3751"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}