/ VBscript

Dynamic Methods in VBScript

Drawing Hands
Drawing Hands by M.C. Escher, 1948 Lithograph.

One challenge that comes up with VBScript is "how to make your scripts modular" so that you don't have to repeat code in multiple VBScript files. When building automation frameworks for example, it is reasonable to think that you will build up a library of methods that should be reusable. In the spirit of the "Don't Repeat Yourself" (DRY) pragmatic programming principle, you should refactor your VBScript programs to eliminate duplication across files in your project. There are at least 2 ways to include "modules" in your VBScript programs.

  1. Using Execute or ExecuteGlobal
  2. Using Windows Script Files

In this post I will look at something a little more interesting than just importing reusable VBScript modules or code fragments. The sample provided is still using ExecuteGlobal so you should stil be able to sort out how Option 1 above would be used to import reusable VBScript code.

Runtime Method Definition

Computer programming languages typically maintain some form of rules for separation of code inside the runtime environment to identify the scope of a piece of code. When a VBScript program is executed the interpreter reads the program into memory, parses it, and then executes it from top to bottom. Any methods declared in the body of the script are placed in the "global" scope of the program unless they are declared within a Class or external library such as Scripting.FileSystemObject. VBScript has a Global or Local scope (via Class and Method) that defines what a piece of code can access. You can read all about that straight from the source. Anything outside of the global scope is considered local.

Scope Diagram

This means that you can call your methods from anywhere in your program (or access variables) so long as they have already been declared somewhere in the global scope. Remember, VBScript is reading your entire program into memory so you can technically call methods BEFORE they are actually implemented in your source code. Are you familiar with C programming-style header files or function prototypes? There is no need for this in VBScript.

// C-style prototype. (Not used in VBScript)  
void setMeaning(int a);  
void main()  
{
    setMeaning(42);
}

It is interesting to note that the global scope of your VBScript runtime environment is not immutable (read-only). That is, you can define methods when your program starts and then redefine them at runtime (dynamically). This is achieved using the ExecuteGlobal keyword. The following sample VBScript code is a bit like Escher's "drawing hands" in that the method defines (and redefines) itself. Other languages have features such as "mixins" or "method overrides" that provide a similar behavior.

Sample.vbs

Option Explicit  
Dim strExample  
strExample = "Hello, World"

Call MakeItDynamic() ' RTMD (Run Time Method Definition)  
Call MakeItDynamic() ' Only says hello

Sub MakeItDynamic()  
    WScript.Echo "First run through."

    '
    ' Runtime method-overloading
    '
    ExecuteGlobal _ 
    "Option Explicit: " & _
    "Sub MakeItDynamic(): " & _
    "    WScript.Echo strExample: " & _
    "End Sub:"

End Sub

If you run this program (using cscript.exe) it should give you output on the command line that looks like this:

Microsoft (R) Windows Script Host Version 5.8
...
First run through.
Hello, World

There is a little bit of magic happening here so I want to bring your attention to a few points.

  1. MakeItDynamic() calls ExecuteGlobal and redefines itself. This happens before the method returns.
  2. Once MakeItDynamic() completes, the new method definition has replaced the code in the global scope.
  3. From inside the "dynamic definition", there is a reference to a global variable that should already exist. This example is intentionally small and lacks error checking. Build in some error checking in your real code!

This is a little bit of magic in the VBScript runtime and is definitely a double edge sword. It has the potential to solve some problems but it can also make debugging a little more difficult. Use this at your own risk and realize, you are essentially "injecting" code into your program at runtime. Cool! And, proceed with caution. In this example I executed a hard-coded fragment of VBScript source code but when I use this technique in practice I am typically reading source code from a text file to be executed. Next up I'll show an example.

Possible Use Case

When I am building automation frameworks in VBScript I find it useful to be able to define the core of the system in a primary script while isolating the specific actions (or Commands) in context, in separate VBScript souce code files. In this case, I use a Command pattern and separate VBScript files contain different definitions of an "Execute" method. The framework is able to make decisions at runtime to determine which Command file to execute by reading the VBScript code as text and evaluating it Globally by calling ExecuteGlobal.

Automation Framework Diagram

I usually setup the primary script (start.vbs in this example) to interpret command-line parameters and to locate the Command file to execute. There are other use cases for defining code at runtime and this is not the only way to do it but it is an approach I like for this particular use case. Note: You should plan to incorporate Option Explicit in your scripts, especially when using this approach. This will improve your debugging and ensure that all variables and methods are defined somewhere.

Benefits of the Approach

Although some developer awareness and additional error checking are required, in my opinion, there are several benefits to implementing your VBScript with dynamic method definitions at runtime in this context.

  1. Importing code at runtime means the definition can change dynamically or based on some context (cmd-line parameters, environment (dev, qa, prd), etc.).
  2. Eliminating the monolithic VBScript file and separating your code into reusable bits (the framework) allows you to organize and understand the system in a modular way.
  3. The framework code does not need to be repeated in every VBScript file which reduces the amount of maintenance. (DRY)
  4. Someone cannot accidentally execute a process (especially on a production system) by "double-clicking" on the program.

If you are still using VBScript and found your way here, I hope you found this information useful.

Cheers!