-
Notifications
You must be signed in to change notification settings - Fork 7.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Respect ErrorActionPreference universally #7774
Comments
For a complete overview of PowerShell's current error handling, see MicrosoftDocs/PowerShell-Docs#1583 |
This is precisely the subject of my feature request. Everyone I have seen encounter this behavior have thought they found a bug and all used strong language when I explained the underlying behavior. This behavior is extremely counter-intuitive and worth changing in the next major release, even if it's a breaking change. |
I hear you. I pointed you to the docs issue to get the full picture. It's unclear if there will ever be a release that changes such fundamental behaviors, because the commitment to backward compatibility has been ironclad so far. For a meta-discussion, see #6745 |
Given that all cmdlets already respect ErrorActionPreference, can you elaborate on what you mean by this? Thanks. |
You're right, the
PS> & { $ErrorActionPreference = 'SilentlyContinue'; Get-Item -NoSuchParam; 'Done' }
Done # Statement-terminating error was silently ignored. By contrast, the - ostensibly equivalent - command-scoped mechanism, the # The error is still reported, because it is (statement-)terminating.
PS> & { WriteOutput -ErrorAction SilentlyContinue (Get-Item -NoSuchParam); 'Done' }
Get-Item : A parameter cannot be found that matches parameter name 'NoSuchParam'.
....
Done If I understand @chriskuech correctly, his preference is for |
I didn't say all errors, I said the preference variables applies to all cmdlets and @chriskuech was asserting that it did not. I was wondering what he meant by that. Still am. I'm not sure I understand the example you included. Is this yours or is it from the documentation? PS> & { $ErrorActionPreference = 'SilentlyContinue'; Get-Item -NoSuchParam; 'Done' }
Done # Statement-terminating error was silently ignored. There are three statement inside the scriptblock (';' separates statements). Only the statement with the statement-terminating error is terminated so of course
Very specifically PSCore (1:105) > try { throw 'Yikes' } catch { $e = $_ }
PSCore (1:106) > $e.Exception.WasThrownFromThrowStatement
True but that's used internally by the engine for exception processing.
$ErrorActionPreference doesn't apply to terminating errors (exceptions). As for applying the existing preference variable/parameter to terminating errors, that actually makes no sense. The preference variables control disposition of the error. Applying the same variable setting to two distinct error "channels" with inherently different behaviours makes no sense. |
Let me just say that the gist of what's in the OP and his follow-up comment suggest that his concerns are about:
But enough speculation - I'll let @chriskuech respond. Let's talk about terminology first:
I'll be using these terms in the rest of this post.
It is what I said, however, because it is true (though I should have be more precise: all types of runtime errors (as opposed to parse-time errors)), and it also encompasses your claim.
The example, which I've created, demonstrates that (The only reason I used
A statement-terminating error issued by a cmdlet is not the same as a script-terminating error generated with
It already does: PS> & { $ErrorActionPreference = 'SilentlyContinue'; Throw 'a hissy fit'; 'Done' }
Done As you can see, the
Again:
This inconsistency, along with the lack of distinction between statement- and script-terminating errors in the docs have caused much, much confusion over the years. |
I assumed The exact case that keeps coming up:
Further, the word "terminating" is not present anywhere in the docs for this cmdlet and I assume the same is true of other cmdlets that throw terminating errors. |
In reply to @mklement0
In other words, it is that the statement never gets executed because it's unclear how to do so, and it is not the case that the statement gets terminated. Throwing a terminating error signals a severe error condition for which the continuation of the cmdlet isn't meaningful. For example, if you try to download and parse a JSON, it makes no sense to start parsing if the downloading didn't succeed, thus the terminating error. In other words, it is used by cmdlets to control their flow in addition to reporting to the upstream commands why they didn't complete normally. It's like writing the error and calling Let me restate the OP's question in a clearer way. function x
{
[cmdletbinding()]param()
begin{
$PSCmdlet.ThrowTerminatingError(
[System.Management.Automation.ErrorRecord]::new(
[System.NotImplementedException]::new(),
'Not implemented.', 'NotSpecified', $null));
}
}
x -ea silentlycontinue; 'Done 1';
x; 'Done 2';
& { $ErrorActionPreference = 'silentlycontinue'; x -ea silentlycontinue; 'Done 3'; }
& { $ErrorActionPreference = 'silentlycontinue'; x; 'Done 4'; } It makes no sense to continue the execution of that cmdlet, but it makes sense to decide by Current behavior is equivalent to the following:
In other words, the terminating error is written to the error stream in the caller's scope, respecting the caller's
As I said, intuitively, throwing a terminating error could be interpreted as calling WriteError and then break. However, it is now keep the error, call break, go back to the caller's scope, write error. |
It doesn't, it creates a statement-terminating error, which you can verify as follows: PS> Get-Item -NoSuch -ErrorAction Ignore; 'Done'
Get-Item : A parameter cannot be found that matches parameter name 'nosuch'.
...
Done That is, PS> (Get-Item -NoSuch) + '!'
Get-Item : A parameter cannot be found that matches parameter name 'NoSuch'. That is, only the error message printed and not also No one is disputing that it makes sense to not even attempt execution if a given command invocation's arguments are formally incorrect. Thanks for the technical explanation for the current behavior, but what matters in the end is whether the behavior: (a) makes sense to users: it shouldn't surprise them to begin with and should be simple enough to remember, so that it doesn't trip them up repeatedly. To put it in @chriskuech's words again:
(b) doesn't create unnecessary work for the typical use case. It makes much more sense for a severe error condition to also be script-terminating by default. |
By saying that, I meant "the error is non-terminating for the calling scope". Since the statement never gets executed, there isn't a child scope to talk about, whence my phrasing. Could @mklement0 provide a reference to the term "statement-terminating error"? Inventing new jargons doesn't always help understanding the issue. Also, I am not sure how each participant in the thread understands the issue, so I provided my understanding. Put in a list,
The counter-intuitive behavior is due to the (arte-)fact that the writing happens in the calling scope, in which In other words, I was conveying the idea that the current description of this issue is incorrect. It is not that terminating/non-terminating errors are treated differently. It is that the terminating error's I'd like to state my position on this issue clearly (so that no one has to guess my position from the verbose replies): I find the current behavior counter-intuitive and it should be changed. The proposed change is: Throwing a terminating is performed as if an The following is a technical analysis of how one could implement the change if it is to be done so. The proposed change is to change the behavior from
to
I browsed the current implementation and did some code-path chasing. Here's what I found:
At this point, OP's wishes could be implemented by the following technical change:
int dispatchIndex = 0;
dispatchNextStmt:
try
{
switch (dispatchIndex)
{
case 0: goto L0;
case 1: goto L1;
case 2: goto L2;
}
L0: dispatchIndex = 1; stmt1;
L1: dispatchIndex = 2; stmt2;
}
catch (FlowControlException) { throw; }
catch (TerminatingErrorThrownException) { goto dispatchNextStmt; }
catch (Exception e)
{
if (!(e is TerminatingErrorThrownException))
{
ExceptionHandlingOps.CheckActionPreference(functionContext, e);
}
goto dispatchNextStmt;
}
L2: |
Follow-up: after more reading of the docs, it is unclear to me what consists of a "pipeline" (which a terminating error terminates). Does every statement count as a pipeline? Does the whole invocation (line of interactive command) count as a pipeline? The correct behavior can only be determined if we know what a pipeline is. Case 1: each statement counts as a pipeline Then the following script should produce function x { [cmdletbinding()]param()
write-error 1 -ea stop;
}
& { $ErrorActionPreference = 'Continue'; x; 2; } However, it doesn't. The written record is terminating the whole invocation. Case 2: the whole invocation counts as a pipeline This is highly undesirable, as it renders terminating error nearly unrecoverable. In this case, |
Just a quick meta note, @GeeLaw:
Please don't refer to me in the third person when I'm clearly a direct participant in a discussion - it's off-putting.
Please refrain from making such incendiary statements - they add nothing to the discussion and serve only to antagonize. |
In reply to @mklement0: My apologies if you find the comments offending. I used at because I felt tired of using "In reply to" and the jargon thing is expressing the preference to reason about the issue in PowerShell's documented terminology & ideas/views. |
Thanks, @GeeLaw.
No need for that; simply
Or, if you believe that punctuation saves lives:
I fully agree with that preference; I took issue with how you expressed that preference. (That what prompted this expression of preference was based on a misconception is secondary - see later comments.) |
@GeeLaw: Re terminology: Generally:
(There are several cases where I feel that the lack of explaining things conceptually and/or giving concepts an official name in the official documentation has hindered understanding / adoption of certain features; to name a few: member enumeration, namespace variable notation, delay-bind script blocks (the latter were given that name only very recently).) Specifically:
|
P.S., @GeeLaw: By saying (emphasis added):
you did use official terminology - but you used it to mean something else - which prompted my clarification. |
To get back on track, @GeeLaw.
|
See @KirkMunro mosaic on control flow variables: PowerShell/PowerShell-RFC#198 |
PowerShell has two types of errors: terminating and non-terminating. Non-terminating errors are handled consistently with common parameters and scoped
$ErrorActionPreference
. Terminating errors do not support this interface and instead rely on the programmer to determine that the error is non-terminating (usually after a period of confusion), and handle with separate but equivalent logic. Terminating errors are therefore only an inconvenience to advanced users and a source of significant confusion for new users.All cmdlets should respect the ErrorActionPreference
The text was updated successfully, but these errors were encountered: