| RFC | RFCNNNN-RFC-Native-Command-Exit-Errors |
|---|---|
| Author | Micheal Padden |
| Status | Draft |
| Area | Engine, Language |
| Version | 1.0.0 |
Although Powershell has an exception-based error handling framework, it does not currently apply to native commands.
Exception-based error handling makes it easier to write robust code as less boilerplate code is needed to check and handle errors.
Powershell scripts using native commands would benefit from being able to use error handling features like those used by cmdlets.
Native commands return an exit code to the calling application which will be zero for success or non-zero for failure. A robust script will not assume success, instead checking the exit code after any statement calling a native command, using boilerplate like the following:
if( ! $? )
{
exit $lastexitcode;
}The Bourne again shell provides a set -eo pipefail exit code handling mode
that acts as though this boilerplate were included in certain contexts.
Like exception-based error handling, this can exit a stack of functions,
scripts and shells. This allows robust scripts to be written with a minimal
amount of boilerplate.
To support a similar style of error handling with native commands, this
RFC proposes converting native command errors into PowerShell errors.
The Bourne shell compatible set -e mode (without set -o pipefail) is
not supported.
The specification and alternative proposals are based on the
Equivalent of bash set -e #3415
committee review of the associated
pull request, and
implementation plan
This RFC proposes including native commands in the error handling framework when the feature is enabled by adding an error to the error stream if the exit status of a native command is false.
The $PSNativeCommandErrorAction preference variable will implement a version
of the $ErrorActionPreference variable for native commands. The value will
default to Ignore for compatibility with existing behaviour.
- For all values except
Ignore, anErrorRecordwill be added to$Errorthat wraps the exit code and the command executed that returned the exit code. - Initially, only the existing values of
$ErrorActionPreferencewill be supported. - The set of values may be extended later to include
MatchErrorActionPreference, which should apply the$ErrorActionPreferencesetting to native commands also. - An enum converter will convert between
$PSNativeCommandErrorand$ErrorActionPreferencevalues, whereMatchErrorActionPreferenceis converted to the current value of$ErrorActionPreference
The reported error record should be created with the following details:
- exception:
ExitCode, with the exit code of the failed command. - error id:
"Program {0} failed with unhandled exit code {1}", with the command name and the exit code, from resource stringProgramFailedToComplete. - error category:
ErrorCategory.NotSpecified. - object: the (boxed) exit code.
This does not provide the actual semantics of bash set -eo pipefail as Bourne shell
style integration with existing existing status handling syntax is not implemented.
As a result, PowerShell script logic for handling native command exit codes would need
to use either try..catch or existing exit status handling language constructs,
according to the setting of this preference variable.
One way of overriding$ErrorActionPreference for a single native command and handling
its exit status explicitly would be to put this logic into a script block and call it
with the invocation operator (&).
A number of tweaks to existing behaviours could be combined to give equivalent
functionality to bash set -eo pipefail
The $PSStrictNativeCommand preference should treat creation of an ErrorRecord
for native commands in the same way as this is treated elsewhere.
Possible values are:
$false: (the default) ignore non-zero exit codes. This is the same as existing Powershell treatment of this case.$true: Populate the error stream of the native command with anErrorRecordassociated with anExitExceptionexception.
The error will throw an exception, potentially terminating the session, in the same
situation as other non-terminating errors will do this, i.e. where
$ErrorActionPreference is set to "stop". This would not be the desired behaviour on
commands where the script already handles a non-zero exit code, which would require
the addition of extra boilerplate to use multiple native commands in combination.
There are a number of ways that this could be made more flexible by integrating exit
code and exit status handling into the language syntax.
This approach implements semantics equivalent to bash set -eo pipefail
in the runtime layer.
The $PSStrictPipeLine preference variable would govern promotion of a non-terminating
error to a terminating error on getting an object from the pipeline output stream.
Possible values would be:
$false: (the default) an object can be collected from the pipeline output stream regardless of the command exit value. This is the same as existing PowerShell treatment of this case.$true: where the exit status of a native command is$false, trying to get an object from its output stream will create a terminating error from the non-terminating errors in its error stream. Conversion to boolean would be structured to ensure that this returns$falseif the output pipeline does not contain anything without trying to get an actual value from it.
This would allow syntax like if, while and pipeline chain operators to be usefully
combined with native commands.
This option in combination with the above enables functionality analogous to
bash set -eo pipefail
PowerShell converts the output stream to a boolean value where a native command is used
as a "condition", i.e. the -not operator, or an if, elseif or while statement.
This is not particularly useful with native commands, which would tend to produce no
output on success, at least when executed in batch as opposed to interactive mode.
The $PSUseNativeExitStatus variable would govern whether exit status is used
in determining the boolean value of a native command for a conditional context.
Possible values would be:
$false: (the default) the boolean value of native command is defined as whether or not the length of the output stream is non-zero. This is the same as existing PowerShell treatment of this case.$true: the boolean value of a native command is it's exit status ($?), and
This would allow syntax like if and while to be usefully combined with
native commands.
Treat only ignored exit statuses as exceptions.
The $PSStrictPipeLineChain preference variable would govern the exit
status in the last command of a pipeline.
Possible values would be:
$false: (the default) would ignore$falseexit status on the last command. This is the same as existing PowerShell treatment of this case.$true: for a pipeline that is being used in a conditional context (see above), and where the exist status of the last command in the pipeline is$false, would create a terminating error from the non-terminating errors in the command error stream.
This should improve on the basic specification by allowing the idiom to be usefully combined with pipeline chaining.
This approach implements dynamic scoping for native command error management using a cmdlet.
Some of the above would be enabled with Set-StrictMode -version 6
instead of with boolean preference variables.
The dynamic scoping approach would improve on earlier listed approaches by limiting the scope of error handling configuration so that script functions could not have an effect on the error handling mode in the calling scope.
If Set-StrictMode is used for this, it would need to enable some combination
of enhancements that will not raise errors on scripts that have already implemented
strong native command error handling.
Dynamic scoping approach would have side-effects on called scripts. This is analogous to the behaviour of Bourne type shells, which maintain this behaviour for historic compatibility reasons.
Lexical scoping of native command error handling would improve on earlier options by integrating native command error handling fully into the existing exception handling without out any side-effects in calling or called scripts.
With this approach, native command error handling mode would be used through language syntax instead of preference variables or cmdlets. A possible syntax might be to add a strictness option to the try statement which applies to the lexical scope of the try statement.
Implementing this functionality only as a lexically scoped extension could be the preferred option because it becomes a kind of syntactic sugar which is implemented by the parser and compiler, improving testability, and keeping complexity out of the runtime layer.