Friday, July 13, 2012

[JScript.NET] Parsing the commandline, working with MS Access

JScript.NET: A bit of a backwater these days. I'm told that Visual Studio 2005 was the last iteration of that IDE to have good support for it. Now one must jump through a couple of hoops to use VS2010 for JScript.NET debugging and otherwise one just has the commandline compiler, jsc. Interestingly, every iteration of the .NET framework has had a jsc compiler, so someone out there must still love it a little. In general, however, it's use is deprecated in favour of other .NET languages like IronPython, CS-Script and IronJS.

Still, I've been wanting to get something working in JScript.NET ...

A while back I wrote a tool in JScript to coordinate the Convert and Repair of Access databases. It works fine, albeit a little slowly being interpreted. It's short and, as it turned out, converted fairly easily once I began to get my head around the shift from Windows Scripting Host (WSH) to .NET.

In the original, I connected to the Scripting.FileSystemObject, using ActiveXObject(). I still do that in the JScript.NET version, simply because I haven't found out what the .NET equivalents are yet.
var oFSO = new ActiveXObject("Scripting.FileSystemObject");
Next came parsing the command line. This is very easy in JScript and VBScript thanks to the WScript object's Arguments object, which even parses the command line into a dictionary, so that /name:<thing> is accessible as WScript.Arguments.Named("name") (as below):
var sDatabaseName = WScript.Arguments.Named("name");
var sDataFolder = WScript.Arguments.Named("data");
var sTempFolder = WScript.Arguments.Named("temp");
var sBackupFolder = WScript.Arguments.Named("backup");
However, .NET's System.Environment.GetCommandLineArgs() is nowhere near as helpful, so I had to write something to parse the commandline that would at least come fairly close to the original in functionality.
function argNamed( sname : String ) {
 var result : String = "";
 var aCmdline = System.Environment.GetCommandLineArgs();
 var i : short = 1;
 for ( ; i < aCmdline.length; i++ ) {
  if (aCmdline[i].toLowerCase().slice(0, sname.length + 2) == ( "/" + sname.toLowerCase() + ":" )) {
   var inner : String = aCmdline[i].slice( sname.length + 2 ) 
   result = (inner.charAt(0) == '"' ? inner.slice(1,inner.length-1) : inner);
  }
 }
 return result;
}

var sDatabaseName : String = argNamed("name");
var sDataFolder : String = argNamed("data");
var sTempFolder : String = argNamed("temp");
var sBackupFolder : String = argNamed("backup");
Note that I'm using type annotations in the code to give the compiler a few hints about how to handle the intent of the code. Next, the handling the command line itself. Not a particularly good way of doing it in the original, but hey, it worked.
var bWorking = true;

if ( undefined === sDatabaseName ) {
        WScript.Echo( "Specify database name with /name:" );
        bWorking = false;
}

if ( undefined === sDataFolder ) {
        WScript.Echo( "Specify data path with /data:" );
        bWorking = false;
}

if ( undefined === sTempFolder ) {
        WScript.Echo( "Specify temp folder with /temp:" );
        bWorking = false;
}

if ( undefined === sBackupFolder ) {
        WScript.Echo( "Specify backup folder with /backup:" );
        bWorking = false;
}

if ( ! bWorking ) {
        WScript.Quit();
}
With WScript.Quit() unavailable, I had to find something that would work in JScript.NET. I tried return and break but the compiler complained. Finally, I stumbled over System.Environment.Exit();.
if (  sDatabaseName == "" ) {
        print( "Please specify database name on commandline with /name:" );
        System.Environment.Exit(1);
}

if ( sDataFolder == "" ) {
        print( "Please specify data path on commandline with /data:" );
        System.Environment.Exit(2);
}

if ( sTempFolder == "" ) {
        print( "Please specify temp folder on commandline with /temp:" );
        System.Environment.Exit(3);
}

if ( sBackupFolder == "" ) {
        print( "Please specify backup folder on commandline with /backup:" );
        System.Environment.Exit(4);
}
(Having a more sensible help text is next on the agenda.) Next I create the filenames that will be used later in the Access Compact and Repair process. These also appear in the JScript.NET version for the usual reasons (that is, I don't know yet how to do a BuildPath equivalent in .NET.)
var sDatabaseFile = oFSO.BuildPath(sDataFolder, sDatabaseName);
var sBackupFile = oFSO.BuildPath(sBackupFolder, sDatabaseName);
var sTempFile = oFSO.BuildPath(sTempFolder, sDatabaseName);
Next, assorted deletes, moves and the setup and execution of the CompactRepair Access.Application's CompactRepair() method.
try {
 oFSO.DeleteFile( sTempFile );
} catch ( e ) {
 //~ WScript.Echo( e.message + ': ' + sTempFile );
}

WScript.Echo("CompactRepair",sDatabaseFile,"to",sTempFile);
var oACC = new ActiveXObject('Access.Application');
oACC.CompactRepair( sDatabaseFile, sTempFile, true );
oACC.Quit();

try {
 oFSO.DeleteFile( sBackupFile );
} catch( e ) {
 //~ WScript.Echo( e.message + ': ' + sBackupFile );
}
WScript.Echo("Moving",sDatabaseFile,"to",sBackupFile);
oFSO.MoveFile( sDatabaseFile, sBackupFile );

// copy temp to source, overwriting
try {
 oFSO.DeleteFile( sDatabaseFile );
} catch( e ) {
 //~ WScript.Echo( e.message + ': ' + sDatabaseFile );
}
WScript.Echo("Moving",sTempFile,"to",sDatabaseFile);
oFSO.MoveFile( sTempFile, sDatabaseFile );

WScript.Quit();
The JScript.NET version is almost exactly the same:
try {
 oFSO.DeleteFile( sTempFile );
} catch ( e ) {
 //~ print( e.message + ': ' + sTempFile );
}

print("CompactRepair ",sDatabaseFile," to ",sTempFile);
var oACC = new ActiveXObject("Access.Application");
oACC.CompactRepair( sDatabaseFile, sTempFile, true );
oACC.Quit();

// copy source to backup, overwriting
try {
 oFSO.DeleteFile( sBackupFile );
} catch( e ) {
 //~ print( e.message + ': ' + sBackupFile );
}
print("Moving ",sDatabaseFile," to ",sBackupFile);
oFSO.MoveFile( sDatabaseFile, sBackupFile );

// copy temp to source, overwriting
try {
 oFSO.DeleteFile( sDatabaseFile );
} catch( e ) {
 //~ print( e.message + ': ' + sDatabaseFile );
}
print("Moving ",sTempFile," to ",sDatabaseFile);
oFSO.MoveFile( sTempFile, sDatabaseFile );
System.Environment.Exit(4);
Notice the change from WScript.Quit() to System.Environment.Exit(). Notice also the change from WScript.Echo() to print(). One weird thing about the latter is that the arguments in a WScript.Echo() are output separated by a space, but are not separated at all in a print(), thus the extra spaces in the quoted strings. Sample invocation:
acarNET.exe /name:database.mdb /data:c:\acartest\data /temp:c:\temp /backup:c:\acartest\backup 
Compiled, the JScript.NET version runs very quickly in comparison to the original JScript version. It almost makes me wish JScript.NET had a future. As it stands, I'm going to have to redo this in something else. Sigh. © Copyright Bruce M. Axtens, 2012

1 comment:

Visual FoxPro to .Net said...

Excellent information about jscript.