8

I need to detect if a batch file is already running or not.

In the batch I simply loop and repeatedly do some tasks. Sometimes the loop cannot complete. I run the batch file at a scheduled time. When it runs, it should check if a previous instance exists and is healthy. If it doesn't exist it should start a new instance. If the instance that exist is not healthy/outdated, the new instance should kill/cancel the older process.

A few theories:

  1. How can I detect a previous instance in a batch file? Can I give an ID like thing?
  2. I can use a pulse mechanism to say the batch is healty; ie, a global variable, a newly created dummy file, a timestamp in a dummy file, etc.
  3. Can I detect a kind of "timeout", if the last pulse is expired then allow a new instance...

Yes, I all want them in a batch file - instead of a Windows app - I think it's not so hard for one who is a batch-file addict and takes this question as challenge!

6
  • did you check my answer here: superuser.com/questions/362164/…
    – stijn
    Nov 28, 2011 at 14:54
  • Yes, thanks. I need much more than recognizing the running batch; ie. how about multiple instances? Can I give them a name? To kill them individually? I don't want to do it manually from Task Manager.
    – Nime Cloud
    Nov 28, 2011 at 14:59
  • not sure what you mean - your question is about allowing a single instance. But now you're asking about multiple instances? anyway killing them can be done using taskkill or pskill
    – stijn
    Nov 28, 2011 at 15:06
  • Single instance #1: monitor.bat domain1.com Single instance #2: monitor.bat domain2.com
    – Nime Cloud
    Nov 28, 2011 at 15:16
  • that's not a single instance of the batch file itself.. You could still use the title trick if you set the titles to mybatch domain1.com mybatch domeain2.com etc. Or resort to text files in which you keep a list of those which is better in the long run.
    – stijn
    Nov 28, 2011 at 15:36

7 Answers 7

7

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.

  1. Your batch file enters execution
  2. Your batch file loops
  3. 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.

1
  • If all instances are terminated then TS starts a new instance. That's why I use TS. Plus when OS starts, TS creates the first instance, etc.
    – Nime Cloud
    Nov 29, 2011 at 7:24
7

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
1
  • 2
    What is happening in this script? Can we use it in multiple batch scripts?
    – Angelina
    Apr 8, 2016 at 13:16
3

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 ########################
2
  • 1) You cover "process_count<2", "process_count=3", and "process_count>3"... What if "process_count=2" ? (Or will that never happen?). 2) You talk about using the "Title" command to set the "WINDOWTITLE", but you don't give an example of what to set the "Title" to, when (where in the code) "Title" should be set, and if/when "Title" should be reset to "Command Prompt". 3) In the "FOR" command you use %PROCESS_NAME_TO_COUNT%, but again, you don't set it before using it (it's UNDEFINED), you only "Reset" it to "NULL" after the "FOR" command completes. (Part 1/2: Continued-->) Jul 29, 2023 at 22:54
  • (Part 2) --- It really seems like it should work, but I tried various settings for the "Title" command and "%PROCESS_NAME_TO_COUNT%" and almost got it working but never reliably/repeatedly. And, if 2 instances are started within a short time (some few seconds), "%PROCESS_COUNTER%" is always "=1" for both, so both continue to run. Jul 29, 2023 at 23:00
1

With regards to the first part of the question, if you are using tasklist.exe to find a process, note that that the 'windowtitle' filter will only work for windows in a separate process. So if you want to make a batch file detect ITSELF (i.e. check if there is already a running instance) you should use 'imagename' instead:

tasklist /V /NH /FI "imagename eq cmd.exe"| find /I /C "UNIQUETITLE" > nul

Also note that without a filter the process may take a very long time on later versions of Windows. Here is an example of a batch file which can only be run as one instance at a time:

@echo off
:: Test with tasklist.exe to make sure the process is not already running:
:: Window title must be unique.
set loop_batch_title=Only this instance is allowed to run.
:: Run tasklist in verbose mode (which returns window titles) and search for the window title of this batch file:
tasklist /V /NH /FI "imagename eq cmd.exe"| find /I /C "%loop_batch_title%" > nul
:: If the window title is found then the errorlevel variable will be 0, which means the process is already running:
if %errorlevel%==0 goto AlreadyRunning
:: If the process isn't already running then the window title can be set:
title %loop_batch_title%

::Put your program loop here
:Loop
echo This is a loop.
ping -n 3 127.0.0.1>nul
goto Loop

:AlreadyRunning
echo.
echo Process already running.
endlocal
1

I have found a solution that utilizes text files to keep track of all previous PIDs the bat file has had. It attempts to kill them silently and then adds the current PID to the list after.

If you don't want it to kill the old, already existing process, simply replace the line that has "taskkill" with whatever you were wanting to do with it.

(might require you to run as admin in order to have permissions to kill the duplicate process. see permission elevation code below for optional implementation if you don't want to have to run as admin every time.)

@echo off
set WorkingDir=%cd% 
if exist MostRecentPID.txt ( del "PIDinfo.txt"  /f /q ) > nul
cd ..\..\..\..\..\..\..
title mycmd
tasklist /v /fo csv | findstr /i "mycmd" > %WorkingDir%\PIDinfo.txt
set /p PIDinfo=<%WorkingDir%\PIDinfo.txt

REM below, the 11 means get substring starting a position 11 with length of 5 characters. The tasklist command gives a long and verbose value so this will get just the PID part of the string.
set PID5chars=%PIDinfo:~11,5%
set PID4chars=%PIDinfo:~11,4%

if exist PreviousPIDs.txt (
    for /F "tokens=*" %%A in (PreviousPIDs.txt) do taskkill.exe /F /T /PID %%A > nul 2>&1
    goto CheckIfFourCharPID
)

:CheckIfFourCharPID
if %PID4chars% gtr 8100 (
    for /F "tokens=*" %%A in (PreviousPIDs.txt) do taskkill.exe /F /T /PID %%A > nul 2>&1
    echo %PID4chars% >> "PreviousPIDs.txt"
) else (
    echo %PID5chars% >> "PreviousPIDs.txt"
)

Explanation: (warning: very technical)

-This solution gets a substring of the tasklist command to get just the PID. There will not be a PID for cmd.exe that is greater than 18100 so check if PID4chars is greater than 8100 so we know if it's a 4 digit or 5 digit number

case 1: a 5 digit PID like 17504 has a PID5chars val 17504 and a PID4chars val of 1750, so we add PID5chars to the text files of PIDs to kill

case 2: a 4 digit PID like 8205 has a PID5chars val of 8205" and a PID4chars val of 8205, so we add PID4chars to the text files of PIDs to kill

case 3: a 4 digit PID like 4352 has a PID5chars val of 4352" and a PID4chars val of 4352, so we add PID4chars to the text files of PIDs to kill


OPTIONAL PERMISSION ELEVATION CODE

(put this at the top of your bat file and it will auto-run it as admin.)

    @echo off
    setlocal DisableDelayedExpansion

    set "batchPath=%~0"
    for %%k in (%0) do set batchName=%%~nk
    cd ..\..\..\..\..\..\..\..
    if exist %cd%\Temp (
    set temp=%cd%\Temp
    goto vbsGetPrivileges
    )
    if exist %cd%\Windows\Temp (
    set temp=%cd%\Windows\Temp
    goto vbsGetPrivileges
    )
    set temp=%cd%

    :vbsGetPrivileges
    set "vbsGetPrivileges=%temp%\OEgetPriv_%batchName%.vbs"
    setlocal EnableDelayedExpansion

    :CheckIfRunningAsAdmin
    net session >nul 2>&1
    if %ERRORLEVEL% == 0 ( 
        goto gotPrivileges 
    ) else ( goto ElevatePermissions )

    :ElevatePermissions
    if '%1'=='ELEV' (echo ELEV & shift /1 & goto gotPrivileges)
    ECHO Set UAC = CreateObject^("Shell.Application"^) > "%vbsGetPrivileges%"
    ECHO args = "ELEV " >> "%vbsGetPrivileges%"
    ECHO For Each strArg in WScript.Arguments >> "%vbsGetPrivileges%"
    ECHO args = args ^& strArg ^& " "  >> "%vbsGetPrivileges%"
    ECHO Next >> "%vbsGetPrivileges%"
    ECHO UAC.ShellExecute "!batchPath!", args, "", "runas", 1 >> "%vbsGetPrivileges%"
    "%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %*
    exit /B

    :gotPrivileges
    setlocal & pushd .
    cd /d %~dp0
    if '%1'=='ELEV' (del "%vbsGetPrivileges%" 1>nul 2>nul  &  shift /1)
    net session >nul 2>&1
    if %ERRORLEVEL% == 0 (
        goto Continue
    ) else (
        REM unable to elevate permissions so tell user to run file as admin manually
        echo Please re-run this file as administrator. Press any key to exit...
        pause > nul
        goto Exit
     )

    :Continue
    <insert rest of code here>
1
  • If there are multiple instances of the batch file running, PID5chars and PID4chars will only find one of them, and you will only know about that instance... Am I missing something? --- What I am trying to do is, upon running a batch file, check if there is an instance of that specific batch file already running. If there is another one running, just quit and let the previously running batch file continue to run. If there is no other one running, just continue to run. Jul 30, 2023 at 0:35
0

I just finished the batch file which one ensures both monitoring itself -the batch file- and the job file -facerecog.exe- are running smoothly. I've used hello.exe as a test probe tool, you may replace your own; ie. ping.exe, etc. When it starts without any parameter it fires another instance and quit, so you can run it as a scheduled task in specific intervals. I hope it helps someone who don't want to use advanced tools.

    @goto code

    :comments
    @REM RunMagick for FaceRecog
    @REM Runs FaceRecog forever
    @REM Author Emin Akbulut [email protected]
    @REM 29/11/2011
    @REM This code is freeware


    :code
    @C:
    @CD C:\NET\FaceRecog

    :settings
    set jobexe=FaceRecog.exe
    set remotehost=192.168.35.211
    set remoteport=1333
    @REM Timeout for response of the batch, in seconds
    set /a timeout=120
    set batchtitle=Monitoring FaceRecog...


    :requirements
    @if NOT exist "stopwatch.exe" (
    @echo. stopwatch.exe is missing!
    @ping localhost > nul
    start http://www.jfitz.com/dos/stopwatch10.zip
    goto end
    ) 




    :menu
    @if "%1" == "/monitor" goto monitor
    @if "%1" == "/stop" goto request-stop
    @if "%1" == "/force-stop" goto stop


    :batch-instance-check 
    tasklist /v /fi "IMAGENAME eq cmd.exe" | find /I /c "%batchtitle%"
    @if "%ERRORLEVEL%" == "0" goto batch-instance-found
    goto start

    :batch-instance-found
    @REM Check the previous instance is healthy/responsive or not
    stopwatch stop < pulse.log > elapsed.log
    set /a elapsed=999
    for /F %%a in ('type elapsed.log') do set /a elapsed=%%a
    if %elapsed% gtr %timeout% (
    taskkill /F /FI "WINDOWTITLE eq %batchtitle%*"
    goto start
    ) else (
    @echo Already running...
    @ping localhost > nul
    goto end
    )

    del elapsed.log



    @goto monitor



    :run-job
    @REM Test FaceRecog process
    tasklist | Find "FaceRecog.exe"
    @If Not ErrorLevel 1 Goto monitor

    taskkill /f /im:conime.exe
    @PING 1.1.1.1 -n 1 -w 1000 >NUL
    start FaceRecog.exe
    @REM Take a breathe
    @PING 1.1.1.1 -n 1 -w 1000 >NUL
    @goto run-job

    :start
    @REM Fire another instance with /monitor command and exit
    start "%batchtitle%" /low /I %0 /monitor
    goto end

    :stop
    @REM Stop the job
    taskkill /F /IM:%jobexe%
    @REM Stop the batch
    taskkill /F /FI "WINDOWSTITLE eq %batchtitle%*"
    del stop.log > nul
    title Done
    goto end

    :request-stop
    @REM Ask programs to quit
    @REM %jobexe% /quit
    @REM Take a breathe
    @ping localhost > nul
    stopwatch start > stop.log
    goto end

    :monitor
    stopwatch start > pulse.log
    @REM Test /stop requested
    @if exist stop.log goto stop
    @REM Test FaceRecog process
    tasklist | Find "%jobexe%"
    @If Not ErrorLevel 0 Goto run-job

    :shorttest
    @Echo Shorttest...
    hello.exe %remotehost% %remoteport% | Find "Version" 
    @If Not ErrorLevel 1 Goto job-ok

    :crashcheck
    @REM No response, crashed?
    tasklist | Find "WerFault.exe"
    @If ErrorLevel 1 Goto longcheck
    taskkill /F /IM:WerFault.exe
    @goto shorttest

    :longcheck
    @Echo Longtest...
    @REM Last check, maybe FaceRecog was busy
    hello.exe %remotehost% %remoteport% | Find "Version" 
    @If Not ErrorLevel 1 Goto job-ok

    :nonresponsive
    @REM Kill non-responsive FaceRecog process and wait for a while to auto-revive
    taskkill /F /IM:%jobexe%



    :job-ok
    @echo OK
    @REM 10 seconds to wait then loop again
    @PING 1.1.1.1 -n 1 -w 10000 >NUL
    @goto monitor


    :end

Thanks stijn for his great comment. Thanks surfasb for his warning about Scheduled Task that already running.

0

2023... Windows 11's use of WindowsTerminal.exe (batch not run as admin on cmd.exe)

@echo off
SET _title=Prepare and launch POS and CCTV Client
tasklist /fi "Windowtitle eq %_title%"|find /i "WindowsTerminal.exe" >nul 2>&1
if not "%ERRORLEVEL%"=="1" echo. & echo  "%_title%" is already running & timeout /T 5 & EXIT
title %_title%
REM ...rest of your batch

version #2

@echo off
SET _title=Prepare and launch POS and CCTV
FOR /F "tokens=* USEBACKQ" %%F IN (`tasklist /fi ^"Windowtitle eq %%_title%%^" ^| find /I /C ^"WindowsTerminal.exe^"`) DO (
SET _runstate=%%F
)

if not "%_runstate%"=="0" echo. & echo  "%_title%" is already running & timeout /T 5 & EXIT
if not "%ERRORLEVEL%"=="0" echo. & echo  "%_title%" has error state & timeout /T 5 & EXIT

title %_title%
REM ...rest of your batch

version #3: Works if run as admin and if CMD.exe or WindowsTerminal.exe and combinations of both

SET _title=Prepare and launch POS and CCTV
SET _numInstances=1
FOR /F "tokens=* USEBACKQ" %%F IN (`tasklist /fi ^"imagename eq CMD.exe^" /v /fo:csv /nh ^| find /i /c ^"%%_title%%^"`) DO (
SET _runCountA=%%F
)
FOR /F "tokens=* USEBACKQ" %%F IN (`tasklist /fi ^"imagename eq WindowsTerminal.exe^" /v /fo:csv /nh ^| find /i /c ^"%%_title%%^"`) DO (
SET _runCountB=%%F
)
SET /a "_runCount=%_runCountA%+%_runCountB%"
IF "%_runCount%" GEQ "%_numInstances%" echo. & echo  "%_title%" already running %_runCount% of %_numInstances% & TIMEOUT /T 3 & EXIT
IF NOT "%ERRORLEVEL%"=="0" echo. & echo  "%_title%" ERROR STATE & TIMEOUT /T 5 & EXIT

title %_title%
REM ...rest of your batch
1
  • Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.
    – Community Bot
    May 12, 2023 at 9:33

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .