0

How can I execute set of batch files from single batch file in parallel and get the exit code from each. When I use start it executes the batch file in parallel (new cmd window) but don't return the exit code from each. And while using call, I can get the exit code but the batch file execution happens sequentially. I have following code:

ECHO ON
setlocal EnableDelayedExpansion
sqlcmd -S server_name -E  -i select_code.sql  >\path\output.txt

for /f "skip=2 tokens=1-3 delims= " %%a in ('findstr /v /c:"-"  \path\output.txt') do (

echo $$src=%%a>\path1\%%c.prm
echo $$trg=%%b>>\path1\%%c.prm
set param_name=%%c

start cmd \k  \path\exec_pmcmd_ctrm.bat workflow_name  %%param_name%%
ping 1.1.1.1 -n 1 -w 5000 > nul
set exitcode="%V_EXITCODE%"
echo %exitcode%>>\path\exitcode.txt
)   

This executes the exec_pmcmd_ctrm.bat 3 times with different variable in parallel , but I am unable to get the exit code from each execution. I tried using call but then I miss the parallel execution of bat file. Any help in this regard?

Anurag
  • 11
  • 1
  • 4
  • You'll need another method of gathering the exitcodes. If you use start to run the programs asynchroneously there is no single point to receive the exitcodes. I darkly remember a tool written by Frank P. Westlake with NamedPipes/MailSlots which could be used for that. It was free but I don't know if it is still downloadable. [msdn:About Mailslots](https://msdn.microsoft.com/de-de/library/windows/desktop/aa365130(v=vs.85).aspx) –  Dec 08 '16 at 12:59
  • where did you get that `%V_EXITCODE%`? – phuclv Dec 09 '16 at 02:21

2 Answers2

2

First of all, the "exit code" from another Batch file (that ends with exit /B value command) is taken via %ERRORLEVEL% variable (not %V_EXITCODE%). Also, if such a value changes inside a FOR loop, it must be taken via Delayed Expansion instead: !ERRORLEVEL! (and EnableDelayedExpansion at beginning of your program). However, these points don't solve your problem because there is a misconception here...

When START command is used (without the /WAIT switch), a parallel cmd.exe process start execution. This means that there is not a direct way that the first Batch file could know in which moment the parallel Batch ends in order to get its ERRORLEVEL at that point! There is not a "wait for a started Batch file" command, but even if it would exist, it don't solve the problem of have several concurrent Batch files. In other words, your problem can not be solved via direct commands, so a work around is necessary.

The simplest solution is that the parallel Batch files store their ERRORLEVEL values in a file that could be later read by the original Batch file. Doing that imply a synchronization problem in order to avoid simultaneous write access to the same file, but that is another story...

Aacini
  • 65,180
  • 12
  • 72
  • 108
  • 1
    You can avoid any synchronization problems by having each batch file write to a different file (which could be supplied as a command line parameter) and gathering the data from them once everything has finished executing – Richard Dec 08 '16 at 16:16
1

Here is a pure solution. Basically, this script executes all batch files located in the same directory simultaneously, where the exit code (ErrorLevel) of each one is written to an individual log file (with the same name as the batch file and extension .log); these files are checked for existence; as soon as such a log file is found, the stored exit code is read and copied into a summary log file, together with the respective batch file name; as soon as all log files have been processed, this script is terminated; the exit code of this script is zero only if all the exit codes of the executed batch files are zero too. So here is the code -- see all the explanatory rem remarks:

@echo off
setlocal EnableExtensions DisableDelayedExpansion

rem // Collect available scripts in an array:
set /A "INDEX=0"
for %%J in ("%~dp0*.bat" "%~dp0*.cmd") do (
    if /I not "%%~nxJ"=="%~nx0" (
        set "ITEM=%%~fJ"
        call set "$BATCH[%%INDEX%%]=%%ITEM%%"
        set /A "INDEX+=1"
    )
)

rem // Execute scripts simultaneously, write exit codes to individual log files:
for /F "tokens=1,* delims==" %%I in ('set $BATCH[') do (
    start "" /MIN cmd /C rem/ ^& "%%~fJ" ^& ^> "%%~dpnJ.log" call echo %%^^ErrorLevel%%
)

rem // Deplete summary log file:
> "%~dpn0.log" rem/

rem // Polling loop to check whether individual log files are available:
:POLLING
rem // Give processor some idle time:
> nul timeout /T 1 /NOBREAK
rem /* Loop through all available array elements; for every accomplished script,
rem    so its log file is availabe, the related array element becomes deleted,
rem    so finally, there should not be any more array elements defined: */
for /F "tokens=1,* delims==" %%I in ('set $BATCH[') do (
    rem // Suppress error message in case log file is not yet available:
    2> nul (
        rem // Read exid code from log file:
        set "ERRLEV=" & set "FILE="
        < "%%~dpnJ.log" set /P ERRLEV=""
        if defined ERRLEV (
            rem // Copy the read exit code to the summary log file:
            set "NAME=%%~nxJ"
            >> "%~dpn0.log" call echo(%%ERRLEV%%    "%%NAME%%"
            rem // Undefine related array element:
            set "%%I="
            rem // Store log file path for later deletion:
            set "FILE=%%~dpnJ.log"
        )
        rem // Delete individual log file finally:
        if defined FILE call del "%%FILE%%"
    )
)
rem // Jump to polling loop in case there are still array elements:
> nul 2>&1 set $BATCH[ && goto :POLLING

rem // Check individual exit codes and return first non-zero value, if any:
set "ERRALL="
for /F "usebackq" %%I in ("%~dpn0.log") do (
    if not defined ERRALL if %%I neq 0 set "ERRALL=%%I"
)
if not defined ERRALL set "ERRALL=0"

endlocal & exit /B %ERRALL%

This approach is quite similar to the one I used in the following Improving Batch File for loop with start subcommand, but there the outputs of simultaneously executed commands are collected.

aschipfl
  • 33,626
  • 12
  • 54
  • 99
  • Thanks @aschipfl.. I made it work based on the above script. Just a small doubt now.. I have collected some parameters in a txt file and need to pass them row by row to a stored proc. tried below code: `for /f "tokens=1-3 delims=," %%a in ('\path\batch_output.txt') do( sqlcmd -S servername -E -i path\spu_update_src_trg_ref.sql -v %%a,%%b,%%c)` but getting error as 'do(' is unexpected here. – Anurag Dec 09 '16 at 12:35
  • I've tried to use this approach but it's not clear for me how this script itself can use the errorcodes of the subscript to return succes or failure. In other words this script always returns an exitcode > 0 despite the subscripts being succesfull. Any ideas on how to change this? – boojum Jan 25 '19 at 10:13
  • The script is intended to collect in a log file the exit codes of the sub-scripts it executes, but its own exit code does not depend on them, it is always `1` because of the last `set $BATCH[` command; I'll try to update the script so that its exit code is zero if all sub-scripts return zero too... – aschipfl Jan 25 '19 at 12:14
  • @boojum, I extended the script a bit now, so its exit code depends on the sub-scripts... – aschipfl Jan 25 '19 at 12:33