How to pass environment variables as parameters by reference to another batch file?

Here is the full first batch file rewritten:

@echo off
setlocal EnableExtensions DisableDelayedExpansion

rem Get start time region independent in seconds and hundredths of a second
for /F "tokens=2 delims==" %%I in ('%SystemRoot%\System32\wbem\wmic.exe OS GET LocalDateTime /VALUE') do set "LocalTime=%%I"
set /A Start100S=(1%LocalTime:~8,2%-100)*360000 + (1%LocalTime:~10,2%-100)*6000 + (1%LocalTime:~12,2%-100)*100 + (1%LocalTime:~15,2%-100)

rem Modules found
set "anz_Modules=0"
rem Modules build successful
set "SR_Modules=0"
set "CPP_Modules=0"

call "%~dp0Pearl_p2cpp.bat" EXTRA      auto anz_Modules SR_Modules CPP_Modules
call "%~dp0Pearl_p2cpp.bat" FKT        auto anz_Modules SR_Modules CPP_Modules
call "%~dp0Pearl_p2cpp.bat" FKTREP     auto anz_Modules SR_Modules CPP_Modules
call "%~dp0Pearl_p2cpp.bat" FKTZU      auto anz_Modules SR_Modules CPP_Modules
call "%~dp0Pearl_p2cpp.bat" KALIB      auto anz_Modules SR_Modules CPP_Modules
call "%~dp0Pearl_p2cpp.bat" LASER      auto anz_Modules SR_Modules CPP_Modules
call "%~dp0Pearl_p2cpp.bat" MOAB       auto anz_Modules SR_Modules CPP_Modules
call "%~dp0Pearl_p2cpp.bat" MOENDE     auto anz_Modules SR_Modules CPP_Modules
call "%~dp0Pearl_p2cpp.bat" MOZU       auto anz_Modules SR_Modules CPP_Modules
call "%~dp0Pearl_p2cpp.bat" MTERM      auto anz_Modules SR_Modules CPP_Modules
call "%~dp0Pearl_p2cpp.bat" MTERM\RTOS auto anz_Modules SR_Modules CPP_Modules
call "%~dp0Pearl_p2cpp.bat" PAKZU      auto anz_Modules SR_Modules CPP_Modules
call "%~dp0Pearl_p2cpp.bat" PE         auto anz_Modules SR_Modules CPP_Modules
call "%~dp0Pearl_p2cpp.bat" SRS        auto anz_Modules SR_Modules CPP_Modules
call "%~dp0Pearl_p2cpp.bat" SRTBEG2    auto anz_Modules SR_Modules CPP_Modules
call "%~dp0Pearl_p2cpp.bat" STDBY      auto anz_Modules SR_Modules CPP_Modules
call "%~dp0Pearl_p2cpp.bat" VDE        auto anz_Modules SR_Modules CPP_Modules
call "%~dp0Pearl_p2cpp.bat" VGL        auto anz_Modules SR_Modules CPP_Modules

rem Get end time region independent in seconds and hundredths of a second
for /F "tokens=2 delims==" %%I in ('%SystemRoot%\System32\wbem\wmic.exe OS GET LocalDateTime /VALUE') do set "LocalTime=%%I"
set /A Stop100S=(1%LocalTime:~8,2%-100)*360000 + (1%LocalTime:~10,2%-100)*6000 + (1%LocalTime:~12,2%-100)*100 + (1%LocalTime:~15,2%-100)

rem Test midnight rollover. If so, add one day=8640000 1/100ths seconds
if %Stop100S% LSS %Start100S% set /A Stop100S+=8640000
set /A TookTime=Stop100S - Start100S
echo ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
echo ----------------------------------------------------------------------------------------------------
echo Es wurden insgesammt [%anz_Modules%]*.P (PEARL)-Module gefunden.
echo Aus diesen wurden [%SR_Modules%]-SREC und [%CPP_Modules%]-C++ Dateien in %TookTime:~0,-2%.%TookTime:~-2% Sekunden erzeugt!
echo ----------------------------------------------------------------------------------------------------
echo ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
endlocal

This batch file defines first with the first two command lines completely the execution environment:

  • The command echo mode is turned off.
  • The command extensions are enabled.
  • The delayed variable expansion is disabled.

The command SETLOCAL

  • pushes the current status of command extensions on stack,
  • pushes the current status of delayed expansion on stack,
  • pushes the full qualified path of the current directory on stack,
  • pushes the pointer to the current environment variables list on stack,
  • creates a copy of the current environment variables list and uses that copy as active environment variables list.

This is always done by the command SETLOCAL independent on being run with no, one or two parameters.

The command ENDLOCAL

  • discards the current environment variables list,
  • pops the pointer to the previous environment variables list from stack and makes this environment variables list again the active environment variables list,
  • pops the full qualified path of the previous current directory from stack and makes this directory the current directory if still existing,
  • pops the previous status of delayed expansion from stack and sets status of delayed expansion accordingly,
  • pops the previous status of command extensions from stack and sets status of command extensions accordingly.

This is done also by the Windows command processor cmd.exe implicitly for each SETLOCAL executed within a batch file for which no ENDLOCAL execution was done explicitly before exiting the processing of a batch file. For that reason many batch files using SETLOCAL somewhere at top do not contain ENDLOCAL somewhere at end.

See also:

So the batch file runs in its own local execution environment which does not depend on something defined outside of the batch file with the exception of the current directory because of all the directory paths passed from this batch file to the called batch file Pearl_p2cpp.bat are relative to current directory defined outside of the batch file. That is perhaps not so good, but I can´t rate that.

It would be possible to use as third command line pushd "%~dp0" to push the current directory path on stack and make the directory of the batch file the current directory and use as last but one line popd to pop from stack the directory path of the initial current directory and set this directory again as current directory.

The next two command lines get current local time with hundredths of a second region/country independent and calculate the value for the start time. For an explanation see Time is set incorrectly after midnight.

Each two digit decimal number of the time is preceded in the arithmetic expression by character 1 resulting in all numbers being now three digit numbers with lowest value being 100. This is done because of SET interprets a number in an arithmetic expression starting with 0 as octal number and not as decimal number. The digits 8 and 9 are invalid in an octal number. For that reason 08 and 09 would be interpreted as invalid octal numbers instead of decimal numbers 8 and 9. The simple solution to avoid the interpretation of the decimal numbers as octal numbers is inserting before each two digit decimal number the character 1 in string of the arithmetic expression and subtracting 100 from each decimal number on evaluation of the arithmetic expression.

A better code to get start/end time region/country dependent with time format hh:mm::ss.xx would be:

rem Get start time region dependent in seconds and hundredths of a second.
set "LocalTime=%TIME%"
set "Hour=%LocalTime:~0,2%"
if "%Hour:~0,1%" == "0" (set "Hour=%Hour:~1%") else if "%Hour:~0,1%" == " " set "Hour=%Hour:~1%"
set "Minute=%LocalTime:~3,2%
if "%Minute:~0,1%" == "0" set "Minute=%Minute:~1%"
set "Second=%LocalTime:~6,2%"
if "%Second:~0,1%" == "0" set "Second=%Second:~1%"
set "HundredthOfSecond=%LocalTime:~9,2%
if "%HundredthOfSecond:~0,1%" == "0" set "HundredthOfSecond=%HundredthOfSecond:~1%"
set /A Start100S=Hour*360000 + Minute*6000 + Second*100 + HundredthOfSecond

rem Or for the end time the lines above with last line modified to:
set /A Stop100S=Hour*360000 + Minute*6000 + Second*100 + HundredthOfSecond

The octal number problem is avoided here with additional IF conditions which redefine the appropriate environment variable on first character of string value being 0 (or a space on hour value) with just the second character. So no number in arithmetic expression has a leading 0.

Please never reference an environment variable within an arithmetic expression being the string after set /A with % or !. That is unnecessary and counterproductive. Environment variables can be referenced in an arithmetic expression by just their names to make it easier to use them especially in an IF condition block or a FOR loop. There are exceptions like the environment variable name contains a space or a character being interpreted as a mathematical operator in an arithmetic expression, or just a part of the string value of an environment variable should be used in the arithmetic expression. But in general just the variable name can be used in an arithmetic expression on using good environment variable names.

The next command lines define three environment variables with value 0. There should not be used in general an arithmetic expression to define an environment variable with a number. An environment variable is always a string in memory and never an integer. So it takes much more CPU instructions to define an environment variable with an arithmetic expression like set /A "anz_Modules = 0" in comparison to a simple string definition with set "anz_Modules=0" although no user notices ever the difference as CPUs are very fast nowadays. See also: Why is no string output with ‘echo %var%’ after using ‘set var = text’ on command line?

The next block of command lines are simple calls of the batch file Pearl_p2cpp.bat with fully qualified file name. This batch file is always stored in same directory as the first batch file and for that reason the usage of %~dp0 expanding to full path of first batch file ending always with a backslash makes sure (nearly always) that the second batch file is really called independent on which directory is the current directory.

The command lines with command CD are all removed as they should not be necessary anymore with the PUSHD and POPD usage in batch file Pearl_p2cpp.bat.

Note: Using the command CALL on an internal command of cmd.exe like CD is dangerous and at least increases the execution time of the batch file. The Windows command processor expects after CALL either an argument string starting with a colon being interpreted as a label in current batch file which should be called like a subroutine, i.e. a batch file inside a batch file, or the name of a batch file without or with path and without or with file extension. So call cd results in cmd.exe searching in current directory and next in all the directories in value of environment variable PATH for a file with file name cd (case-insensitive) and with a file extension listed in value of environment variable PATHEXT and when it really finds such a file, this file is executed with passing the arguments of internal command CD to this executable or script instead of running the internal command to change the current directory.

Next the stop/end time is determined and the time difference is calculated for the final information output at end of execution of the batch file.

Here is the full second batch file Pearl_p2cpp.bat rewritten, but not fully tested:

@echo off
echo Kompiliere und portiere Module im Unterverzeichnis: "%~1"
pushd "..\%~1" || exit /B
echo "%CD%"
echo *.Px und *.SR Dateien werden entfernt.

if /I not "%~2" == "auto" pause

if exist *.Px del /Q *.Px
if exist *.SR del /Q *.SR

if /I "%~2" == "auto" GOTO no_time_rec

rem Get start time region independent in seconds and hundredths of a second
for /F "tokens=2 delims==" %%I in ('%SystemRoot%\System32\wbem\wmic.exe OS GET LocalDateTime /VALUE') do set "LocalTime=%%I"
set /A Start100S=(1%LocalTime:~8,2%-100)*360000 + (1%LocalTime:~10,2%-100)*6000 + (1%LocalTime:~12,2%-100)*100 + (1%LocalTime:~15,2%-100)

:no_time_rec

setlocal EnableExtensions EnableDelayedExpansion
echo Konvertierungen starten:
set "SRecordSuccess=0"
set "CppSuccess=0"
set "numberOfModules=0"

for /F "eol=| delims=" %%I in ('dir /A-D /B *.P 2^>nul') do (

    echo Modul %%I
    set "doCompilePort=true"

    if /I "%%I" == "INCL.P"     set "doCompilePort="
    if /I "%%I" == "INCL_FKR.P" set "doCompilePort="
    if /I "%%I" == "INCL_GLO.P" set "doCompilePort="
    if /I "%%I" == "INCL_PE.P"  set "doCompilePort="
    if /I "%%I" == "INCL_STB.P" set "doCompilePort="
    if /I "%%I" == "INCL_VDE.P" set "doCompilePort="

    if defined doCompilePort (
        echo doCompilePort:true
        echo start compiler

        set /A numberOfModules+=1
        echo module number !numberOfModules!

        echo Convert
        if exist "%%~nI.cpp" del /F "%%~nI.cpp"
        echo returnValue del !errorlevel!

        "C:\programme\compiler\rtos-uh\pe\pe.exe" "C:\programme\compiler\rtos-uh\ppc\PEARL90\vers16_6\P16Q6H.VBI" -si="%%I" -co="%%~nI.SR" -lo=no -e0
        if errorlevel 1 (
            rem Do something here to address the error.
            echo returnValue Pearl Compiler !errorlevel!
        ) else set /A SRecordSuccess+=1

        call pqprep %%I
        echo returnValue pqprep !errorlevel!

        call p2cpp %%Ix
        if errorlevel 1 (
            rem Do something here to address the error.
            echo returnValue p2cpp !errorlevel!
        ) else set /A CppSuccess+=1
    ) else echo doCompilePort:false

    echo ----------------------------------------------------------------------------------------------------
)
echo ----------------------------------------------------------------------------------------------------

echo number of modules: %numberOfModules%
echo successfully created S-Records: %SRecordSuccess%
echo successfully created cpp-files: %CppSuccess%

if %numberOfModules% == %SRecordSuccess% if %numberOfModules% == %CppSuccess% echo all modules successful

rem Globale Variablen aus *_ALLES.bat addieren.
if "%~3" == "" goto noParams
if "%~4" == "" goto noParams
if "%~5" == "" goto noParams

set /A numberOfModules+=%~3
set /A SRecordSuccess+=%~4
set /A CppSuccess+=%~5

:noParams
if /I "%~2" == "auto" goto no_time_rec2

rem Get end time region independent in seconds and hundredths of a second
for /F "tokens=2 delims==" %%I in ('%SystemRoot%\System32\wbem\wmic.exe OS GET LocalDateTime /VALUE') do set "LocalTime=%%I"
set /A Stop100S=(1%LocalTime:~8,2%-100)*360000 + (1%LocalTime:~10,2%-100)*6000 + (1%LocalTime:~12,2%-100)*100 + (1%LocalTime:~15,2%-100)

rem Test midnight rollover. If so, add 1 day=8640000 1/100ths seconds
if %Stop100S% LSS %Start100S% set /A Stop100S+=8640000
set /A TookTime=Stop100S - Start100S

echo Die Dateien wurden in %TookTime:~0,-2%.%TookTime:~-2% Sekunden verarbeitet und in PEARL-SRs kompiliert.
echo ----------------------------------------------------------------------------------------------------

:no_time_rec2
echo *.Px Dateien werden entfernt.
echo ----------------------------------------------------------------------------------------------------
if /I not "%~2" == "auto" pause
if exist *.Px   del /Q *.Px
if exist *.phx  del /Q *.phx
if exist *.INCx del /Q *.INCx
if exist *.incx del /Q *.incx

if not "%~3" == "" (endlocal & set "%~3=%numberOfModules%" & set "%~4=%SRecordSuccess%" & set "%~5=%CppSuccess%") else endlocal
echo Vorgang beenden
popd

This batch file pushes first the full path of current directory on stack and makes the directory according to passed first argument string with a path relative to current directory the new current directory. It is expected by the batch file that the first passed directory is a subdirectory of the parent directory of the current directory.

Please read the Microsoft documentation about Naming Files, Paths, and Namespaces for an explanation of replacing .././ by ..\. The directory separator on Windows is the backslash \ and not the slash / as on Linux/Mac. Most file/folder argument strings of Windows work also with / because of Windows file management replaces all / by \ before passing the file/folder name string to the file system. But I know several use cases on which the usage of / in a file/folder name strings results in an unexpected behavior. A .\ (current directory) after ..\ (parent directory) is always useless. The current directory of the parent directory is the parent directory referenced already with ..\ as long as there is a parent directory at all.

File/folder strings should be always enclosed in " to make sure that they commands using them always work even on containing a space or one of these characters &()[]{}^=;!'+,`~.

The command pwd (print working directory) is a Linux command. One replacement on Windows is echo "%CD%" or not safe echo %CD%. The latter does something different than just outputting the current directory path if the current directory path contains one or more &. So it is better to enclose the current directory path in double quotes, see paragraph above. A safe alternative without " would be setlocal EnableDelayedExpansion & echo !CD!& endlocal. But a lot is done in the background just to output the current directory path without double quotes in this case.

The command rm (remove) is also a Linux command. The Windows command is del (delete) for files and rd (remove directory) for directories.

The improved second batch file contains still the command SETLOCAL to enable delayed expansion. But this is done only for some echo command lines in the for loop. The really needed command lines do not need anymore delayed environment variable expansion. All other command lines than a few echo command lines use the recommended syntax which makes the usage of delayed expansion not necessary.

I could not test anything inside the FOR loop and so can only hope that this part of the second batch file works as expected by you.

The two file names passed to pqprep and p2cpp (batch files, executables?) in the current directory set at top of the batch file are not enclosed in " as it should be done because of I don’t know if these two scripts or executables support file names enclosed in double quotes. The two rewritten batch files do that now with the exception of the two command lines with pqprep and p2cpp in comparison to the two original batch files which would fail multiple times for directory or file names with one or more space or &()^=;!'+, in file/directory names. Well, an exclamation mark in name of a *.P file to process by the FOR loop would be still a problem because of enabled delayed expansion.

It is necessary to pass the name of the environment variables to modify by the second batch file to the second batch file and not their current values as otherwise the second batch file would not know which environment variables to redefine in the environment variables list of the first batch file.

I don’t understand why there is the deletion of *.INCx and of *.incx files because of Windows file systems are case-insensitive. But the current directory is perhaps a network drive and the file system on the network resource is a Linux file system which is case-sensitive.

Most important is the last IF condition command line:

if not "%~3" == "" (endlocal & set "%~3=%numberOfModules%" & set "%~4=%SRecordSuccess%" & set "%~5=%CppSuccess%") else endlocal

The environment variables numberOfModules, SRecordSuccess and CppSuccess are local environment variables of the second batch file. Their values have been increased some lines above by the values of the environment variables anz_Modules, SR_Modules and CPP_Modules of which names are passed to the second batch file.

It is necessary now to explicitly run ENDLOCAL to restore the environment variables list of first batch file before exiting processing of the second batch file with additionally redefine the environment variables anz_Modules, SR_Modules and CPP_Modules in the environment variables list of the calling batch file. This is done by using on same line respectively in same command block starting with ( and ending with ) three times the command SET with the environment variable names passed to the batch file as arguments and the current values of the three local environment variables.

The Windows command processor CMD first processes the entire command line with the IF condition as described in detail by How does the Windows Command Interpreter (CMD.EXE) parse scripts? During this processing of the command line %~3 is replaced by anz_Modules and %numberOfModules%" by the current value of local environment variable numberOfModules. The same is done also for %~4, %SRecordSuccess%, %~5 and %CppSuccess%. The IF command line finally executed does not contain anymore an argument or an environment variable reference. The entire command line contains only all the referenced strings. For that reason the three environment variables of the calling batch file are redefined with the three values of the local environment variables which do not exist anymore after execution of the command ENDLOCAL in either true or in false branch of the IF condition.

The last IF condition could be written also as:

if not "%~3" == "" (
    endlocal
    set "%~3=%numberOfModules%"
    set "%~4=%SRecordSuccess%"
    set "%~5=%CppSuccess%"
) else endlocal

The result would be the same as the Windows command processor parses the entire block and replaces all argument and environment variable references by their string values before execution of the command IF.

Last the second batch file restores the initial current directory which makes the usage of command CD in the first batch file unnecessary.

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

  • call /?
  • cmd /?
  • del /?
  • dir /?
  • echo /?
  • endlocal /?
  • for /?
  • goto /?
  • if /?
  • pause /?
  • popd /?
  • pushd /?
  • set /?
  • setlocal /?
  • wmic /?
  • wmic os /?
  • wmic os get /?

See also single line with multiple commands using Windows batch file for an explanation of the operators & and ||.

Read the Microsoft documentation about Using command redirection operators for an explanation of 2>nul. The redirection operator > must be escaped with caret character ^ on FOR command line to be interpreted as literal character when Windows command interpreter processes this command line before executing command FOR which executes the embedded dir command line with using a separate command process started in background with %ComSpec% /c and the command line within ' appended as additional arguments.

Leave a Comment