@@ -15,7 +15,7 @@ Exception-based error handling makes it easier to write robust code
15
15
as less boilerplate code is needed to check and handle errors.
16
16
17
17
Powershell scripts using native commands would benefit from being able
18
- to error handling features like those used by cmdlets.
18
+ to use error handling features like those used by cmdlets.
19
19
20
20
## Motivation
21
21
@@ -30,207 +30,172 @@ calling a native command, using boilerplate like the following:
30
30
}
31
31
```
32
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.
33
+ The Bourne again shell provides a ` set -eo pipefail ` exit code handling mode
34
+ that acts as though this boilerplate were included in certain contexts.
35
+ Like exception- based error handling, this can exit a stack of functions,
36
+ scripts and shells. This allows robust scripts to be written with a minimal
37
+ amount of boilerplate.
38
38
39
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.
40
+ RFC proposes converting native command errors into PowerShell errors.
41
+ The Bourne shell compatible ` set -e ` mode (without ` set -o pipefail ` ) is
42
+ _ not_ supported.
42
43
43
44
The specification and alternative proposals are based on the
44
45
[ 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
-
46
+ committee review of the associated
47
+ [ pull request] ( https://github.com/PowerShell/PowerShell/pull/3523 ) , and
48
+ [ implementation plan ] ( https://github.com/PowerShell/PowerShell-RFC/pull/88#issuecomment-613653678 )
48
49
49
50
## Specification
50
51
51
52
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.
53
+ framework when the feature is enabled by adding an error to the error
54
+ stream if the exit status of a native command is false.
55
+
56
+ The ` $PSNativeCommandErrorAction ` preference variable will implement a version
57
+ of the ` $ErrorActionPreference ` variable for native commands. The value will
58
+ default to ` Ignore ` for compatibility with existing behaviour.
59
+ - For all values except ` Ignore ` , an ` ErrorRecord ` will be added to ` $Error ` that wraps
60
+ the exit code and the command executed that returned the exit code.
61
+ - Initially, only the existing values of ` $ErrorActionPreference ` will be supported.
62
+ - The set of values may be extended later to include ` MatchErrorActionPreference ` ,
63
+ which should apply the ` $ErrorActionPreference ` setting to native commands also.
64
+ - An enum converter will convert between ` $PSNativeCommandError ` and
65
+ ` $ErrorActionPreference ` values, where ` MatchErrorActionPreference ` is converted to the
66
+ current value of ` $ErrorActionPreference `
62
67
63
68
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
69
+ - exception: ` ExitCode ` , with the exit code of the failed command.
70
+ - error id: ` "Program {0} failed with unhandled exit code {1}" ` , with the command
66
71
name and the exit code, from resource string ` ProgramFailedToComplete ` .
67
72
- error category: ` ErrorCategory.NotSpecified ` .
68
73
- object: the (boxed) exit code.
69
74
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.
75
+ This does not provide the actual semantics of bash ` set -eo pipefail ` as Bourne shell
76
+ style integration with existing existing status handling syntax is not implemented.
77
+ As a result, PowerShell script logic for handling native command exit codes would need
78
+ to use either ` try ` ..` catch ` or existing exit status handling language constructs,
79
+ according to the setting of this preference variable.
77
80
78
- ## Alternate Approaches and Considerations
81
+ A useful workaround for this case may be to embed logic that runs a native command and
82
+ tests its exit status in a script block that overrides ` $ErrorActionPreference ` .
79
83
80
- ### Control native command error management with ActionPreference value
84
+ ## Alternative Approaches and Considerations
81
85
82
- Another preference option could be added to ` $ErrorActionPreference ` .
86
+ A number of tweaks to existing behaviours could be combined to give equivalent
87
+ functionality to bash ` set -eo pipefail `
83
88
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.
89
+ ### Add "strict" native command option.
88
90
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.
91
+ The ` $PSStrictNativeCommand ` preference should treat creation of an ` ErrorRecord `
92
+ for native commands in the same way as this is treated elsewhere.
93
+ Possible values are:
94
+ - ` $false ` : (the default) ignore non-zero exit codes.
95
+ This is the same as existing Powershell treatment of this case.
96
+ - ` $true ` : Populate the error stream of the native command with an ` ErrorRecord `
97
+ associated with an ` ExitException ` exception.
133
98
134
- ### Treat errors as exceptions only on untested commands
99
+ ### Modifying existing semantics to consider exit code and exit status
135
100
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.
101
+ The error will throw an exception, potentially terminating the session, in the same
102
+ situation as other non-terminating errors will do this, i.e. where
103
+ ` $ErrorActionPreference ` is set to ` "stop" ` . This would not be the desired behaviour on
104
+ commands where the script already handles a non-zero exit code, which would require
105
+ the addition of extra boilerplate to use multiple native commands in combination.
106
+ There are a number of ways that this could be made more flexible by integrating exit
107
+ code and exit status handling into the language syntax.
139
108
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 ` .
109
+ #### Convert non-terminating errors to terminating where the command output is used.
143
110
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.
111
+ This approach implements semantics equivalent to bash ` set -eo pipefail `
112
+ in the runtime layer.
147
113
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.
114
+ The ` $PSStrictPipeLine ` preference variable would govern promotion of a non-terminating
115
+ error to a terminating error on getting an object from the pipeline output stream.
116
+ Possible values would be:
117
+ - ` $false ` : (the default) an object can be collected from the pipeline output stream
118
+ regardless of the command exit value.
119
+ This is the same as existing PowerShell treatment of this case.
120
+ - ` $true ` : where the exit status of a native command is ` $false ` , trying to get an
121
+ object from its output stream will create a terminating error from the non-terminating
122
+ errors in its error stream.
123
+ Conversion to boolean would be structured to ensure that it returns ` $false ` if the
124
+ output pipeline does not contain anything without trying to get an actual value from it.
125
+
126
+ #### Sanitise semantics of treating a native command as a conditional value.
127
+
128
+ This option in combination with the above enables functionality analogous to
129
+ bash ` set -eo pipefail `
130
+
131
+ PowerShell converts the output stream to a boolean value where a native command is used
132
+ as a "condition", i.e. the ` -not ` operator, or an ` if ` , ` elseif ` or ` while ` statement.
133
+ This is not particularly useful with native commands, which would tend to produce no
134
+ output on success, at least when executed in batch as opposed to interactive mode.
135
+
136
+ The ` $PSUseNativeExitStatus ` variable would govern whether exit status is used
137
+ in determining the boolean value of a native command for a conditional context.
138
+ Possible values would be:
139
+ - ` $false ` : (the default) the boolean value of native command is defined as whether
140
+ or not the length of the output stream is non-zero.
141
+ This is the same as existing PowerShell treatment of this case.
142
+ - ` $true ` : the boolean value of a native command is it's exit status (` $? ` ), and
151
143
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.
144
+ This would allow if, while and ! to be combined with native commands in a useful way.
159
145
160
- ### Allow output of pipeline to be treated as tested
146
+ #### Add strict pipeline chain failure semantics.
161
147
162
- This approach varies the above approaches to "untested" commands so
163
- that the output of a pipeline can be treated as tested.
148
+ Treat only ignored exit statuses as exceptions.
164
149
165
- A ` $PSIncludeNativeCommandInErrorActionPreference ` variable with three
166
- possible values would be used instead of a boolean variable.
150
+ The ` $PSStrictPipeLineChain ` preference variable would govern the exit
151
+ status in the last command of a pipeline.
167
152
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.
153
+ - ` $false ` : (the default) would ignore ` $false ` exit status on the last command.
154
+ This is the same as existing PowerShell treatment of this case.
155
+ - ` $true ` : for a pipeline that is being used in a conditional context (see above),
156
+ and where the exist status of the last command in the pipeline is ` $false ` ,
157
+ would create a terminating error from the non-terminating errors in the command error
158
+ stream.
159
+
160
+ This should improve on the basic specification by allowing the idiom to be combined with
161
+ the normal usage of pipeline chains.
189
162
190
163
### Use dynamic scope/Set-StrictMode
191
164
192
- This approach implements dynamic scoping for native command error
193
- management using a cmdlet.
165
+ This approach implements dynamic scoping for native command error management using a
166
+ cmdlet.
194
167
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 reported 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.
168
+ Some of the above would be enabled with ` Set-StrictMode -version 6 `
169
+ instead of with boolean preference variables.
202
170
203
171
The dynamic scoping approach would improve on earlier listed approaches
204
172
by limiting the scope of error handling configuration so that script
205
173
functions could not have an effect on the error handling mode in the
206
174
calling scope.
207
175
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.
176
+ If ` Set-StrictMode ` is used for this, it would need to enable some combination
177
+ of enhancements that will not raise errors on scripts that have already implemented
178
+ strong native command error handling.
215
179
216
- The dynamic scoping approach would have side-effects on called scripts.
217
180
218
181
### Use lexical scope/Exception handling extensions
219
182
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.
183
+ Dynamic scoping approach would have side-effects on called scripts.
184
+ This is analogous to the behaviour of Bourne type shells, which maintain
185
+ this behaviour for [ historic compatibility] ( http://austingroupbugs.net/view.php?id=52 )
186
+ reasons.
230
187
231
188
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.
189
+ earlier options by integrating native command error handling fully into
190
+ the existing exception handling without out any side-effects in calling
191
+ or called scripts.
192
+
193
+ With this approach, native command error handling mode would be used through
194
+ language syntax instead of preference variables or cmdlets. A possible syntax might be
195
+ to add a strictness option to the try statement which applies to the lexical scope of
196
+ the try statement.
197
+
198
+ Implementing this functionality only as a lexically scoped extension could
199
+ be the preferred option because it becomes a kind of syntactic sugar which is
200
+ implemented by the parser and compiler, improving testability, and keeping complexity
201
+ out of the runtime layer.
0 commit comments