Search code examples
windowsbatch-fileexit-codeerrorlevel

Using !ERRORLEVEL! in code block piped to another process


Why the !ERRORLEVEL! does not work in the following example?

@echo off
setlocal enabledelayedexpansion
(
  winscp.com /?
  echo !ERRORLEVEL!
) | findstr /v "word"
exit

I get !ERRORLEVEL! on the output.

While if I remove the | findstr /v "word":

@echo off
setlocal enabledelayedexpansion
(
  winscp.com /?
  echo !ERRORLEVEL!
)
exit

I get the error level on the output.


Solution

  • It's because a pipe executes both sides in new cmd.exe instances (when the command is a batch command).

    The commands/code blocks are converted and used as parameters for cmd.exe, in your example it becomes:

    C:\WINDOWS\system32\cmd.exe /S /D /c"( winscp.com & echo !errorlevel!)"
    

    But as a new cmd.exe instance is invoked, the delayed expansion is disabled by default.

    You could also examine the behaviour by using the cmdcmdline pseudo variable.

    @echo off
    (
      echo %%cmdcmdline%%
      echo !ERRORLEVEL!  
    ) | findstr /n "^"
    

    Shows:

    1:C:\WINDOWS\system32\cmd.exe /S /D /c" ( echo %cmdcmdline% & echo !ERRORLEVEL! )"
    2:!ERRORLEVEL!

    For more information Why does delayed expansion fail when inside a piped block of code?

    Using errorlevel inside a code block with piping

    You could simply use another expansion method

    (
      winscp.com /?
      call echo %%ERR^^ORLEVEL%%
    ) | findstr /v "word"
    

    Obviously, this works by the fact, that the converted block looks like

    C:\WINDOWS\system32\cmd.exe  /S /D /c" ( winscp.com /? & call echo %ERR^ORLEVEL%  )" 
    

    After cmd.exe parsed the block the percent expansion is already done and the block looks like

    ( winscp /? & call echo %errorlevel% )
    

    The caret is important here, to avoid that the expansion occurs before the line is executed.

    Another solution

    Create your own cmd.exe instance with delayed expansion enabled by

    (
      winscp.com /?
      cmd /V:on /c echo !SOME_VARIABLE!
    ) | findstr /v "word"
    

    But this only works with "normal" variables, NOT with ERRORLEVEL or cmdcmdline, because they are changed by starting the new instance

    Using a sub batch file

    Instead of using a code block on the left side of the pipe, you could use a batch file, there it's possible to enable the delayed expansion again.

    I'm using a trampoline function, to put the complete code into one batch file.

    @echo off
    FOR /F "tokens=3 delims=:" %%X in ("%0") do goto :%%X
    
    "%~d0\:myblock:\..\%~pnx0" | findstr /v "word"
    exit /b
    
    :myblock
    setlocal enabledelayedexpansion
    (
      winscp.com /?
      echo !ERRORLEVEL!
    )
    exit /b