Easily stop a WSH with an IE window
Scripts' silent, invisible execution and automatic exit are usually advantages. But some sort of GUI—even a primitive one that could show you that a script is indeed executing and could let you terminate a script that runs continuously—would be useful at times. You can use Task Manager to stop long-running scripts, but this technique is an ugly one. You must click several times, and you see the name of the host executable rather than the names of the scripts the host is running, so you must end all the scripts rather than just the one you want to end.
You can use Windows Script Host (WSH) to provide a GUI for a script. WSH can connect event sources of COM objects that it creates to special functions inside a script. A script can instantiate a COM server, then go off and do other work. When an event fires in the COM server, a procedure in the script hooked to that event automatically executes. The procedure is known as the event's sink.
You can use almost any COM object that has a GUI and exposes its exit event to provide a GUI for a script; I use Microsoft Internet Explorer (IE) because it's present on almost all Windows PCs and its versatility makes it a useful tool for extending script functionality. After I show you the core concept in basic form, I describe a wrapper that provides a simple pair of functions for execution control that you can drop into any script.
This wrapper isn't the ultimate answer. For one thing, it doesn't perform any cleanup when it shuts down a script. It does give you the option of a planned script bailout, though, and a window for viewing any running script. Another disadvantage of my solution is that launching IE from a script requires quite a bit of memory, which might be problematic on low-memory or low-speed systems. However, the memory penalty drops dramatically with the second IE launch of this type. My first instance of script-launched IE uses 9300KB of RAM, the second takes 500KB or so more, and subsequent instances drop to about 200KB added memory load per instance. And because IE isn't performing any tasks, the program itself doesn't use the CPU at all.
Capturing IE's Events
IE has the version-independent programmatic identifier (ProgID) InternetExplorer.Application; it also exposes several events, including one called onQuit, which fires when you use the Close button, Alt+F4, or the menu bar's File, Exit option to exit IE. I use WSH's event-sinking capabilities to stop my script when I close the instance of IE that the script creates.
Listing 1 presents basic code to start and stop IE. At callout A in Listing 1, I use WScript's CreateObject method, not VBScript's internal CreateObject method, to start IE. WScript.CreateObject is different from plain CreateObject and is necessary for using events in a script. After looking at callout A, you might wonder what the argument "IE_" is for. The documentation for WScript.CreateObject (at http://msdn.microsoft.com/library/en-us/script56/html/wsmthcreateobject.asp) explains that the optional strPrefix argument is a "string value indicating the function prefix." The documentation offers more explanation in the form of an example: "For example, if strPrefix is MYOBJ_ and the object fires an event named OnBegin, Windows Script Host calls the MYOBJ_OnBegin subroutine located in the script. The CreateObject method returns a pointer to the object's IDispatch interface." (Note that the documentation contains an error, which I've corrected. The documentation incorrectly omits the underscore after the first mention of MYOBJ.) By the way, if you're wondering why an underscore (and not some other character) is the last character in the prefix, the answer is tradition. Visual Basic (VB) and other programming environments that automatically create sinks for events raised by objects historically append an underscore to the object name when creating the event sinks.
What's the documentation telling us? Simply that you can write event handlers in a script. IE exposes an onQuit event; I declared a function prefix of IE_ so that I can include an IE_onQuit subroutine in my script. When IE's onQuit event fires, the script will immediately execute the IE_onQuit subroutine, no matter what the script is doing, as the code at callout C in Listing 1 shows. The ability to capture events from an object that a script creates has many implications. For one thing, it lets us build an escape hatch into a script that might loop forever (or for a long time).
The middle portion of my script—the code at callout B in Listing 1—first makes IE visible. (Like many other COM servers, IE isn't automatically visible when instantiated.) The script then enters a loop that will run forever if left to its own devices. The loop's wscript.sleep line is a standard feature of any script that runs a repeating test loop while the script waits for something to change. If you don't force a script to regularly pause while in a loop, it might consume most of your CPU cycles.
If you use CScript or WScript to execute the script, you'll see IE open. If you look in Task Manager, WScript or CScript will be running. As soon as you exit IE, the script displays the word exiting. If you were running the script in Cscript, CScript will immediately disappear from the task list. If you were using WScript as the host, you must close the WScript.Echo pop-up window before WScript will quit.
A Clean Stop
This technique provides an easy way to create an emergency exit for a script, but it has some disadvantages. A bunch of full-sized IE instances makes for a messy desktop and uses up a lot of screen space. In addition, these IE instances are navigable, which means that you could accidentally use one of them as a browser window, then close it—resulting in unpredictable consequences for the script that launched it. Finally, if multiple scripts are running, determining which IE instance belongs to which script is difficult.
You can overcome all these inconveniences quite readily by making use of IE's object model. When you start working with IE, you'll discover that its incredibly rich internal object model makes it a perfect complement to WSH. The script that Listing 2 shows encapsulates the IE launch in some simple drop-in code that you can use in any script, that handles the potential problems I just mentioned, and that's unlikely to interfere with anything else in your script.
I can easily perform the encapsulation with the IE object model and another subroutine. Instead of directly creating a reference to IE, as the code in Listing 1 does, the code at callout A in Listing 2 declares a public variable IE, then calls a subroutine named IEScriptWindow. In the code at callout B in Listing 2, the IE_onQuit event sink makes the script exit if the user closes the IE window. The code at callout C in Listing 2 is the IEScriptWindow subroutine, which customizes the IE script-control window. To use the Listing 2 script with an existing script, you simply insert the code at callout A before the main body of your existing script and the code at callouts B and C in after the body of your existing script.
The IEScriptWindow subroutine shows you how to set some of IE's properties and methods and might give you some ideas for further modifying this tool. (You can find more information about IE's properties and methods in the Microsoft Developer Network—MSDN—Library documentation at http://msdn.microsoft.com/workshop/browser/webbrowser/reference/objects/internetexplorer.asp.)
The code at callout D in Listing 2 forces IE to navigate to the local blank page display, then loop until IE is ready to start. This step is a good idea because, in some ways, IE is more of a loose confederation of components than it is one application. An IE window contains a document, which in turn contains a body, which in turn contains a collection of defined elements. IE is running and hooked up to your script before the document it contains—even a blank default document—is fully instantiated. Displaying the local blank page until IE is ready prevents the script from being terminated with an ambiguous error message. When IE's ReadyState property is at READYSTATE_COMPLETE, IE is ready to perform the actions the script spells out.
The code at callout E in Listing 2 removes some extraneous elements to prevent you from accidentally using this IE instance for file or Web site browsing. One-line settings easily remove the address, menu, tool, and status bars so that you can easily see that this IE instance is special.
The next step is to size the IE window, which the code at callout F in Listing 2 shows. The first line at callout F ensures that you don't accidentally click the Maximize button and have the window fill your screen. (You can still minimize the window and restore it.) You could set both height and width to 0 to give yourself only a taskbar icon, but I prefer a height of 52 pixels and a width of 200 pixels. A height of 32 pixels would show only the title bar on my screen; a slightly taller window lets you display pop-up body text. Setting the width to 200 ensures that the window can display a filename that has eight characters and a three-character extension even on a PC with a default oversized font setting.
You also need to make sure that IE tells you which script started it. You can set IE's Document.Title property to accommodate the entire path to the script, but you might have to stretch the title bar and you'd have to guess how long the bar should be for a given path. In addition, an IE title bar will accept no more than 95 characters. The first line at callout G in Listing 2 sets the title to just the filename: Wscript.ScriptName. The filename fits perfectly if you use standard filenames that have eight characters and a three-character extension.
The script uses the IE Body.Title property for expanded information. For most Web page elements, you can set a Title property that's displayed as pop-up text when you pause with the mouse cursor over the element. I set Body.Title to the script start time and the full script path so that IE displays this descriptive information only when I pause over the small body element.
The code at callout G adds two final touches. The body of an IE window is automatically scrollable and a window as short as this one displays some very ugly scroll tabs, so I set the Body.Scroll property to No to hide them. The last step, of course, is to make the window visible.
When you run the Listing 2 script, it displays the window that Figure 1 shows. As you can see, the IE window is just large enough to accommodate the script name in the title bar with some room to spare; the Maximize button is disabled, but the Minimize and Close buttons are available; a pop-up window displays the start time and full script path when the user pauses the mouse cursor over the body area.
Best of all, the IE-launching code is well encapsulated. To open the IE window, you just put the IE_onQuit and IEScriptWindow procedures in a script and make sure that your script calls IEScriptWindow at the beginning of the script.
Using the IE Control Window Script
The IE control window is best suited to batch processing tasks that run for an extended time and that you can stop at any point in time without harm. One example of such a task is a script that processes a large set of documents. The script that Web Listing 1 (http://www.winscriptingsolutions.com, InstantDoc ID 38664) shows looks in the folder that targetPath specifies and compresses any uncompressed documents older than the number of days that maxAge specifies. The point of this script is not its technical details but the fact that it performs a real-world task, might run for a long period of time, and might need to be stopped.
To give the file-compression script a GUI, all I need to do is drop it into the middle of my IE window script. In the code that Web Listing 2 shows, I simply inserted the file-compression script (which the code at callout A at Web Listing 2 shows) into the IE window script immediately after calling the IEScriptWindow procedure. I then added one final touch: Right after the last global statement in the file compression script, I added the line IE.Quit (which the code at callout B at Web Listing 2 shows) to close the IE window when the file compression script is terminated so that I can tell that the file-compression script is finished.
Arbitrarily stopping execution isn't appropriate for all scripts. For example, scripts that perform complex administration tasks might cause unpredictable side effects if stopped abruptly. But for scripts such as the file-compression script that you can stop and restart later with no harm done, an IE window can be a useful way of controlling execution.