Powershell with Git Command Error Handling - automatically abort on non-zero exit code from external program
Solution 1:
The $ErrorActionPreference
variable does not apply to calling external utilities ([console] applications) such as git
, up to at least PowerShell 7.2.x
- A potential future enhancement is being discussed in RFC #277, for proper integration of calls to external programs into PowerShell's error-handling.
There are only two ways to determine success vs. failure of an external utility:
-
By examining the automatic
$LASTEXITCODE
variable, which PowerShell sets to the **exit code reported by the external utility**.
By convention, a value of0
indicates success, whereas any nonzero value indicates failure. (Do note that some utilities, e.g.,robocopy.exe
, use certain nonzero exit codes to communicate non-error conditions too.) -
If you're not interested in the specific exit code reported, you can examine the Boolean automatic variable
$?
, which reflects$True
for exit code0
, and$False
for any nonzero exit code.-
Caveats: As of PowerShell (Core) 7.1, there are two bugs; both these are tentatively fixed via the
PSNotApplyErrorActionToStderr
experimental feature (!) in preview versions of 7.2 (all experimental features are turned on by default in newly installed preview versions) - let's hope these fixes make it into 7.2:-
$?
can falsely reflect$false
when$LASTEXITCODE
is0
and a stderr redirection (2>
or*>
) is used and the command emitted stderr output (which in itself doesn't necessarily indicate failure) - see GitHub issue #10512.- It is therefore a good habit to form to query only
$LASTEXITCODE
, not$?
to infer failure vs. succcess.
- It is therefore a good habit to form to query only
-
If
$ErrorActionPreference
is set to'Stop'
and you happen to use2>
or*>
with an external program and that program produces actual stderr output,$ErrorActionPreference
unexpectedly does apply and causes a script-terminating error (whether or not the external program signals actual failure via a nonzero exit code) - see GitHub issue #4002
-
-
Acting on a failure requires explicit action, typically by using the Throw
keyword to generate a script-terminating error.
Clearly, checking $LASTEXITCODE
/ $?
after every external-utility call is cumbersome, so here's a wrapper function that facilitates this process:
Note: While this function works, it does not try to compensate for the broken-up-to-at-least-v7.2.x passing of arguments with embedded double quotes to external programs. To get this compensation, you can use the iee
function from the Native module, installable from the PowerShell Gallery via Install-Module Native
.
function Invoke-Utility {
<#
.SYNOPSIS
Invokes an external utility, ensuring successful execution.
.DESCRIPTION
Invokes an external utility (program) and, if the utility indicates failure by
way of a nonzero exit code, throws a script-terminating error.
* Pass the command the way you would execute the command directly.
* Do NOT use & as the first argument if the executable name is not a literal.
.EXAMPLE
Invoke-Utility git push
Executes `git push` and throws a script-terminating error if the exit code
is nonzero.
#>
$exe, $argsForExe = $Args
# Workaround: Prevents 2> redirections applied to calls to this function
# from accidentally triggering a terminating error.
# See bug report at https://github.com/PowerShell/PowerShell/issues/4002
$ErrorActionPreference = 'Continue'
try { & $exe $argsForExe } catch { Throw } # catch is triggered ONLY if $exe can't be found, never for errors reported by $exe itself
if ($LASTEXITCODE) { Throw "$exe indicated failure (exit code $LASTEXITCODE; full command: $Args)." }
}
Now you just need to prepend Invoke-Utility
to all your git
calls, and if any of them reports a nonzero exit code, the script is aborted.
If that is too verbose, define an alias for your function: Set-Alias iu Invoke-Utility
, in which case you only need to prepend iu
:
iu git init
iu git remote add origin '%env.gitFolder%'
iu git fetch
# ...
Solution 2:
I believe the problem here is that the error thrown by git
is not trappable by PS.
Illustration:
try {
git push
Write-Host "I run after the git push command"
}
catch {
Write-Host "Something went wonky"
}
Note the missing Write-Host
from the catch
block!
This is where we need to look at the exit code of the git commands.
The easiest way (I know) in PowerShell is to check the value of $?
(more information on $?
here: What is `$?` in Powershell?)
try {
git push
if (-not $?) {
throw "Error with git push!"
}
Write-Host "I run after the git push command"
}
catch {
Write-Host "Something went wonky"
throw
}
Check our custom error (now caught by the catch
block)!