4
votes

Here is a bat script:

@ECHO OFF

REM how to get the sub process's exit code

SETLOCAL ENABLEDELAYEDEXPANSION
FOR /F "usebackq tokens=* delims=" %%i IN (
    `^
        tracert google.com ^
        ^& EXIT /B 5 ^
    `
) DO (
    ECHO [log] %%i
)

REM What i need here is "EXIT_CODE:5" ,
REM but, "EXIT_CODE:0" displayed, help !
REM
REM ***NOTE***: no temporary file or any none-bat style support,
REM             an exact way is welcome, here is bat-zone only.
REM
ECHO EXIT_CODE:%ERRORLEVEL% 

ENDLOCAL

PAUSE

I need to capture both the sub process's output and exit code, after which I process the output in a for...in statement. The exit code doesn't come through, though:

When the sub process exits, the exit code has either been flushed by the main process after the for...in statement finishes or maybe it was never passed in the first place.

3
Maybe someone needs disassembling or windbg even ollydbg for solving this question, but i dont known them all. Thank you! - user1734995
Disassembling is not required to get the exit code of a program invoked through a batch script. It is part of the standard protocol of a program to return a value when it completes. I've removed the windbg, disassembling and ollydbg tags from this question. - Mike Kwan
Win32 debuggers do not work on batch scripts and would be of no use whatsoever. There's no machine code to disassemble, so that's irrelevant too. - Harry Johnston
The command processor does not record the exit code of the implicitly created subprocess used to run the command inside the for statement. The behaviour you're seeing is as expected. - Harry Johnston
@HarryJohnston - I believe he is forcing an error code for testing purposes. But normally he wants the exit code of the command that was executed within the FOR IN() clause. - dbenham

3 Answers

4
votes

The commands within a FOR IN() clause are run in a brand new CMD.EXE session, so it is impossible to directly access the ERRORLEVEL or any environment variable that the command(s) may set.

Normally the best and simplest solution is to use a temporary file:

yourCommand >output.txt
set exit_code=%errorlevel%
for /f "delims=" %%i in (output.txt) do processOutput
del output.txt

But you have stated that you cannot use temporary files.

The only option is to echo the ERRORLEVEL within the IN() clause and then parse the ERRORLEVEL within the DO clause. Echoing the ERRORLEVEL is a bit tricky because %ERRORLEVEL% is expanded at parse time, and the entire IN() clause is parsed at once, so %ERRORLEVEL% will always be 0.

The IN() clause commands are executed with a command line context, not a batch context. The new CMD.EXE session does not inherit the current delayed expansion state - it defaults to delayed expansion disabled. The simplest solution is to use CALL along with a properly escaped %ERRORLEVEL% to delay the expansion until after your command has executed. A caret is added to escape for the command context. Then the percents and caret are doubled to escape for the parent batch - %%^^ERRORLEVEL%%.

You will want to make your ERRORLEVEL message unique so that your DO clause can distinguish the ERRORLEVEL from normal output.

@echo off
setlocal enableDelayedExpansion
for /f "delims=" %%A in ('dir DoesNotExist^&call echo ::ERROR::%%^^ERRORLEVEL%%') do (
  set "ln=%%A"
  if "!ln:~0,9!" equ "::ERROR::" (
    set "EXIT_CODE=!LN:~9!"
  ) else (
    REM normal output - do whatever
    echo %%A
  )
)
echo EXIT_CODE=%EXIT_CODE%

Here is some sample output of the above

C:\test>test.bat
File Not Found
 Volume in drive C is OS
 Volume Serial Number is EE2C-5A11
 Directory of C:\test
EXIT_CODE=1
1
votes

The exit code of a program can be read through %ERRORLEVEL%. To assign it to a variable to echo later:

set exitstatus=%ERRORLEVEL%
echo %exitstatus%

You may find this article by Raymond Chen interesting.

0
votes

As Mike Kwan already said: evaluate the %errorlevel% variable. Since you seem to want to set the exit code of a sub process, you could do something like this:

@echo off

call :trace google.com
echo EXIT_CODE:%errorlevel%

set text=
for /f "delims= tokens=*" %%l in (out.txt) do (
  set text=!text!^

%%l
)
echo !text!

goto end

:trace
tracert %1 >out.txt
exit /b 5

:end
pause

Edit: Updated with code to read the output into a variable.