Hey, Scripting Guy! Here’s what I’d like to do: I want a script that will launch two executable files. After the first application closes I’d like the script to close the second application and then quit. How can I do that?
Hey, MK. You know, this is the kind of question we like. Why? Because it sounds really complicated and really hard, and if anyone wants anything from us we can just say, “You know, I am trying to write a script that will launch two applications, wait for the first one to close, and then automatically close the second one.” And inevitably they’ll say, “Oh, I’m sorry; obviously you’re very busy” and then leave us alone.
What they don’t know, of course, is that this only sounds hard. In truth, it’s no more difficult than this:
strComputer = "." Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2:Win32_Process") errResult = objWMIService.Create("calc.exe", null, null, intCalcID) errResult = objWMIService.Create("notepad.exe", null, null, intNotepadID) Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2") Set colProcesses = objWMIService.ExecNotificationQuery _ ("Select * From __InstanceDeletionEvent " _ & "Within 1 Where TargetInstance ISA 'Win32_Process'") Do Until i = 999 Set objProcess = colProcesses.NextEvent If objProcess.TargetInstance.ProcessID = intCalcID Then Exit Do End If Loop Set colProcesses = objWMIService.ExecQuery _ ("Select * from Win32_Process Where ProcessID = " & intNotepadID) For Each objProcess in colProcesses objProcess.Terminate() Next
No, trust us: this is actually pretty simple once you know what the script is doing. We begin by connecting to the WMI service on a computer and, more specifically, binding to the Win32_Process class. That’s what we do here:
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2:Win32_Process")
Next we use the Create method to create two new processes: Calc.exe and Notepad.exe. For each new process we use code similar to this:
errResult = objWMIService.Create("calc.exe", null, null, intCalcID)
All we’re doing is calling the Create method followed by:
The name of the executable file (depending on how your computer is set up, you might have to specify the full path name for the application).
A pair of Null parameters. These two parameters enable us to specify a different working folder for the application as well as configure some additional startup options. We aren’t going to worry about either of those things in this sample script, so we just set the parameter values to Null.
A variable (named intCalcID) that functions as an “out parameter.” When our process gets created, the ProcessID number assigned to the process will also be assigned to our out parameter variable.
The net result is that we’ll start Calculator, and the variable intCalcID will contain the process ID assigned to that instance of Calculator. We’ll then start Notepad, and the variable intNotepadID will contain the ProcessID assigned to that instance of Notepad. This is how we start the applications, and how we’ll keep track of them.
What we need to do next is, well, basically nothing: we want the script to pause until Calculator has been dismissed. To do that, we reconnect to the WMI service and then use ExecNotificationQuery to monitor for any processes that are deleted. We need to reconnect to the WMI service because, at the beginning of the script, we connected specifically to the Win32_Process class; our object reference (objWMIService) thus refers only to that class. We need to connect to the “generic” WMI service, so we just reuse the object reference objWMIService and make a new connection:
Set colProcesses = objWMIService.ExecNotificationQuery _ ("Select * From __InstanceDeletionEvent " _ & "Within 1 Where TargetInstance ISA 'Win32_Process'")
Why do we do that? Each time a process is deleted, an instance of the __InstanceDeletionEvent class is generated. We want to examine each of these instances and see whether it happens to be a process with our target ID, the ID assigned to intCalcID. If the deleted process has a different ID, then it’s not our instance of Calculator; in that case, the script will resume monitoring. If the deleted process does have the same ID as intCalcID, then it must be our instance of Calculator (because process IDs must be unique). In that case, we’ll want to stop monitoring, and then close Notepad.
Here’s the code that actually does the monitoring:
Do Until i = 999 Set objProcess = colProcesses.NextEvent If objProcess.TargetInstance.ProcessID = intCalcID Then Exit Do End If Loop
What we’ve done here is set up a loop that will run until the variable i is equal to 999. Now, the truth is, i will never equal 999; this is just a little trick to ensure that our loop runs until Calculator has been dismissed. (How do we know i will never equal 999? Well, we don’t assign a value to i; consequently, it takes on the default value of 0. And because we never do anything to change that value, i will always be 0, and thus never be equal to 999.)
Inside the loop we use this line of code to wait for the next deleted process:
Set objProcess = colProcesses.NextEvent
Each time a process is deleted we check to see if the ProcessID matches up with the process ID assigned to Calculator. If it does, we then use the Exit Do command to break out of our loop and continue with the script. If it doesn’t have the same ID, then we simply loop around and wait for the next deleted process. (Like we said, i will never equal 999, but that’s OK: the Exit Do command will always break us out of a loop.)
Note. We realize that we’ve kind of skimmed over the whole idea of event monitoring. If you’re a bit confused by things like __InstanceDeletionEvent and colProcesses.NextEvent you might want to take a look at the Scripting Guys webcast An Ounce of Prevention: An Introduction to WMI Events.
Now all we have to do is terminate the instance of Notepad we started. To do that we use this WMI query to retrieve a collection of all processes that have the process ID assigned to Notepad:
Set colProcesses = objWMIService.ExecQuery _ ("Select * from Win32_Process Where ProcessID = " & intNotepadID)
After we have that collection, we use this block of code to loop through the set of processes (there will be only one) and then use the Terminate method to close the application:
For Each objProcess in colProcesses objProcess.Terminate() Next
Incidentally, this approach will work on remote computers as well as on local computers; just change the value of the variable strComputer to the name of the remote machine. Keep in mind, however, that on Windows XP and Windows Server 2003 the processes you start on the remote computer will run in an invisible window; they will not be visible on screen. That means that, when it comes to dealing with remote computers, this approach is very useful for applications that require no user interaction, but far less useful (in fact, downright useless) for applications that do require user intervention.