Debugging Monad Scripts, Part 7 (Final): How Traps Work

Did your command or script fail and/or report an error?  We hope to have a proper script debugger in a future version, but until then, MSH has some handy features to help you figure out what went wrong.  In this series of blog entries, I will present some of those features.  Thanks to Jim Truher [MSFT], Bruce Payette [MSFT], and Jeff Jones [MSFT] for their help in putting this together.

See the Windows "Monad" Shell Beta 2 Documentation Pack (https://www.microsoft.com/downloads/details.aspx?FamilyID=8a3c71d1-18e5-49d7-952a-c55d694ecee3&displaylang=en) for general information about Monad.

Part 1: https://blogs.msdn.com/monad/archive/2005/11/04/489138.aspx (Terminating vs. Non-Terminating Errors, ErrorRecord)
Part 2: https://blogs.msdn.com/monad/archive/2005/11/08/490130.aspx ($error)
Part 3: https://blogs.msdn.com/monad/archive/2005/11/09/490625.aspx (write-host)
Part 4: https://blogs.msdn.com/monad/archive/2005/11/09/491035.aspx (set-mshdebug)
Part 5: https://blogs.msdn.com/monad/archive/2005/11/11/491967.aspx (Preferences and Commandline Options)
Part 6: https://blogs.msdn.com/monad/archive/2005/11/15/492769.aspx (Trace-Expression, Breakpoint Script)

Jon Newman [MSFT]

 


How Traps Work

You can use the "trap" language feature to manage terminating errors in your script. "Getting Started.doc" and "MonadScripting-HandsOnLab.doc" from the Documentation Pack contain some details, but here is a little more detail.

If you don't use "trap" at all, terminating errors halt the command which is executing, and then the script moves on to the next command. In this example, you can see that the "terminating error" only terminates the command where it occurs, and execution continues with the next command, "ending a".

MSH C:\temp> get-content .\test.msh
function a
{
write-host "starting a"
write-host (1 / $null)
write-host "ending a"
}
write-host "starting main"
a
write-host "ending main"
MSH C:\temp> .\test.msh
starting main
starting a
Attempted to divide by zero.
At C:\temp\test.msh:4 char:4
+ 1 / <<<< $null
ending a
ending main
MSH C:\temp> get-content .\test.msh

You can add "trap" statements to your script to handle terminating errors. Inside the trap statement, "$_" is the ErrorRecord for the error, and you can use "break;" to break script execution, or "continue;" to continue with the next command after the one which hit the error.

MSH C:\temp> get-content .\test.msh
function a
{
trap [System.DivideByZeroException] { write-host ("trapped " + $_.Exception.GetType().Name); continue; }
write-host "starting a"
write-host (1 / $null)
write-host "ending a"
}
write-host "starting main"
a
write-host "ending main"
MSH C:\temp> .\test.msh
starting main
starting a
trapped DivideByZeroException
ending a
ending main
MSH C:\temp>

Here's the tricky bit: If the current scope does not contain a trap, but any outer scope does, script execution after a terminating error will break out to the nearest scope where any trap (whether it matches the error or not) is defined. In this example, we move the trap statement out of "function a" and into the outer scope:

MSH C:\temp> get-content .\test.msh
trap [System.DivideByZeroException] { write-host ("trapped " + $_.Exception.GetType().Name); continue; }
function a
{
write-host "starting a"
write-host (1 / $null)
write-host "ending a"
}
write-host "starting main"
a
write-host "ending main"
MSH C:\temp> .\test.msh
starting main
starting a
trapped DivideByZeroException
ending main
MSH C:\temp>

Note that "ending a" doesn't appear in the output; execution broke out of function a.

This applies whether or not the error which occurred matches the exception you are trapping:

MSH C:\temp> get-content .\test.msh
trap [System.ArgumentException] { write-host ("trapped " + $_.Exception.GetType().Name); continue; }
function a
{
write-host "starting a"
write-host (1 / $null)
write-host "ending a"
}
write-host "starting main"
a
write-host "ending main"
MSH C:\temp> .\test.msh
starting main
starting a
Attempted to divide by zero.
At C:\temp\test.msh:6 char:4
+ 1 / <<<< $null
ending main
MSH C:\temp>

You may ask, why is this? Remember that trap statements operate in the scope in which they are declared. We had implementation and performance problems in exiting the "function a" scope, running the trap block in the outer scope, then returning to the "function a" scope.