3

These are not working for me.

Any help to definitelly corret the four examples below ?

The EXAMPLE01 just echoes "continue", even if I have three CMD.exe opened.

---------- EXAMPLE 01 ------------

@echo off 
wmic process where name="cmd.exe" | find "cmd.exe" /c
SET ERRORLEVEL=value if "%value%" GTR 1 ( 
    ECHO This batch is not first  
    ECHO quitting ...
    )
if "%value%" LSS 2 ECHO continue

I am getting the "unexpected i error" message in the EXAMPLE 02!

----------- EXAMPLE 02 -------

@echo off
FOR /F "usebackq tokens=2" %i IN (`tasklist ^| findstr /r /b "cmd.exe"`)
   DO taskkill /pid %%i

I am getting the "is first" message in the EXAMPLE 03, even with three CMD.exe opened!

----------- EXAMPLE 03 -------

 @echo off
    wmic process where name="cmd.exe" | find "cmd.exe" /c
    if "%errorlevel%" LEQ 1 echo CMD is first
    if "%errorlevel%" GTR 1 echo CMD is already running

It is also possible that I will not have access to the Wmic command at work, so, another possibility is found in the EXAMPLE 04 ... but to no avail.

----------- EXAMPLE 04 -------

@echo off
Tasklist /FI "IMAGENAME eq cmd.exe" 2>NUL | find /I /N "cmd.exe">NUL
if "%ERRORLEVEL%"==0 do (goto Use) else (goto Cont)
:Cont
ECHO Only one instance running
pause

:Use
echo Application running already. Close this window

Kind regards, Maleck

Luiz Vaughan
  • 675
  • 1
  • 15
  • 33
  • Are you looking to see if there are multiple CMD.EXE instances, or are you looking to see if your batch file is running multiple times? – dbenham Jul 23 '12 at 23:17

2 Answers2

7

wmz identified a number of errors in the OP's code, and also has an excellent suggestion to use a lock file for concurrency control.

Below is a robust batch solution that uses a lock file to prevent multiple instances of the batch file from running at the same time. It uses a temporary lock file for the concurrency control. The mere presence of the lock file does NOT stop the script from running. The script will only fail if another process has an exclusive lock on the lock file. This is important in case the script should crash or be killed without deleting the lock file. The next process to run the script will still succeed because the file is no longer locked.

This script assumes the script is installed on a local drive. It allows only one instance for the entire machine. There are lots of variations to control the amount of concurrency allowed. For example, incorporating the %USERNAME% into the lock file name would allow one instance per user in a network environment. Incorporating %COMPUTERNAME% in the name would allow one instance per machine in a network environment.

@echo off
setlocal

:: save parameters to variables here as needed
set "param1=%~1"
:: etc.

:: Redirect an unused file handle for an entire code block to a lock file.
:: The batch file will maintain a lock on the file until the code block
:: ends or until the process is killed. The main code is called from
:: within the block. The first process to run this script will succeed.
:: Subsequent attempts will fail as long as the original is still running.
set "started="
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
)
exit /b

:start
:: The main program appears below
:: All this script does is PAUSE so we can see what happens if
:: the script is run multiple times simultaneously.
pause
exit /b


EDIT

The error message "The process cannot access the file because it is being used by another process." can be suppressed by redirecting stderr output to nul in an outer block.

2>nul (
  9>"%~f0.lock" (
    set "started=1"
    call :start
  )
)

The problem with the above is that all error messages for the main program will also be suppressed. That can be fixed by 1st saving the current definition of stderr to another unused file handle, and then adding yet another inner block that redirects stderr back to the saved file handle.

8>&2 2>nul (
  9>"%~f0.lock" (
    2>&8 (
      set "started=1"
      call :start
    )
  )
)
dbenham
  • 127,446
  • 28
  • 251
  • 390
  • Very nice Dbenham ! It works great but when executed more than once I get a "file is being used by another process" message just before the echo "Process aborted ...". How can I nullify the first message? – Luiz Vaughan Jul 24 '12 at 05:55
  • Tested in several ways and it works like a charm. Thank you. Problem solved ! – Luiz Vaughan Jul 25 '12 at 17:51
  • @LuizMaleck - Don't forget to accept the answer (click on the check mark near the upper left hand corner of the answer) if it solves all your problems. That action lets others know the question has been answered, it awards you 2 rep points, and awards the owner of the question 15 points. – dbenham Jul 25 '12 at 18:24
  • I've been using this solution. Thanks. There is one query I have though. My `:start` routine starts some other processes in background using START command. These command too inherit the FD 9 & keep the lock file busy. Is there a way for them to not inherit the FD 9? – anishsane Dec 05 '14 at 08:07
  • 1
    @anishsane - Unfortunately, I think not. It drives me nuts because it doesn't make sense. See http://stackoverflow.com/q/20849558/1012053, and my answer that follows. – dbenham Dec 05 '14 at 08:56
3
  1. You do not set value anywhere. Even if you did it would not work as variables are expanded on parse. You would need to use delayed expansion if you need to both set and test variable against what was set. Quotes on comparison are not required (see 3). help set will show you info delayed expansion.
  2. %i should be %%i. DO taskkill /pid %%i must be on same line as rest of for command. You also use findstr in regex mode, which means it will search for cmd[any character]exe
  3. You use string (lexical) comparison: "1" leq 1 is true (as "4" leq 1 also is...). Use errorlevel 1 which is equivalent to errorlevel equal or greater than 1. help if shows syntax
  4. Same as 3 plus do in do (goto Use) should be removed. %errorlevel%==0 would work, but it's normally advised to use errorlevel number.

How to check if there is only 1 cmd running:

@echo off
for /f %%i in ('Tasklist /FI "IMAGENAME eq cmd.exe" 2^>NUL' ^| find /I /c  "cmd.exe"') do (
if %%i leq 2 (echo only one instance) else (echo More than one instance)
)

Note: it's just an example. I do not recommend this as a real method of concurrency control. I would rather use lock file for that.

wmz
  • 3,645
  • 1
  • 14
  • 22
  • I kinda thought that `cmd.exe` is just an example. That's way too broad... anyway, have an example on how to count them. – wmz Jul 23 '12 at 23:45
  • +1, I like the way you think - I also would use a lock file. See [my answer](http://stackoverflow.com/a/11622676/1012053) for a robust example. – dbenham Jul 24 '12 at 00:58