Waiting for Parallel batch scripts or command lines - batch-file

I am finding it difficult to modify the script here to suit my requirements:
https://stackoverflow.com/a/12665498/4683898
#echo off
setlocal
set "lock=%temp%\wait%random%.lock"
:: Launch one and two asynchronously, with stream 9 redirected to a lock file.
:: The lock file will remain locked until the script ends.
start "" cmd /c 9>"%lock%1" one.bat
start "" cmd /c 9>"%lock%2" two.bat
:Wait for both scripts to finish (wait until lock files are no longer locked)
1>nul 2>nul ping /n 2 ::1
for %%N in (1 2) do (
( rem
) 9>"%lock%%%N" || goto :Wait
) 2>nul
::delete the lock files
del "%lock%*"
:: Launch three and four asynchronously
start "" cmd /c three.bat
start "" cmd /c four.bat
I am using a batch script, not to execute further batch scripts, but simply executing cmd commands (which run for a period of time) in parallel, and that works fine with the above script.
However, I want to be able to run more than just 2 commands/scripts, i.e. 3, 4, 5, (or whatever I desire) commands at a single time, running in parallel, let's call it, x.
So, I want to run x amount of cmd commands (that are executing in parallel), wait for them to terminate (using /c), and then executing the next bunch of x amount of cmd commands, and then the next bunch, etc, etc, until all cmd commands have executed.
How could I modify that script accordingly? (I have made a few attempts albeit with repeating errors "The process cannot access the file because it is being used by another process." in the initiating batch script; I presume this 'file' refers to the lock file.)
Thanks
EDIT:
#echo off
setlocal
set "lock=%temp%\wait%random%.lock"
call :a start cmd.exe /c somecommandA 1
call :a start cmd.exe /c somecommandB 2
call :wait
call :a start cmd.exe /c somecommandC 1
call :a start cmd.exe /c somecommandD 2
call :a start cmd.exe /c somecommandE 3
exit /b
:a
start "%~2" cmd /c 9>"%lock%%2" %1
exit /b
:wait
1>nul 2>nul ping /n 2 ::1
for %%N in (%lock%*) do (
( rem
) 9>"%%N" || goto :Wait
) 2>nul

You can call the files easily with this script:
#echo off
setlocal
set "lock=%temp%\wait%random%.lock"
call :a one.bat 1
call :a two.bat 2
call :wait
call :a three.bat 1
call :a name.bat 2
call :a gfwagwa.bat 3
exit /b
:a
start "%~2" cmd /c 9>"%lock%%2" %1
exit /b
:wait
1>nul 2>nul ping /n 2 ::1
for %%N in (%lock%*) do (
( rem
) 9>"%%N" || goto :Wait
) 2>nul
call :wait simply replaces the waiting. Whenever you have called all files that are to be run asynchronously, call the wait function. Then you can call more scripts.
The second parameter is the number of the lock file. Make sure you don't have duplicate numbers before all those scripts using them are closed (i.e. before the next call :wait). Though you're not going to run out of numbers anyway, no reason to use duplicates.

just to offer an alternative way:
#ECHO off
start "MyCommand-%~n0" cmd.exe /c ping localhost
start "MyCommand-%~n0" cmd.exe /c ipconfig /all
start "MyCommand-%~n0" cmd.exe /c sysinfo
:loop
tasklist /fi "windowtitle eq MyCommand-%~n0" | find "===" >nul && goto :loop
echo finished!
Edit for your comment. bunch is the number of commands running in parallel.
#ECHO off
setlocal enabledelayedexpansion
set bunch=3
for /f "delims=:" %%a in ('findstr /n /b "REM ==" %~f0') do set /a datastart=%%a+1
set count=0
for /f "skip=%datastart% usebackq delims=" %%a in ("%~f0") do (
set /a "count=(count+1) %% %bunch%"
echo starting: %%a
start "MyCommand-%~n0" cmd.exe /c %%a
if !count!==0 echo waiting & call :loop
)
echo waiting & call :loop
echo finished!
goto :eof
:loop
tasklist /fi "windowtitle eq MyCommand-%~n0" | find "===" >nul && goto :loop
goto :eof
REM == START DATA ==
ping localhost
ipconfig /all
systeminfo
tasklist
echo hello
schtasks /query
wmic bios get /value
timeout 10
the first for just gets the line number where your commands are (start of DATA section)

Related

Weird behavior for nested labels in batch script

Here is the batch program:
#echo off
SETLOCAL EnableDelayedExpansion
set /a incr=0
set arg1=%1
del test_output.csv > NUL 2>&1
del test_output.log > NUL 2>&1
set startTime=%time%
for /d %%i in ("%cd%\*") do call :run_test "%%i"
.\log_parser.exe
type test_output.csv
echo Start Time: %startTime%
echo Finish Time: %time%
exit /B
:run_test
if not "%~nx1" == "shared" (
echo Test: %~nx1
cd %1
del pass_fail_output.csv > NUL 2>&1
echo Running...
cd temp_root_fs
start application.exe
set /a incr=0
:while1
tasklist /fi "IMAGENAME eq application.exe" 2>NUL | find /i /n "application.exe">NUL
if "%ERRORLEVEL%"=="0" (
if %incr% leq 60 (
echo Still running...
timeout /t 1 > NUL 2>&1
set /a incr+= 1
goto :while1
)
echo Test timed out...
taskkill /im application.exe /f
)
echo Test completed...
cd logs
.\pass_fail_parser.exe
type log.log >> ..\..\..\test_output.log
copy pass_fail_output.csv ..\..\
cd ..\..\
)
echo Cleaning...
rmdir /S /Q temp_root_fs
cd ..
)
This is my expected execution:
Loop through folders
Run the application
Wait for 60 seconds and either kill the application after 60 seconds passes or continue if the application finishes
Do some other executions to port log messages into an overall log file
The first loop works fine, but this is what my current output looks like when I execute it:
Test: test1
Initializing...
Running...
Still running...
Still running...
Still running...
Still running...
Still running...
Still running...
Still running...
Still running...
Still running...
Still running...
Test completed...
1 file(s) copied.
Cleaning...
I know this is not working properly because I have 3 more folders for tests, so it should continue on to the other folders, but somehow it seems to break out of the for loop early.
I have read about the /I option that supposedly prevents the goto from breaking out of iff and do loops, but I am not entirely sure how it works (I tried adding it as a parameter but it either errors out or does not seem to do anything).
Any help would be greatly appreciated!
:run_test
if not "%~nx1" == "shared" (
echo Test: %~nx1
cd %1
del pass_fail_output.csv > NUL 2>&1
echo Running...
cd temp_root_fs
start application.exe
set /a incr=0
CALL :while1
echo Test completed...
cd logs
.\pass_fail_parser.exe
type log.log >> ..\..\..\test_output.log
copy pass_fail_output.csv ..\..\
cd ..\..\
)
echo Cleaning...
rmdir /S /Q temp_root_fs
cd ..
)
GOTO :EOF
:while1
tasklist /fi "IMAGENAME eq application.exe" 2>NUL | find /i /n "application.exe">NUL
if "%ERRORLEVEL%"=="0" (
if %incr% leq 60 (
echo Still running...
timeout /t 1 > NUL 2>&1
set /a incr+= 1
goto :while1
)
echo Test timed out...
taskkill /im application.exe /f
)
GOTO :EOF
In your code, if not "%~nx1" == "shared" ( to the final ) is a code block. Labels are not permitted within a code block. The values %var% are replaced when the if statement is parsed by the values of those variables at that time, not as the change due to actions executed within the block. Beware of the delayed expansion trap
The above code converts the :while1 loop to an internal subroutine invoked by CALL :while1 (the colon is required to indicate the call is to an internal label)
Note the GOTO :EOF statements. These transfer execution to the physical end-of-file (the colon is again, required) The first is to prevent execution from proceeding from :run_test into :while1 by flow-through. The second is to force a return to the statement following CALL :while1 when the :while1 routine finishes. The :while1 routine may be placed after any goto statement in the batch's mainline (ie. not a goto within a code block).

Run bat files in parallel and wait until they are finished before run new set of bat files

I have a series of bat files that I want to run in parallel, for example:
start program1_1.bat
start program1_2.bat
start program1_3.bat
wait until program1 finished then
start program2_1.bat
start program2_2.bat
start program2_3.bat
wait until program2 finished then
...
So far, what I've tried is this function:
:waitForFinish
set counter=0
for /f %%i in ('tasklist /NH /FI "Imagename eq cmd.exe') do set /a counter=counter+1
if counter GTR 2 Goto waitForFinish
But it just launched the first 3 bat files and stop... How can I solve this problem?
Thanks,
EDIT: This is the content of program1_i.bat file:
program1.exe input_i.txt
It will run program1.exe for each input file. The same for program2_i.bat.
Your question is a little vague on the exact expected results.
I am however assuming that you want to do something like this.
start /wait program1_1.bat | start /wait program1_2.bat | start /wait program1_3.bat
start /wait program2_1.bat | start /wait program2_2.bat | start /wait program2_3.bat
The single pipe separators let's us launch the first three commands in parallel and only start the next three commands once the first three has all completed, simply because the next 3 commands are in the next batch line the use of start /wait
* Update *
The solution provided here works nicely to achieve parallel running of subprograms without needing user entry (pause) or a fixed length Timeout between program groups.
Combined with the original answer:
#echo off
:controller
Call :launcher "program1_1.bat" "program1_2.bat" "program1_3.bat"
Call :launcher "program2_1.bat" "program2_2.bat" "program2_3.bat"
pause
EXIT
:launcher
For %%a in (%*) Do (
Start "+++batch+++" "%%~a"
)
:loop
timeout /t 1 >nul
tasklist /fi "windowtitle eq +++batch+++*" |find "cmd.exe" >nul && goto :loop
GOTO :EOF
Original Answer:
This is a simple way to ensure each group of programs is finished before the next. The fault in the tasklist method is that if there's other cmd.exe processes running, the If condition may not be true when expected, causing the script to hang.
The start /wait option is not ideal, as you intend on running multiple programs simultaneously - and if the subprogram you wait on finishes before the other subs, you're back to square 1.
#echo off
:controller
Call :launcher "program1_1.bat" "program1_2.bat" "program1_3.bat"
Call :launcher "program2_1.bat" "program2_2.bat" "program2_3.bat"
pause
EXIT
:launcher
For %%a in (%*) Do (
Start "" "%%~a"
)
pause
GOTO :EOF
Ok, here is what worked for me:
#echo off
setlocal enableextensions enabledelayedexpansion
call :InitDos
start program1_1.bat
start program1_2.bat
start program1_3.bat
call :waitForFinish
start program2_1.bat
start program2_2.bat
start program2_3.bat
call :waitForFinish
Goto:eof
: waitForFinish
set /a counter=0
for /f %%i in ('tasklist /NH /FI "Imagename eq cmd.exe"') do (
set /a counter+=1
)
if !counter! GTR !init_count! Goto waitForFinish
goto :eof
: InitDos
set /a init_count=0
for /f %%i in ('tasklist /NH /FI "Imagename eq cmd.exe"') do (
set /a init_count+=1
)
goto :eof
Try /WAIT switch on start command. I guess if you wrap bats into script called with wait switch, it could work.

How to close a bat after a specific time without intrerupting the execution?

I want to manage to execute and manipulate my bat file, but, în background i want a countdown so that after a specific time, no matter what, my bat calls another one that deletes it, or simply closes.
Also i want the bat file to show up on execution maximized. I have set /max after start command, but still minimized. I used în my bat /min and it worked, cant figure out why /max doesent work.
To make it start maximized put this at the top of your script, below #echo off
if not "%1" == "max" start /MAX cmd /c %0 max & exit/b
I'll update this answer later with the other part, but I think opening a new batch with a timeout as soon as this one starts with start /B should help a lot.
EDIT
So this starts your script with a second script in it. That second script kills the first script and starts a third cmd to delete itself:
#echo off
if not "%1" == "max" start /MAX cmd /c %0 max & exit/b
set T=%TEMP%\sthUnique.tmp
wmic process where (Name="WMIC.exe" AND CommandLine LIKE "%%%TIME%%%") get ParentProcessId /value | find "ParentProcessId" >%T%
set /P A=<%T%
SET PID=%A:~16%
echo #echo off > killParent.bat
:: replace the 5 on the next line with any other number
echo timeout /t 5 /nobreak >> killParent.bat
echo start "" cmd ^/c timeout ^/t 1^^^&del "%%~f0"^^^&exit ^/b >> killParent.bat
echo taskkill /f /PID %PID% >> killParent.bat
START /B CMD /C CALL killParent.bat >NUL 2>&1
::any logic here instead of pause
pause
Place your code where the pause is and replace the 5 with the number of seconds you want to wait.
This does have the drawback of not being able to finish before the timer runs out, you can fix that by putting echo title parentKiller >> killParent.bat below echo #echo off > killParent.bat and putting
del killParent.bat
taskkill /F /FI "WINDOWTITLE eq parentKiller *" /T
at the end of your execution path, so at the bottom of your batch file normally. This would then look like this:
#echo off
if not "%1" == "max" start /MAX cmd /c %0 max & exit/b
set T=%TEMP%\sthUnique.tmp
wmic process where (Name="WMIC.exe" AND CommandLine LIKE "%%%TIME%%%") get ParentProcessId /value | find "ParentProcessId" >%T%
set /P A=<%T%
SET PID=%A:~16%
echo #echo off > killParent.bat
echo title parentKiller >> killParent.bat
:: replace the 5 on the next line with any other number
echo timeout /t 5 /nobreak >> killParent.bat
echo start "" cmd ^/c timeout ^/t 1^^^&del "%%~f0"^^^&exit ^/b >> killParent.bat
echo taskkill /f /PID %PID% >> killParent.bat
START /B CMD /C CALL killParent.bat >NUL 2>&1
::any logic here instead of pause
pause
del killParent.bat
taskkill /F /FI "WINDOWTITLE eq parentKiller *" /T

Combination of `sleep` and `pause` command

Ok, after seeing crazy stuff being completed in so little code, I have high hopes this is possible.
Pretty much, I want to use the pause command normally, however, if the user doesn't input anything for a specified duration of time, it automatically continues.
In pseudo code:
(sleep %sleep-time%&Echo Pass)1>0 & pause
I thought at first I could do this using start /b to create a process that echoed input while being paused i the current thread, but that could cause problems if the user does input something.
Bonus
What would be really cool is if the errorlevel would be changed based on whether the user inputted something, or if the pause command timed out.
I suggest using timeout:
timeout /T 60 >NUL
This will sleep your script for 1 minute, or unless the user hits a key.
#echo off
setlocal
rem TimedPause.bat - Antonio Perez Ayala
if "%1" equ ":PausePart" goto PausePart
if "%1" neq "" goto begin
echo TimedPause.bat seconds
echo/
echo Wait for given seconds or until user press a key
echo At end, the presence of keyPressed.txt file indicate the cause of exit
goto :EOF
:begin
set seconds=%1
start "" /B "%~F0" :PausePart
for /F "skip=2 tokens=2 delims=," %%a in ('tasklist /FI "IMAGENAME eq cmd.exe" /FO CSV') do (
set PausePart=%%a
goto TimePart
)
:TimePart
ping -n 2 localhost > NUL
if exist keyPressed.txt goto :EOF
set /A seconds-=1
if %seconds% gtr 0 goto TimePart
taskkill /PID %PausePart% /F > NUL
goto :EOF
:PausePart
del keyPressed.txt 2> NUL
pause
echo %time% > keyPressed.txt
exit

batch file to check if exe is running if so taskkill

I'm having problems with the firefox Flashplayerplugin eating up too much ram and lagging my system when it's not in use. The only solution I found was killing the flashplayerplugin while using firefox, uninstalling, reinstalling or a fresh firefox install or new profile doesn't solve it; however, it's becoming very tedious having to check taskmanager all the time and kill it and the flashplayerplugin always seems to start on it's own.
The question I have is if it's possible to create a batch file to check if FlashPlugin_11_8_800_94.exe is running and kill it after a period of time (5-10 seconds) and continue running the batch file actively, in a loop, scanning if FlashPlugin_11_8_800_94.exe has started again, then kill it after 5 - 10 seconds, rinse and repeat?
Edit:
Found a batch file and modified it, but also seems to be missing some perimeters to actively search if it's running, even when it is not. It doesn't work either way though.
#echo off
:search
TASKLIST|FIND "FlashPlayerPlugin"
IF %ERRORLEVEL% = 0 THEN (GOTO found)
TIMEOUT /T 5
GOTO search
:found
taskkill /im FlashPlayerPlugin_11_8_800_94.exe
--
This batch file doesn't work either.
set tasklist=%windir%\System32\tasklist.exe
set taskkill=%windir%\System32\taskkill.exe
-------------------------------------------------------
:STOPPROC
set wasStopped=0
set procFound=0
set notFound_result=ERROR:
set procName=%1
for /f "usebackq" %%A in (`%taskkill% /IM %procName%`) do (
if NOT %%A==%notFound_result% (set procFound=1)
)
if %procFound%==0 (
echo The process was not running.
goto :EOF
)
set wasStopped=1
set ignore_result=INFO:
:CHECKDEAD
"%windir%\system32\timeout.exe" 3 /NOBREAK
for /f "usebackq" %%A in (`%tasklist% /nh /fi "imagename eq %procName%"`) do (
if not %%A==%ignore_result% (goto :CHECKDEAD)
)
goto :EOF
-------------------------------------------------------
:MAIN
call :STOPPROC FlashPlayerPlugin_11_8_800_94.exe
taskkill /im FlashPlugin_11_8_800_94* /f >nul 2>&1
For anyone who still might find it useful:
This is a small script that scans tasklist for processes containing processname once every 5 seconds. For example, if you put "notepad" for the processname, it will end processes like "notepad.exe" and "notepad++.exe". To use the script, copy and paste the following into notepad and save has "simple_pk.cmd". processname can have any characters except double quotes("), ampersands (&), or commas (,).
::Simple monitor and kill process
#echo off&prompt :&mode con cols=50 lines=10
set processname=flashplayerplugin
:loop
cls&echo Searching for %processname%...
for /f "tokens=1 delims=," %%a in ('tasklist /fo csv ^|FINDSTR /I /C:"%processname%"') do call :killprocess %%a
ping -n 6 127.0.0.1>NUL
goto :loop
:killprocess
echo. |set /p d=killing %*...
taskkill /f /im "%*">nul 2>&1
set err=%errorlevel%
set success=Success
if not %err%==0 set success=fail (err code: %err%)
if %err%==128 set success=fail (process not found)
echo %success%&goto :eof
This is a slightly different version of the same script. This will only end processes that match the whole name exactly:
::Simple monitor and kill process (exact name)
#echo off&prompt :&mode con cols=50 lines=10
set processname=FlashPlayerPlugin_11_8_800_94.exe
:loop
cls&echo Searching for %processname%...
for /f "tokens=1 delims=," %%a in ('tasklist /fo csv ^|FINDSTR /C:"%processname%"') do call :killprocess %%a
ping -n 6 127.0.0.1>NUL
goto :loop
:killprocess
set name=%*
set name=.,;%name:"=%;,.
echo %name%|FINDSTR /C:".,;%processname%;,.">nul || goto :eof
echo. |set /p d=killing %*...
taskkill /f /im "%*">nul 2>&1
set err=%errorlevel%
set success=Success
if not %err%==0 set success=fail (err code: %err%)
if %err%==128 set success=fail (process not found)
echo %success%&goto :eof
Your IF is not correct:
#echo off
:search
TASKLIST|FIND "setup.exe"
IF %ERRORLEVEL% equ 0 (GOTO found)
TIMEOUT /T 5
GOTO search
:found
taskkill /im setup.exe
More simpler form
#echo off
:search
TASKLIST|FIND "setup.exe"
IF %ERRORLEVEL% equ 0 (taskkill /im setup.exe
exit)
TIMEOUT /T 5
GOTO search
I was looking over the first bit of code at top and I find a way, I believe. This is what I got; really simple.
#echo off
cls
:start
timeout /t 5
tasklist|find "explorer.exe"
goto found
goto start
:found
taskkill /f /im explorer.exe
goto start
Of course, any program will work.

Resources