How can I prevent to run same batch file twice to allow only one instance?
This is rather difficult from a batch file standpoint, mainly because there is no guaranteed method for a batchfile to detect instances across processes.
IMO, I think you are using the wrong tool. While, yes, it is a challenge, the more important concern is "Does it work?"
I run batch file in a scheduled time.
This tells me you should be using a scheduling program, like Task Scheduler. Task Scheduler will guarantee there is only one instance of your file, even if it is running on another user.
For example, if you run the task every 30 minutes, you configure the task to run for a duration of 30 minutes. Then you set the "if task is already running" setting to "do not create a new instance." Or you have to option of "kill the old one", "Run another instance", or "Start a new task as soon as the old one is done".
For Vista and up: http://technet.microsoft.com/en-us/library/cc748993.aspx#BKMK_cmd
For Xp and up: http://technet.microsoft.com/en-us/library/bb490996.aspx
The challenging method is to create states.
- Your batch file enters execution
- Your batch file loops
- Your batch file ends
You can create an arbitrary file in %allusersprofile%, like >>Startmybatchfile.txt echo 1
for each state, then check if it exists. It will also help with trouble shooting. But don't delete the file at the end. Just change it to End State. That way it's deterministic if your batch file has truly finished and no longer running.
I would not use a global variable as it requires admin privileges to run.
You could also use the %temp% variable as but that changes from user to user. I guess that isn't a concern if you are a single login user machine.
This is my latest and most trusted script:
:init
set "started="
2>nul (
9>"%~f0.lock" (
set "started=1"
call :start
)
)
@if defined started (
del "%~f0.lock" >nul 2>nul
) else (
echo Process aborted: "%~f0" is already running
@ping localhost > nul
)
exit /b
:start
cd /d %~dp0
:: REST OF THE SCRIPT
The best way I've found to do this is to set the title on a batch script.
From there you can use tasklist to query the count of running processes by the title name. The below code snippet anything below process_count=2 means the process is not running, 3 means exactly 1 instance is running, and if above 3, there's more than 3 processes running.
Hackish, but batch is what it is.
For a continuously Looping script, I'm not smart enough to think of a way to write the start time to the title field and query that from another batch script. I have thought of copying and renaming the file-name with the time then executing that, but to me anything but a direct query is more mess than it's worth.
Yeah I can drop to text file, but I'm worried about cache corruption.
@REM ########################
:COUNT_PROCESS_INSTANCES
@REM @@@@@@@@@@@@@@@@@@@@@@@@
SET /A PROCESS_COUNTER=0
@REM No way to inject a counter into this for statement.
FOR /F "DELIMS=" %%A in ('tasklist /FI "WINDOWTITLE EQ %USERNAME%: %PROCESS_NAME_TO_COUNT%*"') do (CALL :SUB_INCRIMENT_PROCESS_COUNTER)
@REM If one instance is running, or no instances, that's fine. If more than one instance is running, time to exterminate.
@ECHO PROCESS COUNTER IS: "%PROCESS_COUNTER%"
SET PROCESS_NAME_TO_COUNT=NULL
GOTO :EOF
:SUB_INCRIMENT_PROCESS_COUNTER
SET /A PROCESS_COUNTER=%PROCESS_COUNTER%+1
GOTO :EOF
@REM @@@@@@@@@@@@@@@@@@@@@@@@
@REM ########################
Also, for anyone interested; here's how to find the PID of the currently executing process. Now if you pass a variable into a sub-script, you can pass that variable back up the chain via text document (I think there's another way to do this too but that escapes me).
@REM @@@@@@@@@@@@@@@@@@@@@@@@
:FIND_SELF_PID
@REM ########################
@REM Leaving this here for later if needed. This was a byproduct of a bad approach.
FOR /F "TOKENS=1,2,*" %%A in ('tasklist /FI "WINDOWTITLE EQ %USERNAME%: %CURRENT_TITLE%"') do (SET SELF_PID=%%B)
echo %self_pid%
GOTO :EOF
@REM @@@@@@@@@@@@@@@@@@@@@@@@
@REM ########################