Event-driven WSH scripts
Before the advent of Windows, many programs strictly followed a procedural design. You ran the program, and it did its work while you waited. When the program was finished, the command prompt returned and you could run another program. With Windows, a new paradigm became dominant: event-driven programming. As you interact with an application, events occur. For example, in programming terms, clicking a command button on a form or Web page raises a button's Click event. The programmer then writes a section of code to react to this event. If no such code exists, Windows simply discards the event.
You can apply the same event concepts to scripts. In other words, you can create event-driven scripts. Before I describe how to create these types of scripts, you need to know how events work and the terms used to describe that process.
An event source signals the occurrence of an event. Broadcasting this signal is called raising the event. Any object can be an event source, which is often called an event firer or event sender. An event receiver responds to an event, a process that's referred to as trapping or sinking an event. Event receivers, which are often called event handlers or event sinks, are subprocedures. So, to sum up, when an event firer raises an event, the event handler traps the event and performs an action.
Broadly speaking, you can categorize events that you might want to deal with in scripts into four types:
- events triggered by ActiveX controls
- events triggered by errors
- events triggered by custom code you create through Windows Script Components (WSC)
- events triggered by custom code you create through Windows Management Instrumentation (WMI)
In this article, I concentrate on ActiveX, error, and WSC events. In a later article, I'll cover WMI events.
As a scriptwriter, you're likely familiar with how to use ActiveX controls. Such controls tend to come with objects that have methods, properties, and events. When those controls support automation, you can interact with some or all of the methods, properties, and events in scripts. For example, the InternetExplorer.Application object—the root object of Microsoft Internet Explorer (IE)—supports automation, so you can use that object in event-driven scripts.
The WScript object—the root object in the Windows Script Host (WSH) object model hierarchy—comes with the ability to trap events that ActiveX objects raise. WScript is available in any WSH script, and you never need to instantiate this object before invoking its methods and properties.
You use the WScript object's CreateObject method to create an instance of an ActiveX object. The reason for using WSH's CreateObject method rather than VBScript's CreateObject function is clearly explained in "Rem: Using the Correct CreateObject," July 2003, http://www.winnetmag.com/windowsscripting, InstantDoc ID 39115. The article states, "The functional difference between the CreateObject method and CreateObject function lies in their optional second parameters (strPrefix and location, respectively). The CreateObject method's second parameter supports capturing events from automation objects that fire events. The CreateObject function's second parameter supports creating objects on remote computers."
The CreateObject method's second parameter is the one you need to use to create an event handler. For example, the code
WScript.CreateObject( _ "InternetExplorer.Application", _ "IE_")
creates an InternetExplorer.Application object and handles events that this object raises. The second parameter (IE_) denotes the prefix that you're going to use in the event handler's name. An event handler's name consists of this prefix (which is typically a shortened version of the object's name) followed by the name of the event being trapped. For example, the name of an event handler that traps IE's onQuit event might be IE_onQuit.
However, you aren't required to follow this naming convention. Some scriptwriters prefer to use the term SINK_ as the prefix instead of the object's name. This prefix works as long as you don't need to monitor events raised by two or more objects in a script. At that point, you would have to call them SINK1_, SINK2_, and so on, which might lead to obfuscation.
As I mentioned previously, event handlers are subprocedures, so you can use VBScript's Sub statement to create them. For example, the subprocedure to create an event handler that traps IE's onQuit event would take the form
Sub IE_onQuit ' Actions to take here. End Sub
The actions that you take in response to the event depend on the event and what you're trying to accomplish. For example, let's take a look at SimpleEventSink.vbs, which Listing 1 shows. I start this script by using the Option Explicit statement and declaring the objIE variable. I then create an instance of the InternetExplorer.Application object and assign that object reference to the objIE variable. Because the object will be trapping events, I create the IE_onQuit subprocedure, which the code at callout A in Listing 1 shows, to trap the onQuit event. The IE_onQuit event handler performs two actions when it traps an event. First, it displays a message that the event has been trapped. Second, it uses the WScript object's Quit method to forcibly stop the script. I had the event handler stop the script because, in the main body of the script, I make IE visible and start an infinite loop that waits for an event to arrive. Thus, if the event handler didn't end the script, the script would never end.
The infinite loop uses the WScript object's Sleep method to suspend the script's execution for 5 milliseconds (5ms). You must include a sleep line in any infinite loop like this one. Without such a line, the WSH process would take up more than 90 percent of the CPU, and you would have to use Windows Task Manager to forcibly quit the WSH process.
Also be aware that handling the onQuit event halts IE's close process. Capturing any event can cause the event source to wait on the result. The delay doesn't usually cause a problem, but if your code goes into an extended process, it could prevent IE from closing.
If you want to see another example of how to trap IE events, check out the article "Using IE to Control Script Execution," June 2003, http://www.winnetmag.com/windowsscripting, InstantDoc ID 38664. This article shows you how to trap and use the IE_onQuit event so that you can create a GUI for WSH scripts.
WSH automatically traps and responds to error events in scripts. For example, suppose that you write a four-line script that generates an error on line three. When the script encounters the error in the third line, WSH raises an error event. WSH then automatically traps the error event, terminates the script, and writes the error details to the standard output, which is a dialog box or the command prompt, depending on whether you ran the script from wscript.exe or cscript.exe, respectively.
You can tell WSH not to respond to any error events by including the On Error Resume Next statement in your script. When you use this statement and WSH encounters an error, WSH continues to execute lines as well as it can. In other words, WSH doesn't stop the script's execution. Typically, you place the On Error Resume Next statement at the beginning of a script, although you can place it anywhere.
When you use On Error Resume Next, you need to use an alternative method to check for errors in your code. The article "Scripting Solutions with WSH and COM: Trapping and Handling Errors in Your Scripts," May 2001, http://www.winnetmag.com/windowsscripting, InstantDoc ID 20500, describes several error-checking methods you can use in WSH scripts. One of these methods uses the CheckError subprocedure to trap and handle runtime errors. CheckError takes advantage of VBScript's Err object, which returns information about runtime errors. This intrinsic object has global scope, which means you don't need to create an instance of the object in your code. Because you can set the Err object's properties, you can raise errors in your scripts that either WSH or an error handler (such as CheckError) can automatically handle.
The script SimpleRaiseError.vbs, which Listing 2 shows, demonstrates how to use the Err object. To raise your own errors, you must use a hexadecimal value higher than 80040000. So, in SimpleRaiseError.vbs, I set the vbObjectError constant to a hex value of 80040000 and later increment this value by 1.
Next, I use the Err object's Clear method to make sure no previous errors exist. Clearing the error fields before you raise an error is good practice; otherwise, the Err object might include data left over from a previous error. When WSH encounters the Clear method or the On Error Resume Next statement, WSH resets the Err object's properties to 0 (i.e., an empty string).
After clearing the error fields, I use the Err object's Raise method with three out of five possible parameters. The first parameter is the error number that I want to use, which is 80040001 in this case. The second parameter is a string that specifies the source of the object or script that caused the error. The third parameter is a string that provides a simple description of the error. The two parameters that I didn't include are the helpfile and helpcontext parameters, which provide pointers to a Help file and a topic in a Help file, respectively. You're unlikely to need to use these two parameters in a script. Finally, I end the script by using the MsgBox function to display the information that the Err object captures.
For background information about error handling, go to the TechNet Script Center (http://www.microsoft.com/technet/scriptcenter/default.asp). In the directory on the left, navigate to Scripting Guides, Windows 2000 Scripting Guide, Scripting Concepts and Technologies for System Administration, VBScript Primer, VBScript Reference, Error Handling.
You can create custom controls that support automation—and you don't have to be a programmer or have a program-development tool to do so. As an alternative to programming, you can create custom automation-supporting controls and register them for use. These controls can have methods, properties, and events, just like ActiveX controls. Such controls are known as WSCs.
I don't cover WSCs any further in this article. I've already done so in a previous four-part series:
- "Scripting Solutions with WSH and COM: Using WSC to Build a Progress Bar Dialog Box, Part 1," September 2000, http://www.winnetmag.com/windowsscripting, InstantDoc ID 9802
- "Scripting Solutions with WSH and COM: Using WSC to Build a Progress Bar Dialog Box, Part 2," October 2000, InstantDoc ID 15641
- "Scripting Solutions with WSH and COM: Using WSC to Build a Progress Bar Dialog Box, Part 3," January 2001, InstantDoc ID 16297
- "Scripting Solutions with WSH and COM: Using WSC to Build a Progress Bar Dialog Box, Part 4," February 2001, InstantDoc ID 16406
You can also find out more about WSC in the Microsoft Developer Network (MSDN) Library (http://msdn.microsoft.com/library). In the directory on the left, navigate to Web Development, Scripting, Documentation, Windows Script Technologies, Windows Script Components.
An Eventful Beginning
I hope that this article has given you a good introduction to events in WSH. In a follow-up article, I'll show you how to create more advanced event-driven scripts, including scripts that trap error events for remote scripts and scripts that use WMI for synchronous and asynchronous event monitoring.