I've encapsalated much of the code into generic functions that should make it easier to launch a series of parallel processes and wait for all of them to finish.
I've also developed a demo that shows how the functions can be used. It launches a combination of a non-console GUI process, an interactive console process in a new window, and non-interactive console processes with output displayed in the main window.
The big news is I show how to establish dependencies between parallel processes. The example has the dependent process waiting for a single parent process to finish before launching. But the entire master batch script waits for all processes to finish. There is no reason why a master cannot launch another master, and then have a dependent process wait for the child master to finish

The demo is totally silly - it launches useless commands just to show how the functions work. But a real world script would typically be launching complex scripts.
Code: Select all
@echo off
setlocal enableDelayedExpansion
:: Initialize parallel processing
call :launchInitialize
:: Start multiple parallel asynchronous processes, with a callback cleanup
:: method for each one that gets executed when the process terminates.
:: Both the process command and the cleanup method MUST be stored in
:: environment variables whose names are passed to the launch routines.
:: The use of variables enables the commands to be complex with spaces,
:: pipes, redirection, and quotes as needed.
:: Demonstrate launching a non-console process that has a dependent console
:: process that gets launched once this one terminates.
set "cmd=calc.exe"
set "clean=:cleanupLaunch calc.exe "help shift""
call :launchIgnoreOutput cmd clean
:: Demonstrate launching a complex console command that uses a pipe.
:: Show the output in the main console window.
set "cmd=(systeminfo|findstr /b OS)"
set "clean=:cleanupShowOutput systeminfo
call :launchShowOutput cmd clean
:: Demonstrate launching a complex console command that generates an error
:: message. Show both stdout and stderr in the main console window.
:: The complex command uses both concatenated commands and redirection.
set "cmd=(dir :DoesNotExist & ping /n 5 ::1 2>nul 1>nul)"
set "clean=:cleanupShowOutput dir"
call :launchShowOutput cmd clean
:: Demonstrate launching an interactive console command that has a
:: dependent console process that gets launched once this one terminates.
:: This silly example just asks the user to press a key to continue.
set "cmd=pause"
set "clean=:cleanupLaunch pause "ping /n 5 ::1""
call :launchConsole cmd clean
:: Wait for the launched processes to terminate
call :launchWait
::set launchClean
:: We have reached the end!
echo(
echo ========================================================================
echo That's all folks!
exit /b
:: *****************************************************************************
:: The following callback cleanup routines should be customized to meet your
:: needs. Their purpose is to peform cleanup operations that must be executed
:: once a launched process terminates. The routines below print output to the
:: master screen and launch dependent processes. But there really is no limit
:: to the complexity of what you might do. You may create as many cleanup
:: routines as are needed.
:cleanupLaunch endingApp launchApp
echo(
echo ------------------------------------------------------------------------
::use FOR to gain access to the :launchWait FOR variables
for %%. in (1) do echo Process %%N (%~1) has ended
echo Now launching: %~2
set cmd=%~2
set "cleanup=:cleanupShowOutput %2
call :launchShowOutput cmd cleanup
exit /b
:cleanupShowOutput endingApp
echo(
echo ------------------------------------------------------------------------
::use FOR to gain access to the :launchWait FOR variables
for %%. in (1) do (
echo process %%N: (%~1^) results:
echo(
type "%launchLock%%%N"
)
exit /b
:: *****************************************************************************
:: The following routines are static generic routines to manage the process
:: of launching multiple processes in parallel and waiting for them to finish.
:: Each launched process can have a callback method specified that gets executed
:: when the process terminates.
::
:: Each of the 3 routines that actually launch a process takes one required
:: argument and one optional argument.
::
:: cmdVar - The name of a variable that contains a string specifying the
:: command to be executed. It can be arbitrarilly complex. However,
:: it cannot call a label within the master script.
::
:: [cleanupVar] - The name of a variable that contains a string specifying a
:: batch file, :label, or command that gets called once
:: the launched process terminates. The called method can
:: include arguments of arbitrary complexity.
::
:: Because variables are used instead of passing string literals, the command
:: strings can include spaces, quotes, redirection, pipes, etc. as needed.
:launchInitialize
:: Get a unique base lock name for this particular instantiation.
:: Incorporate a timestamp from WMIC if possible, but don't fail if
:: WMIC not available. Also incorporate a random number.
set "launchLock="
for /f "skip=1 delims=-+ " %%T in ('2^>nul wmic os get localdatetime') do (
set "launchLock=%%T"
goto :break
)
:break
set "launchLock=%temp%\launchLock%launchLock%_%random%_"
:: Initialize the counters
set /a "launchStartCount=0, launchEndCount=0"
:: Clear any existing end flags
for /f "delims==" %%A in ('2^>nul set launchEndFlag') do set "%%A="
exit /b
:launchConsole cmdVar [cleanupVar]
:: This launches a console application, batch file, script, or command in a
:: new console window. Both stdout and stderr are left alone so the user can
:: see the output and interact with the console as needed. An unused file
:: handle is used for the lock file.
set /a launchStartCount+=1
start "" cmd /c 9^>"%launchLock%%launchStartCount%" !%~1!
set "launchCleanup%launchStartCount%=!%~2!"
exit /b
:launchShowOutput cmdVar [cleanupVar]
:: This should be a console app for which you want to see the outut
:: displayed on the master screen, but you want to make sure the output
:: is not interleaved with the output of other processes. The process must
:: not require any user interaction. Both stdout and stderr are captured
:: in the lock file, so it is safe to run the process within the master
:: console using the /B option. The cleanup callback method should TYPE
:: the output (the lock file) to the screen.
set /a launchStartCount+=1
start /b "" cmd /c 1^>"%launchLock%%launchStartCount%" 2^>^&1 !%~1!
set "launchCleanup%launchStartCount%=!%~2!"
exit /b
:launchIgnoreOutput cmdVar [cleanupVar]
:: This could launch a non-console application, perhaps with a GUI, that may
:: or may not require user interaction. Or it could launch a non-interactive
:: console application, batch file, script, or command for which you don't
:: need to see the output on the screen. Both stdout and stderr are redirected
:: to nul, so it is safe to run the process within the master console using
:: the /B option. An unused file handle is used for the lock file.
set /a launchStartCount+=1
start /b "" cmd /c 9^>"%launchLock%%launchStartCount%" 1^>nul 2^>nul !%~1!
set "launchCleanup%launchStartCount%=!%~2!"
exit /b
:launchWait - Wait for all launched processes to finish before returning.
:: Poll each lock file status in a loop to detect when finished.
:: Incorporate a ~1 sec delay so the CPU is not inundated.
:: Use append mode to test the lock so output is not clobbered!
:: Call the callback method as each application finishes.
:: Delete all lock files once all processes have finished.
>nul 2>nul ping -n 2 ::1
for /l %%N in (1 1 %launchStartCount%) do (
if not defined launchEndFlag%%N if exist "%launchLock%%%N" (
if defined launchCleanup%%N call %%launchCleanup%%N%%
set /a "launchEndCount+=1, launchEndFlag%%N=1"
) 9>>"%launchLock%%%N"
) 2>nul
if %launchEndCount% neq %launchStartCount% goto :launchWait
2>nul del "%launchLock%*"
exit /b
Here is what the output might look like (the order of events will vary depending on when the interactive processes are closed).
Code: Select all
------------------------------------------------------------------------
Process 4 (pause) has ended
Now launching: ping /n 5 ::1
------------------------------------------------------------------------
process 3: (dir) results:
Volume in drive C is OS
Volume Serial Number is EE2C-5A11
Directory of C:\Users\Public\utils
File Not Found
------------------------------------------------------------------------
Process 1 (calc.exe) has ended
Now launching: help shift
------------------------------------------------------------------------
process 5: (ping /n 5 ::1) results:
Pinging ::1 from ::1 with 32 bytes of data:
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Ping statistics for ::1:
Packets: Sent = 5, Received = 5, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
Minimum = 0ms, Maximum = 0ms, Average = 0ms
------------------------------------------------------------------------
process 6: (help shift) results:
Changes the position of replaceable parameters in a batch file.
SHIFT [/n]
If Command Extensions are enabled the SHIFT command supports
the /n switch which tells the command to start shifting at the
nth argument, where n may be between zero and eight. For example:
SHIFT /2
would shift %3 to %2, %4 to %3, etc. and leave %0 and %1 unaffected.
------------------------------------------------------------------------
process 2: (systeminfo) results:
OS Name: Microsoftr Windows VistaT Home Premium
OS Version: 6.0.6002 Service Pack 2 Build 6002
OS Manufacturer: Microsoft Corporation
OS Configuration: Standalone Workstation
OS Build Type: Multiprocessor Free
========================================================================
That's all folks
Dave Benham