4

I am writing some PowerShell scripts to do some build automation. I found here that echo $? returns true or false depending on previous statement. I just found that echo is alias for Write-Output. Write-Host $? also works. But I am still not clear how this $? works. Can someone kindly say some words bout this. Searching for echo $? on net did not give me much.

Community
  • 1
  • 1
VivekDev
  • 13,304
  • 19
  • 90
  • 150

2 Answers2

5

To complement Martin Brandl's helpful answer with more detailed information:

tl;dr

  • Automatic variable $? (see Get-Help about_Automatic Variables) contains a Boolean that reflects whether any non-terminating error occurred in the most recent statement.

    • Since $? is set after every statement, you must either check it right after the statement of interest, or save it for later inspection.
    • See below for potentially counter-intuitive behavior.
  • Automatic variable $LASTEXITCODE complements this by recording the specific exit code of the most recently executed external command-line utility (such as findstr).

    • $LASTEXITCODE complements $? in that $? reflects only abstract success or failure of the external utility - exit code 0 is mapped to $True, any nonzero exit code to $False - whereas $LASTEXITCODE contains the actual exit code.
    • Since $LASTEXITCODE is only set for external command-line utilities, its value typically remains valid longer than $?, which is set after every statement.

There are many subtleties around how $? is set, and what, precisely, its value indicates:

  • $? only reflects the occurrence of nonterminating errors, because (the much rarer) terminating errors by default terminate execution of the current command line / script, and to handle them you need to use try / catch (preferred) or trap (see the Get-Help about_Try_Catch_Finally and Get-Help about_Trap).

  • Unless explicitly ignored (with the common -ErrorAction Ignore cmdlet parameter), all non-terminating errors (and caught terminating errors) are collected in the automatic $Error collection, in reverse chronological order; that is, element $Error[0] contains the most recent error.

  • For commands to which multiple input objects were passed, $? containing $False only tells you that processing of at least one input object failed. In other words: an error could have occurred for any subset of the input objects, including all of them.

    • To determine the exact error count and the offending input objects, you must examine the $Error collection.
  • With non-remoting indirect-execution cmdlets to which you pass a target command to execute - such as Invoke-Expression, Start-Process and Start-Job and Invoke-Command without the -ComputerName parameter (which doesn't involve remoting - see below) - $? only reflects whether the target command could be invoked in principle, irrespective of whether that command then reports errors or not.

    • A simple example: Invoke-Expression '1 / 0' sets $? to $True(!), because Invoke-Expression was able to parse and invoke the expression, even though the expression itself then fails.
    • Again, inspecting the $Error collection tells you if and what errors the target command reported.
  • With remoting (invariably indirect-execution) cmdlets, notably with Invoke-Command with the -ComputerName parameter (as is typical), but also with implicitly remoting cmdlets, $? does reflect whether the target command reported any errors.

    • A simple example (must be run from an elevated console and assumes that the local machine is already set up for remoting):
      Invoke-Command -ComputerName . { 1 / 0 }, because remoting is involved, indeed sets $? to $False to reflects the failure of target command 1 / 0.
      Note that even though the local computer (.) is targeted, use of -ComputerName invariably uses remoting.

    • Note that, by design, remoting reports normally terminating errors that occur remotely as non-terminating ones, presumably so that a normally terminating error on one target machine doesn't abort processing on all others.


  • Examples of commands that DO reflect errors in $?:

    # Invoking a non-existing cmdlet or utility directly.
    NoSuchCmd
    
    # Ditto, via call operator &.
    # Note, however, that using a *script block* with & behaves differently - see below.
    & 'NoSuchCmd'
    
    # Invoking a cmdlet with invalid parameter syntax.
    Get-ChildItem -NoSuchParameter
    
    # Invoking a cmdlet with parameter values that cause a (non-terminating) runtime error.
    Get-ChildItem NoSuchFile
    
    # Invoking an external utility that reports a nonzero exit code. 
    findstr -nosuchoptions
    # The specific exit code is recorded in $LASTEXITCODE, 
    # until the next external utility is called.
    
    # Runtime exceptions
    1 / 0
    
    # A cmdlet that uses remoting:
    # (Must be run from an elevated session, and the local machine must
    # be configured for remoting first - run `winrm quickconfig`).
    # Note that remoting would NOT be involved WITHOUT the -ComputerName parameter, 
    # in which case `$?` would only reflect whether the script block could be
    # _invoked_, irrespective of whether its command(s) then fail or not.
    Invoke-Command -ComputerName . { 1 / 0 }
    
    # A .NET method that throws an exception.
    # Note: Outside of a `try/catch` handler, this is a non-terminating error.
    # Inside a `try/catch` handler, .NET exceptions are treated as terminating
    # and trigger the `catch` block.
    [System.IO.Path]::IsPathRooted('>')
    
  • Examples of commands that DO NOT reflect errors in $?:

    <#
      Non-remoting indirect execution cmdlets:
    
      $? reflects only whether the specified command could be 
      *invoked*, irrespective of whether the command itself then failed or not.
    
      In other words: $? is only $False if the specified command could not even be
      executed, such as due to invalid parameter syntax, an ill-formed target
      command, or a missing target executable. 
    
    #>
    
    # Invoking a command stored in a script block.
    & { 1 / 0 }
    
    # Invoking an expression stored in a string.
    Invoke-Expression '1 / 0'
    
    # Starting a background job.
    Start-Job { 1/ 0 }
    
    # The *non-remoting* form of Invoke-Command (WITHOUT -ComputerName).
    Invoke-Command { 1 / 0 }
    
Community
  • 1
  • 1
mklement0
  • 312,089
  • 56
  • 508
  • 622
4

You find a complete Punctuation chart here. The answer (taken from the chart):

Execution status of the last operation ($true or $false); contrast with $LastExitCode that reports the exit code of the last Windows-based program executed.

Martin Brandl
  • 51,813
  • 12
  • 118
  • 151
  • 1
    Thanks Martin for the chart, its succent. In the chart, at the bottom, I got this [link](https://technet.microsoft.com/en-us/library/hh847768.aspx), guess this is official documentation. – VivekDev Oct 05 '16 at 05:11
  • 1
    Yes, thats the official documentation. Your welcome. – Martin Brandl Oct 05 '16 at 05:13