Using JScript as a batch scripting language (Part I)

As I mentioned a few days ago, I chose JScript to script of the optimized PHP build process that I’ve built. JScript in-box on pretty much every modern Windows operating system, and provides a great deal of flexibility and benefits for a scripting language:

- it’s syntax is C like. Very tasty.

- it gives access to *a lot* of functionality via COM and WMI.

- if you know enough about Windows Scripting Host and JavaScript, you can accomplish darn near anything if you want it bad enough.

- JScript’s regular expressions.  While not the universe’s most powerful, they are certainly an integral part of the language.

- Prototypes allow you to do things to classes that can significantly boost productivity.

It however does lack a few of the basic things that I’d like to see in a batch* scripting language:

- an #include mechanism.

- Easy interaction with environment variables.

- Interaction and leveraging of external processes

- Analogs to the built in command-line functions like DIR, MKDIR, ERASE, RMDIR, etc.

* And, by “batch” scripting, I mean the scripting of external commands and programs to automate something that would otherwise be done by hand.

I was thinking about all of this over the last few months and started experimenting. Along the way I came up with a basic library of functions that address the deficiencies in a rather clever way.

First, let’s fix the lack of #include

JScript (well, and VBScript) really didn’t do us any favors by not supplying us with the ability to reuse code in a simple fashion (And yes, I know about .WSC components, and I’m not keen on how *that* turned out. Ask me about that again later some time)

Anyway, #including another JScript file is pretty easy if you know what to do. Not pretty, but easy:

 //---[Test01.js]----------------------------------------------------------
// includes the scripting library
eval( new ActiveXObject("Scripting.FileSystemObject").OpenTextFile("Scripting.js", 1, false).ReadAll() );

The eval function gives us the ability to just run code that we pass in at runtime. This will give us a few little bumps along the way later, but for the most part, is pretty darn good.

How about those environment variables

The WScript.Shell object has some methods that let us get at environment variables, but I wouldn’t exactly consider them “Script Friendly”. So, the first thing I did, was create a some basic functionality for exposing the environment as global variables. (Some of how this gets useful, comes a bit later.)

 //----------------------------------------------------------------------------
// Global variables
var GLOBALS=this;
var WSHShell = WScript.CreateObject("WScript.Shell");
var procEnvironment=WSHShell.Environment("PROCESS")
GlobalInit();

// Loads the environment from into UPPERCASE variables in the global namespace.
// each variable is prefixed with a $
function loadEnvironment() {
    env = CollectionToStringArray(procEnvironment);
    for(each in env) {
        var v= env[each];
        if(typeof(v)=='string') 
            if ((p = v.indexOf('=')) == 0 )
                continue;
            else
                GLOBALS['$'+v.substring(0,p).toUpperCase()] = v.substring(p+1) ;
    }
}

// Sets environment variables with the all string variables in the global namespace
// that start with a $
function setEnvironment() {
    for(each in GLOBALS) {
        var t = typeof(GLOBALS[each]);
        if(t =='string' || t=='number')  {
            if( each.indexOf("$") == 0 ) {
                if( IsNullOrEmpty(GLOBALS[each]) )
                    procEnvironment.Remove(each.substring(1));
                else
                    procEnvironment(each.substring(1)) = GLOBALS[each];
            }
        }
    }
}

// takes one of those funky-groovy COM collections and gives back a JScript 
// array of strings. 
function CollectionToStringArray(collection){
    var result = new Array();
    for( e = new Enumerator(collection); !e.atEnd(); e.moveNext() )
        result.push(""+e.item());
    return result;
}

// returns true if the string is null or empty
// Yeah, I was thinking of c# when I wrote this.
function IsNullOrEmpty(str) {
    return (str || "").length == 0;
}

// Our function for bootstrapping the required environment.
function GlobalInit() {
    loadEnvironment();
}

Now, we can easily access environment variables:

 //---[Test02.js]----------------------------------------------------------
// includes the scripting library
eval( new ActiveXObject("Scripting.FileSystemObject").OpenTextFile("Scripting.js", 1, false).ReadAll() );

WScript.echo( "Path is :" + $PATH );

Next time, I’ll show how I added code to execute and capture other external commands, and show a few cool functions that make playing in JScript a bit simpler.