Using Includes in VBScript

There is one very useful feature missing from both VB and VBScript—the ability to have includes. Includes allow you to create code snippets in separate files and import them into your script at runtime. We’re going to create a cool work around to allow us to use them.

Before I begin, I’d like to thank Dr. Tobias Weltner for giving me the idea.  His feature-packed VBS editor, SystemScripter, includes this workaround.  I’ve taken his idea and written it using my own code.  In the process of writing this article I’ve seen several other similar solutions.  Since I don’t know who had the idea first, I’ll give credit to the person who turned me on to it.

So here’s the challenge:  to create a function or subroutine that will allow us to use includes in our scripts.  Code should be as concise as possible to reduce the amount of extra code we need while being able to operate under nearly any scripting situation.

It should also be flexible.  We want to have a drop-in snippet that we can reuse in any code.  We also want it to be able to accept different attributes for different situations.  We’ll get into that in more detail later when you can see actual demonstrations of what I mean.

So let’s get started.

{mospagebreak title=Building the basic routine.}

To begin, we first need to understand what we’re trying to accomplish.  In essence, we want to read code from one file and execute it from another.  That seems simple enough.  First let’s look at how we can read in code from another file.

Sub Import(strFile)

   Set objFs = CreateObject("Scripting.FileSystemObject")

   Set objFile = objFs.OpenTextFile(strFile)

   strCode = objFile.ReadAll

   objFile.Close

End Sub

What we’ve done here is very simple.  You should be able to recognize this code immediately if you’ve been reading my other articles.  For those who are just tuning in, we’ll go over it very briefly.

I’ve created a subroutine called Import that accepts a single parameter.  (Hey, I’m a Python fan.  You could easily call this subroutine Include or any other sensible name).  My subroutine first instantiates, or creates and instance of, the VBScript FileSystemObject that we can use to work with the external file.

Next, it uses the OpenTextFile method to open the file path that was passed to the subroutine.  The OpenTextFile method returns a file object that we’ve assigned to the objFile variable.

We finish up by using the ReadAll method to return all of the text from our file object, which we assign to a string variable, before closing the file.  See?  I told you it was simple.

Now that we have the code from our external file it’s time to execute it.

{mospagebreak title=Executing the external code}

There are a couple of ways to execute code dynamically in VBS.  Basically, this means that VBS will interpret a text input and execute it as if it were hard coded into the script.  I’m only going to focus on the method that we need in this particular instance.

ExecuteGlobal textinput

The ExecuteGlobal statement accepts a single text string input.  The code is then executed in the global namespace.  Why have I chosen to execute it globally?  The answer is very simple.  I’m calling this method from within a subroutine and I want any contained variables or returns to be available in my script at the point where my subroutine was called.

The Execute and ExecuteGlobal statements aren’t very well documented.  Both accept a single string input that contains one or more statements to be executed.  The ExecuteGlobal statement executes the code in the global namespace while the Execute statement executes code locally.

All we need to do now is add an ExecuteGlobal statement to our subroutine and supply it with the contents of our strCode variable.  Our final subroutine looks like this:

Sub Import(strFile)

   Set objFs = CreateObject("Scripting.FileSystemObject")

   Set objFile = objFs.OpenTextFile(strFile)

   strCode = objFile.ReadAll

   objFile.Close

   ExecuteGlobal strCode

End Sub

Well, that was simple enough.  We now have a working subroutine that can emulate includes in VBScript.  Just add your subroutine to the end of your script file and make calls like the following anywhere that you need an include.

Import "C:myinclude.vbs"

Myinclude.vbs can be any valid script file that you wish to use.

Of course, if you know me at all by now, we’re not done with this code.  We need to add some flexibility to it.  We’ll do that in the next section.

{mospagebreak title=Allowing for relative or absolute path names}

First things first, I want this code to work with either absolute or relative file paths.  To do that, we’re going to add a single line before trying to open the input file.

Sub Import(strFile)

   Set objFs = CreateObject("Scripting.FileSystemObject")

   strFile = objFs.GetAbsolutePathName(strFile)

   Set objFile = objFs.OpenTextFile(strFile)

   strCode = objFile.ReadAll

   objFile.Close

   ExecuteGlobal strCode

End Sub

In this line, I use the FileSystemObject’s GetAbsolutePathName to reassign the strFile variable with a full path to the file to be included.  What’s nice is how the GetAbsolutePathName works in this particular case.

If you are using a script editor with a debugger to test this script, it may fail at this point.  This is because most debuggers will not execute your code from the directory where your script resides.  If this happens, try hard coding a path while testing your script.

The GetAbsolutePathName method will return a text string containing a full file path.  If a full path is not supplied to it, it will create one by assuming the partial path is relative to the script’s execution directory.  Here’s what that means.

If we supply only a filename, it will assume the file resides in the same directory as our script and return a full path to that file.  If we supply something like “includesmyinclude.vbs”, it will assume that the “includes” directory resides in the same directory as our script and return a full path accordingly.

{mospagebreak title=Allowing for platform-specific paths}

For some people this may be enough, but for me it isn’t.  What if I’m using this in a script that passes a path containing environmental variables?  Maybe I’m calling a script that resides in the Windows directory like this:

Import "%windir%myscript.vbs"

We can allow for this too.  Before the last line of code we added, we can stop and expand any environmental variables using the WSH Shell object’s ExpandEnvironmentStrings method.  Don’t forget to create an instance of the WSH Shell object in your subroutine.  Now we have this:

Sub Import(strFile)

   Set objFs = CreateObject("Scripting.FileSystemObject")

   Set WshShell = CreatObject("Wscript.Shell")

   strFile = WshShell.ExpandEnvironmentStrings(strFile)

   strFile = objFs.GetAbsolutePathName(strFile)

   Set objFile = objFs.OpenTextFile(strFile)

   strCode = objFile.ReadAll

   objFile.Close

   ExecuteGlobal strCode

End Sub

Presto!  Just like that our subroutine now works with paths containing environmental strings as well.  Now I have one more thing to take into consideration.  What if I’m using the strFile variable somewhere else in my code and I don’t want it to change?

The way this subroutine is written now, the strFile variable will contained the fully exploded full path to my file when the subroutine finishes.  I can prevent this from happening very simply by using the ByVal statement when creating my subroutine.

The ByVal statement will take the strFile attribute (if provided as a variable) by its value.  This means that it will not alter the variable’s contents outside of my subroutine’s namespace.

Sub Import(ByVal strFile)

   Set objFs = CreateObject("Scripting.FileSystemObject")

   Set WshShell = CreateObject("WScript.Shell")

   strFile = WshShell.ExpandEnvironmentStrings(strFile)

   file = objFs.GetAbsolutePathName(strFile)

   Set objFile = objFs.OpenTextFile(strFile)

   strCode = objFile.ReadAll

   objFile.Close

   ExecuteGlobal(strCode)

End Sub

We now have a fully customized subroutine that will allow us to use includes in our script.  Let’s put it to the test.

{mospagebreak title=Testing the subroutine}

We’re going to create two files.  The first one, called test.vbs, will be our test script and the second one, called include.vbs, will contain our include code.  The first should look like this:

‘ test.vbs

 

Import "include.vbs"

 

Sub Import(ByVal strFile)

   Set objFs = CreateObject("Scripting.FileSystemObject")

   Set WshShell = CreateObject("WScript.Shell")

   strFile = WshShell.ExpandEnvironmentStrings(strFile)

   file = objFs.GetAbsolutePathName(strFile)

   Set objFile = objFs.OpenTextFile(strFile)

   strCode = objFile.ReadAll

   objFile.Close

   ExecuteGlobal(strCode)

End Sub

Our include file is going to be very simple.  We’ll just use the Wscript Echo method to create a “Hello World” script.

‘ include.vbs

 

Wscript.Echo "Hello World!"

Save both files to the same folder and double click test.vbs.  Just like magic, you should get a VBScript message box saying “Hello World!”.

There you have it.  A simple subroutine that you can reuse in any of your scripts to add file include capabilities to VBScript.  I like to use this to include reusable classes.  This way, if I make updates to those classes I can easily update all of my other scripts as well without having to update each one individually.

I hope that you have found this solution as helpful as I did.  Find ways to customize it or expand upon it to make it fit your own needs.  That’s all for me this time.  Until next time, keep coding!

5 thoughts on “Using Includes in VBScript

  1. Everyone loves a good workaround. This one lets you take advantages of code includes in VBScript. Let me know what you thought of this article. Feedback is always appreciated.

  2. Thank you so much for posting your code. This is EXACTLY what I was looking for. Clean, concise, and performs exactly as expected.

    You are a hero in my book! =)

  3. This will save me hours of time by centralising certain functions and being able to manage them globally. Well done.

[gp-comments width="770" linklove="off" ]