How Can I Start a Process and Then Wait For the Process to End Before Terminating the Script?
Hey, Scripting Guy! How can I start a process and then wait until that process quits before terminating the script?
Hey, FG. Before we answer your question, the Scripting Guy who writes this column has a confession to make: he’s the one who ruined Christmas for everyone. Not that he meant to, mind you, but that doesn’t matter; all that matters is that he ruined Christmas. Last night this Scripting Guy was watching TV when he noticed a commercial featuring a delicatessen where orders were filled like clockwork: you came in, ordered, got your food, paid for your purchase, and then went on your merry way, all in a matter of seconds. And all done to snappy music to boot.
Well, at least until some idiot had the audacity to – believe it or not – pay for his purchase using money instead of a debit card! Not only did the finely-tuned delicatessen machine come to a screeching halt, but the looks of disgust on the faces of both the cashier and the other deli patrons made everyone’s feelings very clear: “Thanks a lot, buddy. You not only ruined Christmas for yourself, but you ruined it for everyone else in the world, too. We hope you’re happy.”
As you probably guessed, the Scripting Guy who writes this column is that idiot. It’s true: although he has a debit card he only uses it at the ATM machine. When it comes to actually buying stuff he always pays cash. Always. And when it comes time to pay the bills he always writes a check, puts the check in an envelope, puts a stamp on the envelope, and mails the thing. (You better sit down; you look a little faint.) No automatic deductions from his checking account, no Internet bill-paying; he still pays his bills the very same way Neanderthals paid their bills.
Note. OK, you got us there: even Neanderthals used pay-by-phone whenever possible.
Needless to say, the Scripting Guy who writes this column humbly apologizes for his boorish and unthinking behavior, and deeply regrets having ruined Christmas for everyone else.
So does this mean that the Scripting Guy who writes this column is going to come to his senses and finally join the modern world? Oh, heavens no; he likes being an anachronism. However, he will at least show you how to write a script that starts a process and then waits around until that process has finished before terminating:
strComputer = "." Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2:Win32_Process") objWMIService.Create "notepad.exe", null, null, intProcessID Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2") Set colMonitoredProcesses = objWMIService.ExecNotificationQuery _ ("Select * From __InstanceDeletionEvent Within 1 Where TargetInstance ISA 'Win32_Process'") Do Until i = 1 Set objLatestProcess = colMonitoredProcesses.NextEvent If objLatestProcess.TargetInstance.ProcessID = intProcessID Then i = 1 End If Loop Wscript.Echo "Notepad has been terminated."
As you can see, we start off by connecting to the WMI service on the local computer. Could we also run this script against a remote computer? Sure we could; however, keep in mind that any process you start on a remote computer will, for security reasons, run in an invisible window and will not appear onscreen. That means you could use this script to run command-line tools that do their thing and then automatically terminate themselves upon finishing; those would work just fine. When it comes to GUI applications, which typically don’t terminate themselves, this script is less useful, at least when run against remote computers. That’s because the GUI application will run in an invisible window, making it far more difficult for someone to terminate that application.
But don’t let that deter you: for command-line tools or for any application started on the local computer this approach works great.
Speaking of the WMI service, we should point out that, when making the connection to this service, we need to bind directly to the Win32_Process class:
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2:Win32_Process")
Why? Because in order to call the Create method and create a new process, we need to be connected to the class itself. The typical WMI approach – using ExecQuery to return a collection of all instances of the class – doesn’t do us any good here.
After making the connection we use the following line of code to create an instance of Notepad.exe (a GUI application, but that’s OK: in this example we’re running it locally anyway). With this line of code we call the Create method followed by the executable file we want to run (Notepad.exe). The executable file name is then followed by two Null parameters (startup parameters not used in this script), then followed by an “out parameter” named intProcessID:
objWMIService.Create "notepad.exe", null, null, intProcessID
What was that again: an out parameter? Yes. Typically when you call methods you supply information (in parameters) to the method; for example, the executable file name (Notepad.exe) is an in parameter. With an out parameter, the method supplies information to you; in this case, that will be the process ID (PID) of the new process. All we have to do is provide the name of the variable where we want that information stored and the Create method will take care of the rest.
At this point we want the script to simply sit around and wait until Notepad is done doing whatever it is Notepad does. To do that we create a second WMI object reference (reusing the variable named objWMIService) and then use the following query to monitor process deletion:
Set colMonitoredProcesses = objWMIService.ExecNotificationQuery _ ("Select * From __InstanceDeletionEvent Within 1 Where TargetInstance ISA 'Win32_Process'")
We won’t discuss monitoring scripts in any detail today; for more information, see the webcast An Ounce of Prevention: An Introduction to WMI Events. Suffice to say that we’re simply asking WMI to notify us any time a process is deleted (terminated). And we’d like WMI to check for any new deletions every second, which is what the Within 1 is for.
After we issue the query we set up the following Do loop, a loop designed to run until a variable named i is equal to 1 (because i has not been initialized, it begins life equal to 0):
Do Until i = 1 Set objLatestProcess = colMonitoredProcesses.NextEvent If objLatestProcess.TargetInstance.ProcessID = intProcessID Then i = 1 End If Loop
Inside this loop we use the following line of code to wait until WMI notifies us that a process has been deleted:
Set objLatestProcess = colMonitoredProcesses.NextEvent
So what happens if a process does get deleted? Well, in that case we check to see if the ProcessID of the deleted process (objLatestProcess.TargetInstance.ProcessID) is equal to the ProcessID of the process we created:
If objLatestProcess.TargetInstance.ProcessID = intProcessID Then
If the ProcessIDs differ we loop around and wait for notification of the next deleted process. If the ProcessIDs match that can only mean one thing: the process we started has been terminated. In that case we set the value of i to 1, something which, in turn, causes us to exit the loop.
At that point we simply echo back a message stating that Notepad has been terminated. Needless to say, though, you can do anything you want at this point; after all, it’s your script. (Or don’t do anything at all; if you prefer, you can just exit the loop and the script will automatically terminate right then and there.)
So will the Scripting Guy who writes this column even consider using a debit card to pay for purchases from now on? Well, to tell you the truth, that did look a lot faster and a lot easier than paying by cash. Of course, the people on this commercial simply swiped their card and then moved on: they didn’t have to enter a PIN number, they didn’t have to wait for authorization, and they didn’t have to sign anything. And none of them ever asked if they could get cash back, either. But maybe none of that happens anymore. After all, the makers of a commercial would never fudge the truth a little just to make their product look better. Heaven forbid!
Tell you what: Christmas is already ruined, but we’ll see if we can avoid ruining Valentine’s Day for you. We can’t promise anything, but we’ll see.