Capturing ERRORLEVEL while using TEE command

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Post Reply
Message
Author
mirrormirror
Posts: 129
Joined: 08 Feb 2016 20:25

Capturing ERRORLEVEL while using TEE command

#1 Post by mirrormirror » 01 Mar 2016 02:35

Is there a way to capture %errorlevel% while using a "tee" command. I've tried the following using two different tee utilities and both have the same results:

Code: Select all

call boguscmd
@ECHO ERRORLEVEL: %ERRORLEVEL%

call boguscmd |tee tmp1.txt
@ECHO ERRORLEVEL: %ERRORLEVEL%

output:

Code: Select all

c:\>call boguscmd
'boguscmd' is not recognized as an internal or external command,
operable program or batch file.
ERRORLEVEL: 1

c:\>call boguscmd   | tee tmp1.txt
'boguscmd' is not recognized as an internal or external command,
operable program or batch file.
ERRORLEVEL: 0

I guess %errorlevel% is behaving as expected because "tee.exe" is completing successfully while the prior "boguscmd" failed. Just wondering if anyone else figured a way around this problem.

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: Capturung ERRORLEVEL while using TEE command

#2 Post by dbenham » 01 Mar 2016 06:30

Each side of the pipe is run in a separate cmd.exe process. The right side ERRORLEVEL is returned to the parent process once the right side process terminates. But the left ERRORLEVEL is lost when the left side process terminates.

Also, any variables defined by either side are also lost once the pipe is closed.

The simplest method to capture the left ERRORLEVEL is to write the value out to a temp file so that it can be read by the parent process.

Normally I would recommend delayed expansion with something like BOGUSCMD & ECHO !ERRORLEVEL!. But the child process delay state will reset (normally to OFF), regardless whether delayed expansion is ON in the parent process. So for this application I would use the CALL trick.

The command I want is BOGUSCMD & >ERR.TMP ECHO %ERRORLEVEL%.

The child process has a command line context, and I want the expansion of ERRORLEVEL to be delayed, so I use CALL ECHO %^ERRORLEVEL%. On the first expansion pass (before the CALL) the caret is treated as part of the name, which should not exist, and the command line preserves the percents. But the caret is consumed, so the CALL pass then sees and expands %ERRORLEVEL%.

Lastly, the percents, command concatenation, redirection, and caret must all be escaped so that they pass properly from the parent batch process to the child command line process. Also, the left side must be enclosed in parentheses.

Note that BOGUSCMD does not need CALL because it is in a child process.

Code: Select all

(boguscmd ^& ^>err.tmp call echo %%^^errorlevel%%) | tee tmp1.txt
set /p "leftErr=" <err.tmp
del err.tmp
echo leftErr=%leftErr%


Dave Benham

mirrormirror
Posts: 129
Joined: 08 Feb 2016 20:25

Re: Capturing ERRORLEVEL while using TEE command

#3 Post by mirrormirror » 01 Mar 2016 17:13

Thank you - for both the solution and the explanation.
I was unaware of the differences between these two commands:
(boguscmd)
and
boguscmd
Do you have any links that explain the rules of commands run inside parentheses?
For example, is there a difference in how these two lines are handled?

Code: Select all

   FOR /F %%_ IN ('VER ^|FIND /I "XP"') DO SET isWinXP=1
   FOR /F %%_ IN ('VER ^|FIND /I "XP"') DO (SET isWinXP=1)
Last edited by mirrormirror on 01 Mar 2016 19:32, edited 1 time in total.

ShadowThief
Expert
Posts: 1166
Joined: 06 Sep 2013 21:28
Location: Virginia, United States

Re: Capturing ERRORLEVEL while using TEE command

#4 Post by ShadowThief » 01 Mar 2016 19:30

mirrormirror wrote:Thank you - for both the solution and the explanation.
I was unaware of the differences between these two commands:
(boguscmd)
and
boguscmd
Do you have any links that explain the rules of commands run inside parentheses?

There is no difference between the two commands you have written. However, parentheses are used to group multiple commands together. Without them,

Code: Select all

boguscmd ^& ^>err.tmp call echo %%^^errorlevel%% | tee tmp1.txt

would run boguscmd and then once boguscmd finished, the script would pipe the output of

Code: Select all

^>err.tmp call echo %%^^errorlevel%%)
to

Code: Select all

tee tmp1.txt

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: Capturing ERRORLEVEL while using TEE command

#5 Post by dbenham » 02 Mar 2016 00:41

Actually, the parentheses do make a surprising difference in the presence of a pipe - something that I have never seen before.

I was mistaken in my earlier code in that the & and > did not need to be escaped because the parentheses are present. The following will work just fine:

Code: Select all

(boguscmd & >err.tmp call echo %%^^errorlevel%%) | tee tmp1.txt
set /p "leftErr=" <err.tmp
del err.tmp
echo leftErr=%leftErr%


The following without parentheses or escaping does not work because only the CALL ECHO is piped; BOGUSCMD is not piped.

Code: Select all

boguscmd & >err.tmp call echo %%^^errorlevel%%) | tee tmp1.txt

But there is an additional subtle difference.

Without parentheses, it should be possible to work without parentheses if you escape the & and > so that the batch parser only sees a single command to be piped. I will first demonstrate with a valid command to show what I mean.

First, I'll create a simple batch script that puts out a line of output and exits with an error:
err.bat

Code: Select all

@echo off
echo This script generates an error.
exit /b 1

The following works fine:

Code: Select all

@echo off
setlocal
err.bat ^& call echo %%^^errorlevel%% ^>err.tmp | findstr /n "^"
set /p "leftErr=" <err.tmp
del err.tmp
echo leftErr=%leftErr%

--OUTPUT--

Code: Select all

1:This script generates an error.
leftErr=1

The 1: prefix proves that the left side was piped properly, and the leftErr value proves the error was properly captured.

But if I substitute an invalid command, then the pipe is never executed, and the batch script is terminated :!: :shock:

Code: Select all

@echo off
setlocal
boguscmd ^& ^>err.tmp call echo %%^^errorlevel%% ^& echo OK | findstr /n "^"
set /p "leftErr=" <err.tmp
del err.tmp
echo leftErr=%leftErr%

--OUTPUT--

Code: Select all

'boguscmd' is not recognized as an internal or external command,
operable program or batch file.


If I add parentheses, then all is well. Note that I preserved the escapes to show that it is just the parentheses that have changed. But they aren't really needed.

Code: Select all

@echo off
setlocal
(boguscmd ^& ^>err.tmp call echo %%^^errorlevel%% ^& echo OK) | findstr /n "^"
set /p "leftErr=" <err.tmp
del err.tmp
echo leftErr=%leftErr%

--OUTPUT--

Code: Select all

'boguscmd' is not recognized as an internal or external command,
operable program or batch file.
1:OK
leftErr=9009

:? What is going on here :?: :!:

I cannot arrive at a rational reason for this behavior. Without parentheses, it seems as if the batch parser recognizes that the command is invalid, so it never executes the pipe. I can just about accept this. But I cannot understand why the pipe causes the invalid command to be treated as a fatal error. Without the pipe, the the error message is simply printed and the script continues.

For some unknown reason, adding the parentheses delays detection of the invalid command, the pipe works, and everything works as expected.

I know of one other case where parentheses change the behavior of a pipe - the parentheses prevent delayed expansion in the presence of a pipe. See http://stackoverflow.com/q/8192318/1012053 for more info.


Dave Benham

mirrormirror
Posts: 129
Joined: 08 Feb 2016 20:25

Re: Capturing ERRORLEVEL while using TEE command

#6 Post by mirrormirror » 02 Mar 2016 03:10

Note that BOGUSCMD does not need CALL because it is in a child process.

When I first read this, assumed that the parentheses forced a "CALL" on the command - maybe I misread it but I tried this out in a batch file:

Code: Select all

(boguscmd)
boguscmd

the first command is executed (treated as a "call"?) - it fails but the script continues and the 2nd command also executes but terminates the script.
Switching it around:

Code: Select all

boguscmd
(boguscmd)

- the 1st command fails and terminates the script before running the 2nd one.
Another curiosity (for me at least), the errorlevels are different when running the code you provided:

Code: Select all

call boguscmd
@ECHO ERRORLEVEL: %ERRORLEVEL%

(boguscmd ^& ^>err.tmp call echo %%^^errorlevel%%) | tee tmp1.txt
set /p "leftErr=" <err.tmp
del err.tmp
echo leftErr=%leftErr%

output:

Code: Select all

'boguscmd' is not recognized as an internal or external command,
operable program or batch file.
ERRORLEVEL: 1
'boguscmd' is not recognized as an internal or external command,
operable program or batch file.
leftErr=9009

ERRORLEVEL=0 for: call boguscmd
ERRORLEVEL=9009 for: (boguscmd ^& ^>err.tmp call echo %%^^errorlevel%%) | tee tmp1.txt

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: Capturing ERRORLEVEL while using TEE command

#7 Post by dbenham » 02 Mar 2016 07:20

mirrormirror wrote:
dbenham wrote:Note that BOGUSCMD does not need CALL because it is in a child process.

When I first read this, assumed that the parentheses forced a "CALL" on the command
I misled you on that. I originally thought that the CALL was there because BOGUSCMD was a batch script (that just happened to be missing). So in that sense my statement was correct. But when I wrote that, I did not realize that the CALL also makes a difference in the behavior of an invalid command in the context of a pipe.


mirrormirror wrote:maybe I misread it but I tried this out in a batch file:

Code: Select all

(boguscmd)
boguscmd

the first command is executed (treated as a "call"?) - it fails but the script continues and the 2nd command also executes but terminates the script.
Switching it around:

Code: Select all

boguscmd
(boguscmd)

- the 1st command fails and terminates the script before running the 2nd one.
I cannot reproduce that behavior. For me, BOGUSCMD without parentheses or CALL only terminates the script if it is issued in the context of a pipe:

Code: Select all

@echo off

boguscmd
echo BOGUSCMD does not terminate script
echo(

(boguscmd)|rem
echo (BOGUSCMD)^|REM does not terminate script
echo(

echo But the next BOGUSCMD^|REM does terminate the script
boguscmd|rem
echo This is not executed because the script was terminated
--OUTPUT--

Code: Select all

'boguscmd' is not recognized as an internal or external command,
operable program or batch file.
BOGUSCMD does not terminate script

'boguscmd' is not recognized as an internal or external command,
operable program or batch file.
(BOGUSCMD)|REM does not terminate script

But the next BOGUSCMD|REM does terminate the script
'boguscmd' is not recognized as an internal or external command,
operable program or batch file.


mirrormirror wrote:Another curiosity (for me at least), the errorlevels are different when running the code you provided:

Code: Select all

call boguscmd
@ECHO ERRORLEVEL: %ERRORLEVEL%

(boguscmd ^& ^>err.tmp call echo %%^^errorlevel%%) | tee tmp1.txt
set /p "leftErr=" <err.tmp
del err.tmp
echo leftErr=%leftErr%

output:

Code: Select all

'boguscmd' is not recognized as an internal or external command,
operable program or batch file.
ERRORLEVEL: 1
'boguscmd' is not recognized as an internal or external command,
operable program or batch file.
leftErr=9009

ERRORLEVEL=0 for: call boguscmd
ERRORLEVEL=9009 for: (boguscmd ^& ^>err.tmp call echo %%^^errorlevel%%) | tee tmp1.txt

Indeed, CALL does change the ERRORLEVEL, but it changes it to 1, not 0.

Note that the pipe is not needed to demonstrate the behavior

Code: Select all

@echo off
boguscmd
echo ERRORLEVEL=%errorlevel%
echo(

call boguscmd
echo ERRORLEVEL=%errorlevel%
--OUTPUT--

Code: Select all

'boguscmd' is not recognized as an internal or external command,
operable program or batch file.
ERRORLEVEL=9009

'boguscmd' is not recognized as an internal or external command,
operable program or batch file.
ERRORLEVEL=1

I think CALL's effect on ERRORLEVEL is related to the fact that || does the same thing, as described at http://stackoverflow.com/a/34937706/1012053.

Code: Select all

@echo off
boguscmd
echo %errorlevel%
echo(

boguscmd||rem
echo %errorlevel%
--OUTPUT--

Code: Select all

'boguscmd' is not recognized as an internal or external command,
operable program or batch file.
9009

'boguscmd' is not recognized as an internal or external command,
operable program or batch file.
1


Dave Benham

Post Reply