I suspect there is no good solution, but perhaps I'm overlooking something:
What I'm after is a way to:
(a) call a batch file from PowerShell in a way that robustly reflects its - implicit or explicit - exit code in PowerShell's automatic
$LASTEXITCODEvariable.Notably, calling a batch file that exits with, say,
whoami -nosuch || exit /b, should result in$LASTEXITCODEreflectingwhoami's exit code, i.e.1. This is not the case when you invoke a batch file (by name or path) from PowerShell: the exit code is0(by contrast, inside acmd.exesession%ERRORLEVEL%is set to1).Also note that the invocation should remain integrated with PowerShell's output streams, so I am not looking for solutions based on
System.Diagnostics.Process.Furthermore, I have no knowledge of or control over the batch files getting invoked - I'm looking for a generic solution.
(b) without double-quoted arguments passed to the batch file getting altered in any way, and without
cmd.exe's behavior getting modified in any way; notably:^characters should not be doubled (see below).- Enabling delayed expansion with
/V:ONis not an option.
The only way I know how to solve (a) is to invoke the batch file via cmd /c call.
Unfortunately, this violates requirement (b), because the use of call seemingly invariably doubles ^ characters in arguments. (And, conversely, not using call then doesn't report the exit code reliably).
Is there a way to satisfy both requirements?
Note that PowerShell is only the messenger here: The problem lies with cmd.exe, and anyone calling a batch file from outside a cmd.exe session is faced with the same problem.
Example (PowerShell code):
# Create a (temporary) batch file that echoes its arguments,
# provokes an error, and exits with `exit /b` *without an explicit argument*.
'@echo off & echo [%*] & whoami -nosuch 2>NUL || exit /b' | Set-Content test.cmd
# Invoke the batch file and report the exit code.
.\test.cmd "a ^ 2"; $LASTEXITCODE
The output should be:
["a ^ 2"]
1
However, in reality the exit code is not reported:
["a ^ 2"]
0 # !! BROKEN
If I call with cmd /c call .\test.cmd instead, the exit code is correct, but the ^ characters are doubled:
PS> cmd /c call .\test.cmd "a ^ 2"; $LASTEXITCODE
["a ^^ 2"] # !! BROKEN
1 # OK
cmd '/c .\test.cmd "a ^ 2" & exit /b'. It returns correct output from your test batch file and if I put gibberish there I get9009in$LASTEXITCODE(MSG_DIR_BAD_COMMAND_OR_FILE).|| exit /b, which should work; trying& exit /bhadn't even occurred to me - it suggests thatcmd.exeitself isn't aware that the batch file failed, yet somehow does pass its nonzero exit code through withexit /b. 🤦♂️%ERRORLEVEL% is set to 1gave me a hint that something like this might work ;).