Search code examples
powershellcmdexit-code

wrong exit code indicated by powershell


I have huge legacy cmd script that is used as part of the setup of the some software we use. Script is called from both cmd and powershell scripts. Sometimes it fails, which is indicated by exit code. Issue with the script is that it creates a lot of environment variables and does not handle them very well, so if run twice in the same environment, second execution might not work as expected. It's an issue only when it is run from cmd, Powershell always starts cmd scripts in subshell, so not a problem there.

Now, I quick fix it by forcing it to run in a subshell (so when script terminates, subshell is terminated, too, and we end up in a clean environment ready to run script again). "Fix" looks like this:

@echo off

if "%running_in_subshell%"=="" (
    set running_in_subshell=1
    cmd /c %0
    if errorlevel 1 (
        set running_in_subshell=
        echo fail
        exit /b 1
    ) else (
        set running_in_subshell=
        echo success
        exit /b 0
    )
    goto :eof
)

rem lot of weird old code noone wants to touch here, now run in a subshell

echo exiting with error, subshell: %running_in_subshell%

rem it does not always exits with "1", but for the sake of this question let return error always
exit 1

Fun part begins now. After that fix was done, Powershell scripts that call it started to have issues with detecting failure. Suppose, we saved script above as a.cmd. Cmd script:

@echo off
call a.cmd
if errorlevel 1 (
    echo ^>^> script failed
) else (
    echo ^>^> script succeeded
)

produces following output:

exiting with error, subshell: 1
fail
>> script failed

which is expected, but powershell script:

.\a.cmd
if ($lastexitcode -gt 0)
{
    write-output ">> script failed"
} 
else
{
    write-output ">> script succeeded"
}

produces

exiting with error, subshell: 1
fail
>> script succeeded

WAT??? Now even more fun part: if I remove goto :eof from the original script, it starts to work as expected (I left it there accidentally after some debugging). But it's not even supposed to execute as both branches of if that precede it exit the script. What am I missing? Why do I see what I see?

I currently use Windows 10, but I saw the same behavior on Windows Server 2012, Windows Server 2012 R2 and Windows 8, so I believe it's not OS dependent.


Solution

  • Looks like cmd bug. Let us have this Test.cmd file:

    @(
        exit /b 1
        rem
    )
    

    Now type following commands in command line:

    > Test.cmd
    
    > echo %errorlevel%
    1
    
    > cmd /c Test.cmd
    
    > echo %errorlevel%
    0
    

    As you can see, error level not properly set if you run this .cmd file in subshell.