I've written a batch file that checks if only one instance of a server is running at any given time. The server is on a shared folder on a cloud, where multiple users have access to it.
If the server is running, a .txt file will be created, and as long as it's there, no one can start the server. When the server shuts down, the .txt file is deleted and another user is free to start it again.
minecraft_server.1.8.1.exe starts a Java process javaw.exe, which is the process that we need to monitor.
The code goes like this:
#echo off
IF EXIST *_RUNNING.txt (
echo "ERROR, SERVER ALREADY RUNNING as %computername%"
pause
EXIT
) ELSE (
copy NUL %computername%_RUNNING.txt
START /WAIT minecraft_server.1.8.1.exe
tasklist /FI "IMAGENAME eq javaw.exe" 2>NUL | find /I /N "javaw.exe">NUL
:loop
IF "%ERRORLEVEL%"=="0" (
TIMEOUT /t 60
GOTO loop
) ELSE (
del %computername%_RUNNING.txt
echo "Server ended."
pause
EXIT
)
)
Everything works except the loop. It keeps returning ") was unexpected at this time".
I'm new to writing batch files so please help.
#echo off
IF EXIST *_RUNNING.txt (
echo "ERROR, SERVER ALREADY RUNNING as %computername%"
pause
EXIT
)
copy NUL %computername%_RUNNING.txt
START /WAIT minecraft_server.1.8.1.exe
:loop
tasklist /FI "IMAGENAME eq javaw.exe" 2>NUL | find /I /N "javaw.exe">NUL
IF "%ERRORLEVEL%"=="0" (
TIMEOUT /t 60
GOTO loop
)
del %computername%_RUNNING.txt
echo "Server ended."
pause
EXIT
Effectively, you have a label :loop in a block (a parenthesised sequence of statements) in your original. Labels terminate blocks.
With these modifications, the unnecessary else clauses have been removed. If batch exits or gotos then the else is not required - the next statements in the batch will be executed if the goto/exit doesn't happen.
Note that your label :loop is in the wrong place. As it stood, errorlevel would be established for the first and only invocation of tasklist. Thereafter, the loop would perpetually find errorlevel 0 if it was set to 0 on the first instance. Moving it to the indicated location would execute tasklist/find with a delay of 60 sec until errorlevel became non-zero, when the non-goto path to terminate the procedure would be taken.
(not tested, of course...)
Related
Hello I am very new in this and I was able to run one instance of a batch file.
However, another file is being created called something.lock but the file is not been deleted by itself when I stop the batch or close it.
The new file create is the one that helps to have one instance running.
Can the new file ".lock " be deleted after I close the script with the "X" or because an user ended correctly with going to label end:
The code that I have is
:init
set "started="
2>nul (
9>"%~f0.lock" (
set "started=1"
call :start
)
)
#if defined started (
del "%~f0.lock"
) else (
cls
ECHO Only one instance is allowed
timeout /NOBREAK /T 3 >nul
cls
)
exit /b
:start
cd /d %~dp0
cls
:initial
pause >nul
You are misapplying the lock file. You are simply checking to see if the file exists, which means you must guarantee that the file is deleted upon batch termination.
There is a much better way, which you have only partially implemented. Only one process can have the file open for write access. You just need to determine if the file is already locked by another process.
Once the process with the exclusive lock terminates, the lock will be released. This is true no matter how the script terminates - even if it was the result of Ctrl-C or window closure. The file might not be deleted, but the next time the script runs, the file won't be locked, so the script will proceed nicely.
In the code below I save the current definition of stderr to an unused file handle before I redirect sterr to nul. Within the inner block I redirect stderr back to the saved definition. In this way I prevent the error message if the file is already locked, but the CALLed :start routine will still print out error messages normally.
#echo off
:init
8>&2 2>nul ( 2>&8 9>"%~f0.lock" call :start ) || (
cls
ECHO Only one instance is allowed
timeout /NOBREAK /T 3 >nul
cls
)
del "%~f0.lock" 2>nul
exit /b
:start
cd /d %~dp0
cls
del asdfasdfasdf
:initial
pause >nul
The difficulty is that your batch thread itself won't have its own PID. There's no graceful way to tell whether your batch script is running or when it has terminated. And there's no way to wake the dead, to let the script have the last word when a user red X's or Ctrl+C's. When it's over, it's over.
There are a few ways you can do what you want to do. Try them all and see which you prefer. Use dbenham's solution. His is correct. The following efforts are left here as an exercise in futility, although Solution 4 seems to work very well. In the end, it's still just a hack; whereas dbenham's redirection sleight-of-hand provides a correct implementation of lock files the way lock files are supposed to work.
...
Solution 1
One simple way is to use powershell to minimize the current window, re-launch your script with start /wait, then after completion call powershell again to restore.
#echo off
setlocal
set "lock=%temp%\~%~n0.lock"
if "%~1" neq "wrapped" (
if exist "%lock%" (
echo Only one instance is allowed.
timeout /nobreak /t 3 >NUL
exit /b
)
rem :: create lock file
>"%lock%" echo 1
rem :: minimize this console
powershell -windowstyle minimized -command ""
rem :: relaunch self with "wrapped" argument and wait for completion
start /wait "" cmd /c "%~f0" wrapped
rem :: delete lock file
del "%lock%"
rem :: restore window
powershell -windowstyle normal -command ""
goto :EOF
)
:: Main script goes here.
:loop
cls
echo Simulating script execution...
ping -n 2 0.0.0.0 >NUL
goto loop
This should be enough for casual use and should account for any cause of the batch file's termination short of taskkill /im "cmd.exe" /f or a reboot or power outage.
Solution 2
If you need a more bulletproof solution, you can get the current console window's PID and intermittently test that it still exists. start /min a helper window to watch for its parent window to die, then delete the lock file. And as long as you're creating a watcher anyway, might as well let that watcher be the lock file.
Biggest drawback to this method is that it requires to end your main script with exit to destroy the console window, whether you want it destroyed or not. There's also a second or two pause while the script figures out its parent's PID.
(Save this with a .bat extension and run it as you would any other batch script.)
#if (#a==#b) #end /* JScript multiline comment
:: begin batch portion
#echo off
setlocal
:: locker will be a batch script to act as a .lock file
set "locker=%temp%\~%~nx0"
:: If lock file already exists
if exist "%locker%" (
tasklist /v | find "cleanup helper" >NUL && (
echo Only one instance allowed.
timeout /nobreak /t 3 >NUL
exit /b
)
)
:: get PID of current cmd console window
for /f "delims=" %%I in ('cscript /nologo /e:Jscript "%~f0"') do (
set "PID=%%I"
)
:: Create and run lock bat.
>"%locker%" echo #echo off
>>"%locker%" echo setlocal
>>"%locker%" echo echo Waiting for parent script to finish...
>>"%locker%" echo :begin
>>"%locker%" echo ping -n 2 0.0.0.0^>NUL
>>"%locker%" echo tasklist /fi "PID eq %PID%" ^| find "%PID%" ^>NUL ^&^& ^(
>>"%locker%" echo goto begin
>>"%locker%" echo ^) ^|^| ^(
>>"%locker%" echo del /q "%locker%" ^&^& exit
>>"%locker%" echo ^)
:: Launch cleanup watcher to catch ^C
start /min "%~nx0 cleanup helper" "%locker%"
:: ==================
:: Rest of script
:: blah
:: blah
:: blah
:: ==================
:end
echo Press any key to close this window.
pause >NUL
exit
:: end batch portion / begin JScript
:: https://stackoverflow.com/a/27514649/1683264
:: */
var oShell = WSH.CreateObject('wscript.shell'),
johnConnor = oShell.Exec('%comspec% /k #echo;');
// returns PID of the direct child of explorer.exe
function getTopPID(PID, child) {
var proc = GetObject("winmgmts:Win32_Process=" + PID);
return (proc.name == 'explorer.exe') ? child : getTopPID(proc.ParentProcessID, PID);
}
var PID = getTopPID(johnConnor.ProcessID);
johnConnor.Terminate();
// output PID of console window
WSH.Echo(PID);
Solution 3
You can also test a lock file and see whether it's stale by setting a timestamp within the lock file, and setting that same timestamp in your console window title. Only problem with this is that the window title doesn't revert to normal if the user terminates with Ctrl+C, so you can't run the script twice without closing the cmd window. But closing the window and opening a new one for subsequent launches may not be too terrible a price to pay, as this is the simplest method described thusfar.
#echo off
setlocal
set "started=%time%"
set "lockfile=%temp%\~%~n0.lock"
if exist "%lockfile%" (
<"%lockfile%" set /P "locktime="
) else (
set "locktime=%started%"
)
tasklist /v | find "%locktime%" >NUL && (
echo Only one instance allowed.
timeout /nobreak /t 3 >NUL
exit /b
)
title %~nx0 started at %started%
>"%lockfile%" echo %started%
:: rest of script here
echo Simulating script execution...
:loop
ping -n 2 0.0.0.0 >NUL
goto loop
Solution 4
Here's a bit more polished solution, combining methods 1 and 3. It re-launches itself in the same window, then sets the window title to a unique ID. When the script exits gracefully, the lock file is deleted. Whether the script exits gracefully or forcefully, the window title reverts back to its default. And if no window exists in the task list with a title matching the unique ID, the lock file is deemed stale and is overwritten. Otherwise, the script notifies the user that only one instance is allowed and exits. This is my favorite solution.
#echo off
setlocal
if "%~1" neq "wrapped" (
cmd /c "%~f0" wrapped %*
goto :EOF
)
:: remove "wrapped" first argument
shift /1
:: generate unique ID string
>"%temp%\~%~n0.a" echo %date% %time%
>NUL certutil -encode "%temp%\~%~n0.a" "%temp%\~%~n0.b"
for /f "usebackq EOL=- delims==" %%I in ("%temp%\~%~n0.b") do set "running_id=%%I"
del "%temp%\~%~n0.a" "%temp%\~%~n0.b"
set "lockfile=%temp%\~%~n0.lock"
if exist "%lockfile%" (
<"%lockfile%" set /P "lock_id="
) else (
set "lock_id=%running_id%"
)
tasklist /v | find "%lock_id%" >NUL && (
echo Only one instance allowed.
timeout /nobreak /t 3 >NUL
exit /b
)
title %running_id%
>"%lockfile%" echo %running_id%
:: rest of script here
echo Press any key to exit gracefully, or Ctrl+C to break
pause >NUL
del "%lockfile%"
goto :EOF
I have two scripts. First script creates .csv files which are used as input for second script.
Can someone please let me know what additional steps I need to write in between below two lines so that second script will only start execution when first script has completely written all the files in .csv format.
start "" /wait /b "D:\ITSMaaS\BTscripts\capgemini\BESExtract\bin\BES_EXTRACT.exe" "-f D:\ITSMaaS\BTscripts\capgemini\BESExtract\conf\BES_EXTRACT.CONF"
start "" /wait /b "D:\ITSMaaS\BTscripts\capgemini\BESExtract\bin\BES_DATA_MAP.exe" "-f D:\ITSMaaS\BTscripts\capgemini\BESExtract\conf\BES_DATA_MAP.conf"
You could try something like this. It loop untill the process "BES_EXTRACT.exe" is finished. when the task is running %ERRORLEVEL% will be "0" when the task is finished %ERRORLEVEL% will change to "1" and the loop will end. so when "BES_EXTRACT.exe" is finished %ERRORLEVEL% will change to "1" and "BES_DATA_MAP.exe" will start
Echo Date:%date% Time:%time% >> error.txt
#echo off
(
Echo Date:%date% Time:%time%
start "" /wait /b "D:\ITSMaaS\BTscripts\capgemini\BESExtract\bin\BES_EXTRACT.exe" "-f D:\ITSMaaS\BTscripts\capgemini\BESExtract\conf\BES_EXTRACT.CONF"
:loop
for /f "tokens=2 delims=: " %%a in ('tasklist ^| find "BES_EXTRACT.exe"' ) do (
if "%ERRORLEVEL%"=="0" (
ping -n 10 localhost > nul 2>nul
goto loop
)
)
start "" /wait /b "D:\ITSMaaS\BTscripts\capgemini\BESExtract\bin\BES_DATA_MAP.exe" "-f D:\ITSMaaS\BTscripts\capgemini\BESExtract\conf\BES_DATA_MAP.conf"
) >> Output.txt 2>> error.txt
Explanation
the the for loop uses the "tasklist" and the find command to check if the task "BES_EXTRACT.exe" is running if it is running it sets %ERRORLEVEL% to "0". If the %ERRORLEVEL% is "0" the script will ping yourown PC 10 times then go back and start the for loop again the Ping command is only there to count 10 Seconds (each ping is 1 second) then when the first script is finished the for loop will set %ERRORLEVEL% to "1" which ends the loop and starts the second script. This is a short explaination of the script if you require more of an explaination let me know :)
Have you tried it without the start command?
Normal command line programs will pause until done before the next line is executed.
#echo off
"D:\ITSMaaS\BTscripts\capgemini\BESExtract\bin\BES_EXTRACT.exe" "-f D:\ITSMaaS\BTscripts\capgemini\BESExtract\conf\BES_EXTRACT.CONF"
"D:\ITSMaaS\BTscripts\capgemini\BESExtract\bin\BES_DATA_MAP.exe" "-f D:\ITSMaaS\BTscripts\capgemini\BESExtract\conf\BES_DATA_MAP.conf"
I have a bat file that I created, it adds keys to the windows registry and then calls another bat file, QGIS.bat (this bat file starts an application called QGIS).
It works most of the time but every now and then, when it calls QGIS.bat nothing happens, the command window stays open but QGIS (started by the QGIS.bat file) will not start.
In the command window(cmd) all it says is call .\usbgis\apps\qgis\bin\qgis.bat
(Just a note QGIS is a portable application that runs from a USB memory stick, might that be part of the problem?)
So my question. Is there a way you can terminate a bat file if it douse not close in 3 min or if the other bat file douse not start?
Thanks,
This is what I'm talking about in my comment:
#echo off
setlocal enabledelayedexpansion
set sPath=C:\Program Files\Internet Explorer
set sprog=iexplore.exe
set inc=0
:loop
if exist "%sPath%\%sProg%" (echo %sProg%) else exit /b
set /a inc+=1
if "!inc!" equ "30" (Echo Exiting & exit /b)
for /f %%a in (
'tasklist /NH /FI "Imagename eq %sProg%"^|findstr /i "INFO:"') do (
if not errorlevel 1 (
ping -n 11 127.0.0.1>nul
goto :loop
)
)
Obviously change the path and file to match yours. I just needed something for testing here.
I want to start two programs from the batch file, (also batch files) - they will execute few seconds. Then in the main batch file I want to wait while both are finished and then continue main execution. Is that possible at all?
You can, sort of:
#echo off
set Token=MAIN_%RANDOM%_%CD%
start "%Token%_1" cmd /c child1.cmd
start "%Token%_2" cmd /c child2.cmd
:loop
ping -n 2 localhost >nul 2>nul
tasklist /fi "WINDOWTITLE eq %Token%_1" | findstr "cmd" >nul 2>nul && set Child1=1 || set Child1=
tasklist /fi "WINDOWTITLE eq %Token%_2" | findstr "cmd" >nul 2>nul && set Child2=1 || set Child2=
if not defined Child1 if not defined Child2 goto endloop
goto loop
:endloop
echo Both children died.
child1 and child2 were just executing pause. This uses a more or less unique token to identify the child batch files. They are started in an own window with start and each one is given a specific window title. Using that window title we can find them again using tasklist and determine whether they are still running.
It gets a little tricky figuring out whether the batch files are still running. tasklist with the window title filter does what it's supposed to do, but it won't return a sensible errorlevel. That's why we have to pipe through findstr to pick up the process name (to avoid being language-sensitive by picking up the error message).
I have 4 batch files. I want to run one.bat and two.bat at once, concurrently. After completion of these two batch files, three.bat and four.bat should run at once, in parallel. I tried with many ways but mot works fine.
Can anyone help me over this?
This is easily done using a much simplified version of a solution I provided for Parallel execution of shell processes. Refer to that solution for an explanation of how the file locking works.
#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 had this same dilemma. Here's the way I solved this issue.
I used the Tasklist command to monitor whether the process is still running or not:
:Loop
tasklist /fi "IMAGENAME eq <AAA>" /fi "Windowtitle eq <BBB>"|findstr /i /C:"<CCC>" >nul && (
timeout /t 3
GOTO :Loop
)
echo one.bat has stopped
pause
You'll need to tweak the
<AAA>, <BBB>, <CCC>
values in the script so that it's correctly filtering for your process.
Hope that helps.
Create a master.bat file that starts one.bat and two.bat. When one.bat and two.bat end correctly, they echo to file they have finished
if errorlevel 0 echo ok>c:\temp\OKONE
if errorlevel 0 echo ok>c:\temp\OKTWO
Then the master.bat wait for the existence of the two files
del c:\temp\OKONE
del c:\temp\OKTWO
start one.bat
start two.bat
:waitloop
if not exist c:\temp\OKONE (
sleep 5
goto waitloop
)
if not exist c:\temp\OKTWO (
sleep 5
goto waitloop
)
start three.bat
start four.bat
Another way is to try with the /WAIT flag
start /WAIT one.bat
start /WAIT two.bat
but you don't have any control on errors.
Here's some references
http://malektips.com/xp_dos_0002.html
http://ss64.com/nt/sleep.html
http://ss64.com/nt/start.html
Just adding another way, maybe the shortest.
(one.cmd | two.cmd) && (three.cmd | four.cmd)
Concept is really straight forward. Start one and 2 in paralel, once done and errorlevel is 0 run three and four.