Fear the Cowboy

Life of Microsoft Open Source Developer

Using JScript as a batch scripting language (Part III)

clock May 18, 2009 13:19 by author Garrett Serack

Now that I’ve shown how to build a cool .Format() method for strings, we can put it to good use in a lot of places.

In batch scripting, it’s really nice to be able to make nearly every call support replacement arguments in a consistent fashion. In cmd.exe batch scripts, we use %var% all over the place. In JScript batch scripting, we simply use {$VAR} instead, and put a little bit of code in the top of our functions to help out with that.

First, the one-stop-universal-arguments helper, which we can add to the source from before:

Scripting.js
function ArgsToArray(x) { return Array.prototype.slice.call(x);} 

// FormatArguments must be passed either:
//     one argument: 
//          containing the arguments object from the caller
//          where the first argument of that should be the format string.
//          == or ==
//          just the format string.
//
//     two arguments:
//          the first argument is the format string
//          the second argument is the argument collection from the caller.
function FormatArguments(args, moreargs) {
    var result = "";

    if (arguments.length == 1) {
        if (typeof (args) == "object") {
            args = ArgsToArray(args);
            result = "" + (args.shift());

            if (args.length == 1 && arguments[0].length > 0)
                args = arguments[0];

            return result.Format(args);
        } else return ("" + args).Format();
    } else if (arguments.length == 2) {
        if (typeof (args) == "string" && typeof (moreargs) == "object") {
            result = args;
            args = ArgsToArray(moreargs);
            args.shift();

            return result.Format(args);
        }
    }
    throw "Invalid Argument passed to FormatArguments";
}

FormatArguments()  gives us the ability to do variable substitution in any function, in a very flexible way. You can simply use the function to give you a completed string:

Test-4.js
// use the first string as the format string, 
// the rest are potential value substitutions
function Test1() {
    var foo = FormatArguments(arguments)
    WScript.echo( foo );
}

Test1("the path is {$PATH}");

// both arguments are used as format strings. any 
// parameter substitution should use numbers starting
// at {1}, since the 'destfile' parameter is techincally {0}
function Test2(srcfile, destfile) {
    var srcfile = FormatArguments(srcfile, arguments);
    var destfile = FormatArguments(destfile, arguments);
    var msg = "Copy {0} to {1}".Format(srcfile, destfile);
    WScript.echo(msg);
}

Test2("{$WINDIR}\\system32\\notepad.exe" , "{$USERPROFILE}\\desktop\\notepad.exe");

Knowing that, we can create a few functions that will use the FormatArguments function:

Scripting.js
// Some global objects we'll need 
var WSHShell = WScript.CreateObject("WScript.Shell");
var WinShell = WScript.CreateObject("Shell.Application");
var procEnvironment=WSHShell.Environment("PROCESS")
var fso = new ActiveXObject("Scripting.FileSystemObject");

function Print() {
    WScript.echo(FormatArguments(arguments));
}

function cd() {
    WSHShell.CurrentDirectory = FormatArguments(arguments);
}

function pwd() {
    return WSHShell.CurrentDirectory;
}

function erase(file) {
    file = FormatArguments(arguments);
    if (exists(file))
        fso.DeleteFile(file);
}

function rmdir(folder) {
    folder = FormatArguments(arguments);
    if (folderExists(folder))
        fso.DeleteFolder(folder, true);
}

function exists(file) {
    return fso.FileExists(FormatArguments(arguments));
}

function folderExists(folder) {
    return fso.FolderExists(FormatArguments(arguments));
}

function mkdir(folder) {
    folder = FormatArguments(arguments);
    if (!folderExists(folder))
        fso.CreateFolder(folder);
}

Now, we can do some pretty nifty little batch scripting:

test-5.js
// Bootstrap Scripting Library
eval(new ActiveXObject("Scripting.FileSystemObject").OpenTextFile("Scripting.js", 1, false).ReadAll());

$ORIG_DIR = pwd();

if( exists("{$WINDIR}\\log.txt") ) {
    Print("Deleting log file");
    erase("{$WINDIR}\\log.txt");
}

mkdir("{$TEMP}\\tempdir");

Next time, we’ll see how we can call on command-line tools to do our bidding, and play with the results.




PHP on Windows Optimized build—Thread Safe version too!

clock May 18, 2009 12:14 by author Garrett Serack

I’ve just finished tweakin’ out the PGO (Profiled Guided Optimization) build script for PHP on Windows to crank out the thread-safe version of PHP as well.

So, now you can test PHP 5.3 RC3-dev PGO optimized for Windows with Apache 2.2!

What’s the difference between thread-safe and non-thread-safe?

The non-thread-safe version of PHP should be used when there is a single request per instance of PHP--like, when you use FastCGI—a single PHP-CGI.EXE handles a single request at a time, and the Web Server spins off multiple instances of PHP-CGI.EXE to handle requests in parallel. Because each instance is in a separate process, there is no need to have all the thread-safety code in PHP.

The thread-safe version is required when you use PHP as a module—as you would in Apache on Windows—and the WebServer handles multiple requests in the same process.

Given the choice, the non-thread-safe version should be faster, and if you can, you should probably use that one.

You can read more about this at http://www.iis-aid.com/articles/my_word/difference_between_php_thread_safe_and_non_thread_safe_binaries

If you already use the thread-safe version of PHP on Windows, please download and test this version if you can—and send me some feedback! 


Links to the latest PHP 5.3 Builds:

PHP 5.3-RC-dev snapshots:

http://windows.php.net/downloads/snapsoptimized/

Non-thread-safe:

http://windows.php.net/downloads/snapsoptimized/php-5.3-nts-win32-VC9PGO-x86-latest.zip

Thread Safe:

http://windows.php.net/downloads/snapsoptimized/php-5.3-ts-win32-VC9PGO-x86-latest.zip




Using JScript as a batch scripting language (Part II)

clock May 15, 2009 16:14 by author Garrett Serack

Last time, I wrote about synthesizing an #include facility along with handling environment variables in a trivial way.

This time, let’s look at filling in a couple more gaps in JScript’s basic scripting functionality.

What’s wrong with String?

The String class in JavaScript/JScript is … ok. If you work in .NET enough, you’ll eventually get to the point where you’d like a couple of functions that seem to be missing. First, is Trim, which I’ve always thought as obvious:

Scripting.js – String Prototypes
//----------------------------------------------------------------------------
// String Prototypes



String.prototype.Trim = function() { return (this || "").replace(/^\s+|\s+$/g, ""); }

And secondly, is something I miss dearly from when I’m in .NET, is a string Format function. Now, I’ve seen a couple of these that were pretty simple, but I wanted to be able to do some really cool Format strings (and, since the rest of my scripting will rely on them quite a bit, I’m going for the gold here.

Scripting.js – String Prototypes
String.prototype.Format = function() {
    var args = (arguments.length == 1 && typeof (arguments[0]) == "object") ? arguments[0] : arguments;

    result = this;
    while (z = /{(.*?)}/.exec(result))
        try { result = result.replace(z[0], isNaN(z[1]) ? eval(z[1]) : (args[z[1]]||"??<" + z[1] + ">??" )); }
    catch (x) { result = result.replace(z[0], "??<" + z[1] + ">??"); }
    return result.replace("??<", "{").replace(">??", "}");
}

So, with this tasty little script in my pocket, I can do ad-hoc format strings that use one of a few types of replacement:

test-3.js
// simple parameter # replacement like .NET
var foo = "My Name is {0}. Please to meet you {1}".Format("Garrett", "Mr. Serack");
WScript.echo(foo);

// *global* variable replacement
ABC = "this is a test";
var bar = "'{ABC}' is really a replacement string".Format();
WScript.echo(bar);

// really, any legal expression in there is fine.
bar = "'{100+200/75}' is really a replacement value?".Format();
WScript.echo(bar);

// and if it doesn't match, just leave it alone...
bar = "'{teehee}' isn't really a replacement value.".Format();
WScript.echo(bar);

// even if it's just a number.
bar = "'{1}' isn't really an index.".Format();
WScript.echo(bar);

Which spits out the following:

Ouptut
My Name is Garrett. Please to meet you Mr. Serack
'this is a test' is really a replacement string
'102.66666666666667' is really a replacement value?
'{teehee}' isn't really a replacement value.
'{1}' isn't really an index.

Basically, it just looks for anything in braces, and checks to see if it’s a number. if it is, it tries to substitute in the n’th parameter passed in. Otherwise it just does an eval() on it—which, will replace expressions, or global variables. If something goes haywire, it just leaves it alone (well, it does some switcheroo stuff to get past the while loop, but it puts it back in the end).

Next time, we’ll see how to use this… everywhere.




Using JScript as a batch scripting language (Part I)

clock May 8, 2009 10:02 by author Garrett Serack

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.




PHP 5.3 RC2 *Highly-Optimized* for Windows available

clock May 7, 2009 13:14 by author Garrett Serack

Howdy,

I've been working for many months with Pierre Joye"”well really, many people in the PHP community--on getting PHP to run faster on Windows.

Pierre has been working rapidly on upgrading libraries (Pierre pioneered the work to get PHP and its hoard of dependent libraries updated and properly compiling on Windows), replacing old POSIX-emulation code with native calls, patching bugs, and about a million other things, all of which had a huge impact on performance and stability of PHP on Windows.

For my part, I've been spending my time behind the scenes by feeding information to Pierre that he needs, testing, analyzing, and finally by constructing a new build process that enables us to take advantage of some pretty sweet optimization technology in Visual Studio.

Starting today, you can find snapshot builds of PHP 5.3 that are built using my optimized build process on the windows.php.net site:

Current PGO Optimized Snapshot:

http://windows.php.net/downloads/snapsoptimized/php-5.3-nts-win32-VC9PGO-x86-latest.zip

A few Notes:

Over the course of the next couple of weeks, I'll be explaining how this build process works, and making available the tools that make it all possible.

Only the non-thread-safe version is available, so you need to use FastCGI with IIS in order to use it.

Since this is a radically different build than the ones that had been traditionally used to create the Windows PHP binaries, you should download the binaries and test with them, but you probably should avoid using them in production just yet.

If you have any feedback about the builds, leave me a comment, or send me mail at

garretts@microsoft.com




Choosing a batch scripting language on Windows

clock May 4, 2009 13:54 by author Garrett Serack

Right now, I'm automating an optimized build process for PHP on Windows that requires a substantial amount of scripting to create the results that I'm looking for. I wrote the first master script in CMD's batch script, which gave me almost satisfactory results. Almost.

Finding myself with the need to recreate the build script, I first had to choose which language to do it in. There are really only two criteria I use when selecting a computer language for a particular task:

1. Does the language have the features/syntax to do the task as simply as possible?

2. What's the bootstrap? (ie, what the heck does it take to get the program up and running where it's going.)

Now, on one hand, I've been tweakin' around building my own scripting language for about a year and a half now (g#), and when I'm working on something that only I will use, I'll often use that. g# is a classless c# dialect that I added powershell to and tacked on a slew of extension methods and scripting hacks that make it kindof sweet. I'm not ready to release it to the world yet (I've decided upon a better architecture, and I'm going to re-write it) , so I don't use it on things that I need to give to others"”which makes it fail #2.

Ok, so I compile a short list of potential candidates, and weigh their potential:

.CMD/Batch:

Aside from the fact that it's already failed me, and I've tried to make it "˜smarter', in the end, there are some things that just end up getting to be too damn messy to implement. Oh, and it sucks.

BASH

Now, that would have been a great idea. Except that BASH isn't native to Windows, and Cygwin isn't"¦ isn't"¦ ugh, don't even bother.

Perl, Python

In my book, they can pass muster on #1"¦ but #2"¦ ooh. no thanks.

PowerShell

I'm *still* not a fan of PowerShell. Even assuming that it might already be on the target box, which takes care of #2, out of all the languages I've encountered, it's the least pleasing to me.

C#

Scripting with C#? "¦ well, it's not really meant to do that. I'd like to just "˜run' the script y'know? I did do a proof of concept a year or so ago, where I wrapped the c# in some JScript, that would automatically call the compiler, and pass arguments thru to the generated EXE:

//@cc_on //@if (@_jscript_version < 5)
#if false
//@end //@if (@_jscript_version < 6)
var args="";
var exename = WScript.ScriptFullName.replace( /\.js$/gi , ".exe" );

var et=" /t:exe"; if( WScript.FullName.toUpperCase().indexOf("WSCRIPT.EXE" ) != -1 ) et=" /t:winexe";
for( each =0; each< WScript.Arguments.length; each++)
args= args+" "+WScript.Arguments(each);
var compilers = new Object(); compilers['.js.js'] = 'jsc.exe'; compilers['.cs.js'] = 'csc.exe'; compilers['.vb.js'] ='vbc.exe';
var WSHShell = WScript.CreateObject("WScript.Shell") ;
var fso = WScript.CreateObject("Scripting.FileSystemObject");
var compiler = compilers[WScript.ScriptName.substr( WScript.ScriptName.length - 6).toLowerCase()];
if( ""+compiler == "undefined" )
WScript.quit(WSHShell.popup("Unable to determine compiler type from filename"));
try {
compiler = WSHShell.RegRead("HKLM\\Software\\Microsoft\\.NETFramework\\InstallRoot") + "v2.0.50727\\"+ compiler;
if( !fso.FileExists( compiler ) )
throw new Error(".NET 2.0 not found");
if( fso.FileExists( exename ) )
fso.DeleteFile( exename );
} catch( e ) {WScript.quit(WSHShell.popup("You do not appear to have .NET 2.0 installed.\n\nYou must install .NET 2.0."));}
var Pipe = WSHShell.Exec(compiler+et+" /nologo /out:\""+exename+"\" \""+WScript.ScriptFullName+"\"" );
while(!Pipe.StdOut.AtEndOfStream)
WScript.StdOut.WriteLine(Pipe.StdOut.ReadLine());
if( fso.FileExists( exename) )
var Pipe =WSHShell.Exec(exename);
while(!Pipe.StdOut.AtEndOfStream)
WScript.StdOut.WriteLine(Pipe.StdOut.ReadLine());
WScript.quit(0);
//@else //@if (@_jscript_version < 5)
#endif
//@end
using System.Windows.Forms;

public class MyMain
{
public static void Main( string[] args)
{
if( args.Length > 1 )
MessageBox.Show("Hello world to you "+args[1]);
else
MessageBox.Show("Hello world!");
}
}
//@end

but, really that's just a tad weird. Plus, it's just not convenient to do batch scripting in.
VBScript

VBScript is a popular choice, it's installed pretty much on all Windows boxes these days, but "¦ I dunno.. There are some things that are just a PITA to do, and scripting external apps isn't fun at all.

JScript

JScript is not too popular of choice--which in my opinion, very weird. Since JScript is essentially JavaScript, it offers a pretty damn flexible language that people enjoy, and there is lots of good references for how to do alot of things. Again, it's already installed pretty much on all Windows boxes, and you have full access to COM and WMI, which on Windows can get you a huge amount of functionality.

On the down side, it's not very "batch friendly""”it lacks the simplicity of executing a program, and leveraging the results for other purposes (like real batch scripting languages like BASH , or "¦ PowerShell).

But, maybe with a bit of linguistic legerdemain, I can fix the shortcomings of JScript, and get a pretty cool language, with very little effort. (Which, I did, and I'll post about next!)




The Cowboy

What I'm Tweetering about...

 

follow me on Twitter

Calendar

<<  September 2010  >>
MoTuWeThFrSaSu
303112345
6789101112
13141516171819
20212223242526
27282930123
45678910

View posts in large calendar

Sign in