Reputation: 42033
This is either a bug report prelude or a question about the design logic and usefulness. And in any case, a question on how to develop cmdlets with true terminating errors.
In a clean session, i.e. with $ErrorActionPreference = 'Continue'
and without
try
or trap
, invoke any of the following commands (or even all together):
# script module, missing path
Save-Module InvokeBuild -Path zzz -ErrorAction Stop; 'Continued!'
# cmdlet, invalid data
ConvertFrom-Json zzz -ErrorAction Stop; 'Continued!'
# cmdlet, invalid command syntax
Get-Module -Name zzz -FullyQualifiedName zzz -ErrorAction Stop; 'Continued!'
Export-Csv -Path zzz -LiteralPath zzz -InputObject 1 -ErrorAction Stop; 'Continued!'
As a result, 'Continued!'
is invoked after each command that uses ThrowTerminatingError()
.
It turns out this method terminates the command itself but not the calling script.
These are just a few contrived examples, there are many others. Examples are not real but the issue bites for real sometimes.
Note that -ErrorAction Stop
does not help, it is not designed for such errors.
It affects non-terminating errors (WriteError()
, Write-Error
).
And the practical question: is there a way to emit a terminating error which is terminating for the current command and the calling script?
It looks like in scripts this will do (in some cases, not all):
Write-Error -ErrorAction Stop "message"
Note that throw "message"
is really terminating.
But there is important difference from Write-Error
and ThrowTerminatingError()
.
Its error location points to the line of throw
. This is not always as useful as
the line with the failing command invocation, especially on syntax or
input issues, i.e. terminating issues by nature.
So scripts have something. But what can cmdlets do?
Available WriteError()
is not terminating.
Raw throw
is not much different from ThrowTerminatingError()
.
UPDATE
Indeed, $ErrorActionPreference = 'Stop'
makes ThrowTerminatingError()
terminating in the calling script. So do try
and trap
.
This is not what I am looking for. I specifically noted $ErrorActionPreference
= 'Continue'
, the default. I am looking for absolute terminating errors
regardless if the default settings. Compare, in scripts throw
is always
terminating (though it is not perfect, as mentioned). I am looking for the
analogue in .NET cmdlets.
Users forget to set $ErrorActionPreference = 'Stop'
. I forget this sometimes.
And some cases like invalid commands should stop the calling script regardless
of settings. At least command authors should be able to choose this option.
Upvotes: 1
Views: 1400
Reputation: 22122
You can throw the same exception as throw
statement in PowerShell throw:
Add-Type -TypeDefinition @‘
using System;
using System.Management.Automation;
[Cmdlet(VerbsDiagnostic.Test, "Throw")]
public class TestThrow : Cmdlet {
protected override void ProcessRecord() {
throw new RuntimeException("Message") { WasThrownFromThrowStatement=true };
}
}
’@ -PassThru|Select-Object -First 1 -ExpandProperty Assembly|Import-Module
Test-Throw; 'Not printed'
In scripts it can be done in this way (requires calling internal method):
function Test-ScriptThrow {
$ErrorRecordType = [System.Management.Automation.ErrorRecord]
$ErrorRecord = $ErrorRecordType::new([Exception]::new("Message"), 'ErrorId', 'NotSpecified', $null)
$ErrorRecordType.InvokeMember('SetInvocationInfo', 'Instance, NonPublic, InvokeMethod', $null, $ErrorRecord, $MyInvocation)
throw $ErrorRecord
}
Test-ScriptThrow; 'Not printed'
It seems that same thing is necessary for C# cmdlet:
Add-Type -TypeDefinition @‘
using System;
using System.Reflection;
using System.Management.Automation;
[Cmdlet(VerbsDiagnostic.Test, "Throw2")]
public class TestThrow2 : PSCmdlet {
protected override void ProcessRecord() {
ErrorRecord errorRecord = new ErrorRecord(new Exception("Message"), "ErrorId", ErrorCategory.NotSpecified, null);
typeof(ErrorRecord).InvokeMember("SetInvocationInfo", BindingFlags.Instance|BindingFlags.NonPublic|BindingFlags.InvokeMethod, null, errorRecord, new[] { MyInvocation });
throw new RuntimeException(null, null, errorRecord) { WasThrownFromThrowStatement=true };
}
}
’@ -PassThru|Select-Object -First 1 -ExpandProperty Assembly|Import-Module
Test-Throw2 | % { Some other command }; 'Not printed'
Otherwise it underline whole statement as error, not only one command:
Test-Throw | % { Some other command }; 'Not printed'
Upvotes: 2
Reputation: 174555
As a result, 'Continued!' is invoked after each command that uses
ThrowTerminatingError()
. It turns out this method terminates the command itself but not the calling script.
Of course not - your $ErrorActionPreference
is set to Continue
- so when a cmdlet invocation throws an error, the repl (or script) simply resumes (continues) execution.
$ErrorActionPreference
is local to the scope in which it is defined, so you can set it inside a function or script to override the user's preference (but only within the scope of the function/script):
[CmdletBinding()]
param()
$ErrorActionPreference = 'Stop'
# script module, missing path
Save-Module InvokeBuild -Path zzz -ErrorAction Stop; 'Continued!'
# cmdlet, invalid data
ConvertFrom-Json zzz -ErrorAction Stop; 'Continued!'
# cmdlet, invalid command syntax
Get-Module -Name zzz -FullyQualifiedName zzz -ErrorAction Stop; 'Continued!'
Export-Csv -Path zzz -LiteralPath zzz -InputObject 1 -ErrorAction Stop; 'Continued!'
It'll return immediately after the error from Save-Module
(even if $ErrorActionPreference
is set to Continue
in the calling scope)
Upvotes: 0