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.
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.
$?
is set after every statement, you must either check it right after the statement of interest, or save it for later inspection.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.$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
).
$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.
$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.
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.$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 }
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With