11

I'm using the following code snippet to determine what process ID my vbscript is running as:

On Error Resume Next
Dim iMyPID : iMyPID = GetObject("winmgmts:root\cimv2").Get("Win32_Process.Handle='" & CreateObject("WScript.Shell").Exec("mshta.exe").ProcessID & "'").ParentProcessId
If Err.Number <> 0 Then Call Handle_Error(Err.Description)
On Error Goto 0

On my Windows 7 (32-bit) machine this works about 90% of the time and iMyPID contains the process ID of the currently running script. However 10% of the time Handle_Error gets called with the error message "SWbemServicesEX: Not found".

Recently someone else running Windows 7 (64-bit) reported that Handle_Error always gets called with the error message "Out of memory". This seems an insane error message just to find out your own process ID!

Can anyone recommend a better way of doing this?

Richard
  • 1,471
  • 7
  • 23
  • 47

11 Answers11

14

mshta terminates itself immediately. Maybe it's too late to achieve parent process id by using WMI service.
So, I'd use something like this to eliminate concurrent script processes.

  1. Generate random things.
  2. Determine an application which could be installed on each system, never terminates by itself (e.g. command prompt with /k parameter).
  3. Start the application in hidden mode with generated random argument (WshShell.Run).
  4. Wait a few milliseconds
  5. Query the running processes by using command line argument value.
  6. Get the ParentProcessId property.
Function CurrProcessId
    Dim oShell, sCmd, oWMI, oChldPrcs, oCols, lOut
    lOut = 0
    Set oShell  = CreateObject("WScript.Shell")
    Set oWMI    = GetObject(_
        "winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
    sCmd = "/K " & Left(CreateObject("Scriptlet.TypeLib").Guid, 38)
    oShell.Run "%comspec% " & sCmd, 0
    WScript.Sleep 100 'For healthier skin, get some sleep
    Set oChldPrcs = oWMI.ExecQuery(_
        "Select * From Win32_Process Where CommandLine Like '%" & sCmd & "'",,32)
    For Each oCols In oChldPrcs
        lOut = oCols.ParentProcessId 'get parent
        oCols.Terminate 'process terminated
        Exit For
    Next
    CurrProcessId = lOut
End Function

Dim ProcessId
ProcessId = CurrProcessId 'will remain valid indefinitely

WScript.Echo ProcessId
Nathan Tuggy
  • 2,237
  • 27
  • 30
  • 38
Kul-Tigin
  • 16,728
  • 1
  • 35
  • 64
  • Note that this depends on hoping that the random number generated will be unique, which is risky if a number of scripts are being launched simultaneously. – Harry Johnston Jan 30 '14 at 22:24
  • 1
    @HarryJohnston You're right! Now it's much more robust with a v4 UUID. – Kul-Tigin Mar 13 '14 at 12:17
  • Note that file names (sCmd) might include characters that have special meaning for the WMI like operator (e.g. %, _, ^, [ and ], see: https://msdn.microsoft.com/en-us/library/aa392263(v=vs.85).aspx), therefore it is saver to check the commandline within the For Each loop. – iRon May 15 '15 at 14:17
  • 1
    @iRon You're right about WQL's Like operator behaviours but `sCmd` isn't a file name here and it cannot contain that special characters. It's just something like `/K {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}`so it's safe to use in a WMI Query with Like operator. – Kul-Tigin May 15 '15 at 14:40
6

Here's an even better code snippet:

      ' ***********************************************************************************************************
      ' lng_MyProcessID finds and returns my own process ID. This is excruciatingly difficult in VBScript. The
      ' method used here forks "cmd /c pause" with .Exec, and then uses the returned .Exec object's .ProcessID 
      ' attribute to feed into WMI to get that process's Win32_Process descriptor object, and then uses THAT
      ' WMI Win32_Process descriptor object's .ParentProcessId attribute, which will be OUR Process ID, and finally
      ' we terminate the waiting cmd process. Execing cmd is what causes the brief cmd window to flash at start up,
      ' and I can' figure out out how to hide that window.

      ' returns: My own Process ID as a long int; zero if we can't get it.
      ' ************************************************************************************************************

      Function lng_MyProcessID ()

        lng_MyProcessID = 0                     ' Initially assume failure

        If objWMIService Is Nothing Then Exit Function      ' Should only happen if in Guest or other super-limited account

        Set objChildProcess = objWshShell.Exec ( """%ComSpec%"" /C pause" ) ' Fork a child process that just waits until its killed

        Set colPIDs= objWMIService.ExecQuery ( "Select * From Win32_Process Where ProcessId=" & objChildProcess.ProcessID,, 0 )

        For Each objPID In colPIDs                  ' There's exactly 1 item, but .ItemIndex(0) doesn't work in XP

          lng_MyProcessID = objPID.ParentProcessId          ' Return child's parent Process ID, which is MY process ID!

        Next

        Call objChildProcess.Terminate()                ' Terminate our temp child

      End Function ' lng_MyProcessID
Asok Smith
  • 57
  • 1
  • 2
4

I like Kul-Tigin's idea (+1), and Asok Smith's idea (based on .Exec) deserve respect (+1), and it w'd been even better if .Exec run hidden process. So, to feed my curiosity, I also toyed with this and this's what I did.

ts1 = Timer : res1 = CurrProcessId : te1 = Timer - ts1
ts2 = Timer : res2 = ThisProcessId : te2 = Timer - ts2
WScript.Echo "CurrProcessId", res1, FormatNumber(te1, 6), _
    vbCrLf & "ThisProcessId", res2, FormatNumber(te2, 6), _
    vbCrLf & "CurrProcessId / ThisProcessId = " & te1 / te2

'> CurrProcessId 6946 0,437500
'> ThisProcessId 6946 0,015625
'> CurrProcessId / ThisProcessId = 28

Function ThisProcessId
    ThisProcessId = 0
    Dim sTFile, oPrc
    With CreateObject("Scripting.FileSystemObject")
        sTFile = .BuildPath(.GetSpecialFolder(2), "sleep.vbs")
        With .OpenTextFile(sTFile, 2, True)
            .Write "WScript.Sleep 1000"
        End With
    End With
    With CreateObject("WScript.Shell").Exec("WScript " & sTFile)
        For Each oPrc In GetObject("winmgmts:\\.\root\cimv2").ExecQuery(_
        "Select * From Win32_Process Where ProcessId=" & .ProcessID)
        Exit For : Next
        ThisProcessId = oPrc.ParentProcessId
    End With
End Function

28 times faster(!), not bad :)

Panayot Karabakalov
  • 3,109
  • 3
  • 19
  • 28
4

You may use Sleep from kernel32 instead of mshta.

MsgBox GetProcId()

Function GetProcId()
    With GetObject("winmgmts:\\.\root\CIMV2:Win32_Process.Handle='" & CreateObject("WScript.Shell").Exec("rundll32 kernel32,Sleep").ProcessId & "'")
        GetProcId = .ParentProcessId
        .Terminate
    End With
End Function

Code taken from here.

Also there is parent process name detection based on this approach.

omegastripes
  • 12,351
  • 4
  • 45
  • 96
  • I've only been able to briefly test this but it looks to be the shortest and cleanest solution. – Richard Oct 14 '20 at 09:12
2

Here is a better one, but in JScript (sorry, you translate it to VB ...)

var WshShell = WScript.CreateObject("WScript.Shell");
var objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\\\.\\root\\cimv2");
var childProcess =
    WshShell.Exec
    (
        '"' + WshShell.Environment('PROCESS')('ComSpec') + '"'
        +
        " /C Echo \"Text lines\" && Set /p VarName="
    );
childProcess.StdOut.ReadLine();
var current_pid =
    objWMIService.ExecQuery
        (
        "Select * From Win32_Process Where ProcessId=" + childProcess.ProcessID
        );
current_pid = (new Enumerator(current_pid)).item().ParentProcessId;
if (current_pid)
{
    childProcess.StdIn.WriteLine("value");  // child process should now exit
    WScript.Echo("Current PID: " + current_pid);
}
else
{
    WScript.StdErr.WriteLine("Get current PID from WMI failed.");
    WScript.Quit(7);
}
Toughy
  • 767
  • 6
  • 5
1

I just found this thread that partly solved my problem. Thank you all.

"the code is unable to determine which process ID belongs to which script" : true, but as this is the first task that your script must achieve , you can keep the Pid that has the shortest lifetime.

 Set com = CreateObject("Wscript.Shell")

 Set objSWbemServices = GetObject ("WinMgmts:Root\Cimv2")
 Set colProcess = objSWbemServices.ExecQuery ("Select * From Win32_Process")
 dim toto, thisPid

 thisPid=""
 toto=200 ' just a high value like 200sec 
 For Each objProcess In colProcess

     If InStr (objProcess.CommandLine, WScript.ScriptName) <> 0  Then
        Ptime=((Cdbl(objProcess.UserModeTime)+Cdbl(objProcess.KernelModeTime))/10000000)
        if toto > Ptime then
            toto = Ptime
            thisPid = objProcess.ProcessId
        End If
     End If
 Next

 If thisPid="" then
    WScript.Echo "unable to get the PID"
 Else
    WScript.Echo "PID of this script : "&thisPid
 End If

Except if you fired scripts quicker more than each one can retrieve their Pid, everything must be ok.

Andro Selva
  • 53,910
  • 52
  • 193
  • 240
nobur
  • 11
  • 1
1

To retrieve the own process ID of a VB Script you can rely on the property CreationDate of the Process object.

At the moment a VB Script is started, the process that runs the script will have the latest CreationDate of all processes that runs the same script.

In fact, it will have the highest CreationDate of all running processes.

So, to get the PID, first thing to do is to search for the process with the highest CreationDate.

'Searching for processes
Dim strScriptName
Dim WMI, wql
Dim objProcess
'
'My process
Dim datHighest
Dim lngMyProcessId


'Which script to look for ? 
strScriptName = "WScript.exe"
'strScriptName = "Notepad.exe"

'Iniitialise 
datHighest = Cdbl(0)

Set WMI = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
wql = "SELECT * FROM Win32_Process WHERE Name = '" & strScriptName & "'"
'
For Each objProcess In WMI.ExecQuery(wql)
  'The next If is not necessary, it only restricts the search to all processes on the current VB Script
  'If Instr(objProcess.CommandLine, WScript.ScriptName) <> 0 Then
    If objProcess.CreationDate > datHighest Then
      'Take the process with the highest CreationDate so far
      '  e.g. 20160406121130.510941+120   i.e. 2016-04-06 12h11m:30s and fraction
      datHighest = objProcess.CreationDate
      lngMyProcessId = objProcess.ProcessId
    End If
  'End If
Next

'Show The result
WScript.Echo "My process Id = " & lngMyProcessId
1

Powershell can be used to retrieve the calling VBScript process ID. This approach utilizes the optional argument of the exit command which specifies the program's exit code. And, if the optional 3rd argument of the WShell.Run method is set to True, then it will return the exit code (which is the VBScript process ID) after powershell has closed.

Dim sCmd
Dim WShell

sCmd = _
"powershell -command exit " & _
"(gwmi Win32_Process -Filter " & _
"\""processid='$PID'\"").parentprocessid"
Set WShell = CreateObject("WScript.Shell")
MsgBox WShell.Run(sCmd, 0, True)
Chris D
  • 351
  • 1
  • 13
0

Get the current processID

Set WshShell = CreateObject("WScript.Shell")
currentProgram=wscript.ScriptName
Const strComputer = "."
Dim objWMIService, colProcessList
Set objWMIService = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
query="SELECT * FROM Win32_Process WHERE Name = 'wscript.exe' "  
Set colProcessList = objWMIService.ExecQuery(query)
For Each objProcess in colProcessList 

If (InStr (objProcess.commandLine,wscript.ScriptName) <> 0 )Then
 processDetails="Current ProcessId : "& objProcess.ProcessId & " \n, And Process Name:" & objProcess.name &"\n CommandLine is :"& objProcess.CommandLine
 message = msgbox(processDetails,16,"Details")
End If
Shubham Verma
  • 8,783
  • 6
  • 58
  • 79
  • Unfortunately this code just takes a list of all the running processes and assumes that any process which contains the name of that script is that script. In other words if two scripts are running at the same time, the code is unable to determine which process ID belongs to which script. – Richard Nov 12 '17 at 10:20
0

I used this to get a scripts own process id.

Function GetPid()
    GetPid=GetObject("winmgmts:\\.\root\CIMV2").ExecQuery("Select * From Win32_Process Where CommandLine Like '%" &Wscript.ScriptName& "%'").ItemIndex(0).ProcessId
End Function

Wscript.Echo GetPid()
  • Unless I'm mistaken, your solution will run into problems if there are two processes with the same name. – Richard Dec 30 '22 at 17:16
  • The command line should maintain uniqueness. If the 'script' file is engineered to handle arguments while running multiple instances… then you'd need to build the string e.g. Wscript.ScriptFullName & Wscript.Arguments(0) & etc. Example: https://drive.google.com/file/d/1wgPizUx4p3zyvqktZioYshEXZ371ce75/view?usp=sharing – softwearmale Jan 01 '23 at 03:31
0

This is not my answer, I found this in some google groups discussion forum... See if it helps you.

Set objSWbemServices = GetObject ("WinMgmts:Root\Cimv2")
Set colProcess = objSWbemServices.ExecQuery ("Select * From Win32_Process")

For Each objProcess In colProcess
    If InStr (objProcess.CommandLine, WScript.ScriptName) <> 0 Then
      WScript.Echo objProcess.Name, objProcess.ProcessId, objProcess.CommandLine
    End If
Next

Original Discussion Thread in Google Groups forum

Buddha
  • 4,339
  • 2
  • 27
  • 51
  • 1
    Thanks for this. Unfortunately this code just takes a list of all the running processes and assumes that any process which contains the name of that script is that script. In other words if two scripts are running at the same time, the code is unable to determine which process ID belongs to which script. – Richard Dec 06 '11 at 22:49