What is the best way to detect if an error occurs in a script function? I'm looking for a consistent way to indicate error/success status similar to $? (which only works on cmdlets, not script functions).
Given a particular function might return a value to be used by the caller, we can't indicate success by returning a boolean. A function could use a [ref] parameter and set the value appropriately inside the function and check after the call, but this is more overhead than I'd like. Is there something built-in to PowerShell that we can use?
The best I can come up with is:
For example:
function MyFun {
[CmdletBinding()] # must be an advanced function or this
param () # will not work as ErrorVariable will not exist
process {
# code here....
if ($SomeErrorCondition) {
Write-Error -Message "Error occurred; details..."
return
}
# more code here....
}
}
# call the function
$Err = $null
MyFun -ErrorVariable Err
# this check would be similar to checking if $? -eq $false
if ($Err -ne $null) {
"An error was detected"
# handle error, log/email contents of $Err, etc.
}
Is there something better? Is there a way of using $? in our script functions? I'd rather not throw exceptions or ErrorRecord objects and have tons of try/catch blocks all over the place. I'd also rather not use $Error as would require checking the count before making the function call as there could be other errors in there before the call - and I don't want to Clear() and lose them.
Use the try block to define a section of a script in which you want PowerShell to monitor for errors. When an error occurs within the try block, the error is first saved to the $Error automatic variable. PowerShell then searches for a catch block to handle the error.
The Get-Error cmdlet gets a PSExtendedError object that represents the current error details from the last error that occurred in the session. You can use Get-Error to display a specified number of errors that have occurred in the current session using the Newest parameter.
% is an alias for the ForEach-Object cmdlet. An alias is just another name by which you can reference a cmdlet or function.
SilentlyContinue — Don't display an error message continue to execute subsequent commands. Continue — Display any error message and attempt to continue execution of subsequence commands. Inquire — Prompts the user whether to continue or terminate the action. Stop — Terminate the action with error.
What is the best way to detect if an error occurs in a script function? I'm looking for a consistent way to indicate error/success status similar to $? (which only works on cmdlets, not script functions).
Error handling in PowerShell is a total mess. There are error records, script exceptions, .NET exceptions, $?
, $LASTEXITCODE
, trap
s, $Error
array (between scopes), and so on. And constructs to interact these elements with each other (such as $ErrorActionPreference
). It is very difficult to get consistent when you have a morass like this; however, there is a way to achieve this goal.
The following observations must be made:
$?
is an underdocumented mystery. $?
values from cmdlet calls do not propagate, it is a "read-only variable" (thus cannot be set by hand) and it is not clear on when exactly it gets set (what could possibly be an "execution status", term never used in PowerShell except on the description of $?
in about_Automatic_Variables
, is an enigma). Thankfully Bruce Payette has shed light on this: if you want to set $?
, $PSCmdlet.WriteError()
is the only known way.
If you want functions to set $?
as cmdlets do, you must refrain from Write-Error
and use $PSCmdlet.WriteError()
instead. Write-Error
and $PSCmdlet.WriteError()
do the same thing, but the former does not set $?
properly and the latter does. (Do not bother trying to find this documented somewhere. It is not.)
If you want to handle .NET exceptions properly (as if they were non-terminating errors, leaving the decision of halting the entire execution up to the client code), you must catch
and $PSCmdlet.WriteError()
them. You cannot leave them unprocessed, since they become non-terminating errors which do not respect $ErrorActionPreference
. (Not documented either.)
In other words, the key to produce consistent error handling behavior is to use $PSCmdlet.WriteError()
whenever possible. It sets $?
, respects $ErrorActionPreference
(and thus -ErrorAction
) and accepts System.Management.Automation.ErrorRecord
objects produced from other cmdlets or a catch
statement (in the $_
variable).
The following examples will show how to use this method.
# Function which propagates an error from an internal cmdlet call,
# setting $? in the process.
function F1 {
[CmdletBinding()]
param([String[]]$Path)
# Run some cmdlet that might fail, quieting any error output.
Convert-Path -Path:$Path -ErrorAction:SilentlyContinue
if (-not $?) {
# Re-issue the last error in case of failure. This sets $?.
# Note that the Global scope must be explicitly selected if the function is inside
# a module. Selecting it otherwise also does not hurt.
$PSCmdlet.WriteError($Global:Error[0])
return
}
# Additional processing.
# ...
}
# Function which converts a .NET exception in a non-terminating error,
# respecting both $? and $ErrorPreference.
function F2 {
[CmdletBinding()]
param()
try {
[DateTime]"" # Throws a RuntimeException.
}
catch {
# Write out the error record produced from the .NET exception.
$PSCmdlet.WriteError($_)
return
}
}
# Function which issues an arbitrary error.
function F3 {
[CmdletBinding()]
param()
# Creates a new error record and writes it out.
$PSCmdlet.WriteError((New-Object -TypeName:"Management.Automation.ErrorRecord"
-ArgumentList:@(
[Exception]"Some error happened",
$null,
[Management.Automation.ErrorCategory]::NotSpecified,
$null
)
))
# The cmdlet error propagation technique using Write-Error also works.
Write-Error -Message:"Some error happened" -Category:NotSpecified -ErrorAction:SilentlyContinue
$PSCmdlet.WriteError($Global:Error[0])
}
As a last note, if you want to create terminating errors from .NET exceptions, do try
/catch
and rethrow
the exception caught.
It sounds like you are looking for a general mechanism to log any error occurring in a command called from a script. If so, trap
is probably the most appropriate mechanism:
Set-Alias ReportError Write-Host -Scope script # placeholder for actual logging
trap {
ReportError @"
Error in script $($_.InvocationInfo.ScriptName) :
$($_.Exception) $($_.InvocationInfo.PositionMessage)
"@
continue # or use 'break' to stop script execution
}
function f( [int]$a, [switch]$err ) {
"begin $a"
if( $err ) { throw 'err' }
" end $a"
}
f 1
f 2 -err
f 3
Running this test script produces the following output, without requiring any modification to the called functions:
PS> ./test.ps1
begin 1
end 1
begin 2
Error in script C:\Users\umami\t.ps1 :
System.Management.Automation.RuntimeException: err
At C:\Users\umami\t.ps1:13 char:21
+ if( $err ) { throw <<<< 'err' }
begin 3
end 3
If script execution should stop after an error is reported, replace continue
with break
in the trap handler.
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