2
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?

pico
  • 1,660
  • 4
  • 22
  • 52
  • Try ```function test1 { write-host "hello";}``` – adv12 Dec 21 '21 at 14:24
  • 2
    What problem are you trying to solve? – Paolo Dec 21 '21 at 14:24
  • $? Contains the execution status of the last command. There is no problem here actually. If you want to see it false, you can try something like `gci some/random/path/` , then you can type `$?`, you will encounter it as false – Ranadip Dutta Dec 21 '21 at 14:25
  • You could also use `Write-Verbose` to have your command normally run silently, but display some useful output if you need by adding `-Verbose`. – FoxDeploy Dec 21 '21 at 14:32

2 Answers2

3

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 $?.

FoxDeploy
  • 12,569
  • 2
  • 33
  • 48
  • 1
    that's interesting... i didn't realize the interactive shell was trapping an exception to set $?. I think this proves once again that "return" should be avoided in powershell scripts because it prints crap to your terminal that you don't want to see... the only way i've found to return values from a function is to pass in a hash and set a return value as a hash element. then it nicely doesn't print the return value. – pico Dec 21 '21 at 14:40
  • @pico or you could return the thing that you would otherwise write out--in the above case, 'hello' – adv12 Dec 21 '21 at 14:43
  • Pico, thats a perfectly valid use of functions, passing them in to methods and toggling their values. It's the way to dotnet passing variables as references (which results in actually and permanently changing the input object) – FoxDeploy Dec 21 '21 at 14:53
  • While it is true that a `throw` statement results in `$?` reflecting `$false` _if the runspace stays alive_, it is important to note that `throw` generates a _script_-terminating (runspace-terminating) error, so that if you call a function that uses `throw` _from inside a script_, you must use `try` / `catch` to prevent the script's instant termination, and only then can you query `$?` and only at the very start of the `catch` block. – mklement0 Dec 22 '21 at 22:01
2

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 with Write-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.
  • By contrast, in POSIX-compatible shells such as bash, return is the function-level analog to exit 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.
  • 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 an exit statement.

      • The most recently reported exit code, which is also reflected in the automatic $LASTEXITCODE variable, maps to $? reporting $true, if it is 0, and $false for any nonzero value.
    • 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; see this answer) $PSCmdlet.ThrowTerminatingError() (for a statement-terminating error) methods:

        • Write-Error (for non-terminating errors), unfortunately, does not affect $? - see GitHub issue #3629

        • While 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, with try / catch, and $? must then be queried at the start of the catch 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.
mklement0
  • 382,024
  • 64
  • 607
  • 775
  • Love the note about why `Write-Error` doesn't affect `$?`. That still feels wrong to me, IMHO – FoxDeploy Dec 23 '21 at 03:05
  • 1
    Thanks, @FoxDeploy - I agree that it is wrong. The issue is discussed in [GitHub issue #3629](https://github.com/PowerShell/PowerShell/issues/3629) (I've updated the answer to link to it). – mklement0 Dec 23 '21 at 03:09