5

I would like to make a bat script that performs an action only if fewer than 2 occurrences of cmd.exe are running. I managed to find a solution that stores the occurrence count in a temporary file, but I find it very inelegant. So my question is how to do the same as below without a temporary file, but rather by storing the occurence count given by @TASKLIST | FIND /I /C "%_process%" in a variable. I saw that there may be a solution with a for loop, but I couldn’t get it to work and anyway I would really prefer a solution based on SET (if this is possible).

@SET _process=cmd.exe
@SET _temp_file=tempfiletodelete.txt
@TASKLIST | FIND /I /C "%_process%" > %_temp_file%
@SET /p _count=<%_temp_file%
@DEL /f %_temp_file%
@IF %_count% LSS 2 (
    @ECHO action 1
) ELSE (
    @ECHO action 2
)

Edit: This question is similar to Save output from FIND command to variable, but I wasn’t able to apply the solution to my problem and I wanted to know if a solution without a for loop is possible.

nico
  • 1,130
  • 2
  • 12
  • 26
  • `for /F %%a in ('@TASKLIST ^| FIND /I /C "%_process%"') do @SET "_count=%%a"` – Aacini May 05 '18 at 14:12
  • To capture the output of a command you need to use [`for /F`](http://ss64.com/nt/for_f.html) (you need to escape the pipe like `^|` then); there is no method based on `set`[`/P`], except using a temporary file... – aschipfl May 05 '18 at 14:12
  • 1
    Possible duplicate of [Save output from FIND command to variable](https://stackoverflow.com/questions/9524239/save-output-from-find-command-to-variable) – aschipfl May 05 '18 at 14:18

2 Answers2

6

The command FOR with option /F and the command or command line specified as set (string inside parentheses) additionally enclosed by ' can be used for processing the output of a command or a command line with multiple commands on one line.

You can use this batch file:

@ECHO OFF
SET "_process=cmd.exe"
FOR /F %%I IN ('%SystemRoot%\System32\tasklist.exe /FI "IMAGENAME eq %_process%" ^| %SystemRoot%\System32\find.exe /I /C "%_process%"') DO SET "_count=%%I"
IF /I "%_process%" == "cmd.exe" SET /A _count-=1
IF %_count% LSS 2 (
    ECHO action 1
) ELSE (
    ECHO action 2
)

The command FOR runs in background without a visible console window cmd.exe /C with the command line:

C:\Windows\System32\tasklist.exe /FI "IMAGENAME eq cmd.exe" | C:\Windows\System32\find.exe /I /C "cmd.exe"

TASKLIST option /FI "IMAGENAME eq cmd.exe" filters the output of TASKLIST already to processes with image name (= name of executable) cmd.exe. This makes further processing faster and avoids that a running process with file name totalcmd.exe is counted as cmd.exe as the command line in question does.

The output of TASKLIST written to handle STDOUT is redirected to handle STDIN of command FIND which processes the lines and counts the lines containing cmd.exe anywhere in line. FIND outputs finally the number of lines containing searched string to handle STDOUT of command process running in background.

The redirection operator | must be escaped with caret character ^ on FOR command line to be interpreted as literal character when Windows command interpreter processes this command line before executing command FOR which executes the embedded command line with using a separate command process started in background.

FOR with option /F captures everything written to handle STDOUT of executed command process and processes each line. In this case just one line is captured by FOR containing just a number. Therefore no additional options are needed to assign the number assigned to loop variable I by FOR to environment variable _count using command SET.

The goal of this batch file is counting the number of cmd.exe processes already running. As the command line with TASKLIST and FIND is executed by FOR in background with using one more cmd.exe process, it is necessary to subtract the count by one to get the correct result using an arithmetic expression evaluated with SET /A _count-=1. This decrement by one is needed only for counting right the number of cmd.exe processes. It is not necessary for any other process.

For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.

  • echo /?
  • find /?
  • for /?
  • if /?
  • set /?
  • tasklist /?
Mofi
  • 46,139
  • 17
  • 80
  • 143
  • @Mofi Thank you for answer, it works perfectly and I accepted it. One thing that I don’t understand is why you use `%SystemRoot%\System32\tasklist.exe` and `%SystemRoot%\System32\find.exe` instead of `TASKLIST` and `FIND`? – nico May 05 '18 at 19:00
  • 1
    @nico Please take a look on [What is the reason for '...' is not recognized as an internal or external command, operable program or batch file?](https://stackoverflow.com/a/41461002/3074564) There are so many programmers and therefore also application installers which corrupt environment variable `PATH` that it is simply a matter of coding safety to write batch files being independent on environment variables `PATH` and `PATHEXT` as much as possible by referencing executables in a batch file always with full qualified file name (= full path + file name + file extension). – Mofi May 06 '18 at 07:32
5

You would do it like this, using TaskList in a For loop:

@Set "_process=cmd.exe"
@For /F %%A In ('TaskList^|Find /C /I "%_process%"'
) Do @If %%A Lss 2 (Echo Action 1) Else Echo Action 2
@Pause

You can also perform a similar thing using WMIC too:

@Set "_process=cmd.exe"
@For /F %%A In ('WMIC Process Get Name^|Find /C "%_process%"'
) Do @If %%A Lss 2 (Echo Action 1) Else Echo Action 2
@Pause

These could be refined further to ensure processes containing the string as opposed to matching it aren't counted:

@Set "_process=cmd.exe"
@For /F %%A In ('TaskList /FI "ImageName Eq "%_Process%""^|Find /C "."'
) Do @If %%A Lss 2 (Echo Action 1) Else Echo Action 2
@Pause

 

@Set "_process=cmd.exe"
@For /F %%A In (
    'WMIC Process Where "Name='%_process%'" Get Name^|Find /C "."'
) Do @If %%A Lss 2 (Echo Action 1) Else Echo Action 2
@Pause

Note:
If you're really checking the cmd.exe process, be aware that an the command inside the For loop parentheses is spawned within a new cmd.exe instance, (thereby increasing your count by 1)

Compo
  • 36,585
  • 5
  • 27
  • 39