35

Batch files return the error code of the last command by default.

Is it somehow possible to return the error code of a former command. Most notably, is it possible to return the error code of a command in a pipe?

For example, this one-line batch script

foo.exe

returns the error code of foo. But this one:

foo.exe | tee output.txt

always returns the exit code of tee, which is zero.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
  • 1
    related: http://stackoverflow.com/questions/11170753/windows-command-interpreter-how-to-obtain-exit-code-of-first-piped-command – eckes Jul 07 '16 at 10:40
  • 3
    Possible duplicate of [Windows command interpreter: how to obtain exit code of first piped command](https://stackoverflow.com/questions/11170753/windows-command-interpreter-how-to-obtain-exit-code-of-first-piped-command) – Dave Jarvis Sep 15 '17 at 16:24
  • What exactly is the goal here? Is it simply to be able to respond to the error code, log the output and display it to the console? If so, mkl probably has the best answer, but I didn't realize that until after I posted mine and reviewed everything one more time. – jwdonahue Jan 24 '18 at 23:18
  • This question and all of the answers here are unworthy of the reps they have accumulated. I've removed my own answer. The best explanation of why the above fails can be found at https://stackoverflow.com/questions/8192318/why-does-delayed-expansion-fail-when-inside-a-piped-block-of-code, even though it's not an exact dup of the question. – jwdonahue Jan 24 '18 at 23:25

6 Answers6

13

I had a similar problem and settled on the following solution as I did not need to detect the exact error code just success or failure.

echo > .failed.tmp    

( foo.exe && del .failed.tmp ) | tee foo.log

if exist .failed.tmp (
    del .failed.tmp
    exit /b 1
) else (
    exit /b 0
)
Nick Collier
  • 741
  • 1
  • 7
  • 9
3

The %ERRORLEVEL% variable doesn't get updated before the piped command is run; you have to use a wrapper as advised in the other answers.

However, you can use "IF ERRORLEVEL #". For example:

(
type filename
@REM Use an existing (or not) filename to test each branch
IF ERRORLEVEL 1 (echo ERROR) ELSE (echo OKAY)
) > logfile.txt

The ECHO will only run if an error was returned; however %ERRORLEVEL% seems inconsistent.

Edit: Changed example to be runnable as-is.

whitey04
  • 1,321
  • 11
  • 17
  • 7
    This doesn't work for me I get "ECHO was unexpected at this time." – Sam Mackrill Oct 04 '13 at 13:10
  • 5
    Can the 5 people who upvoted this answer please provide repro steps? I get what @SamMackrill gets. Windows doesn't expect anything at this time. – harpo Jul 25 '14 at 03:51
  • Suggested answer does not work. It fails with a message "( unexpected at this time" This was observed on windows 10 machine. – John Rocha Feb 13 '19 at 20:03
  • Sure does (Win10, in cmd.exe not powershell) with [cmd extensions](https://www.itprotoday.com/windows-78/how-do-i-enabledisable-command-extensions) enabled. – whitey04 Apr 23 '19 at 20:31
3

One workaround is to make an indirection through a file.

Like this

foo.exe > tmp.txt
set FOOERR=%ERRORLEVEL%
cat tmp.txt
exit %FOOERR%
  • 18
    This defeats the whole point of using tee which is to get immediate console output as well as being logged to file. – Sam Mackrill Oct 04 '13 at 12:54
2

After about one day of digging, I found a way to do that:

set error_=0
9>&1 1>&2 2>&9 (for /f "delims=" %%i in ('9^>^&1 1^>^&2 2^>^&9 ^(^(^(2^>^&1 call "%homeDir%%1"^) ^|^| ^(1^>^&2 2^>nul echo FAILED^)^) ^| 2^>nul "%homeDir%mtee" /T /+ "%homeDir%logs\%date_%_%1.log"^)') do (set error_=1))

exit /b %error_%

In the example above "%homeDir%%1" is being executed and its output is piped to "%homeDir%mtee". This line detects failures (I'd suggest you to draw a diagram of batch contexts and their stdin/stdout/stderr assignments in order to understand what it does :-) ). I did not find a good way to extract the actual errorlevel. The best thing I got was to replace the 'echo' command with some batch script call 'call rc.bat' which look like:

@echo %errorlevel%

and then replace 'set error_=1' with 'set error_=%%i'.

But the problem is that this call may fail too, and it is not easy to detect that. Still, it is much better than nothing -- I did not find any solution for that on the Internet.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
crusader
  • 37
  • 1
2

To call tee for entry bat-file, not for single command, and use errorlevel freely, I use trick like this:

if "%1" == "body" goto :body
call %0 body | tee log.txt
goto :eof
:body

set nls_lang=american_america
set HomePath=%~dp0

sqlplus "usr/pwd@tnsname" "@%HomePath%script.sql" 
if errorlevel 1 goto dberror

rem Here I can do something which is dependent on correct finish of script.sql    

:dberror

echo script.sqlerror failed

it separates using tee from calling any commands inside batch.

Nashev
  • 490
  • 4
  • 10
1

You can solve the problem by creating a wrapper around your command file:

rem wrapper for command file, wrapper.cmd

call foo.exe

echo %errorlevel%

if errorlevel 1 goto...

Then append tee to the wrapper:

wrapper.cmd | tee result.log

Of course this does not exactly the same, e.g. if you want to log in several files in the wrapped file, it is not possible, but in my case it solved the problem.

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
Fritz
  • 19
  • 1