Hey, Scripting Guy! Question

Hey, Scripting Guy! How can I find the process ID associated with a batch file?

-- MB

SpacerHey, Scripting Guy! AnswerScript Center

Hey, MB. You know, the Scripting Guys are firm believers in recycling. Why throw that printout away when it can be recycled into newsprint? Why throw that aluminum can away when it can be melted down and reused? Why throw away that Hey, Scripting Guy! column when you can use it to answer someone else’s question a few months later?

In other words, today just happens to be Recycling Day here in the Script Center. A long time ago (October 6, 2004, to be precise) we wrote a column telling people how they could determine which scripts (by file name) were running on a computer. Today we’re going to recycle that column, using a slight variation to show you how you can determine the process ID associated with any batch files that are running on your computer. Some of the stuff we’ll show you today is new, but one thing that hasn’t changed is the disclaimer: this solution works only on Windows XP or Windows Server 2003. (We’ll explain why in a bit.) And, no: we don’t really know of a good way to do this on any earlier version of Windows. Sorry.

Assuming you’re running Windows XP or Windows Server 2003, however, the following script reports back the name and process ID of any batch file running on a computer:

strComputer = "."

Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")

Set colItems = objWMIService.ExecQuery("Select * From Win32_Process")

For Each objItem in colItems
    If InStr(objItem.CommandLine, ".bat") Or InStr(objItem.CommandLine, ".cmd") Then
        Wscript.Echo "Batch file: " & objItem.CommandLine
        Wscript.Echo "Process ID: " & objItem.ProcessID
    End If
Next

So how does this script work? Good question. To begin with, we bind to the WMI service on the local computer. (Although we could easily adapt the script to retrieve the process IDs on any batch files running on a remote computer; all we’d have to do is set the value of the variable strComputer to the name of that remote computer.) After making the connection we then use this line of code to retrieve a collection of all the processes running on the computer:

Set colItems = objWMIService.ExecQuery("Select * From Win32_Process")

So far so good, right? Next we set up a For Each loop to loop through the collection of processes. Inside that loop we use the InStr method to see if the strings .bat or .cmd can be found anywhere within the value of the CommandLine property, which simply reports back the command line value used to start a process. (What if you started the process by double-clicking an icon in Windows Explorer? No problem; in that case CommandLine is equal to the command line value you would have used had you started the process from the command line or from the Run dialog box.)

For example, here’s the kind of information you can expect to see when you look at the value of the CommandLine property:

cmd /c ""C:\Scripts\test.cmd" "
"C:\Program Files\Internet Explorer\IEXPLORE.EXE"
"C:\WINDOWS\system32\NOTEPAD.EXE" C:\Scripts\cmdline.vbs

If you take a look at the first process you can see that we have a batch file named Test.cmd running. By looking for processes where the CommandLine property includes the strings .bat or .cmd (the file extensions used for batch files) we can determine whether any of these processes are actually running batch files. And once we know that, getting the process ID for those batch files is trivial.

Note. Yes, you’re right: a more precise test would be to check and see if the last four characters of the path name are .bat or .cmd. We didn’t do that because the double quotes tacked on the end of the CommandLine value -- cmd /c ""C:\Scripts\test.cmd" " - complicate things a bit. We’re assuming that, in the vast majority of cases, any time you have a .bat or a .cmd in the path then you have a batch file. But if you want to improve upon what we’ve done here, well, who are we to stop you?

In case you’re wondering, this also explains why our script works only on Windows XP and Windows Server 2003: that’s because the CommandLine property is available on only those two platforms.

So what if we do find a batch file? In that case we simply use these two lines of code to echo back the process name and the process ID:

Wscript.Echo "Batch file: " & objItem.CommandLine
Wscript.Echo "Process ID: " & objItem.ProcessID

And there you have it.

Of course, we should point out that, as written, this script returns the name and process ID of all the batch files running on a computer. In your question, MB, you implied that you were interested in only a particular batch file. If that’s the case, then simply modify the If-Then statement so it looks for a CommandLine containing the full batch file name. For example, this modified script checks to see if any processes have the string Test.cmd somewhere in the CommandLine property:

strComputer = "."

Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")

Set colItems = objWMIService.ExecQuery("Select * From Win32_Process")

For Each objItem in colItems
    If InStr(objItem.CommandLine, "test.cmd") Then
        Wscript.Echo "Process ID: " & objItem.ProcessID
    End If
Next

If found, the script reports back the process ID for only the specified batch file.

Want to get really fancy? Here’s a monitoring script that alerts you any time a new batch file starts up:

strComputer = "."

Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")

Set colMonitoredProcesses = objWMIService. _        
    ExecNotificationQuery("select * from __instanceCreationEvent " _ 
        & " within 1 where TargetInstance isa 'Win32_Process'")
i = 0

Do While i = 0
    Set objProcess = colMonitoredProcesses.NextEvent
    If InStr(objProcess.TargetInstance.CommandLine, ".cmd") Or _
        InStr(objProcess.TargetInstance.CommandLine, ".bat") Then 
        Wscript.Echo "Batch file: " & objProcess.TargetInstance.CommandLine
        Wscript.Echo "Process ID: " & objProcess.TargetInstance.ProcessID
    End If
Loop

We won’t discuss this script in any detail today; for more information see the webcast An Ounce of Prevention: An Introduction to WMI Events. (And yes, we are recycling old material again, this time an old Scripting Week 2 webcast. Didn’t we mention that today was Recycling Day?)