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-Hostis 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,returnis the function-level analog toexitand sets an invisible exit code.PowerShell's
$?is an abstract error-condition indicator ($trueif no errors occurred,$falseotherwise), 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 anexitstatement.- The most recently reported exit code, which is also reflected in the automatic
$LASTEXITCODEvariable, maps to$?reporting$true, if it is0, and$falsefor 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$falseAs 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
throwstatement - 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 thecatchblock.
Notably, the above means:
- Getting
$?to reflect$falseinvariably requires producing error output. $?being$falsejust 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
