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.
How does echo $? work?
3.3k Views Asked by VivekDev AtThere are 2 best solutions below

To complement Martin Brandl's helpful answer with more detailed information:
tl;dr
Automatic variable
$?
(seeGet-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.
- Since
Automatic variable
$LASTEXITCODE
complements this by recording the specific exit code of the most recently executed external command-line utility (console application, such asfindstr
).$LASTEXITCODE
complements$?
in that$?
reflects only abstract success or failure of the external utility - exit code0
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
is0
, namely when a2>
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 usetry / catch
(preferred) ortrap
(see theGet-Help about_Try_Catch_Finally
andGet-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
) - seeGet-Help about_Preference_Variables
andGet-Help about_CommonParameters
.
- Conversely, you can opt to have non-terminating errors treated as terminating ones, using preference variable
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.
- To determine the exact error count and the offending input objects, you must examine the
With non-remoting indirect-execution cmdlets to which you pass a target command to execute - such as
Invoke-Expression
,Start-Process
andStart-Job
andInvoke-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
(!), becauseInvoke-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.
- A simple example:
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 command1 / 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 }
You find a complete Punctuation chart here. The answer (taken from the chart):