I'm a C# developer who is trying to build something useful using PowerShell. That's why I'm keep trying to use well-known idioms from .NET world in PowerShell.
I'm writing a script that has different layer of abstractions: database operations, file manipulation etc. At some point I would like to catch an error and wrap it into something more meaningful for the end user. This is a common pattern for C#/Java/C++ code:
Function LowLevelFunction($arg)
{
# Doing something very useful here!
# but this operation could throw
if (!$arg) {throw "Ooops! Can't do this!"}
}
Now, I would like to call this function and wrap an error:
Function HighLevelFunction
{
Try
{
LowLevelFunction
}
Catch
{
throw "HighLevelFunction failed with an error!`nPlease check inner exception for more details!`n$_"
}
}
This approach is almost what I need, because HighLevelFunction
will throw new error and the root cause of the original error would be lost!
In C# code I always can throw new exception and provide original exception as an inner exception. In this case HighLevelFunction
would be able to communicate their errors in a form more meaningful for their clients but still will provide inner details for diagnostic purposes.
The only way to print original exception in PowerShell is to use $Error
variable that stores all the exceptions. This is OK, but the user of my script (myself for now) should do more things that I would like.
So the question is: Is there any way to raise an exception in PowerShell and provide original error as an inner error?
You can throw a new exception in your catch
block and specify the base exception:
# Function that will throw a System.IO.FileNotFoundExceptiopn
function Fail-Read {
[System.IO.File]::ReadAllLines( 'C:\nonexistant' )
}
# Try to run the function
try {
Fail-Read
} catch {
# Throw a new exception, specifying the inner exception
throw ( New-Object System.Exception( "New Exception", $_.Exception ) )
}
# Check the exception here, using getBaseException()
$error[0].Exception.getBaseException().GetType().ToString()
Unfortunately when throwing a new exception from the catch
block as described by this answer, the script stack trace (ErrorRecord.ScriptStackTrace
) will be reset to the location of the throw
. This means the root origin of the inner exception will be lost, making debugging of complex code much harder.
There is an alternative solution that uses ErrorRecord.ErrorDetails
to define a high-level message and $PSCmdlet.WriteError()
to preserve the script stack trace. It requires that the code is written as an advanced function cmdlet. The solution doesn't use nested exceptions, but still fulfills the requirement "to catch an error and wrap it into something more meaningful for the end user".
#------ High-level function ----------------------------------------------
function Start-Foo {
[CmdletBinding()] param()
try {
# Some internal code that throws an exception
Get-ChildItem ~foo~ -ErrorAction Stop
}
catch {
# Define a more user-friendly error message.
# This sets ErrorRecord.ErrorDetails.Message
$_.ErrorDetails = 'Could not start the Foo'
# Rethrows (if $ErrorActionPreference is 'Stop') or reports the error normally,
# preserving $_.ScriptStackTrace.
$PSCmdlet.WriteError( $_ )
}
}
#------ Code that uses the high-level function ---------------------------
$DebugPreference = 'Continue' # Enable the stack trace output
try {
Start-Foo -ErrorAction Stop
}
catch {
$ErrorView = 'NormalView' # to see the original exception info
Write-Error -ErrorRecord $_
''
Write-Debug "`n--- Script Stack Trace ---`n$($_.ScriptStackTrace)" -Debug
}
Output:
D:\my_temp\ErrorDetailDemo.ps1 : Could not start the Foo + CategoryInfo : ObjectNotFound: (D:\my_temp\~foo~:String) [Write-Error], ItemNotFoundException + FullyQualifiedErrorId : PathNotFound,ErrorDetailDemo.ps1 DEBUG: --- Script Stack Trace --- at Start-Foo, C:\test\ErrorDetailDemo.ps1: line 5 at , C:\test\ErrorDetailDemo.ps1: line 14
Our high-level error message 'Could not start the Foo' hides the error message of the underlying exception, but no information is lost (you could access the original error message through $_.Exception.Message
from within the catch handler).
Note: There is also a field ErrorDetails.RecommendedAction
which you could set as you see fit. For simplicity I didn't use it in the sample code, but you could set it like this $_.ErrorDetails.RecommendedAction = 'Install the Foo'
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With