ChatGPT解决这个技术问题 Extra ChatGPT

When should I use Write-Error vs. Throw? Terminating vs. non-terminating errors

Looking at a Get-WebFile script over on PoshCode, http://poshcode.org/3226, I noticed this strange-to-me contraption:

$URL_Format_Error = [string]"..."
Write-Error $URL_Format_Error
return

What is the reason for this as opposed to the following?

$URL_Format_Error = [string]"..."
Throw $URL_Format_Error

Or even better:

$URL_Format_Error = New-Object System.FormatException "..."
Throw $URL_Format_Error

As I understand, you should use Write-Error for non-terminating errors, and Throw for terminating errors, so it seems to me that you should not use Write-Error followed by Return. Is there a difference?

What do you mean? If Write_error allows the script to continue it is very understandable to have a return statement after Write-Error. The error has been written out and you return back to the code that called the function in the first place. Since Throw is for terminating errors it will terminate automatically so that a return statement in a throw declaration is useless
@Gisli: It's important to note that return does not return to the caller in the process block of an (advanced) function; instead, it proceeds to the next input object in the pipeline. Indeed, this is the typical scenario for generating non-terminating errors: if processing further input objects is still possible.
Note that Throw generates a script-terminating error, which is not the same as the statement-terminating errors triggered, for instance, by Get-Item -NoSuchParameter or 1 / 0.

P
Peter Mortensen

Write-Error should be used if you want to inform the user of a non-critical error. By default all it does is print an error message in red text on the console. It does not stop a pipeline or a loop from continuing. Throw on the other hand produces what is called a terminating error. If you use throw, the pipeline and/or current loop will be terminated. In fact all execution will be terminated unless you use a trap or a try/catch structure to handle the terminating error.

There is one thing to note, if you set $ErrorActionPreference to "Stop" and use Write-Error it will produce a terminating error.

In the script you linked to we find this:

if ($url.Contains("http")) {
       $request = [System.Net.HttpWebRequest]::Create($url)
}
else {
       $URL_Format_Error = [string]"Connection protocol not specified. Recommended action: Try again using protocol (for example 'http://" + $url + "') instead. Function aborting..."
       Write-Error $URL_Format_Error
    return
   }

It looks like the author of that function wanted to stop the execution of that function and display an error message on screen but did not want the entire script to stop executing. The script author could have used throw however it would mean you would have to use a try/catch when calling the function.

return will exit the current scope which can be a function, script, or script block. This is best illustrated with code:

# A foreach loop.
foreach ( $i in  (1..10) ) { Write-Host $i ; if ($i -eq 5) { return } }

# A for loop.
for ($i = 1; $i -le 10; $i++) { Write-Host $i ; if ($i -eq 5) { return } }

Output for both:

1
2
3
4
5

One gotcha here is using return with ForEach-Object. It will not break processing like one might expect.

More information:

$ErrorActionPreference: about_Preference_Variables

try/catch: about_Try_Catch_Finally

trap: about_Trap

throw: about_Throw

return: about_Return


Ok, so Throw will stop everything, Write-Error + return will stop only the current function.
@BillBarry I updated my answer a bit with an explanation of return.
What about Write-Error followed by exit(1) to ensure appropriate error code returned to OS? Is that ever appropriate?
m
mklement0

Important: There are 2 types of terminating errors, which the current help topics unfortunately conflate:

statement-terminating errors, as reported by cmdlets in certain non-recoverable situations and by expressions in which a .NET exception / a PS runtime error occurs; only the statement is terminated, and script execution continues by default.

script-terminating errors (more accurately: runspace-terminating), as either triggered by Throw or by escalating one of the other error types via the common -ErrorAction parameter, -ErrorAction Stop, or via the $ErrorActionPreference preference variable, $ErrorActionPreference = 'Stop'. Unless caught, they terminate the current runspace (thread); that is, they terminate not just the current script, but all its callers too, if applicable).

For a comprehensive overview of PowerShell's error handling, see GitHub documentation issue #1583.

The remainder of this post focuses on non-terminating vs. statement-terminating errors.

To complement the existing helpful answers with a focus on the core of the question: How do you choose whether to report a statement-terminating or non-terminating error?

Cmdlet Error Reporting contains helpful guidelines; let me attempt a pragmatic summary:

The general idea behind non-terminating errors is to allow "fault-tolerant" processing of large input sets: failure to process a subset of the input objects should not (by default) abort the - potentially long-running - process as a whole, allowing you to inspect the errors and reprocess only the failed objects later - as reported via the error records collected in automatic variable $Error.

Report a NON-TERMINATING error, if your cmdlet / advanced function: accepts MULTIPLE input objects, via the pipeline input and/or array-valued parameters, AND errors occur for SPECIFIC input objects, AND these errors DO NOT PREVENT PROCESSING of FURTHER input objects IN PRINCIPLE (situationally, there may be no input objects left and/or previous input objects may already have been processed successfully). In advanced functions, use $PSCmdlet.WriteError() to report a non-terminating error (Write-Error, unfortunately, doesn't cause $? to be set to $False in the caller's scope - see GitHub issue #3629). Handling a non-terminating error: $? tells you whether the most recent command reported at least one non-terminating error. Thus, $? being $False can either mean that any (nonempty) subset of input objects weren't properly processed, possibly the entire set. Preference variable $ErrorActionPreference and/or common cmdlet parameter -ErrorAction can modify the behavior of non-terminating errors (only) in terms of error output behavior and whether non-terminating errors should be escalated to script-terminating ones.

accepts MULTIPLE input objects, via the pipeline input and/or array-valued parameters, AND

errors occur for SPECIFIC input objects, AND

these errors DO NOT PREVENT PROCESSING of FURTHER input objects IN PRINCIPLE (situationally, there may be no input objects left and/or previous input objects may already have been processed successfully). In advanced functions, use $PSCmdlet.WriteError() to report a non-terminating error (Write-Error, unfortunately, doesn't cause $? to be set to $False in the caller's scope - see GitHub issue #3629). Handling a non-terminating error: $? tells you whether the most recent command reported at least one non-terminating error. Thus, $? being $False can either mean that any (nonempty) subset of input objects weren't properly processed, possibly the entire set. Preference variable $ErrorActionPreference and/or common cmdlet parameter -ErrorAction can modify the behavior of non-terminating errors (only) in terms of error output behavior and whether non-terminating errors should be escalated to script-terminating ones.

In advanced functions, use $PSCmdlet.WriteError() to report a non-terminating error (Write-Error, unfortunately, doesn't cause $? to be set to $False in the caller's scope - see GitHub issue #3629).

Handling a non-terminating error: $? tells you whether the most recent command reported at least one non-terminating error. Thus, $? being $False can either mean that any (nonempty) subset of input objects weren't properly processed, possibly the entire set. Preference variable $ErrorActionPreference and/or common cmdlet parameter -ErrorAction can modify the behavior of non-terminating errors (only) in terms of error output behavior and whether non-terminating errors should be escalated to script-terminating ones.

Thus, $? being $False can either mean that any (nonempty) subset of input objects weren't properly processed, possibly the entire set.

Preference variable $ErrorActionPreference and/or common cmdlet parameter -ErrorAction can modify the behavior of non-terminating errors (only) in terms of error output behavior and whether non-terminating errors should be escalated to script-terminating ones.

Report a STATEMENT-TERMINATING error in all other cases. Notably, if an error occurs in a cmdlet / advanced function that only accepts a SINGLE or NO input object and outputs NO or a SINGLE output object or takes parameter input only and the parameter values given prevent meaningful operation. In advanced functions, you must use $PSCmdlet.ThrowTerminatingError() in order to generate a statement-terminating error. Note that, by contrast, the Throw keyword generates a script-terminating error that aborts the entire script (technically: the current runspace (thread)). Handling a statement-terminating error: A try/catch handler or trap statement may be used (which cannot be used with non-terminating errors), but note that even statement-terminating errors by default do not prevent the rest of the script from running. As with non-terminating errors, $? reflects $False if the previous statement triggered a statement-terminating error.

Notably, if an error occurs in a cmdlet / advanced function that only accepts a SINGLE or NO input object and outputs NO or a SINGLE output object or takes parameter input only and the parameter values given prevent meaningful operation. In advanced functions, you must use $PSCmdlet.ThrowTerminatingError() in order to generate a statement-terminating error. Note that, by contrast, the Throw keyword generates a script-terminating error that aborts the entire script (technically: the current runspace (thread)). Handling a statement-terminating error: A try/catch handler or trap statement may be used (which cannot be used with non-terminating errors), but note that even statement-terminating errors by default do not prevent the rest of the script from running. As with non-terminating errors, $? reflects $False if the previous statement triggered a statement-terminating error.

In advanced functions, you must use $PSCmdlet.ThrowTerminatingError() in order to generate a statement-terminating error.

Note that, by contrast, the Throw keyword generates a script-terminating error that aborts the entire script (technically: the current runspace (thread)).

Handling a statement-terminating error: A try/catch handler or trap statement may be used (which cannot be used with non-terminating errors), but note that even statement-terminating errors by default do not prevent the rest of the script from running. As with non-terminating errors, $? reflects $False if the previous statement triggered a statement-terminating error.

Not all of PowerShell's own core cmdlets play by these rules:

While unlikely to fail, New-TemporaryFile (PSv5+) would report a non-terminating error if it failed, despite not accepting pipeline input and only producing one output object - this has been corrected as of at least PowerShell [Core] 7.0, however: see GitHub issue #4634.

Resume-Job 's help claims that passing an unsupported job type (such as a job created with Start-Job, which is not supported, because Resume-Job only applies to workflow jobs) causes a terminating error, but that's not true as of PSv5.1.


E
Enrico Campidoglio

The main difference between the Write-Error cmdlet and the throw keyword in PowerShell is that the former simply prints some text to the standard error stream (stderr), while the latter actually terminates the processing of the running command or function, which is then handled by PowerShell by sending out information about the error to the console.

You can observe the different behavior of the two in the examples you provided:

$URL_Format_Error = [string]"..."
Write-Error $URL_Format_Error
return

In this example the return keyword has been added to explicitly stop the execution of the script after the error message has been sent out to the console. In the second example, on the other hand, the return keyword is not necessary since termination is implicitly done by throw:

$URL_Format_Error = New-Object System.FormatException "..."
Throw $URL_Format_Error

if you have $ErrorActionPreference = "Stop", Write-Error will also terminate the process.
Good info, but while PowerShell's error stream is analogous to the text-based stderr stream in other shells, like all PowerShell streams it contains objects, namely [System.Management.Automation.ErrorRecord] instances, which are by default collected in the automatic $Error collection ($Error[0] contains the most recent error). Even if you just use Write-Error with a string, that string gets wrapped in a [System.Management.Automation.ErrorRecord] instance.
C
Community

Addition to Andy Arismendi's answer:

Whether Write-Error terminates the process or not depends on the $ErrorActionPreference setting.

For non-trivial scripts, $ErrorActionPreference = "Stop" is a recommended setting to fail fast.

"PowerShell’s default behaviour with respect to errors, which is to continue on error ...feels very VB6 “On Error Resume Next”-ish"

(from http://codebetter.com/jameskovacs/2010/02/25/the-exec-problem/)

However, it makes Write-Error calls terminating.

To use Write-Error as a non-terminating command regardless of other environment settings, you can use common parameter -ErrorAction with value Continue:

 Write-Error "Error Message" -ErrorAction:Continue

R
Rynant

Write-Error allows the consumer of the function to suppress the error message with -ErrorAction SilentlyContinue (alternatively -ea 0). While throw requires a try{...} catch {..}

To use a try...catch with Write-Error:

try {
    SomeFunction -ErrorAction Stop
}
catch {
    DoSomething
}

Why you need to call Write-Error at all, if you want to suppress the error message ?
y
yzorg

If your reading of the code is correct then you are correct. Terminating errors should use throw, and if you're dealing with .NET types then it is helpful to also follow .NET exception conventions.