1

I have a batch inside a folder whose goal is to execute all the batch files located in its sub-folders and evaluate their errorlevel. If at least one of them is equal to 1, the main batch will return an exit code equal to 1. This does not mean that the main batch have to exit with the first errorlevel equal to 1: all the sub-batch files must be executed anyway.

EDIT: all the sub-batch files return an exit code equal to 1 if they fail or equal to 0 if they pass (they are all batch files for testing purpose I wrote myself).

Problem: the exit_code variable never changes outside the loops.

I found similar problems here on SO (and a very similar one: Counting in a FOR loop using Windows Batch script) but they didn't help (probably I didn't understand... I don't know, I'm new to batch scripting).

Thanks in advance.

CODE:

@echo off
setlocal enabledelayedexpansion
set exit_code=0
for /D %%s in (.\*) do (
    echo ********************  %%~ns  ********************
    echo.
    cd %%s
    for %%f in (.\*.bat) do (
        echo calling %%f
        call %%f
        if errorlevel 1 (
            set exit_code=1
        )
    )
    echo.
    echo.
    cd ..   
)

echo !exit_code!

exit /B !exit_code!
John Reds
  • 117
  • 14
  • What are you expecting `errorlevel` to reveal in `if errorlevel 1`? Are you expecting it to provide an error code for something in the individual batch files, or whether the `call %%f` command was successful? `call %%f` will always be successful if the file is a valid batch file, and exists, _(which must be true because it's just been returned as an existing batch file)_ I'd assume that each of your individual batch files should exit upon success with a known exit code, you could then check for that exit code, to determine if that script was indeed successful. – Compo Oct 29 '20 at 16:28
  • You need to keep the variable `exit_code` undefined. `set "exit_code="` Then use a double `IF` to set the `exit_code` variable. `IF ERRORLEVEl 1 IF NOT DEFINED exit_code set exit_code=1`. – Squashman Oct 29 '20 at 16:54
  • @Compo I edited the question to clarify your point. I catch the errorlevel as I expect, the problem is with `exit_code` – John Reds Oct 30 '20 at 08:46

1 Answers1

2

Let us assume the current directory on starting the main batch file is C:\Temp\Test containing following folders and files:

  • Development & Test
    • Development & Test.bat
  • Hello World!
    • Hello World!.bat
  • VersionInfo
    • VersionInfo.bat
  • Main.bat

Batch file Development & Test.bat contains just the line:

@dir ..\Development & Test

Batch file Hello World!.bat contains just the line:

@echo Hello World!

Batch file VersionInfo.bat contains just the line:

@ver

Batch file main.bat contains following lines:

@echo off
setlocal EnableExtensions DisableDelayedExpansion
cls
set "exit_code=0"
for /D %%I in ("%~dp0*") do (
    echo ********************  %%~nxI  ********************
    echo(
    for %%J in ("%%I\*.bat") do (
        echo Calling %%J
        echo(
        pushd "%%I"
        call "%%J"
        if errorlevel 1 set "exit_code=1"
        popd
    )
    echo(
    echo(
)
echo Exit code is: %exit_code%
endlocal & exit /B %exit_code%

A command prompt is opened in which next the following command lines are executed manually one after the other:

C:\Temp\Test\Main.bat
echo Errorlevel is: %errorlevel%
ren "C:\Temp\Test\Development & Test\Development & Test.bat" "Development & Test.cmd"
C:\Temp\Test\Main.bat
echo Errorlevel is: %errorlevel%

The first execution of Main.bat results in exit with value 1 as it can be seen in command prompt window on the line:

Errorlevel is: 1

The reason is the wrong coded dir command with the directory name not enclosed in double quotes resulting in interpreting Test as command to execute. For that reason the dir command line results in following error output:

 Volume in drive C is TEMP
 Volume Serial Number is 14F0-265D

 Directory of C:\Temp\Test

File Not Found
'Test' is not recognized as an internal or external command,
operable program or batch file.

The exit code of this batch file is not 0 due to the error and for that reason the condition if errorlevel 1 is true and set "exit_code=1" is executed already on first executed batch file.

The processing of the other two batch files always end with 0 as exit code.

The command ren is used to change the file extension of Development & Test.bat to have the batch file next with name Development & Test.cmd resulting in ignoring it by main.bat. The second execution of Main.bat results in exit with 0 as it can be seen on the line:

Errorlevel is: 0

Please read the following pages for the reasons on all the code changes:

Summary of the changes:

  • Delayed expansion is not enabled in Main.bat as not required here to process also correct directory and file names containing an exclamation mark like C:\Temp\Test\Hello World!\Hello World!.bat.
  • I and J are used as loop variables instead of s and f because of the latter two letters could be misinterpreted as loop variable modifiers in some cases. Therefore it is better to avoid the letters which have a special meaning for command for on referencing the loop variables.
  • %~dp0 is used instead of .\ to make sure that the batch file searches for non-hidden subdirectories in the directory of the batch file independent on what is the current directory on starting the batch file. This expression references drive and path of argument 0 which is the full path of currently executed batch file Main.bat. The referenced path of the batch file always ends with a backslash and for that reason %~dp0 is concatenated with * without an additional backslash.
  • Directory and file name arguments are enclosed in double quotes to work also for names containing a space or one of these characters &()[]{}^=;!'+,`~. %%~nxI and %%J in the two echo command lines are not enclosed in double quotes as not necessary as long as delayed expansion is not enabled. The batch file makes sure that this is not the case for Main.bat.
  • The usage of "%~dp0*" instead of just .\* in first FOR loop results in getting assigned to loop variable I the directory names with full path never ending with a backslash. The usage of "%%I\*.bat" makes sure to get assigned to loop variable J the full qualified file name of a non-hidden batch file. It is in general better to use full qualified directory/file names wherever possible. This helps also quite often on errors.
  • The two cd commands are replaced by pushd and popd and moved inside the inner FOR loop. Then it does not matter if a called batch file works only with current directory being the directory of the called batch file or works independent on current directory like Main.bat. Further it does not longer matter if a called batch file changes the current directory as with popd the initial current directory on starting Main.bat is restored as current directory which could be the directory in which files are stored to be processed by the called batch files. The usage of pushd and popd makes this batch file also working on being stored on a network resource and Main.bat is started with its UNC path.

The most important modification is on last command line:

endlocal & exit /B %exit_code%

This command line is first parsed by Windows command processor cmd.exe with replacing %exit_code% by current value of environment variable exit_code defined inside the local environment setup with setlocal EnableExtensions DisableDelayedExpansion. So the command line becomes either

endlocal & exit /B 0

or

endlocal & exit /B 1

Then Windows command processor executes command endlocal to restore the previous environment defined outside Main.bat which results in exit_code no longer defined if not defined in initial execution environment. Then the command exit with option /B to exit just processing of batch file Main.bat is executed with returning 0 or 1 to the parent process which is cmd.exe which assigns the exit code to variable errorlevel.

Well, there is one issue left with the batch file code of Main.bat. A called batch file could modify the value of environment variable exit_code of Main.bat on using the same environment variable without definition of a local environment by using command setlocal. The solution would be to use additionally the commands setlocal before and endlocal after calling a batch file.

@echo off
setlocal EnableExtensions DisableDelayedExpansion
cls
set "exit_code=0"
for /D %%I in ("%~dp0*") do (
    echo ********************  %%~nxI  ********************
    echo(
    for %%J in ("%%I\*.bat") do (
        echo Calling %%J
        echo(
        pushd "%%I"
        setlocal
        call "%%J"
        endlocal
        if errorlevel 1 set "exit_code=1"
        popd
    )
    echo(
    echo(
)
echo Exit code is: %exit_code%
endlocal & exit /B %exit_code%

The command endlocal does not modify errorlevel.

To understand the commands used and how they work, open a command prompt window, execute there the following commands, and read the displayed help pages for each command, entirely and carefully.

  • call /?
  • cls /?
  • echo /?
  • endlocal /?
  • for /?
  • if /?
  • popd /?
  • pushd /?
  • set /?
  • setlocal /?
Mofi
  • 46,139
  • 17
  • 80
  • 143
  • Hi @Mofi, thank you very much: you saved me! The problem was that I was using `exit_code` variable also in the called batch files and so `setlocal` and `endlocal` before and after the call fixed the issue. Thanks again, you've been very clear and helpful. – John Reds Oct 30 '20 at 12:24