Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does echo $? work?

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.

like image 985
VivekDev Avatar asked Jan 06 '23 06:01

VivekDev


2 Answers

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 (console application, 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:

  • In PowerShell versions prior to v7.2, $? there is a risk of false negatives for external command-line utilities, i.e. it can report $false even when $LASTEXITCODE is 0, namely when a 2> redirection is used and actual stderr output is present - see this answer for details.

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

    • Conversely, you can opt to have non-terminating errors treated as terminating ones, using preference variable $ErrorActionPreference or common cmdlet parameter
      -ErrorAction (alias -EA) - see Get-Help about_Preference_Variables and Get-Help about_CommonParameters.
  • 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 }
    
like image 183
mklement0 Avatar answered Jan 07 '23 19:01

mklement0


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.

like image 23
Martin Brandl Avatar answered Jan 07 '23 20:01

Martin Brandl