PS> gci C:\
<<here it doesn't print the value of $? as 'true' after command executes>
PS> $?
true
However when I try to recreate this behavior with my own powershell function I get:
PS> function test1 { write-host "hello"; return $true}
PS> test1
hello
true
PS> $?
true
What gives? why can't I hide the return value of the function from the pipeline, and have it only written to the $? variable?
CodePudding user response:
This is one of many automatic variables in PowerShell, you can see the full list by running:
get-help about_Automatic_Variables
The function of the $?
is to see if the last command ran to completion or encountered an uncaught error.
For instance
function Throw-WhenOdd($value){
if ($value % 2 -ne 0){
throw "OddNumber"
}
}
PS> Throw-WhenOdd 4
PS> $?
True
PS> Throw-WhenOdd 3
Exception:
Line |
3 | throw "OddNumber"
| ~~~~~~~~~~~~~~~~~
| OddNumber
PS>$?
False
PS> #Checking the value of `$?` resets the value
# so checking a second time returns true
PS> $?
true
So to summarize, if you have a function or cmdlet or whatever and it can throw, you can use $?
to see if it threw or not. However, you have to use Throw and not Write-Error and the output is consumed the first time you check $?
.
CodePudding user response:
First, to clarify:
In PowerShell,
return
- if given an argument - outputs that argument to the success output stream, i.e. it returns data - just like implicit output or output withWrite-Output
(whose use is rarely needed) does.- Also note that
Write-Host
is not designed to output data - it is designed for direct-to-host (console) output - see this answer.
- Also note that
By contrast, in POSIX-compatible shells such as
bash
,return
is the function-level analog toexit
and sets an invisible exit code.PowerShell's
$?
is an abstract error-condition indicator ($true
if no errors occurred,$false
otherwise), which is automatically set by PowerShell itself to indicate whether the most recently executed command or expression experienced an error condition - see details below.
As of PowerShell 7.2, you cannot set the automatic
$?
variable; it is set by PowerShell itself - see the bottom section for details.- The need for the ability to set
$?
directly has been recognized and green-lighted in GitHub issue #10917, but has yet to be implemented.
- The need for the ability to set
You can only set it indirectly:
Via exit codes, which only apply to external programs (process exit code) script files (
*.ps1
) if they terminate with anexit
statement.- The most recently reported exit code, which is also reflected in the automatic
$LASTEXITCODE
variable, maps to$?
reporting$true
, if it is0
, and$false
for any nonzero value.
- The most recently reported exit code, which is also reflected in the automatic
For cmdlets and functions, via output to PowerShell's error stream:
For binary cmdlets, emitting at least one non-terminating error or a statement-terminating error results in
$?
reflecting$false
As of PowerShell 7.2, (by definition written-in-PowerShell) functions only affect
$?
if they're advanced functions and use the unwieldy$PSCmdlet.WriteError()
(for a non-terminating error)$PSCmdlet.ThrowTerminatingError()
(for a statement-terminating error) methods:Write-Error
(for non-terminating errors), unfortunately, does not affect$?
- see GitHub issue #3629While a
throw
statement - as shown in FoxDeploy's answer - does end up setting$?
to$false
, it generates a script-terminating (runspace-terminating) error, which requires the caller to explicitly catch the error, withtry / catch
, and$?
must then be queried at the start of thecatch
block.
Notably, the above means:
- Getting
$?
to reflect$false
invariably requires producing error output. $?
being$false
just tells you that some error occurred - it isn't a reflection of an explicit intent to signal success vs. failure of the command as a whole.
- Getting