Tuesday, November 4, 2014

Making new Windows commands using Windows Scripting Host and Jscript

I'm glad that all the Windows machines I use at home and at work come with at least one default programming language. There is a lot of momentum in the Javascript world now that HTML5 is taking hold and node.js is gaining in popularity. So I thought now would be a good time to learn some Javascript, since I'm starting to realize that it can be found EVERYWHERE.

I thought that a good way to learn more about Javascript would be to build some small tools for the machines I use at work. I've learned that Javascript on Windows machines come in at least 3 different flavors, Jscript hosted on the Windows Scripting Host, Jscript .NET compiled with the jsc compiler, and the Jscript running on Internet Explorer and Firefox. Not only that but doing things on Windows XP turns out to be slightly different than on Windows 7. Take for example embedding Jscript in a batch file so that I can call the tool from anywhere on the command line. What I found on StackOverflow was this cool calendar example:

REM Let's call this command "cal"
@set @junk=1 /*
@echo off
cscript //nologo //E:jscript %0 %*
goto :eof
*/
x = WScript.Arguments
Yr = x(0) ; Mo = x(1)

YS = "JanFebMarAprMayJunJulAugSepOctNovDec"
MN = Mo<1 || Mo>12 ? Mo : YS.substr(3*Mo-3, 3) // Month Name
WScript.echo(" ", Yr, "         ", MN)
WScript.echo(" Mo Tu We Th Fr Sa Su")
WD = new Date(Yr, Mo-1, 1).getDay() ;
if (WD==0) WD = 7 // Week Day Number of 1st
LD = new Date(Yr, Mo, 0).getDate() // Last Day of month
Wk = "" ; for (D=1 ; D < WD ; D++) Wk += "   "

for (D=1 ; D<=LD ; D++) {
  Wk = Wk + " " + (D<10 ? "0"+D : D) ; WD++
  if ((WD==8) || (D==LD)) { WScript.echo(Wk) ; WD = WD-7 ; Wk = "" }
  }

WScript.echo("        ------       ")
 
Calling this script in Windows XP works, but it doesn't work on Windows 7. More Googling showed that for Windows 7, I need to change the batch script code to the following:

@if (@CodeSection == @Batch) @then
@echo off
cscript //nologo //E:jscript %~f0 %*
goto :eof
@end

Then the script will work and testing with "cal 2014 11" outputs:

  2014           Nov
 Mo Tu We Th Fr Sa Su
                            01 02
 03 04  05 06  07 08 09
 10 11  12 13  14 15 16
 17 18  19 20  21 22 23
 24 25  26 27  28 29 30
---------------------------------

Another handy tool for Windows scripting would be a command line read-eval-print-loop similar to what Python and Lisp have. Jscript does not come with a REPL built in, but finding a basic example online was not hard. Here is the code that works good enough for my purposes.

@if (@CodeSection == @Batch) @then
@echo off
cscript //nologo //E:jscript %~f0 %*
goto :eof
@end

function hex(n) {
    if (n >= 0) {
        return n.toString(16);
    } else {
        n += 0x100000000;
        return n.toString(16);
    }
}
var scriptText;
var previousLine;
var line;
var result;
while(true) {
    WScript.StdOut.Write("jscript> ");
    if (WScript.StdIn.AtEndOfStream) {
        WScript.Echo("Bye.");
    break;
    }
    line = WScript.StdIn.ReadLine();
    scriptText = line + "\n";
    if (line === "") {
        WScript.Echo(
            "Enter two consecutive blank lines to terminate multi-line input.");
        do {
            if (WScript.StdIn.AtEndOfStream) {
                break;
            }
            previousLine = line;
            line = WScript.StdIn.ReadLine();
            line += "\n";
            scriptText += line;
        } while(previousLine != "\n" || line != "\n");
    }
    try {
        result = eval(scriptText);
    } catch (error) {
        WScript.Echo("0x" + hex(error.number) + " " + error.name + ": " +
            error.message);
    }
    if (result) {
        try {
            WScript.Echo(result);
        } catch (error) {
            WScript.Echo("<<>>");
    }
    }
    result = null;
}
 
One application I have in mind for using Jscript at work is as a quick and small footprint command line calculator. In the past I have used Matlab, Google Sheets, the basic Windows calculator, and Excel. Most of these are overkill for what I need to do, and Windows calculator is too basic. Hard-coding the calculations into the command line sounds a lot more convenient for the things I need to do. Here is calculation I have been doing a lot recently on Google Sheets. It realigns the offset for the camera I am looking through to the desired coordinate system.

@if (@CodeSection == @Batch) @then
@echo off
cscript //nologo //E:jscript %~f0 %*
goto :eof
@end

// realign.bat
// Jovan Trujill
// Arizona State University
// 11/04/2014
//
// Usage: realign XActual YActual XDesired YDesired XOffset_Old YOffset_Old
// This command will return the alignment offset coordinates needed to correct
// camera positioning problems on the Cherry prober system.
//

var X_Actual = 0.0;
var Y_Actual = 0.0;
var X_Desired = 0.0;
var Y_Desired = 0.0;
var dX = 0.0;
var dY = 0.0;
var X_Offset_Old = 0.0;
var Y_Offset_Old = 0.0;
var X_Offset_New = 0.0;
var Y_Offset_New = 0.0;

var args = WScript.Arguments;
if (args.length != 6) {
    WScript.echo("Usage: realign XActual YActual XDesired YDesired OldXOffset OldYOffset");
} else {
    X_Actual = parseInt(args(0));
    Y_Actual = parseInt(args(1));
    X_Desired = parseInt(args(2));
    Y_Desired = parseInt(args(3));
    X_Offset_Old = parseInt(args(4));
    Y_Offset_Old = parseInt(args(5));
   
    dX = X_Desired - X_Actual;
    dY = Y_Desired - Y_Actual;
   
    X_Offset_New = X_Offset_Old - dX;
    Y_Offset_New = Y_Offset_Old + dY;
   
    WScript.echo("X_Actual Y_Actual");
    WScript.echo(X_Actual + " " + Y_Actual);
    WScript.echo("X_Desired Y_Desired");
    WScript.echo(X_Desired + " " + Y_Desired);
    WScript.echo("X_Old_Offset Y_Old_Offset");
    WScript.echo(X_Offset_Old + " " + Y_Offset_Old);
    WScript.echo("X_New_Offset Y_New_Offset");
    WScript.echo(X_Offset_New + " " + Y_Offset_New);
}

 As the library of functions gets more complicated I will need to figure out a way to call JScript functions from other JScript programs. I think the most convenient way to do that will be:

/* include.js */
(function () {
    var L = {/* library interface */};
    L.hello = function () {return "greetings!";};
    return L;
}).call();
 
And then the main function calls "include.js" like this:
 
var Fs = new ActiveXObject("Scripting.FileSystemObject");
var Lib = eval(Fs.OpenTextFile("include.js", 1).ReadAll());
WScript.echo(Lib.hello()); /* greetings! */ 
 
 The example works but I have not tested it on anything more substantial.
 

No comments:

Post a Comment