Detecting Process ID of the current cmd.exe

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Message
Author
siberia-man
Posts: 129
Joined: 26 Dec 2013 09:28
Contact:

Detecting Process ID of the current cmd.exe

#1 Post by siberia-man » 20 Dec 2014 11:19

cmd.exe doesn't provide its own Proccess ID (or PID).

What could PID be useful for? It is unique number in the current moment.
-- With PID it is easy to refer to own process. The TASKLIST utility is not robust - just launch two instances of cmd.exe and you can't say which one is your own.
-- It can be very useful when creating temporary files or directories. The %RANDOM% variable as the part of the filename can lead to conflicts in pipes.

In this post I am offering tiny script cmdpid.bat which helps to detect PID of the current cmd.exe instance. The found value is stored to %CMDPID% variable. Functionality is implemented by powershell. The essence of the solution is expressed in 4 lines of the script after the :cmdpid label that can be copied-and-pasted in your scripts. The rest of the script is wrapper for convenient usage as a standalone tool.

Code: Select all

@echo off

if "%~1" == "/?" goto :help
if "%~1" == "-?" goto :help

if /i "%~1" == "/h" goto :help
if /i "%~1" == "-h" goto :help


for %%p in ( powershell.exe ) do if "%%~$PATH:p" == "" (
   >&2 echo:%%p required.
   exit /b 1
)


:cmdpid
for /f "tokens=*" %%p in ( '
   set "PPID=(Get-WmiObject Win32_Process -Filter ProcessId=$P).ParentProcessId" ^& ^
   call powershell -NoLogo -NoProfile -Command "$P = $pid; $P = %%PPID%%; %%PPID%%"
' ) do set CMDPID=%%p

goto :EOF


:help
echo:Calculates the Process ID of the Command Prompt
echo:and assigns it to the CMDPID variable.
echo:
echo:Usage: %~n0

Squashman
Expert
Posts: 4114
Joined: 23 Dec 2011 13:59

Re: Detecting Process ID of the current cmd.exe

#2 Post by Squashman » 20 Dec 2014 12:01

You could always set the TITLE to a unique value at the top of your batch file so that you know what instance of cmd.exe you are looking for with TASKLIST. Well at least this has always been good enough for me.

Code: Select all

set ttime=%time%%random%
TITLE %ttime%
for /F "tokens=2" %%G in ('tasklist /V /NH /FO table ^|find "cmd.exe" ^|find "%ttime%') do set cmdpid=%%G

siberia-man
Posts: 129
Joined: 26 Dec 2013 09:28
Contact:

Re: Detecting Process ID of the current cmd.exe

#3 Post by siberia-man » 20 Dec 2014 12:33

Squashman, your suggestion is good enough for cases when you don't need piping one script to another one.

Look here
The %RANDOM% variable as the part of the filename can lead to conflicts in pipes.

and try this one in pipe

Code: Select all

:: script | script
@echo:%time% %random%>&2


The result of this pipe can describe why your suggestion doesn't work and can't cover the issue anymore.

Squashman
Expert
Posts: 4114
Joined: 23 Dec 2011 13:59

Re: Detecting Process ID of the current cmd.exe

#4 Post by Squashman » 20 Dec 2014 12:37

You are going to have to give a better example. I am not following your example.

aGerman
Expert
Posts: 3787
Joined: 22 Jan 2010 18:01
Location: Germany

Re: Detecting Process ID of the current cmd.exe

#5 Post by aGerman » 20 Dec 2014 17:15

You can achieve the same with the WMIC command.

Code: Select all

@echo off &setlocal

setlocal EnableDelayedExpansion
set "uid="
for /l %%i in (1 1 128) do (set /a "bit=!random!&1" &set "uid=!uid!!bit!")
for /f "tokens=2 delims==" %%i in (
  'WMIC Process WHERE "Name='cmd.exe' AND CommandLine LIKE '%%!uid!%%'" GET ParentProcessID /value'
) do for /f %%j in ("%%i") do (endlocal &set "PID=%%j")

echo %PID%
pause

It uses a fake UID. It's a string instead of a 128 bits wide numeric structure but the effect is the same and it's as safe as any other UID or GUID based algorithm..

Regards
aGerman

dbenham
Expert
Posts: 2289
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: Detecting Process ID of the current cmd.exe

#6 Post by dbenham » 20 Dec 2014 22:29

aGerman wrote:It uses a fake UID. It's a string instead of a 128 bits wide numeric structure but the effect is the same and it's as safe as any other UID or GUID based algorithm.
Far from it :(

Every cmd.exe process that starts within the same second will get the same random number seed and generate the exact same sequence of pseudo random numbers. See http://stackoverflow.com/a/19697361/1012053 for more info.


Dave Benham

siberia-man
Posts: 129
Joined: 26 Dec 2013 09:28
Contact:

Re: Detecting Process ID of the current cmd.exe

#7 Post by siberia-man » 21 Dec 2014 04:57

aGerman
I briefly mentioned in the beginning about the problem of using %RANDOM%. dbenham gave good explanation in his SO answer.

Regarding this topic I have one question that I can't estimate or solve. The issue is as follows. Could a restricted user run the script properly and obtain an adequate response (PID of cmd.exe)?

dbenham
Expert
Posts: 2289
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: Detecting Process ID of the current cmd.exe

#8 Post by dbenham » 21 Dec 2014 06:24

The code below in this post is outdated - it has a minor bug. See viewtopic.php?p=38870#p38870 for the updated code

Here is a foolproof WMIC solution, provided the user has access to WMIC, and TEMP points to a valid path where the user has write access. I use %time% to create a nearly unique lock file path. There can only be a collision if two batch processes with the same name attempt to get the PID within the same 0.01 second time interval. This is handled by establishing an exclusive file lock on the lock file, such that only one process can proceed. Any process that fails loops back, getting a new lock file each time, until it succeeds.

The full lock file path is transformed into a unique ID - certain characters must be encoded to be valid in the WMIC query string. I use : for encoding because it cannot appear in the path other than after the drive letter.

Code: Select all

@echo off

:getPID  [rtnVar]
setlocal disableDelayedExpansion
set "time="
:getLock
set "lock=%temp%\%~nx0.%time::=.%.lock"
set "uid=%lock:\=:b%"
set "uid=%uid:,=:c%"
set "uid=%uid:_=:u%"
set "uid=%uid:'=:q%"
2>nul ( 9>"%lock%" (
  for /f "skip=1 delims=" %%A in (
    'wmic process where "name='cmd.exe' and CommandLine like '%%<%uid%>%%'" get ParentProcessID'
  ) do for /f "delims=" %%B in ("%%A") do set "PID=%%B"
  (call )
))||goto :getLock
del "%lock%" 2>nul
endlocal & if "%~1" equ "" (echo(%PID%) else set "%~1=%PID%"
exit /b


Dave Benham
Last edited by dbenham on 23 Dec 2014 12:40, edited 2 times in total.

siberia-man
Posts: 129
Joined: 26 Dec 2013 09:28
Contact:

Re: Detecting Process ID of the current cmd.exe

#9 Post by siberia-man » 21 Dec 2014 10:22

too complicated. isn't it?

foxidrive
Expert
Posts: 6033
Joined: 10 Feb 2012 02:20

Re: Detecting Process ID of the current cmd.exe

#10 Post by foxidrive » 21 Dec 2014 10:32

siberia-man wrote:too complicated. isn't it?

It's more robust, according to the discussion.

Yury
Posts: 115
Joined: 28 Dec 2013 07:54

Re: Detecting Process ID of the current cmd.exe

#11 Post by Yury » 21 Dec 2014 12:46

Code: Select all

@echo off& setlocal enableextensions enabledelayedexpansion

set "f=%~f0"
set d=00000000000000.000000+000

for /f "skip=1" %%i in ('
 wmic process where "Name='cmd.exe' and CommandLine like '%%%f:\=\\%%%'" get CreationDate
') do (
 for /f %%j in ("%%i") do if %%j gtr !d! set d=%%j
 )

for /f "skip=1" %%i in ('
 wmic process where "Name='cmd.exe' and CreationDate='%d%'" get ProcessId
') do (
 for /f %%j in ("%%i") do set PID=%%j
 )

echo %PID%

pause>nul& endlocal& exit /b

Squashman
Expert
Posts: 4114
Joined: 23 Dec 2011 13:59

Re: Detecting Process ID of the current cmd.exe

#12 Post by Squashman » 21 Dec 2014 13:04

siberia-man wrote:too complicated. isn't it?

I don't understand how any of them work! :lol:

dbenham
Expert
Posts: 2289
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: Detecting Process ID of the current cmd.exe

#13 Post by dbenham » 21 Dec 2014 15:04

The Yury "solution" fails for multiple reasons:

  • The cmd.exe process does not have the batch script path in the command line if it was launched from an already running cmd.exe process. This is how I normally run my scripts, in which case the code completely fails for me.

  • The same batch script can be running in multiple sessions at the same time. The first WMIC command will return all processes that have the batch script path in the command line. The last one listed will be the value that is saved. It is impossible to know if the correct one was selected. I introduced a pause at the top of the script, and launched two processes via double clicking the script from Windows Explorer. Both sessions returned the same PID.

  • If you could somehow guarantee that the first WMIC command returns the correct session, then there would be no need to get the CreationDate. You could immediately return the ProcessID without requiring a second WMIC call. But we know that this is a moot point.

  • Again, supposing the first WMIC call did return the correct CreationDate, I'm not sure the value is guaranteed to be unique. I haven't seen anything on the web one way or the other as to whether two processes can be launched at precisely the same time (within precision of the system clock). But I believe it may be possible for two processes to have identical CreationDate values. Again, this is a moot point, since the technique has already failed.

In contrast, my solution is very robust. I have used exclusive file locks on many batch projects to serialize events, and exhaustive testing has never uncovered a problem. The only way I can see my script failing is if someone intentionally attempted to break it. A command session could be started that intentionally mimics the encoded UID within the command line. With enough iterations and a bit of luck, I suppose a collision could eventually occur, thus giving a false result.


Dave Benham

penpen
Expert
Posts: 1731
Joined: 23 Jun 2013 06:15
Location: Germany

Re: Detecting Process ID of the current cmd.exe

#14 Post by penpen » 21 Dec 2014 19:08

You shouldn't run the WMIC and powershell from within a for/F loop, because then you should get the process id of the for/F cmd instance and not the pid from the batch parent:

Code: Select all

:: "test.bat" source code
@echo off
echo(For/F loop
for /F "tokens=*" %%a in ('processInfo.exe') do echo(%%~a
echo(
echo(redirect only
ProcessInfo.exe > processInfo.txt
for /F "usebackq tokens=*" %%a in ("processInfo.txt") do echo(%%~a
goto :eof

Console output:

Code: Select all

Z:\>test
For/F loop
Parent process id: 3884
Parent process name: cmd
Actual process id: 3284
Actual process name: ProcessInfo

redirect only
Parent process id: 2280
Parent process name: cmd
Actual process id: 3124
Actual process name: ProcessInfo

penpen

dbenham
Expert
Posts: 2289
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: Detecting Process ID of the current cmd.exe

#15 Post by dbenham » 21 Dec 2014 19:29

penpen wrote:You shouldn't run the WMIC and powershell from within a for/F loop, because then you should get the process id of the for/F cmd instance and not the pid from the batch parent:

We are taking that into account - in fact, it is crucial to how the algorithm works. If you look carefully at my and aGerman's WMIC code, you will see we are returning the ParentProcessID, not the ProcessID.

The extra FOR /F cmd.exe is important because it allows us to control the text that appears in the command line. We embed a (hopefully) unique ID to make sure we only get the process we just launched. We then get the ParentProcessID to get the PID we actually want.

======================

What on earth is ProcessInfo.exe :?:


Dave Benham

Post Reply