Search code examples
powershellbatch-filecmdparameter-passingexit-code

Call a batch file from PowerShell with proper exit code reporting while avoiding doubling of carets


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 $LASTEXITCODE variable.

    • Notably, calling a batch file that exits with, say, whoami -nosuch || exit /b, should result in $LASTEXITCODE reflecting whoami'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 is 0 (by contrast, inside a cmd.exe session %ERRORLEVEL% is set to 1).

    • 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:ON is 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

Solution

  • I've no idea why this works, but it does:

    cmd /c '.\test.cmd "a ^ 2" & exit'
    $LASTEXITCODE
    

    Output:

    ["a ^ 2"] 
    1