Find my own process ID in VBScript

42,048

Solution 1

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

Solution 2

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

Solution 3

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 :)

Solution 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.

Solution 5

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);
}
Share:
42,048
Richard
Author by

Richard

Updated on October 15, 2020

Comments

  • Richard
    Richard over 3 years

    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
    Richard over 12 years
    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.
  • Panayot Karabakalov
    Panayot Karabakalov about 11 years
    Why you think this method is better? Exec popup visible window.
  • Panayot Karabakalov
    Panayot Karabakalov about 11 years
    Because you have a good idea (+1), I thought that my comments will encourage you to improve your answer, but I lost patience and published it as my own answer.
  • Harry Johnston
    Harry Johnston about 10 years
    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.
  • Kul-Tigin
    Kul-Tigin about 10 years
    @HarryJohnston You're right! Now it's much more robust with a v4 UUID.
  • iRon
    iRon almost 9 years
    Note that file names (sCmd) might include characters that have special meaning for the WMI like operator (e.g. %, _, ^, [ and ], see: msdn.microsoft.com/en-us/library/aa392263(v=vs.85).aspx), therefore it is saver to check the commandline within the For Each loop.
  • Kul-Tigin
    Kul-Tigin almost 9 years
    @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.
  • Richard
    Richard over 6 years
    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
    Richard over 3 years
    I've only been able to briefly test this but it looks to be the shortest and cleanest solution.