Detecting Process ID of the current cmd.exe

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

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

Re: Detecting Process ID of the current cmd.exe

#31 Post by siberia-man » 22 Dec 2014 14:24

dbenham wrote: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.


I have two and half questions regarding your suggestion.

1. Could you explain in more details why do you transform %lock% into %uid%? I see the resulting command is as follows

Code: Select all

wmic process where "name='cmd.exe' and CommandLine like '%<c::bTemp:bcmdpid.bat.23.19.21.93.lock>%'" get ParentProcessID

but I can't understand how CommandLine like '...' can help in searching our cmd instance? How does it work?

2. Why do you clean the %TIME% variable? Also I think only setlocal is enough. Correct?

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

Re: Detecting Process ID of the current cmd.exe

#32 Post by Yury » 22 Dec 2014 16:13

dbenham wrote:
Yury wrote:dbenham, what should I do with your code to run it in a batch file that has the name "&ABC.bat"?

I put my posted code within a file named "&abc.bat" and it works fine for me. I can run the script using either CALL "&ABC" or CALL ^&ABC.



I found the reason. I tried to create a lock file in the directory from where the batch file was launched:

Code: Select all

set "lock=%~nx0.%time::=.%.lock"
.
But the question remains: what to do with an ampersand at the beginning of the file name in this case?

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

Re: Detecting Process ID of the current cmd.exe

#33 Post by penpen » 22 Dec 2014 18:28

npocmaka_ wrote:According to me there's no point to use WMI and C# in this case. C# if far more powerful than jscript (which I prefer only because you can create self-compiled file without toxic output and is a little bit less verbose) and you can use dll import - this should work faster than WMI queries:

http://dotbay.blogspot.com/2009/07/find ... -in-c.html
I only wanted to get a result fast (and not a fast result).
If i wanted to speed it up, i would also have done it the c++ way.

Sidenote:
I'm think there is a flaw in the linked (not optimized) source:
There is missing a "CloseHandle(hSnapshot)" before returning from the function "ParentProcess(...)".
Even if that would not speed up much (if any), i would reorder the code.
It then should look similar to this:

Code: Select all

   // dllImport: kernel32.dll
   using DWORD = System.Int32;
   using LONG = System.Int32;
// (...)
      public static Process ParentProcess (this Process process) {
         DWORD          ppid = -1;
         DWORD          pid  = process.Id;
         HANDLE         h    = CreateToolhelp32Snapshot (TH32CS_SNAPPROCESS, 0);
         PROCESSENTRY32 pe   = new PROCESSENTRY32 ();

         //        = sizeof (PROCESSENTRY32)   // c++
         pe.dwSize = Marshal.SizeOf (typeof (PROCESSENTRY32));
         if (Process32First (h, ref pe)) {
            do {
               if (pe.th32ProcessID == pid) {
                  ppid = (int) pe.th32ParentProcessID;
               }
            } while ((ppid == -1) && Process32Next (h, ref pe));
         }
         CloseHandle (h);

         return Process.GetProcessById (ppid);
      }
// (...)

penpen

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

Re: Detecting Process ID of the current cmd.exe

#34 Post by dbenham » 22 Dec 2014 22:06

I discovered a minor bug in my code - Percent is a legal character in a file path, and it is also the wildcard character in a WMIC LIKE query. So any percents within the UID should be encoded.

Code: Select all

@echo off

:getPID  [RtnVar]
::
:: Store the Process ID (PID) of the currently running script in environment variable
:: RtnVar. If called without any argument, then simply write the PID to stdout.
::
setlocal disableDelayedExpansion
:getLock
set "lock=%temp%\%~nx0.%time::=.%.lock"
set "uid=%lock:\=:b%"
set "uid=%uid:,=:c%"
set "uid=%uid:'=:q%"
set "uid=%uid:_=:u%"
setlocal enableDelayedExpansion
set "uid=!uid:%%=:p!"
endlocal & set "uid=%uid%"
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


siberia-man wrote:1. Could you explain in more details why do you transform %lock% into %uid%? I see the resulting command is as follows

Code: Select all

wmic process where "name='cmd.exe' and CommandLine like '%<c::bTemp:bcmdpid.bat.23.19.21.93.lock>%'" get ParentProcessID

but I can't understand how CommandLine like '...' can help in searching our cmd instance? How does it work?

The WMIC command is executed via a new cmd.exe launched by FOR /F. The WMIC command is performing a search. The search is passed in as a parameter. The search parameter must first be passed as a parameter through cmd - somethiing like:

Code: Select all

    C:\Windows\system32\cmd.exe /c wmic process where "name='cmd.exe' and CommandLine like '%<c::bTemp:bcmdpid.bat.23.19.21.93.lock>%'" get ParentProcessID

So the uid we are looking for is part of the command line - the query is effectively searching for itself :twisted:

Some characters that may appear in the lock file path cause problems in a WMIC query - namely backslash, comma, and single quote. So they must be encoded. Also, the underscore and percent characters are valid path characters, but they are also wildcards for the LIKE operator. We want to treat them as literals, not wildcards, so they also must be encoded.

siberia-man wrote:2. Why do you clean the %TIME% variable? Also I think only setlocal is enough. Correct?

The time is incorporated into the temporary lock file name - but %TIME% contains colons, and colons are not allowed in file names. So I substitute a dot for each colon.

My code requires that delayed expansion be disabled when %~nx0 is expanded, just in case the batch script name contains an exclamation point. Delayed expansion is typically disabled when a script it started, but there are no guarantees - I explicitly disable it just to be sure.

Yury wrote:
dbenham wrote:
Yury wrote:dbenham, what should I do with your code to run it in a batch file that has the name "&ABC.bat"?

I put my posted code within a file named "&abc.bat" and it works fine for me. I can run the script using either CALL "&ABC" or CALL ^&ABC.

I found the reason. I tried to create a lock file in the directory from where the batch file was launched:
Code:
set "lock=%~nx0.%time::=.%.lock"
.
But the question remains: what to do with an ampersand at the beginning of the file name in this case?

I don't understand where you are having problems. My code simply works, even if there is an ampersand in the name. What isn't working for your? Are you using my code?


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

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

Re: Detecting Process ID of the current cmd.exe

#35 Post by siberia-man » 23 Dec 2014 10:28

Dave

This one

Code: Select all

setlocal enableDelayedExpansion
set "uid=!uid:%%=:p!"
...

is executed in the loop. Potentially it could raise the stack overflow error.

I guess, this one could be better (with additional changes in the code below. but it should be checked carefully):

Code: Select all

setlocal enableDelayedExpansion
set "uid=!uid:%%=:p!"
endlocal & set "uid=%uid%"
...

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

Re: Detecting Process ID of the current cmd.exe

#36 Post by dbenham » 23 Dec 2014 11:03

@siberia-man - you are absolutely correct - good catch :!:

I've fixed the code in my prior post. Thanks :D


Dave Benham

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

Re: Detecting Process ID of the current cmd.exe

#37 Post by penpen » 23 Dec 2014 11:44

What i don't understand: Why are you using !uid! within the for loop ('wmic process ... !uid!...'):
I always thought delayed expansion is disabled by default within the for/F loop, so then there should be no need to initialize this variable.

penpen

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

Re: Detecting Process ID of the current cmd.exe

#38 Post by siberia-man » 23 Dec 2014 11:55

Couple of issues

Code: Select all

wmic process where "Name='cmd.exe' and CommandLine like '%%<!uid!>%%'" get ParentProcessID
...
do for /f "delims=" %%B in ("%%A") do set "PID=%%B"


In this code above
-- maybe !uid! should be replaced with %uid%
-- the extra trailing whitespaces come to the resulting string. I'd like to suppose the following wrokaround:

Code: Select all

wmic process where "Name='cmd.exe' and CommandLine like '%%<%uid%>%%'" get ParentProcessID
...
do for %%B in (%%A) do set "PID=%%B"

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

Re: Detecting Process ID of the current cmd.exe

#39 Post by dbenham » 23 Dec 2014 12:38

penpen wrote:What i don't understand: Why are you using !uid! within the for loop ('wmic process ... !uid!...'):
siberia-man wrote:Couple of issues
maybe !uid! should be replaced with %uid%
Aaargh :!: :oops:
My intent was to eliminate all use of delayed expansion after the first ENDLOCAL, but I missed one. :(


siberia-man wrote:the extra trailing whitespaces come to the resulting string. I'd like to propose the following wrokaround:

Code: Select all

wmic process where "Name='cmd.exe' and CommandLine like '%%<%uid%>%%'" get ParentProcessID
...
do for %%B in (%%A) do set "PID=%%B"
Another good find. I'm a bit embarrased that I didn't anticipate this one.


OK, this should be a final good version (sigh):

Code: Select all

@echo off

:getPID  [RtnVar]
::
:: Store the Process ID (PID) of the currently running script in environment variable
:: RtnVar. If called without any argument, then simply write the PID to stdout.
::
setlocal disableDelayedExpansion
:getLock
set "lock=%temp%\%~nx0.%time::=.%.lock"
set "uid=%lock:\=:b%"
set "uid=%uid:,=:c%"
set "uid=%uid:'=:q%"
set "uid=%uid:_=:u%"
setlocal enableDelayedExpansion
set "uid=!uid:%%=:p!"
endlocal & set "uid=%uid%"
2>nul ( 9>"%lock%" (
  for /f "skip=1" %%A in (
    'wmic process where "name='cmd.exe' and CommandLine like '%%<%uid%>%%'" get ParentProcessID'
  ) do for %%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

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

Re: Detecting Process ID of the current cmd.exe

#40 Post by Squashman » 23 Dec 2014 12:55

dbenham wrote:My intent was to eliminate all use of delayed expansion after the first ENDLOCAL, but I missed one. :(
Another good find. I'm a bit embarrased that I didn't anticipate this one.

Just say a few Hail Marys and a few Our Fathers and all will be absolved. :lol:

npocmaka_
Posts: 516
Joined: 24 Jun 2013 17:10
Location: Bulgaria
Contact:

Re: Detecting Process ID of the current cmd.exe

#41 Post by npocmaka_ » 24 Dec 2014 12:29

carlos wrote:npocmaka, In my utility sound.exe source code found here I adapted the function for get the pid of parent cmd.

Code: Select all

http://www.consolesoft.com/p/bg/index.html


This is the pure c no c#:

getpid.c

Code: Select all

#include <windows.h>

// compile with tdm-gcc
// gcc -Wl,-e,__start -nostartfiles -O3 -s getpid.c -o getpid.exe -Wall -Wextra

typedef struct tagPROCESSENTRY32 {
    DWORD dwSize;
    DWORD cntUsage;
    DWORD th32ProcessID;
    ULONG_PTR th32DefaultHeapID;
    DWORD th32ModuleID;
    DWORD cntThreads;
    DWORD th32ParentProcessID;
    LONG pcPriClassBase;
    DWORD dwFlags;
    WCHAR szExeFile[MAX_PATH];
} PROCESSENTRY32W, *LPPROCESSENTRY32W;

HANDLE WINAPI CreateToolhelp32Snapshot(DWORD dwFlags, DWORD th32ProcessID);
BOOL WINAPI Process32FirstW(HANDLE hSnapshot, LPPROCESSENTRY32W lppe);
BOOL WINAPI Process32NextW(HANDLE hSnapshot, LPPROCESSENTRY32W lppe);
#define TH32CS_SNAPPROCESS   0x2


DWORD GetIdOfParentProcess(void);
void _start(void);

void _start(void)
{
   ExitProcess(GetIdOfParentProcess());
}

// Return the pid of parent process
// Return 0 if fails getting it
DWORD GetIdOfParentProcess(void)
{
    HANDLE hSnap;
    DWORD currentProcessId;
    PROCESSENTRY32W pe;
    BOOL fOk;

    hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hSnap == INVALID_HANDLE_VALUE) {
   return 0;
    }

    currentProcessId = GetCurrentProcessId();

    pe.dwSize = sizeof(PROCESSENTRY32W);

    for (fOk = Process32FirstW(hSnap, &pe); fOk;
    fOk = Process32NextW(hSnap, &pe)) {
   if (pe.th32ProcessID == currentProcessId) {
       break;
   }
    }
    CloseHandle(hSnap);
    if (!fOk) {
   return 0;
    }

    return (pe.th32ParentProcessID);
}



You execute it and return in the errorlevel the pid.
This is the executable:

Code: Select all

@Echo Off
Rem Script made using BHX { consolesoft.com/p/bhx }
SetLocal EnableExtensions EnableDelayedExpansion
Set "bin=getpid.ex_"
Set "size=643"
For %%# In (
"getpid.exe"
"!bin!" "!bin!.da" "!bin!.tmp"
) Do If Exist "%%#" (Del /A /F /Q "%%#" >Nul
If ErrorLevel 1 Exit /B 1 )
Findstr /B /N ":+res:!bin!:" "%~f0" >"!bin!.tmp"
(Set /P "inioff=" &Set /P "endoff=") <"!bin!.tmp"
For /F "delims=:" %%# In ("!inioff!") Do Set "inioff=%%#"
For /F "delims=:" %%# In ("!endoff!") Do Set "endoff=%%#"
Set ".=set o=createobject(#scripting.filesystemobject#)"
Set ".=!.!: set w=createobject(#adodb.stream#)"
Set ".=!.!: set r=o.opentextfile(#%~f0#,1,0,0)"
Set ".=!.!: w.charset=#windows-1252# : w.type=2 : w.open"
Set ".=!.!: for i=1 to !inioff! step 1 : r.readline : next"
Set ".=!.!: do while i<!endoff! : d=r.readline"
Set ".=!.!: for j=1 to len(d) step 2"
Set ".=!.!: w.writetext chr(cbyte(#&h#&mid(d,j,2)))"
Set ".=!.!: next : i=i+1 : loop"
Set ".=!.!: w.savetofile #!bin!#,2"
Set ".=!.!: w.close : r.close"
Set ".=!.!: set w=nothing : set r=nothing : set o=nothing"
Set ".=!.:#="!"
Echo !.!>"!bin!.da"
Set "ret=1"
Cscript.exe /B /E:vbs "!bin!.da" >Nul
For %%# In ("!bin!") Do If "%%~z#"=="!size!" Set "ret=0"
If "0"=="!ret!" Expand.exe -r "!bin!" -F:* . >Nul
If ErrorLevel 1 Set "ret=1"
Del /A /F "!bin!" "!bin!.da" "!bin!.tmp" >Nul
Exit /B !ret!

:+res:getpid.ex_:
4D5343460000000083020000000000002C000000000000000301010001000000
000000004700000001000100000800000000000000009645357D200067657470
69642E65786500DD597C9934020008434BF38D9AC0C0CCC0C0C002C4FFFF3330
EC6080000706C2A00188F9E477F1316CE13CABB883D1E7AC62484666B1424151
7E7A5162AE4272625E5E7E894252AA4251699E42669E828B7FB0426E7E4AAA1E
2F2F970AD48C005706061F466686A7A9334260E63E60E067E66664126760823A
0C0836080009010684EB406C268834234219C4E140F1E21606B0BF181814A0FA
04E0FA05903D61C0C0F08708BF920A5C80E6CAE091D72B49AD2801B9859101EE
173046020A0C01097A45298925890C0C2250AF208709C2CB060E7A9910757FA0
7E02AB63C3507700DD1DA19D4FC3C3821BDFF800D51F7751019B7C9C4505A4FB
C50620D179B8F90D87E181E6DFFF4B225E2C070AF46EDDF3F7FFFFCE1215964E
1995E3AD208E0E5075E7F1171381B240C5AD074A455F5B4015BC68810996A85A
EE3D02545CFA14245C0A14EE6EBD02E437BF61E94DFD121D171F7BB8B7448D01
24190194045A892AD1BB471DEADEE637122F5CFF03DDC0A2F2C21428340119FC
57F530706000D101503A044A4740691728ED03A1FF83523D28ED43E89106DC9D
9DAD14344A5272758D34154CF42CF40C4762288C5CA0618060BF3180945BD840
02503C0F88BB80780610AF03E2030684E582189C73F28B533D12F352725219F6
303817A52696A486E4E7E764A4E614181B05E725161467E4038B427146D78ACC
9280A2FCE4D4E26286FD8CEEA925CEA54545A9793031CF14864E6628DBD8C82D
B3A8B8249CA11B21E2072C51C361653A36ECED1AE4E7EA636CA4979293331AF3
100000
:+res:getpid.ex_:



I've just ran this. It creates getpid.exe but when I run it there's no output (Win8.1x64). :?

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

Re: Detecting Process ID of the current cmd.exe

#42 Post by foxidrive » 24 Dec 2014 20:04

Add the top three lines and try it: it works in Win 8.1 32 bit

Code: Select all

getpid
echo %errorlevel%
pause
Exit /B !ret!

:+res:getpid.ex_:

npocmaka_
Posts: 516
Joined: 24 Jun 2013 17:10
Location: Bulgaria
Contact:

Re: Detecting Process ID of the current cmd.exe

#43 Post by npocmaka_ » 25 Dec 2014 02:40

foxidrive wrote:Add the top three lines and try it: it works in Win 8.1 32 bit

Code: Select all

...


Aghh. The PID is returned in the exitcode. Thanks.

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

Re: Detecting Process ID of the current cmd.exe

#44 Post by Squashman » 14 Oct 2017 14:37

Could this be done in a batch file?
How can I find out, from within a .bat- (or .ps1-) script, if it was started by the task scheduler?

One thing I did test was to see what the variable CMDCMDLINE output.

On Windows 10 I noticed that when Task Scheduler Launched the batch file CMDCMDLINE only had one set of quotes.

Code: Select all

C:\WINDOWS\SYSTEM32\cmd.exe /c "C:\Batch\cmdline.bat"


But when I double click the batch file CMDCMDLINE had two sets of quotes.

Code: Select all

C:\WINDOWS\system32\cmd.exe /c ""C:\Batch\cmdline.bat" "


When the batch file is run from a CMD prompt.

Code: Select all

C:\Batch>cmdline.bat
"C:\WINDOWS\System32\cmd.exe"


Or would there be a way to find the parent process to determine if it was launched by Task Scheduler?

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

Re: Detecting Process ID of the current cmd.exe

#45 Post by aGerman » 14 Oct 2017 15:33

I created a scheduled task in order to quickly get an elevalted prompt.
"Program" is set to
C:\WINDOWS\System32\cmd.exe
and "Arguments" to
/c start cmd /k "cd /d "C:\Users\steffen\Desktop"&color 1f"
Guess what the content of cmdcmdline is

Code: Select all

C:\Users\steffen\Desktop>echo %cmdcmdline%
cmd  /k "cd /d "C:\Users\steffen\Desktop"&color 1f"

C:\Users\steffen\Desktop>


However as soon as you found the current PID (and Dave's approach seems to be working) it's just the same procedure as in the SO thread.

Code: Select all

@echo off
call :getPID currentPID

for /f "tokens=2 delims==" %%i in (
  'wmic process where "ProcessID=%currentPID%" get ParentProcessID /value'
) do for /f %%j in ("%%i") do for /f "skip=1 tokens=* eol=" %%k in (
  'wmic process where "ProcessID=%%j" get Caption'
) do for /f "tokens=* eol=" %%l in ("%%k") do set "parentProc=%%~nxl"

echo "%parentProc%"
pause
exit /b


:getPID  [RtnVar]
::
:: Store the Process ID (PID) of the currently running script in environment variable
:: RtnVar. If called without any argument, then simply write the PID to stdout.
::
setlocal disableDelayedExpansion
:getLock
set "lock=%temp%\%~nx0.%time::=.%.lock"
set "uid=%lock:\=:b%"
set "uid=%uid:,=:c%"
set "uid=%uid:'=:q%"
set "uid=%uid:_=:u%"
setlocal enableDelayedExpansion
set "uid=!uid:%%=:p!"
endlocal & set "uid=%uid%"
2>nul ( 9>"%lock%" (
  for /f "skip=1" %%A in (
    'wmic process where "name='cmd.exe' and CommandLine like '%%<%uid%>%%'" get ParentProcessID'
  ) do for %%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

Steffen

Post Reply