Make your VB apps scriptable with the Microsoft Windows Script Control

Integrating an automation language into an application can be an expensive business. Check out this no-cost alternative that leverages the Windows Script Control.

Although the Windows Script Host (WSH) has lately gotten an inordinate amount of bad press as the enabling medium behind scores of e-mail script viruses, it does have some largely untapped potential for application developers. Yes, that's right, the technology that was supposed to replace Network Administrator's reliance on BAT files with something a little more modern might have some usefulness for you, the "real" developer.

You could make a case that one reason for the popularity of Microsoft's Office suite is that all the applications featured in it include a simple development environment that allows somewhat technical users to automate repetitive tasks. Visual Basic for Applications (VBA) provides this environment in Office, and you can license VBA for use in your applications if you can afford the licensing fees that Microsoft would demand from you. In this article, I'll show you how the WSH and the Windows Script Control can provide a cheap but serviceable alternative to VBA for providing automation scripting in a Visual Basic application.

Getting your bearings
The Windows Script Control encapsulates the Active Scripting Engine, which is the heart of WSH, into a UI-less ActiveX control that you can easily use from VB. The control allows you to execute entire scripts or fragments of script written in any language supported by the WSH. You likely already have this control installed on your machine; it's named msscript.ocx and is usually located in your System32 folder. If you don't have it, it's available for download from Microsoft.

The control includes some documentation, but in my experience, it's somewhat incomplete and inaccurate. The best way to get your bearings is, unfortunately, to simply play around with it and see what works and what doesn't. Having done a little of that myself, I created a test project that illustrates some of what's possible with the scripting control, which you can download here. I'll be referring you to parts of that project throughout this article.

Running script
To run some script using the scripting control, you have several options:
  • The Eval method, which is similar to the VBA Eval function, allows you to evaluate expressions that range from simple arithmetic to complex expressions containing embedded script commands or object method calls, and returns the result to your application.
  • The ExecuteStatement method will execute any complete script statement.
  • You can use a combination of the AddCode and Run methods to load and execute custom script routines. This is the most powerful method, and is the one I'll cover in the remainder of this article.

Introducing ScriptingDemo
The ScriptingDemo app consists of a single form that displays a list of script files that it finds in the application's directory, a textbox for arguments to script procedures, and a single textbox that an executing script can use for output. When the application starts, it instantiates a custom collection object, colCustomers, which loads data from the Customers table of the SQL Server Northwind catalog to use as play data for our sample scripts. Figure A shows the application's main form at runtime.

Figure A
ScriptingDemomain form at runtime

When a script file is selected, the app reads in the code line-by-line and adds it to the scripting control's environment using the AddCode method. Clicking on the Run Script button causes the scripting control to attempt to execute a procedure with the same name as the selected file, sans extension.

Consider ASimpleScript.vbs as an example. It contains the following sub:
Sub ASimpleScript()
MsgBox"Hello from VBScript!"
End Sub

Selecting it and clicking the Run Script button produces the output shown in Figure B.

Figure B
The anticlimactic result of running ASimpleScript.vbs

Okay, I admit, that's not too impressive, but it does illustrate that you are actually running script. Besides, we haven't even scratched the surface.

Sharing data with your script
Let's move on to something a little more impressive and take a closer look at the code behind the form's Load event (Listing A). It includes the following two lines:
    ScriptControl1.AddObject "Customers", oCustomers, True
    ScriptControl1.AddObject "Output", oScriptOutput, True

We haven't talked about AddObject yet. It allows you to add references to objects to the script control's execution environment. Objects added in this way become globally accessible to all scripts run by a control, giving a script access to objects created by your application and a way to communicate with your application. In this case, we add a reference to the collection of play data created when the main form loads as Customers, along with an instance of the Output class, which gives scripts write-only access to txtOutput on the main form.

You can see the usefulness of this by examining the code for the EnumCustomer script, which appears in Listing B. The script accepts a single input argument, which represents the index of a customer contained in the Customers collection that we added to the script control's environment via a call to AddObject. The script accesses the Customers collection and enumerates all the available fields of the selected item, printing the results in txtOutput via the Output object.

It's a two-way street
One question that's likely forming in your mind now is, why wrap txtOutput in a class instead of just adding a reference to frmMain.txtOutput via AddObject? Check out UpdateCustomer.vbs for an answer to that question:
Sub UpdateCustomer(num)
  Output.PrintLine("Changing ")
  Output.PrintLine(Customers(num).ContactName )
  Output.PrintLine(" to Bob Hope")
  Customers(num).ContactName = "Bob Hope"
End Sub

This script modifies the contents of an item in the Customers collection, which means that scripts have read and write access to objects added to their environment via AddObject. A mischievous script could do something like this, were I to allow it unfettered access to txtOutput:
txtOutput.Visible= false

This behavior would cause troubles for any subsequently run scripts that attempted to communicate with the user via txtOutput. While I haven't been able to create a script that could successfully change the actual reference of one of the global objects added by AddObject (like Customers=Nothing), I won't go so far as to say it's not possible. So the moral here is to be careful about the access you provide to scripts.

Explore the possibilities
An obvious area where a scriptable application could be useful is end-user customization and automation. A less obvious application could be the use of scripts for temporary bug fixes. With a little ingenuity, it would be possible to provide hooks for scripts to be run on the occurrence of certain application events. Then, you could simply distribute a script that corrects the effect of an application bug, instead of having to distribute a new binary immediately. Custom reporting and data analysis could also be provided in this fashion.

I've missed something, I'm sure
If you've implemented scripting or some other form of automation language into one of your applications, I'd like to hear from you. Send me an e-mail and describe your project.


Editor's Picks