3

When I CALL one Windows .bat file from another, can the called file tell whether ECHO was ON or OFF in the caller?

When a .bat file is entered, ECHO is set to ON (even if it was OFF in the CALLER).

I want to preserve the caller's ECHO status in the called .bat so I can control both (possibly a chain of more than two nested calls) from a single place.

I could pass a parameter or set my own environment variable but is there a better way?

EDIT Just to clarify what's needed: callee.cmd needs to do things with echo off only for the duration of its run. Upon returning to its caller, it must restore the echo state the caller had.

Please fill in the ?????s in the script below:

callee.cmd:

@rem save the echo state
@ ???? HOW???? ????
@ set "savedEchoState=??????"

@rem the following commands must not be echoed
@echo off
command1
command2
command3

@rem restore the previous echo state
echo %savedEchoState%

caller1.cmd:

@echo off
call callee.cmd
echo

caller2.cmd:

@echo on
call callee.cmd
echo

Required output:

caller1.cmd must print echo off and caller2.cmd must print echo on.

Can it be done without creating any files on a disk?

3
  • 3
    "When a .bat file is entered, ECHO is set to ON (even if it was OFF in the CALLER)." --> not true. It is kept off if it was off in the caller. However, this is an important question. We tend to put @echo off in batch files because echo is on at the interactive prompt. However, if we deliberately have echo on in a caller batch, and we call this batch file that starts with @echo off, we would want to set echo back to on before we exit so that the caller keeps its echo-on status. Commented Jan 24, 2018 at 12:11
  • 1
    Thanks Klitos, you are right of course. My called .bat was turning echo off. If I call it directly, that's usually what I want but if I call it from another .bat in which echo is on (for debugging), I'd like to keep it on in the called .bat.
    – Denis Howe
    Commented Jan 25, 2018 at 11:29
  • @klitoskyriacou the edit makes sense, but the principal stays the same in my example. I will change it and update.
    – Gerhard
    Commented Jan 25, 2018 at 16:21

5 Answers 5

5

This is a tough one. Everything involving a pipe or a for doesn't work, as new processes get generated, which invalidates the echo status. It is possible involving a temp file though:

A.BAT:

echo on
echo
call b.bat
echo off
echo
call b.bat
echo

B.BAT:

@(>tmp echo)
@type tmp|find "(ON)" >nul && (@set "oldEcho=ON") || (@set "OldEcho=OFF")
@echo off
echo B:echo was previously %OldEcho%
REM insert your payload here
REM restore previous Echo Status:
echo %OldEcho%

Result:

C:\Folder>a

C:\Folder>echo on

C:\Folder>echo
ECHO ist eingeschaltet (ON).

C:\Folder>call b.bat
B:echo was previously ON

C:\Folder>echo off
ECHO ist ausgeschaltet (OFF).
B:echo was previously OFF
ECHO ist ausgeschaltet (OFF).

This makes use of the (unintuitive) fact that the on/off message is language dependent (German in my example above), but ends with an English suffix (ON) or (OFF) (which should be consistent in every language; if you know a language that does not do this, please let us know)

Edit (Nov-2023): I just realized (now having access to an English Windows), the output of echo in English is ECHO is on./ECHO is off., invalidating the above solution (find "(ON)".

I guess replacing find with findstr /i "\<ON\>" (searching including word boundaries) should solve that, and also avoid false positives (now because /i) with things like ECHO eston deactivon (OFF) (as in @jeb's comment).

Still not sure, if it's bulletproof (working for every language). If you stumble over a language that makes it fail, please @notify me.

5
  • See example in my answer on how it can be used it in for loop.
    – Gerhard
    Commented Jan 24, 2018 at 12:47
  • It works for english and at least german systems, but I'm not sure if it's stable with all possible languages. Does it work with non latin charset systems, too? There could also be languages with words containing on like ECHO eston deactivon (OFF) (poor sample)
    – jeb
    Commented Jan 24, 2018 at 14:19
  • Yeah I tested it and it does work, but I based it on English only.. anyhow, not going to delete the answer, might be useful somewhere. thanks.
    – Gerhard
    Commented Jan 24, 2018 at 14:24
  • 1
    @GerhardBarnard you can make it work on non-english Windows: just enclose the string into quotes: set "echos=%%a" and `echo "%echos%" (btw: shouldn't that be delayed?)
    – Stephan
    Commented Jan 24, 2018 at 14:33
  • Delayed would be better yes. Yes i will add the quotes to cater for other languages. Thanks
    – Gerhard
    Commented Jan 24, 2018 at 14:38
3

To detect the ECHO state without dependency to the windows language you could use a temporary file.
The file is empty when ECHO is OFF, else it contains <PROMPT>REM

@echo off
echo Testing detectEchoState

echo on
@call :detectEchoState

@echo off
@call :detectEchoState off
exit /b


:detectEchoState
@(
    (
        FOR /F %%L in ("1") do REM
    ) > "%TEMP%\echoCheck.tmp"

    FOR /F "delims=" %%F in ("%TEMP%\echoCheck.tmp") do @if %%~zF == 0 (set "EchoState=OFF" ) ELSE (set "EchoState=ON")
    del "%TEMP%\echoCheck.tmp"
)
@echo EchoState=%EchoState%
@exit /b

Solution for Win10 without the creation of a file (Requires vt100)

@echo off

for /F "delims=#" %%a in ('prompt #$E# ^& for %%a in ^(1^) do rem') do set "ESC=%%a"

echo OFF
@call :get_echo_state
@echo state=%state%

echo ON
@call :get_echo_state
@echo state=%state%
@exit /b

:get_echo_state
@(
    setlocal EnableDelayedExpansion
    <nul set /p "=%ESC%7%ESC%[1H"
    set "prompt=%ESC%["
    set "0=call "
    for %%a in (1) do !0!
    <nul set /p "=%ESC%[6n%ESC%8"
    endlocal
) > CON
@(
    set "state=on"
    pause < con > NUL
    pause < con > NUL
    for /F "tokens=1 skip=1 eol=" %%C in ('"replace /w /u ? . < con"') do @(
        if "%%C" == "1" @set "state=off"
    )
    exit /b
)

This uses ansi escape sequences to move the cursor to line 1.
When echo is off the cursor doesn't change, else it moves to the next line.
Then the code reads the line number of the cursor and restores the original cursor position.
The state only depends on the retrieved line number.

5
  • 1
    @GerhardBarnard As it's not possible to fetch the ECHO state without a file. Fetching with FOR or a pipe will result always in an ECHO ON state, as both starts a new cmd.exe instance. Same problem for the states of KEYS or VERIFY see KEYS as boolean variable...
    – jeb
    Commented Jan 24, 2018 at 13:39
  • 1
    @GerhardBarnard Please try to check it with the sample A.BAT file from Stephan's post only change call b.bat to call bat1.bat
    – jeb
    Commented Jan 24, 2018 at 14:40
  • I wonder if we could avoid creating a file by redirecting the output to something that's not writable, and detect the error. I made a few unsuccessful tests with files like C:\hiberfil.sys. This failed because the write protection error occurs in every case when the file is opened by the redirection, not when it's written to. So we need a file that can be opened in write mode, but not be written to, like a full volume, etc. Commented Oct 14, 2021 at 15:53
  • @Jean-FrançoisLarvoire You could redirect to stream 0 1>&0, it only outputs some error texts in case of echo on, but I'm not able to detect the error. See also Dostips: Keys as boolean .... And as dbenham wrote in I/O error detection is broken
    – jeb
    Commented Oct 14, 2021 at 17:20
  • @Jean-FrançoisLarvoire I added a solution avoiding a temporary file, simply with ansi escape sequences
    – jeb
    Commented Oct 14, 2021 at 20:30
1

I had the same problem, and ended up on this page.
Here's a slightly improved version of jeb's script.

  • It uses (call,) instead of rem, which avoids having to put the ) on a separate line.
  • It does not depend on delayed expansion being on for the testing.
@echo off
call :TestEchoState off
call :TestEchoState on
exit /b

:TestEchoState %1=ON or OFF
echo %1
@echo
@call :GetEchoState STATE
@echo off
echo STATE=%STATE%
exit /b

:GetEchoState %1=OUTVAR
@for /f %%F in ("%TEMP%\EchoState%PID%.tmp") do @(
  (FOR /f %%N in ("1") do call,) > "%%F"
  for %%S in ("%%F") do @if %%~zS==0 (set "%1=OFF") else (set "%1=ON")
  del "%%F"
)
@exit /b

And if you accept to just test the errorlevel, it's possible to make the detection routine even shorter:

@echo off
call :TestEchoLevel off
call :TestEchoLevel on
exit /b

:TestEchoLevel %1=ON or OFF
echo %1
@echo
@call :GetEchoLevel
@echo off
if errorlevel 1 (echo STATE=ON) else (echo STATE=OFF)
exit /b

:GetEchoLevel # Returns errorlevel 0 if echo is off
@for /f %%F in ("%TEMP%\EchoLevel%PID%.tmp") do @(
  (for /F %%N in ("1") do call,) > "%%F"
  for %%S in ("%%F") do @del "%%F" & exit /b %%~zS
)
1

( Editing the answer from Stephan, considering the echo state can be also reported in lower case, and that his answer does not allow for edition. )

The first 2 lines and the last one below save and restore the echo state. The lines 3 to 6 should be replaced by your code:

@(>tmp echo)
@type tmp|find /i "ON" >nul && (@set "oldEcho=ON") || (@set "OldEcho=OFF")
    @echo off                               &::line 3
    echo B:echo was previously %OldEcho%    &::line 4
    REM insert your payload here            &::line 5
    REM restore previous Echo Status:       &::line 6
echo %OldEcho%
2
  • I wouldn't rely on the keyword ON, just use @(FOR /F %%L in ("1") do call,) >tmp. In case of ECHO OFF, the file will be empty,
    – jeb
    Commented Nov 30, 2022 at 6:27
  • Why do type file | find ... when you can just do find ... < file? In fact, find, like many commands, takes an input file as a parameter so you don't even need <.
    – Denis Howe
    Commented Nov 30, 2022 at 21:56
0

_echoStatus.bat

@call :call_echo>%~dp0echo_buf.txt
@echo off
setlocal
   for /f "usebackq" %%i in (`find /C "echo 1"^<"%~dp0echo_buf.txt"`) do set check=%%i
    del %~dp0echo_buf.txt
    if "%check%"=="1" ( 
        @echo on
    ) else ( 
        @echo off
    )
endlocal&set %~1=%check%
@goto :eof

:call_echo
echo 1

You can replace the fourth line (for) with

for /f "usebackq" %%i in ("%~dp0echo_buf.txt") do ( 
    if "%%i"=="1" ( 
        set check=0 
    ) else ( 
        set check=1 
    )
    goto end_for
)
:end_for

test.bat

@echo off
@call %~dp0_echoStatus.bat status
@echo %status%
@echo on
@call %~dp0_echoStatus.bat status
@echo %status%

Not the answer you're looking for? Browse other questions tagged or ask your own question.