|
| 1 | +--- |
| 2 | +RFC: RFCNNNN-RFC-Native-Command-Exit-Errors |
| 3 | +Author: Micheal Padden |
| 4 | +Status: Draft |
| 5 | +Area: Engine, Language |
| 6 | +Version: 1.0.0 |
| 7 | +--- |
| 8 | + |
| 9 | +# Native Command Error Handling |
| 10 | + |
| 11 | +Although Powershell has an exception-based error handling framework, it |
| 12 | +does not currently apply to native commands. |
| 13 | + |
| 14 | +Exception-based error handling makes it easier to write robust code |
| 15 | +as less boilerplate code is needed to check and handle errors. |
| 16 | + |
| 17 | +Powershell scripts using native commands would benefit from being able |
| 18 | +to error handling features like those used by cmdlets. |
| 19 | + |
| 20 | +## Motivation |
| 21 | + |
| 22 | +Native commands return an exit code to the calling application which |
| 23 | +will be zero for success or non-zero for failure. A robust script will |
| 24 | +not assume success, instead checking the exit code after any statement |
| 25 | +calling a native command, using boilerplate like the following: |
| 26 | +```Powershell |
| 27 | + if( ! $? ) |
| 28 | + { |
| 29 | + exit $lastexitcode; |
| 30 | + } |
| 31 | +``` |
| 32 | + |
| 33 | +Bourne type shells provide a `set -e` error handling mode that acts as |
| 34 | +if this boilerplate were included in certain contexts. Like exception |
| 35 | +based error handling, this can exit a stack of functions, scripts and |
| 36 | +shells. This allows robust scripts to be written with a minimal amount |
| 37 | +of boilerplate. |
| 38 | + |
| 39 | +To support a similar style of error handling with native commands, this |
| 40 | +RFC proposes including native command errors in Powershell's exception |
| 41 | +handling framework in a similar way. |
| 42 | + |
| 43 | +The specification and alternative proposals are based on the |
| 44 | +[Equivalent of bash `set -e` #3415](https://github.com/PowerShell/PowerShell/issues/3415) |
| 45 | +and committee review of the associated |
| 46 | +[pull request](https://github.com/PowerShell/PowerShell/pull/3523). |
| 47 | + |
| 48 | + |
| 49 | +## Specification |
| 50 | + |
| 51 | +This RFC proposes including native commands in the error handling |
| 52 | +framework, by allowing an error to be reported to the error stream |
| 53 | +when a native command exits with a non-zero exit code, similar to |
| 54 | +the `set -e` option in bourne type shells. |
| 55 | + |
| 56 | +The `$PSIncludeNativeCommandInErrorActionPreference` preference |
| 57 | +variable should govern treatment of non-zero exit codes on native |
| 58 | +commands. Possible values are: |
| 59 | +- `$false`: (the default) ignore non-zero exit codes. |
| 60 | +The effect is the same as existing Powershell treatment of this case. |
| 61 | +- `$true`: report an error for non-zero exit codes on a native command. |
| 62 | + |
| 63 | +The reported error record should be created with the following details: |
| 64 | +- exception: `ExitException`, with the exit code of the failed command. |
| 65 | +- error id: `"Program {0} failed with exit code {1}"`, with the command |
| 66 | +name and the exit code, from resource string `ProgramFailedToComplete`. |
| 67 | +- error category: `ErrorCategory.NotSpecified`. |
| 68 | +- object: the (boxed) exit code. |
| 69 | + |
| 70 | +The existing `$ErrorActionPreference` variable should govern how native |
| 71 | +command errors are handled. The `$ErrorActionPreference` value `"stop"` |
| 72 | +will treat such errors as terminating errors, allowing the error to |
| 73 | +terminate the session, or be caught as an exception. |
| 74 | + |
| 75 | +Alternative approaches outlined below try to address limitations in the |
| 76 | +approach taken with this base specification. |
| 77 | + |
| 78 | +## Alternate Approaches and Considerations |
| 79 | + |
| 80 | +### Control native command error management with ActionPreference value |
| 81 | + |
| 82 | +Another preference option could be added to `$ErrorActionPreference`. |
| 83 | + |
| 84 | +With this approach, an `$ErrorActionPreference` value of |
| 85 | +`"StopIncludingNativeCommand"` would cause an exception to be thrown |
| 86 | +for non-zero exit codes on native commands, as well as any error in |
| 87 | +cmdlets. |
| 88 | + |
| 89 | +This approach is limited to managing errors in native commands when |
| 90 | +this option is set, so integrates less well with overall Powershell |
| 91 | +error handling features. For example, where the preference is "Inquire", |
| 92 | +a non-zero exit code on a native command would not generate an inquiry. |
| 93 | + |
| 94 | +### Treat non-zero exit codes as errors only on untested native commands |
| 95 | + |
| 96 | +A similar approach that ignores the exit code in native commands where |
| 97 | +the output is tested should give useful results. |
| 98 | + |
| 99 | +The `$PSManageNativeCommandErrors` preference variable would govern |
| 100 | +treatment of non-zero exit codes for "untested" native commands. |
| 101 | +Possible values would be: |
| 102 | +- `$false`: (the default) value would ignore any non-zero exit codes. |
| 103 | +- `$true`: would report and error for non-zero exit codes on an |
| 104 | +untested native command. |
| 105 | +Commands would be considered "tested" if used in the lexical scope of |
| 106 | +an `if` or loop condition, the operand of a logical operator |
| 107 | +(`!`,`||` or `&&`). |
| 108 | + |
| 109 | +This should improve on the basic specification by allowing the idiom to |
| 110 | +be combined with normal control flow statement usage. |
| 111 | + |
| 112 | +A common idiom with native commands is to return success or failure in |
| 113 | +the exit code rather than the output stream. Constructs such as if and |
| 114 | +while test the exit code rather than the command output in Bourne type |
| 115 | +shells, so treating non-zero exit code as an exception in such contexts |
| 116 | +would not make sense. Thus `set -e` is applied to "untested" commands |
| 117 | +only, where a "tested" command is any command in the scope of an `if` |
| 118 | +or `while` condition, a logical operator (`!`,`||` or `&&`) or any |
| 119 | +command before the last in a pipeline (see output of pipeline below). |
| 120 | + |
| 121 | +Although PowerShell `if` and loop conditions check the output of the |
| 122 | +command rather than checking the exit code, a native command may be |
| 123 | +able to accept arguments that produce suitable output only on success, |
| 124 | +but still indicate the failure reason using the exit code. Where the |
| 125 | +native command does not provide such an option, it is probably not |
| 126 | +useful to test it's output in an `if` or loop condition. |
| 127 | + |
| 128 | +Note: with bourne type shells, "tested" commands are those in the |
| 129 | +dynamic scope of a tested context rather than the lexical scope of the |
| 130 | +context. This limits the usefulness of `set -e`, but is needed for |
| 131 | +[historic compatibility](http://austingroupbugs.net/view.php?id=52) |
| 132 | +reasons. |
| 133 | + |
| 134 | +### Treat errors as exceptions only on untested commands |
| 135 | + |
| 136 | +This approach varies the basic specification so that errors in all |
| 137 | +commands can be treated exceptions where the command result is not |
| 138 | +tested. |
| 139 | + |
| 140 | +As well as the new boolean preference to enable reporting errors for |
| 141 | +non-zero native command exit codes, a new preference value option would |
| 142 | +be added for `$ErrorActionPreference`. |
| 143 | + |
| 144 | +A `"StopOnDiscarded"` value for `$ErrorActionPreference` would have the |
| 145 | +effect of ignoring non-terminating command errors in a context where |
| 146 | +the result is tested, and throwing an exception in other contexts. |
| 147 | + |
| 148 | +This should improve on the basic specification by allowing the idiom to |
| 149 | +be combined with normal control flow statement usage and applied to |
| 150 | +cmdlets also. |
| 151 | + |
| 152 | +Treating non-zero exit codes on native commands as errors only in |
| 153 | +untested contexts as with earlier listed approaches gives inconsitent |
| 154 | +error handling between native commands and cmdlets. The parameter |
| 155 | +`-ErrorAction SilentlyContinue` would need to be given to a cmdlet to |
| 156 | +prevent an exception being thrown where `$ErrorActionPreference` is |
| 157 | +`"stop"` and a non-terminating error should not cause an exception |
| 158 | +because the output is being tested. |
| 159 | + |
| 160 | +### Allow output of pipeline to be treated as tested |
| 161 | + |
| 162 | +This approach varies the above approaches to "untested" commands so |
| 163 | +that the output of a pipeline can be treated as tested. |
| 164 | + |
| 165 | +A `$PSIncludeNativeCommandInErrorActionPreference` variable with three |
| 166 | +possible values would be used instead of a boolean variable. |
| 167 | +Possible values would be: |
| 168 | +`"none"`:(default) do not report an error on a non-zero exit code |
| 169 | +`"command"`: report an error on a non-zero exit code after a |
| 170 | +native command in an untested pipeline. |
| 171 | +`"pipeline"`: report an error on a non-zero exit code after an |
| 172 | +native command which is the last command in an untested pipeline. |
| 173 | + |
| 174 | +This should add some flexibility for handling errors in multiple |
| 175 | +command pipelines to deal with different expectations. |
| 176 | + |
| 177 | +In Powershell, the status of a pipeline is false if any command in the |
| 178 | +pipeline fails. Thus treating a command that outputs to a pipeline as |
| 179 | +"untested" is arguably be closer to this existing Powershell idiom. |
| 180 | +Korn compatible shells like bash provide similar behaviour with |
| 181 | +`set -o pipefail`. This makes the exit code of the pipeline zero where |
| 182 | +all exit codes were zero, or otherwise the exit code of the last |
| 183 | +command with a non-zero exit code. |
| 184 | + |
| 185 | +However this might not be the expected behaviour in all circumstances, |
| 186 | +as `set -o pipefail` is not enabled on bourne type shells, and commands |
| 187 | +that output into a pipe might sometimes be considered to have their |
| 188 | +success or failure "tested" for error handling purposes. |
| 189 | + |
| 190 | +### Use dynamic scope/Set-StrictMode |
| 191 | + |
| 192 | +This approach implements dynamic scoping for native command error |
| 193 | +management using a cmdlet. |
| 194 | + |
| 195 | +One of the approaches above would be enabled with |
| 196 | +`Set-StrictMode -version 6`, instead of a boolean preference variable. |
| 197 | +For example, |
| 198 | +- An error would be eported for native commands with a non-zero exit |
| 199 | +code. |
| 200 | +- An exception would be thrown for "untested" native commands with a |
| 201 | +non-zero exit code. |
| 202 | + |
| 203 | +The dynamic scoping approach would improve on earlier listed approaches |
| 204 | +by limiting the scope of error handling configuration so that script |
| 205 | +functions could not have an effect on the error handling mode in the |
| 206 | +calling scope. |
| 207 | + |
| 208 | +There might be difficulties using `Set-StrictMode` for this. Other |
| 209 | +strict mode checks tend to identify coding errors based on the internal |
| 210 | +Powershell context, not than the wider OS native environment. Use of |
| 211 | +the latest strict mode with existing scripts that already implement |
| 212 | +robust error handling on native commands would be more difficult, as |
| 213 | +normal control flow statements involving native commands would have to |
| 214 | +be restuctured into `try`/`catch` statements. |
| 215 | + |
| 216 | +The dynamic scoping approach would have side-effects on called scripts. |
| 217 | + |
| 218 | +### Use lexical scope/Exception handling extensions |
| 219 | + |
| 220 | +With the lexical scoping approach, native command error handling mode |
| 221 | +would be used through language syntax instead of preference variables |
| 222 | +or cmdlets. A possible syntax might be to add parameters to the try |
| 223 | +statement. |
| 224 | + |
| 225 | +For example: |
| 226 | +- `WithNativeCommandExitError` parameter, to report an error where a |
| 227 | +native command exits with a non-zero exit code |
| 228 | +- `WithIgnoredErrorException` parameter, to throw an exception for |
| 229 | +reported command errors where the result of the command is not tested. |
| 230 | + |
| 231 | +Lexical scoping of native command error handling would improve on |
| 232 | +earlier listed approaches in a number of ways. Native command error |
| 233 | +handling would sit alongside overall exception handling. There would be |
| 234 | +no side-effects in calling or in called scripts. It could also minimize |
| 235 | +runtime overhead as the facility should be mostly handled by the parser |
| 236 | +and compiler. |
0 commit comments