I've been experimenting with an improved test fixture for PowerShell commands. I am hoping to find a way to distinguish between script- and statement-terminating errors (using the terminology from PowerShell/PowerShell-Docs#1583). A (very) simplified version of the core of the test fixture looks something like this:
function TestFixture
{
param
(
[System.Management.Automation.CommandInfo]
$CommandUnderTest
# ...
)
# ...
try
{
# ...
UpstreamFixturePart $InputObject |
& $CommandUnderTest @NamedArgs @ArgumentList |
DownstreamFixture
# ...
}
catch
{
# ...
}
}
The obvious way to distinguish between a script- and statement-terminating error is to test control flow behavior when the error occurs (see #6098 for an example). The try {} around $CommandUnderTest in TestFixture is necessary to capture and record exceptions as they are thrown, and keep flow of control within the test fixture. Unfortunately, the presence of that try{} block precludes the using control flow behavior to distinguish script- from statement-terminating errors because both kinds of errors result in the same control flow (ie. both jump to the catch block).
I was hoping that .WasThrownByThrowStatement would provide enough information to distinguish between the two. Unfortunately per #6288 and #3647 (comment) that is not currently sufficient to distinguish.
Related #4837, #4781
~Dup #3158~
@iSazonov This issue is not a dup of #3158:
$Error@mklement0 If you have the time, could you take a look at this and see if you have any ideas for how to achieve this?
Hi @alx9r, There aren't really three kinds of errors as such, just three dispositions of one kind of error. For example, if a command writes an error to error output, it's non-terminating. If -ErrorAction Stop is specified, that non-terminating write becomes a statement-terminating error. If there is a trap or try/catch in scope, the statement-terminating error is thrown to the catch block. In this scenario, the same error object eventually becomes all three "types" as it flows through the system - it began as non-terminating, became terminating and then became an exception. (Note that the automatic change from statement-terminating error to exception in the presence of trap or try/catch is a fundamental semantic for PowerShell.)
@BrucePay
If -ErrorAction Stop is specified, that non-terminating write becomes a statement-terminating error.
In my experimentation Write-Error -ea Stop, for example, does not cause a statement-terminating error.
Rather, Write-Error -ea Stop seems to always throw an exception. In my experimentation the effect of -ErrorAction has been consistent with being an implementation detail of the command to which it is passed. Do you mean $ErrorActionPreference = 'Stop' here?
In this scenario, the same error object eventually becomes all three "types" as it flows through the system - it began as non-terminating, became terminating and then became an exception.
I think I understand the escalation concept you are describing. I have not, however, come across any one error source that observably escalates from non-terminating to statement-terminating to exception in the idealized way you describe. Do you have an example of a command that observably behaves the way you describe? Or are you describing an unobservable internal escalation process here?
I arrived at the following table empirically (test code here).
|ref.| Statement | In try{}? | ErrorActionPreference | Exception Thrown by statement? |
|---|-------------|---------------------------|-----------|---------------|
| 1 |throw | x | not SilentlyContinue | yes |
| 2 |throw | yes | SilentlyContinue | yes |
| 3 |throw | no | SilentlyContinue | no |
| 4 |ThrowTerminatingError() | yes | x | yes |
| 5 |ThrowTerminatingError() | no | Stop| yes |
| 6 |ThrowTerminatingError() | no | Continue | no |
| 7 |Write-Error -ea Stop | x | x | yes |
| 8 |Write-Error | x | Stop | yes |
| 9 |Write-Error | x | Continue | no |
x = does not matter
Another way to ask my original question is as follows:
Is there a way to distinguish between a caught exception thrown by throw (ie. ref. 1 and 2) and an exception thrown by ThrowTerminatingError() (ie. ref. 4)?
In yet other words, is there a way to infer from a caught exception whether that exception would have been thrown in the absence of the try{}catch{} that caught it?
@alx9r: Nice writeup; to add to the general part:
Indeed, to recap from the error-handling saga:
_Preference variable_ $ErrorActionPreference = 'Stop' makes _both_ nonterminating and statement-terminating errors throw an unhandled-by-default exception (for which I coined the term _script-terminating_ error, which I'll use in the remainder of this post).
_Common parameter_ -ErrorAction Stop, by contrast, only affects _nonterminating_ errors and promotes them _directly to script-terminating_ errors.
This directly contradicts the docs, which do not distinguish between preference-variable and common-parameter use and categorically claim that Stop only affects _nonterminating_ errors (which is only true with the _common parameter_).
You can't help but wonder if the original intent was:
to never affect statement-terminating errors with _preference variable_ $ErrorActionPreference = 'Stop', in line with the _common parameter_'s behavior and the documentation.
to have both $ErrorActionPreference = 'Stop' and -ErrorAction Stop promote _nonterminating_ errors to _statement_-terminating ones rather than to script-terminating ones.
-ErrorAction Stop has no effect on statement-terminating errors: they already _are_ statement-terminating errors; in other words: they already are in the desired target state.In other words: the original intent may have been to _never_ terminate the script _by default_ , except with the use of Throw, and to instead _require_ use of try/catch or trap in order to effect termination.
Throw documentation confusingly talks about generating a "terminating error" too, without pointing out the fundamental distinction from a _statement_-terminating error.Throw to enforce a mandatory parameter without prompting; of course, using this technique results in a _script_-terminating error, whereas cmdlets normally only generate _statement_-terminating errors, even in the face of incorrect syntax and, if the PowerShell instance was invoked with -NonInteractive, in the absence of mandatory parameters (declared without Throw).@BrucePay, can you shed light on this?
Most helpful comment
@BrucePay
In my experimentation
Write-Error -ea Stop, for example, does not cause a statement-terminating error.Rather,
Write-Error -ea Stopseems to always throw an exception. In my experimentation the effect of-ErrorActionhas been consistent with being an implementation detail of the command to which it is passed. Do you mean$ErrorActionPreference = 'Stop'here?I think I understand the escalation concept you are describing. I have not, however, come across any one error source that observably escalates from non-terminating to statement-terminating to exception in the idealized way you describe. Do you have an example of a command that observably behaves the way you describe? Or are you describing an unobservable internal escalation process here?
I arrived at the following table empirically (test code here).
|ref.| Statement | In
try{}? |ErrorActionPreference| Exception Thrown by statement? ||---|-------------|---------------------------|-----------|---------------|
| 1 |
throw| x | notSilentlyContinue| yes || 2 |
throw| yes |SilentlyContinue| yes || 3 |
throw| no |SilentlyContinue| no || 4 |
ThrowTerminatingError()| yes | x | yes || 5 |
ThrowTerminatingError()| no |Stop| yes || 6 |
ThrowTerminatingError()| no |Continue| no || 7 |
Write-Error -ea Stop| x | x | yes || 8 |
Write-Error| x |Stop| yes || 9 |
Write-Error| x |Continue| no |x = does not matter
Another way to ask my original question is as follows:
Is there a way to distinguish between a caught exception thrown by
throw(ie. ref. 1 and 2) and an exception thrown byThrowTerminatingError()(ie. ref. 4)?In yet other words, is there a way to infer from a caught exception whether that exception would have been thrown in the absence of the
try{}catch{}that caught it?