How, exactly, does PowerShell decide whether .ThrowTerminatingError() terminates only the statement? #6098
Copy link
Copy link
Closed
Labels
Issue-Questionideally support can be provided via other mechanisms, but sometimes folks do open an issue to get aideally support can be provided via other mechanisms, but sometimes folks do open an issue to get aResolution-AnsweredThe question is answered.The question is answered.
Description
Consider the following code:
function a {
param ( [Parameter(ValueFromPipeline)]$x )
process {
$PSCmdlet.ThrowTerminatingError(
[System.Management.Automation.ErrorRecord]::new(
'exception message',
'errorId',
[System.Management.Automation.ErrorCategory]::InvalidOperation,
$null
)
)
}
}Invoking
a
Write-Host 'statement after'outputs
a : exception message
At C:\test1.ps1:15 char:1
+ a
+ ~
+ CategoryInfo : InvalidOperation: (:) [a], Exception
+ FullyQualifiedErrorId : errorId,a
statement after
which seems to be consistent with .ThrowTerminatingError() resulting in a "statement-terminating error".
On the other hand, invoking
try
{
a
Write-Host 'statement after'
}
catch
{
Write-Host 'catch'
}
outputs catch which indicates that, in this case, .ThrowTerminatingError() terminates more than just the statement.
What is happening with flow of control in the code with the try{} block? Does PowerShell search the whole call stack for a try{}catch{}? Are there circumstances aside from a wrapping try{} that results in .ThrowTerminatingError terminating more than just the statement?
FWIW, this arose trying to understand MicrosoftDocs/PowerShell-Docs#1583.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
Issue-Questionideally support can be provided via other mechanisms, but sometimes folks do open an issue to get aideally support can be provided via other mechanisms, but sometimes folks do open an issue to get aResolution-AnsweredThe question is answered.The question is answered.
Activity
iSazonov commentedon Feb 3, 2018
I believe we terminating pipeline #2860 (comment)
alx9r commentedon Feb 3, 2018
If it were that simple, then the output of
would be
statement afternotcatch. The output iscatchindicating that more happened with flow of control than merely terminating the pipeline established by1 | a.mklement0 commentedon Feb 25, 2018
You should view this from the perspective of the
try/catchstatement:If a terminating error occurs - whether script- or statement-terminating - the entire
tryblock is exited at the point the error occurs and execution resumes in thecatchblock.(A nonterminating error, by contrast, is not caught.)
Except for the terminating-vs.-nonterminating error distinction, this is consistent with the usual
try/catchsemantics in, say, C#.alx9r commentedon Feb 25, 2018
Michael Klement (@mklement0)
Just to make sure we're on the same page, the situation is at least this complicated:
Write-Errorthrowcatch{}$PSCmdlet.ThrowTerminatingErrorcatch{}It seems to me that
$PSCmdlet.ThrowTerminating()is neither consistently "non-terminating" nor consistently "terminating".I'm not proficient at C#. Are there C# statements that continue to the next statement if there is no surrounding
try{}block but jump tocatch{}otherwise? That's what$PSCmdlet.ThrowTerminating()seems to do. I would consider PowerShellthrow's behavior consistent with usual try/catch semantics, but not$PSCmdlet.ThrowTerminating().This much is evident from the repro in my OP. But it doesn't really answer these questions from my OP:
Does PowerShell search the whole call stack for a try{}catch{}? Are there circumstances aside from a wrapping try{} that results in .ThrowTerminatingError terminating more than just the statement?
mklement0 commentedon Feb 26, 2018
So far, I don't see anything that contradicts my explanation.
Nothing is - by design - consistently terminating vs. nonterminating in PowerShell:
The point of the
$ErrorActionPreferencepreference variable /-ErrorActionparameter is to control the "terminatingness" of the behavior as needed (albeit in an inconsistent manner, and inconsistent with the documentation, as discussed in the linked docs issue).From what I can tell, yes.
Try this simple example (all scripts are assumed to reside in the current location):
Script
t2.ps1:Script
t1.ps1:Running
./t1.psyields:That is, the statement-terminating error inside
./t2.ps1aborted both thet2.ps- due to thetry/catchon the call stack - and, bubbling up, the thetryblock, ultimately executing only thecatchblock int1.ps1.Not that I'm aware of, but do let us know if you find such cases.
alx9r commentedon Feb 26, 2018
This might be down to semantics, but
throwconsistently terminates in my experience.The trouble with that approach to answering this question is that it proves what happens in that particular set of circumstances and not much else. I can get a more applicable version of that kind of information from unit testing commands that use
$PSCmdlet.ThrowTerminatingError(). But that information doesn't tell me where to expect surprises the way an analytical answer often does. It seems like an analytical answer to this question is possible.mklement0 commentedon Feb 26, 2018
Except if you use
try/catch(so, yes, by defaultthrowis runspace-terminating).Yes,
throwis the "apex error" (what I call a script-terminating error / runspace-terminating error) - it directly throws a by default uncaught exception that terminates the runspace.You can promote other types of errors (nonterminating / statement-terminating) to "apex errors" via
$ErrorActionPreference = 'Stop'/-ErrorAction Stop.In the end, you can catch them with
try/catch, anywhere up the call stack.try/catchwill catch them as-is (just as it catches script-terminating errors, but unlike nonterminating errors).If you don't, they terminate the runspace.
If you find a scenario that doesn't fit this model (which is based on my experiments), let us know.
I don't know what you mean.
alx9r commentedon Feb 26, 2018
You model seems to fit. I think you almost have me convinced. :)
Thanks for your help Michael Klement (@mklement0)!
alx9r commentedon Mar 14, 2018
BrucePay wrote in PowerShell/PowerShell#6286(comment):
mklement0 commentedon Mar 15, 2018
(Replacing my earlier comment; got confused)
To think of this in terms of exceptions is a bit confusing, because the use of exceptions is an implementation detail, given that PowerShell's native "error currency" is error records (
[System.Management.Automation.ErrorRecord]instances).Both
trapandtry/catchallow you to intercept statement-terminating errors and script-terminating errors - which in the presence of these constructs are ignored by default, unless you take explicit action : to truly generate a script-terminating error (unhandled exception), you must, in atrapblock, explicitly executebreak, and, in acatchblock, explicitly executeThrow.By contrast, using
$ErrorActionPreference = 'Stop'is the only way to directly promote both nonterminating and statement-terminating errors directly to script-terminating errors (unhandled exceptions) (the equivalent of usingThrow).While you can also catch those with
trapandtry/catch, in their absence such promoted errors will terminate the script.$ErrorActionPreference = 'Stop'doesn't just promote nonterminating errors to statement-terminating errors, it promotes both types to script-terminating errors.