Creating An XML Viewer With JScript - Exsead XML Power Scripting

This article builds on Exsead to create a JScript DOM parser and an XML viewing GUI:

Some Background On Exsead Etc.

The core concept behind Exsead is covered here. Also, to benefit most from this post, it would really help if you have already looked at the post on using Internet Explorer as a script GUI which is here. The code here also takes advantage of my system for reading binary files in JScript, which is described in detail here. More background on the AJAX used in this post is here. Finally for all these links, the source code and example XML for this post can be found here - XMLViewerCodeExamples.zip.

Inside the code example is 'XMLViewer.js'. This script uses the Model, View Controller concept to structure a simple but effective XML data viewer. An even simpler MVC example is also included (the one from the previous post on using IE as a GUI), it is called MVCExample.js (no imagination at all...). The purpose of any script that parses XML like this is to take the raw data stored in the XML and convert it into usable knowledge. Just by its self, XML is totally static data and is of no use; it must be parsed into a program before it 'comes to life'. This is exactly what XMLViewer.js does.

XMLViewer.js is quite a long script as it performs a few different functions to reach its goal. However, the overall structure is very simple; the parts are like this:

  • ProcessArguments: This function processes arguments passed to the script or if non are past, takes a default action.
  • DisplayXML: This does what it says, it pushes a visual representation of the XML to an Internet Exporter GUI.
  • XMLClean: This is just a simple utility to escape &tl; symbols out of text.
  • ProcessNode: As we will see later, XML is stored as a tree of nodes, this function is used by DisplayXML to run through the entire tree, creating an HTML representation of the XML structure as it goes.
  • GUIWindow: This is an object function - i.e. a function that creates an object. The object it creates is a wrapper around an instance of Internet Explorer. The class can then be used as the method DisplayXML uses for sending the visual (html) representation of the XML to Internet Explorer.
  • BinaryFile: As another object function, this creates BinaryFile objects which permit JScript to read and write files as binary rather than text. It is used to read in XML files.
  • ConcatStringArray: This is a very handy utility which takes an array of strings and returns the string created by joining the all together. This is very much faster than just adding loads of strings together where the strings start to get long.

Process Arguments

If you pass arguments to the script, which is what happens if you drag and drop an XML file onto the script's icon, this function will read the file as a binary file and then pass the contents to DisplayXML. If there are no arguments, then Process Arguments uses AJAX to get an XML data stream from the Nerds-Central ATOM feed and passes the returned XML to DisplayXML.

This is the Arguments/File Read part:
=====================================
        for(var i=0;i<WScript.arguments.count();++i)
        {
            var chunks;
            var fn=WScript.arguments.item(i);
            var bf1=new BinaryFile(fn);
            var xml=bf1.ReadAll();
            DisplayXML(xml);
        }

This is the AJAX Part:
======================
        var ajax=WScript.CreateObject('Microsoft.XMLHTTP');
    
        // Try to get the file 32 times then give up
        for(var i=0;i<32;++i)
        {
            try
            {
                // Set it to point to the web service
                // Note the last argument being false forces the script to
                // wait for the response
                ajax.open('GET','http://nerds-central.blogspot.com/feeds/posts/default',false);
                
                // Set the request header so that the connection is closed right
                // away to avoid locking up server resource
                ajax.setRequestHeader
                (
                    'Connection',
                    'close'
                );
                
                // Send the request and wait
                ajax.send();
    
                // The server returned an error
                if(!ajax.status==200) throw('Got Wrong Status:'+ajax.status);
                break;
            }
            catch(e)
            {
                for(var a in e)
                {
                    WScript.Echo(a + '=' + e[a]);
                }
                // Note the use of the () around the maths makes the numeric calculation
                // occur and the result be turned into a string by the + opperator 
                WScript.echo('Failed to get atom feed from Nerds-Central: tries left='+(32-i));
            }
        }

        // If the loop did not exit due to counting out
        // display the XML
        if(i!=32)DisplayXML(ajax.responseText);

DisplayXML

Given a piece of XML as a string, this function parses it using a Microsoft DOM parser. It then located the Document part of the DOM (a DOM - document object model - has other parts to it as well as the document its self).

    var xmlDOM = new ActiveXObject("Microsoft.XMLDOM");
    xmlDOM.loadXML(xml); 
    var doc=xmlDOM.documentElement;
Once it has the document element, it creates an Array which will be used to store all the output HTML. This approach is taken because new bits of HTML can just be 'pushed' to the end of the array very efficiently and then the whole array converted to a string at the last minute.

To understand the next piece of the puzzle we have to understand how the DOM concept models XML. As with most modern software concepts, it is actually much simpler than many would like use to believe! There is are two core concepts to understand when working with XML:

  1. Everything is a node.
  2. Ignore everything you don't need to worry about.
Nodes as really just simple containers. They can contain other nodes, or they can contain text. So a piece of XML like this <myParent><myChild>Hello World</myChild></myParent> will be stored in the DOM as 3 nodes. The first node will have the nodeName of 'myParent'. It will have one child which will have the nodeName of 'myChild'. The 'myChild' node will have one child as well. But this child does not have a name. However, it does contain the text. It has a nodeValue of 'Hello World'.

So that we can tell the difference between nodes that contain text and those which contain other nodes (or could contain other nodes, but happen to be empty) each node has a 'nodeType'. Node types 3 and 4 contain text.

We are nearly there in understanding nodes! The last piece we need for this post is the concept of 'attributes'. Attributes as the key="value" things that live inside XML. For example <myParent gender="female">Dorris</myParent> could be a piece of XML indicating that someone's mother was called 'Dorris'. Attributes are an alternative to using more complex XML structures like this: <myParent><tname>Dorris</name><gender>female</gender></myParent>. If you were to print out all the discussion on the Internet as to when attributes should or should not be used, you would probably wipe out the Amazon rain forest. So, for now I think we should just accept that they can be used!

The document element of the DOM is its self a node. All XML documents must have one outer node of which all others are descendants. ProcessNode takes a node and generates an HTML representation of that node and all its descendants. So DisplayXML, passes the document element (why it is not called the document node - I do not know) to ProcessNode.

ProcessNode

Want to be scared? Well ProcessNode is a 'recursive descendent processor'. Sounds really complex, mind boggling and scary... But again, it is actually quite simple. The reasoning goes a bit like this: Every node either has a value or children. We have a function and process a parent node. A parent node is just the same as a child node. So, we use the same function to process the parent node and its children. The easiest way to do this is to get the function that processes the parent to call its self for each of the children. A function which calls its self if called recursive. A function that uses recursion to 'walk down a parent/child relationship' is called recursive descendent. Finally, it processes the nodes as it goes, so it is a recursive descendent processor.

// This version has all but the XML handling code stripped out
// See the zip file for the full version
function ProcessNode(node,outArr)
{
    // Is this a text node? Text nodes have type 3 or 4
    if(node.nodeType<3 || node.nodeType>4)
    {
        // !!! - NOT A TEXT NODE - !!!
        // Get the node name here
        ... node.nodeName ...

        // Get the attributes
        var atts=node.attributes;

        // Process and attributes if there are any
        if(atts.length>0)
        {
            for(var i=0;i<atts.length;++i)
            {
                var aNode=atts.item(i);
                // Attributes are nodes as well! They have a name and value name=value
                ... aNode.nodeName ...
                ... aNode.nodeValue ...
            }
        }

        // Process and children, if there are any
        if(node.hasChildNodes())
        {
            var newNode=node.firstChild;
            while(newNode!=null)
            {
                // This is the recursion bit!
                ProcessNode(newNode,outArr);
                newNode=newNode.nextSibling;
            }
        }
    }
    else
    {
        // !!! - IS A TEXT NODE - !!!
        ... node.nodeValue ...
    }
}

Finally we push the HTML visualization of XML to the GUI

Which is created using this piece of code:

    // Get the output
    var out=ConcatStringArray(outArr);

    // Create a GUI
    var gui= new GUIWindow();
    
    gui.SetHTML(out);
    var doc=gui.GetDocument();
    var styleSheet;
    if(doc.styleSheets.length==0)
    {
        styleSheet=doc.createStyleSheet();
    }
    else
    {
        styleSheet=doc.styleSheets[0];
    }
    styleSheet.addRule('body','color: black;');
    styleSheet.addRule('body','font-family: Arial, helvetic, sans-serif;');
    styleSheet.addRule('span.nodeName','font-weight: 700;');
    styleSheet.addRule('ul.attributeList','color: #008;');
    styleSheet.addRule('li.nodeValue','color: #080;');
    styleSheet.addRule('*.missing','color: #888;');

    gui.SetVisible(true);

OK, so this is a lot of stuff to take in, but the result is pretty powerful for just a script!

Using Internet-Explorer As A GUI For VBScript & JScript

This Post Is For All Those People Who Want I GUI For A Script But Find HTAs Clunky!

Also see (new and improved):here and here

Scripts are fine, but sooner or later you are going to want to show something to a user, or interact with a user. People expect Graphical User Interfaces and are no longer happy to see text scrolling by in a CMD box. The next step what we all seem to take is to look into Hyper Text Applications. These seem like the perfect solution as they are scripted but have an HTML GUI with them. However, more often than not, this approach becomes unstuck.

HTAs Fail Because Their Programming Model Is Wrong

HTAs mix up the presentation of the application with the co-ordination of that application with the underlying data that feeds it. All the management code is embedded into the pages that are presented to the user. This results in everything becoming a mess of interdependencies that eventually trips over its own feet. After a few days of being very excited at how fast you are at creating this new HTA you become all sad as development grinds to a halt.

This is not a new problem and it is not isolated to HTAs. Indeed, IBM researched it a long time ago and came up with a programming model to help overcome it. The snag is that over the years the simplicity of their model has been covered over by countless poor descriptions. Please do not switch off at this point - this is very simple - really!

Model View Controller IS VERY SIMPLE INDEED - just read on

As a software architect, I feel very passionate about this, so I am going to say it again: MVC is very simple indeed!.

Here is a picture that illustrates my point:

Just stare at it a bit and let it sink in...

  1. At the bottom is data. The data is stored in a data model. So the data base at the bottom is M for Model.
  2. At the top is a Graphical User Interface. So the GUI at the top is V for View.
  3. In the middle is a brain. Brains control things. So the brain in the middle is C for Controller.
So - let us look at this picture again!

In keeping with the Exsead concept, the brain (controller) is one or more scripts. The GUI (view) is an application which is being controlled by the script. This application could be MS Excel or Word, but in this post it will be Internet Explorer. The database (model) may be an SQL database, web-service, file or internal scripting data-structure. The point is that it has a separate existence to both the controller and the view!

A JScript Class To Use Internet Explorer (IE) As A GUI


function GUIWindow()
{
    var objExplorer = WScript.CreateObject("InternetExplorer.Application")

    with(objExplorer)
    {
        Navigate("about:blank");   
        ToolBar   = 0;
        StatusBar = 0;
        Width     = 500;
        Height    = 400; 
        Left      = 200;
        Top       = 100;
        Visible   = 0;
        document.title = 'Script GUI Window';
    }
    
    this.SetTitle    = function(title)
    {
        objExplorer.document.title=title;
    }
    this.GetDocument = function()
    {
        return objExplorer.document;
    }
    this.SetHTML     = function(html)
    {
        objExplorer.document.body.innerHTML=html;
    }
    this.Quit        = function()
    {
        objExplorer.quit();
    }
    this.MoveTo      = function(top,left)
    {
        objExplorer.Left=left;
        objExplorer.Top=top;
    }
    this.SetSize     = function(width,height)
    {
        objExplorer.Width=width;
        objExplorer.Height=height;
    }
    this.GetWindow   = function()
    {
        return objExplorer.document.parentWindow;
    }    
    this.Alert       = function(msg)
    {
        objExplorer.document.parentWindow.alert(msg);
    }
    this.ScrollTo    = function(x,y)
    {
        objExplorer.document.parentWindow.scrollTo(x,y);
    }
    this.SetVisible  = function(tf)
    {
        if(tf==true)
        {
            objExplorer.Visible=1;
        }
        else
        {
            objExplorer.Visible=0;
        }
    }
    this.WaitOnId    = function(id)
    {
        var elem=objExplorer.document.getElementById(id);
        var val=elem.value;
        while(elem.value==val)
        {
            WScript.sleep(20);
        }
        return elem.value;
    }
}

There it is; it is not the most complex class in the whole world. However, it is very powerful. I am not going to show all the things that can be done with it here. This is an introductory posting. Many of the postings which are in the pipeline will give examples of the sort of stuff which can be done with this class. For now here is a very simple example that uses a script (controller) to get data from a web-server (data) and places it in a GUI windows (view).

Run();

function Run()
{
    // Create an AJAX object
    var ajax=WScript.CreateObject('Microsoft.XMLHTTP');

    // Try to get the file 32 times then give up
    for(var i=0;i<32;++i)
    {
        try
        {
            // Set it to point to the web service
            // Note the last argument being false forces the script to
            // wait for the response
            ajax.open('GET','http://www.nerds-central.com/Logs/nerds.csv',false);
            
            // Set the request header so that the connection is closed right
            // away to avoid locking up server resource
            ajax.setRequestHeader
            (
                'Connection',
                'close'
            );
            
            // Send the request and wait
            ajax.send();

            // The server returned an error
            if(!ajax.status==200) throw('Got Wrong Status:'+ajax.status);
            break;
        }
        catch(e)
        {
            for(var a in e)
            {
                WScript.Echo(a + '=' + e[a]);
            }
            // Note the use of the () around the maths makes the numeric calculation
            // occure and the result be turned into a string by the + opperator 
            WScript.echo('Failed to get csv feed for Nerds-Central: tries left='+(32-i));
        }
    }
    // If the loop exited due to counting out then give up
    if(i==32)WScript.Quit(1);

    // OK, we got the data, now let's display it in a GUI

    // Create a GUI
    var gui= new GUIWindow();
    
    // Make the data nice to view in HTML
    var out=ajax.responseText;
    // Remove (escape) all tag open symbols
    out=out.replace(/</g,'&lt;');
    // Wrap data in pre to make sure it looks OK
    gui.SetHTML('<pre>'+out+'<pre>');
    // We can get hold of the document to style it
    var doc=gui.GetDocument();
    var styleSheet;
    // Now make sure it has a style sheet and set some rules
    if(doc.styleSheets.length==0)
    {
        styleSheet=doc.createStyleSheet();
    }
    else
    {
        styleSheet=doc.styleSheets[0];
    }
    styleSheet.addRule('body','color: black;');
    styleSheet.addRule('body','font-family: courier, fixed;');
    styleSheet.addRule('body','background-color: #FFD;');

    // By default the GUI window is hidden, once it is populated, 
    // show it
    gui.SetVisible(true);
}

// GUI CLASS GOES HERE //

To try this script, copy both code blocks into your editor and name it something like MVCExample.js, then just run it as csript MVCExample.js or double click on it from explorer. Nice a simple!

Nerds Central Gets An AJAX ATOM Feed Reader

Since I wrote this post, I have removed the link from the top of the page. However, a full description of how the atom feed reader works can be found here.

If you look at the top of the Nerds-Central page, you will see the new 'Click Here To See Summary Of All Posts'.

You might think that this will lead you to a static file or a php/asp page that shows results from a database. You would be wrong, very wrong. What it does is use AJAX to contact the Google ATOM WebService. It gets back the ATOM XML feed. The JavaScript embedded into the Nerds-Central page then parses this ATOM feed and creates a new web page from the data it collects. It then displays the new data for you!

It has been rather fiddly to get this to work properly in both IE and Firefox, so I shall be posting an article on how it is done and the tricks required to make a nice clean implementation sometime over the next few days

Sniff HTTP For Free; Find Out What Is Really Happening

tcpdump Or windump Are All You Need To See Every Detail Of HTTP Traffic

It would be very nice if we could rely on our web servers and client applications to behave exactly as we expect. Unfortunately this is not the case. As Web 2.0 pushes the technology and general expectations of reliability increase, there will always be times when something breaks. Very often fixing the problem is straight-forward. The hard bit can be figuring out what has broken!

You can go onto the Internet and find truck loads of programs which promise to tell you everything you ever wanted to know about HTTP (the protocol used to connection from clients to web servers). The difficulty is that these programs are often based on the same assumptions that the web clients (e.g. browsers) are based upon and so break at exactly the same time. What is worse, you have to pay for them!

So, why not rewind to a bit of old school technology. Back in the days when computers communicated to us via green flickering letters on black screens, there was a fabulous program called tcpdump. It still exists, and what is better, there is a Linux and a Windows version.

How to get and install the Linux version will depend a lot on which distribution you have. I run Ubuntu and it comes as standard. The Windows version can be picked up here you will have to get an install WinPcap as well to get it to work (which comes from the same web site).

tcpdump (I will call it tcpdump from now on as windump work identically) is very very powerful and complex utility. It would be very silly indeed for me to try and explain all of what it does in this post. I am going to just concentrate on sniffing HTTP. So, once you have tcpdump in place this is what to do:

Windows:
cd to the directory in which windump.exe exists. Next type in windump.exe -Ap -s65535 port 80.
Linux:
from any directory type tcpdump -Ap -s65535 port 80
You then connect to some site using your web browser (this will not work for SSL on port 443). As you surf you should see tcpdump start dumping out text something like this: (If you don't then check the end of the post for trouble shooting)

18:06:53.985637 IP 192.168.1.132.40469 > l2.login.vip.re3.yahoo.com.www: S 1672715741:1672715741(0) win 5840 <mss 1460,sackOK,timestamp 10064072 0,nop,wscale 2>
E..<.@.@..G....E.p....Pc............}.........
............
18:06:54.084049 IP l2.login.vip.re3.yahoo.com.www > 192.168.1.132.40469: S 2006229297:2006229297(0) ack 1672715742 win 65535 
E..<a.@.4.l.E.p......P..w..1c.......[V.....P.......
$.......
18:06:54.084126 IP 192.168.1.132.40469 > l2.login.vip.re3.yahoo.com.www: . ack 1 win 1460 <nop,nop,timestamp 10064098 612829439>
E..4
.@.@..N....E.p....Pc...w..2...........
....$...
18:06:54.084399 IP 192.168.1.132.40469 > l2.login.vip.re3.yahoo.com.www: P 1:766(765) ack 1 win 1460 <nop,nop,timestamp 10064098 612829439>
E..1
.@.@..P....E.p....Pc...w..2.....{.....
....$...GET / HTTP/1.1
Host: mail.yahoo.com
User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:1.8.1) Gecko/20061010 Firefox/2.0
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: en-gb,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Cookie: F=a=UOjTyGAsvRqjUnkWKKlv04mH.wLJYRZQHjhl39fzqrfGLxBgzokPpljq8DZv&b=pPQI; B=bmj3on12j17bm&b=3&s=bi; U=mt=tGdXgJ2MhYqBhHIPB.lSKrp045UH5pOGYO_.Zw--&ux=VDioFB&un=39397qr4j09b0; YM.sync_funkifunctions=_1%3A5652678393; YM.sync_alex_j_turner=_1%3A9833137895; YM.dpref1=alex_j_turner%3Anewp%257C1%252Cspp%257C1%252Cbtph%257C257%26funkifunctions%3Anewp%257C1%252Cspp%257C1

Now do not be alarmed, this is not as bad as it looks. The trick is to ignore most of the stuff that you do not understand. Consider the line 18:06:53.985637 IP 192.168.1.132.40469 > l2.login.vip.re3.yahoo.com.www: S. The first bit is just a time stamp. Then the next is the address of the machine from which a TCP packet is being sent (my machine), followed by the local port. Next we have the receiver of that packet and the port it received it on then S. You can ignore the ports. Just remember that any communication over the Internet is broken down into chunks called 'packets'. tcpdump will show you the individual packets, so a single communication with a web server may be split across several packets. Packets go from one computer to another. The reply will always come in a different packet.

This is a bunch more stuff which you can ignore:

1672715741:1672715741(0) win 5840 
E..<.@.@..G....E.p....Pc............}.........
............
That lot is all 'chatter' between the two computers which is used to maintain their connection and manage transmission over it. In fact, you only need to get interested when we get down to this bit (I have chopped out some of the chatter to make this easier to read):
18:06:54.084399 IP 192.168.1.132.40469 > l2.login.vip.re3.yahoo.com.www
GET / HTTP/1.1
Host: mail.yahoo.com
User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:1.8.1) Gecko/20061010 Firefox/2.0
This shows my computer (192.168.1.132) sending a packet to 2.login.vip.re3.yahoo.com (a Yahoo server). The packet has an HTTP 'GET' request encoded into it. Underneath the initial GET you can see the headers that my browser sent to the server. When someone talks about sniffing HTTP to see what is really going on - this is the stuff they are on about!

A bit further on you can see a response to my request:

18:06:54.224011 IP l2.login.vip.re3.yahoo.com.www > 192.168.1.132.40469
HTTP/1.1 302 Found
Date: Tue, 30 Jan 2007 18:06:26 GMT
P3P: policyref="http://p3p.yahoo.com/w3c/p3p.xml", CP="CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE GOV"
Location: https://login.yahoo.com/config/login_verify2?&.src=ym
Cache-Control: private
Pragma: no-cache
Expires: Thu, 05 Jan 1995 22:00:00 GMT
Connection: close
Transfer-Encoding: chunked
Content-Type: text/html

0
Here is a perfect example of why sniffing can be so very useful. The response from the Yahoo server was not the expected 200 OK. No, it has said 302 Found, but then asked my browser to relocate to a new URL using the 'Location: https://login.yahoo.com/config/login_verify2?&.src=ym . Not only that, you can see that several 'tricks' have been employed by the server to try and stop any chance of the response being cached. It sets the 'Expires:' header in the past, it also has 'Pragma: no-cache' and 'Cache-Control: private'. From this very short 'sniff' we have managed to learn a lot about what is really going on!

Trouble Shooting

If tcpdump or windump is run without root/admin privileges then the chances are it will fail. For example, on Ubuntu I put sudo tcpdump.exe -Ap -s65535 port 80. Also, if your machine has more than one network interface then you might have to get tcpdump it listen on the correct one. For example, my Windows machine has a VPN interface. By default windump listened on the VPN interface rather than the Ethernet one. If you put windump -D or tcpdump -D it will list the interfaces it could work on and number them. When I did this on my windows box it showed the Ethernet interface to be number two so I put windump.exe -Ap -i2 -s65535 port 80. Finally, if your web server is listening on a different port to the normal port 80 you will have to change the port directive in tcpdump accordingly.

Notes:

Thanks to Ivan Rancati for pointing out that on Linux, the executable is called tcpdump, not tcpdump.exe!

Vista - Microsoft's Most Important Software Release - EVER!

With the possible exception of MSDOS, no release MS has ever made comes close in importance to Vista. This may seem odd considering that Vista appears little different to XP. A fancier, yet already outdated user interface and a few nice power enhancements is all it shows as new. Most people cannot even be bothered to yawn over it!

The critical nature of Vista springs from what it is and when it is: It is a complete re-write of the worlds most populous operating system just when the established notion of a computer is breaking down.

Apple's “iPhone” shows just how much everything is becoming a computer and computers are becoming everything. The “Wallet PC” about which Bill Gates wrote in the second edition of “The Road Ahead” has all but arrived. TVs are becoming media centres, Laptops are delivering TV programs and telephones surf the Internet.

Along with all of this comes the new brand of low power consumption, multi-core CPUs and fast, cheep solid state storage. In no time at all, the world will be stuffed full of portable, highly powerful computers each capable of running a bewilderingly huge range of applications.

To deliver all these applications and all that computer power requires a strong operating system. By “strong” I mean it does not crash, even when an application does. There are really only three contenders for this OS (in no particular order):

  • Windows Vista
  • BSDLite's children including Apple's OSX
  • Linux

New hardware is and will be so powerful that it will easily run any of there three operating systems. The choice of which is used for each product will have enormous implications for the entire technology industry. This is because there will be so many devices running an operating system in the very near future! Everything from your phone to your fridge will, more than likely, use one of these three in the near future.

Without its re-write, Windows would have been at an impossible disadvantage compared with the other two. They came from a long “Open Source” tradition. Students, academics and enthusiasts have laboured to make their design clean and elegant. Because every line of code in the kernel (the core of the operating system) is visible to everyone, it has become a matter of pride and competition to make every line as near perfect as possible.

Compare this to the commercial world of Windows development. No one outside of MS gets to see the code. The commercial requirement has been to get it to work and fast. Problems get “plastered over” rather than fixed. Whilst this approach has served MS well for its years of dominance in the PC market, the patched and repaired structure of Windows would have been an unstable liability moving forward into this new world of “computers everywhere”. Fear of hidden bugs and security flaws would have forced manufactures to opt for the alternatives to power their new handsets and super-intelligent power tools.

In other words, the only way MS could have avoid being stuck in the increasingly niche market of PCs was by a total re-write; i.e. Vista!

Waiting For Events In VBScript and JScript

Unlike VB6 or VBA, VBScript and its sister JScript lack the DoEvents call - But This Does Not Matter...

For those who are impatient, here is the solution!

VBScript:
' For an infinite wait
while true
   WScript.sleep 20
wend

' For a sleep which can be interupted
dim keepSleeping
keepSleeping=true
while keepSleeping
   WScript.Sleep 20
wend

' The set keepSleeping to false in the event procedure.

JScript:
// For an infinite wait
while(true)
{
   WScript.Sleep(20);
}

// For a sleep which can be interupted
var keepSleeping=true;
while(keepSleeping)
{
   WScript.Sleep(20)
}
// Again - the set keepSleeping to false in the event procedure.

COM events within the script are handled in the same thread as the script's code is run. This means that whilst the script's code is running, events cannot be handled. In VB6, the call DoEvents stops the execution of the code, executes all pending events and then returns to executing the next line of code. The reason that DoEvents is not in VBScript or JScript is unclear to me. It may well arise from the fact that Windows Scripting Host was not envisaged as being used for such complex activities as it now is. However, help is at hand because when WScript.Sleep is called, it does a DoEvents for you!

So what is all this Sleep(20) about? A lot of people seeing that will ask firstly, why 20 and secondly, will not that loop take up a lot of CPU. The reason for the 20 is that in Windows the minimum possible wait is usually 16 milliseconds. You could set the sleep to 16, but that is a bit close to the wire, so 20 feels nice a safe. The loop takes almost no CPU at all (no tool I have can measure it), so there is no worry there. If you are working on an application that is receiving events due to user interaction, the maximum delay to reacting to an even being 20 milliseconds is still very much faster indeed than a human can perceive.

As with many things in scripting, I am not going to say I like this approach, but it works!

Exsead - Building Enterprise Level Applications Using Scripting

Background: JScript and VBScript have become the backbone of Windows system administration.

However, critical limitations to what can be achieved with them have usually cause project to require compiled languages eventually. Writing system in a compiled language just because some features cannot be scripted is extremely wasteful of human resource. The fact of the matter is that scripting comes more easily to more people than even VB.net.

Years of working my way up through being a programmer to now being a senior architect have lead me to believe that if we can leverage off the extra availability and productivity of scripting resource, software development can be massively enhanced. This is especially so in the enterprise market. Where as in the domestic market most software come shrink wrapped, the enterprise market has a stable and maybe even growing proportion of bespoke systems. This is because the complexity of a businesses internal mechanisms needs to be reflected in software systems bespoke to match that complexity.

One way of unlocking the advantages of scripting to the enterprise is by bridging the gap between what scripting can do and what compiled systems can do. This is where Exsead comes in. It encompasses ways of using existing applications and technologies to achieve what was previously not considers and a small set of new technologies.

Exsead – What Is It?

  • EXtreme
  • Scripting
  • Enterprise
  • Application
  • Development

By looking at previous nerds-central posts you will be able to see that it is possible to achieve a great deal of enterprise level interconnection with scripting as it is now. Joining the desktop and MS Office to database and web services is very simple once the underlying approach is understood. Exsead encompass these ideas but massively increase the amount that can be done by adding new abilities to scripts and bringing together the concepts into an application development strategy.

Embedding Binary Files Into JScript And VBScript

Nerds Central Icon

The Object Of This Post Is To Embed The Above Image Into A Script Which Can Then Load It Into MS Excel!

A few months ago I would have been totally stumped as to how one could do this. However, it is actually dead easy. The key to messing around with applications like this is to think of the Script as a 'brain' that sits in the middle of different applications and data whilst controlling them all.

Step One, Building A JScript Archiver

Down Load MakeArchive.js Here

As I have discussed in previous posts, it is possible to read and write binary files using JScript and the ADODB.Stream object. Once the binary file is loaded into a JScript string, it is then possible to use the Base64 codec (see previous posts) to encode this into a string which can be inserted into a JScript script. All that has the upshot that we can load the image and create a JScript script that when run will regenerate it.

My script 'MakeArchive.js' does just this. All you have to do is 'drag and drop' a binary file on it and it will archive it into a js file with the same file name with '_archive_' postfixed. This is a long script (see above to download it) so I will just highlight parts of it here for you:


/** Between the START-ARCHIVE-CODE and ---END-ARCHIVE-CODE--- are the binary file
  * and base64 codec functions which are used by both the MakeArchive script and
  * the script it creates.  The MakeArchive script reads it own source and looks
  * for these markers so it can locate the source to include in the output script
  */

//---START-ARCHIVE-CODE---
function BinaryFile(name)
{
   ...
}

//---END-ARCHIVE-CODE---

/** Here we create the TEXT file that will receive the archive script
  */
    var fso=WScript.CreateObject('Scripting.FileSystemObject');

    /** When you drag and drop onto a script in Windows, the file paths
      * of the files you dropped are passed to the script as arguments.
      * This loop is used to go through those dropped files and make an
      * archive for each.
      */
    for(var i=0;i<WScript.arguments.count();++i)
    {
        var chunks;
        var fn=WScript.arguments.item(i);
        var bf1=new BinaryFile(fn);
        var b64=new Base64();   
        chunks=b64.encode(bf1.ReadAll()).split('\n');
       
        var ts=fso.CreateTextFile(fn+'_archive_.js',true);
       
        ts.WriteLine('function UNArchive(name){');
        ts.WriteLine('var encArray=new Array();');

        for(var j=0;j<chunks.length;)
        {
            var inner='';
            for(var k=0;k<8 && j<chunks.length;++j,++k)
            {
                inner+=chunks[j];
            }
            ts.WriteLine('encArray.push("'+inner+'");');

   ...

        var fso=WScript.CreateObject('Scripting.FileSystemObject');
        var ts2=fso.OpenTextFile(WScript.ScriptFullName,1,false);
        while(true)
        {
            var l=ts2.ReadLine();
            if(l=='//---START-ARCHIVE-CODE---')
            {
                break;
            }
        }

        while(true)
        {
            var l=ts2.ReadLine();
            if(l=='//---END-ARCHIVE-CODE---')
            {
                break;
            }
            ts.WriteLine(l);
        }
        ts2.close();
        ts.close();

Once the archiver has run, you can take the output file and use the source code in it to embed binary data into a new script. Below is an outtake of the script I created to place the WebNerd image into Excel. Once it has openned Excel and inserted the image, it also adds a command button to the spreadsheet. The script listens for the 'Click' event of this button and upon recieving it closes Excel and then its self. The script segment below is well commented, so please read the comments to understand how it works.

Down Load WebNerd2Excel.js Here

var excel=RunExcel();
// This is the scripting equivalent of an event loop.
// What happens here is that the script effectively
// 'goes to sleep' but can still process any incoming
// events created by connected objects - line the CommandButton
// that we connected using WScript.ConnectObject.
//
// A lot of research over a long time indicates that setting the time
// to 20 milliseconds is optimal for Windows systems
//
while(true)
{
    WScript.Sleep(20);
    try
    {
        // the next line will fail if Excel dies
        // which ensures that if someone closes
        // the connected Excel application then the
        // script will shut down as well.
        var test=excel.WorkBooks.Item(1).name;
    }
    catch(e)
    {
        WScript.Quit(1);
    }
}


// This function is where the bulk of the work running Excel and
// placing objects into it occurs
function RunExcel()
{
    // Create a new Excel in the usual way
    var excel=WScript.CreateObject('Excel.Application');
    // Make it visible - this is visually appealing but has the
    // disadvantage that if the user  interacts with it during
    // the script run it can cause a crash of the script.
    // I would suggest leaving it visible for all debug work but
    // leaving it invisible till the end of the script for production
    excel.visible=true;

    // Now we get a workbook and then the first worksheet.
    // We get rind of the other worksheets as they are not required
    var wb=excel.WorkBooks.Add();
    var ws=wb.WorkSheets.Item(1);
    ws.name="Image Page";
    wb.WorkSheets.Item(2).Delete();
    wb.WorkSheets.Item(2).Delete();

    // Set the binary form of the embedded image to a temporary fil
    var tempf=GetTempFile();
    tempf=tempf+'.png';
    UNArchive(tempf);
   
    // The image will be inserted with the top left hand
    // corner in the selected cell, so this is the best way
    // of placing the insert of an image
    ws.Cells(1,1).Select();
    ws.Pictures.Insert(tempf);   
       
    // This is how we add command objects like list boxes or
    // command buttons to an excel spread sheet
    var objContainer = ws.OLEObjects.Add
    (
        "Forms.CommandButton.1",
        undefined,              // File Name
        false,                  // Link
        false,                  // Display As Icon
        undefined,              // Icon File Name
        undefined,              // Icon Index
        undefined,              // Icon Label
        400*0.75,               // Left
        190*0.75,               // Top
        80,                     // Width
        20                      // Height
    );
   
    // Excel places the new command object in a container
    // objects called a OLEObject.  To get to that actual
    // command object with which we want to interact, we
    // use the Object property of the OLEObject
    var button1=objContainer.Object;

    // Now we set the caption to whatever we want
    button1.caption='Click To Close';

    // WScript.ConnectObject causes OLE events generated by the
    // object to be routed to the script.  The first argument
    // is the object to which we want to listen.  The second
    // is the prefix of the functions within the script that
    // will receive events.  So this call will mean that a click
    // event raised by the button will be sent to a function
    // called Button1Event_Click (see below)
    WScript.ConnectObject(button1,'Button1Event_');

    return excel;
}

// Because of the ConnectObject call above, this function will
// receive Click events from the button which is embedded in Excel
function Button1Event_Click()
{
    // This just shuts down Excel and exits
    // the try block is there to ensure that
    // the script is still exited even if the
    // call to quit Excel fails for some reason
    try
    {
        excel.DisplayAlerts=false;
        excel.Quit();
    }
    catch(e){}
    WScript.Quit(0);
   
}

Storing Binary Data, Loading An Image Into Excel and Reacting To Excel Object Events

So what have we achieved here. In the face of it, not much possibly. The reality is that we have made a huge step towards creating a framework in which enterprise level, distributed applications can be built from MS Office and Windows Scripting Host. If we couple this work with the previous work on creating management reports using Excel and AJAX, we have removed the boundary between Web Applications and Thick Clients. What we have achieved is a full integration of the familiar MS Office interface with the modern Service Oriented Architecture approach for enterprise applications.

Let us review what we have so far in support of the above statement. We can link MS Office with a remote Web Service and create complex reports. We can drive binary data into MS Office applications. We can respond to MS Office events. This means we can create complex MS Office forms with first rate graphics using images and OLE objects. We can then listen to events created by those forms and from those events trigger communication to Web Services. We can then use Web Services to drive back first rate management information and reports. That sounds like a 21 first century enterprise application interface! But this time it is 100% familiar to the end user, already on their computers and easy to code up.

Exceed Expectations With Exsead

All this put together, along with a few new tricks to fill in the gaps is what I am now calling Exsead - EXtreme Scripting Enterprise Application Development. I shall be covering more on this subject over the coming weeks.

Creating Management Reports Using Excel JScript And Web Services

Using XMLHTTP Request Objects Excel Can Become A WebService Consumer - Easily

All you need to achieve this is Windows (Win98 or better should work), Excel (2000 or later, no need for Office, Office Pro etc) and Internet Explorer 6 (or better). There are no optional installs required. This is just 100% standard stuff! However, it has taken me 3 years of poking around to find the easiest and most effective way to pull the trick off. What I describe here is very simple and very powerful, but - up till now - shockingly underused

The code is written in JScript. I prefer to do coding in JScript to VBScript because I find it more flexible etc. However, if enough people ask, I am happy to write a VBScript version and post it.

The source for the code can be found here (right-click, SaveAs).

Do not click on Excel whilst the script it running. This is a demo - and it shows Excel running. In production Excel would be hidden until the script finished.

So: How Does It Work?

           Excel
             |
          JSCript
             |
         Internet
             |
         WebServer
             |
        WebService

The process runs in the following steps:

  1. JScript contacts the Server over the internet and requests a reply from the WebService. This is done using the XMLHTTP COM object.
  2. The WebServer run the service and returns the result (this is VERY simple in this example).
  3. JScript reads the output of the WebService and converts it into JScript data structures.
  4. JScript opens Excel as a COM object via the WScript.CreateObject method.
  5. JScript pumps the data from its internal data structures into an Excel spreadsheet.
  6. JScript instructs Excel to process the data to produce management reports (in this case a Pivot Table and associated Pivot Chart).
As you can see, the process is fairly straight-forward. Some complex interactions involving multiple WebServices, multiple calls to one or more WebServices and/or interaction with local files are possible but beyond the scope of this article

Stepping Through The Code And Seeing The Results

First here is the function that contacts the WebService and gets the result:

function GetCSVFile()
{
    // Create an AJAX object
    var ajax=WScript.CreateObject('Microsoft.XMLHTTP');

    // Try to get the file 32 times then give up
    for(var i=0;i<32;++i)
    {
        try
        {
            // Set it to point to the web service
            // Note the last argument being false forces the script to
            // wait for the response
            ajax.open('GET','http://www.mightycrap.com/Logs/nerds.csv',false);
            
            // Set the request header so that the connection is closed right
            // away to avoid locking up server resource
            ajax.setRequestHeader
            (
                'Connection',
                'close'
            );
            
            // Send the request and wait
            ajax.send();

            // The server returned an error
            if(!ajax.status==200) throw('Got Wrong Status:'+ajax.status);
            
            // Get the reply from the service OK, return
            return ajax.responseText;
            
        }
        catch(e)
        {
            // Note the use of the () around the maths makes the numeric calculation
            // occure and the result be turned into a string by the + opperator 
            WScript.echo('Failed to get csv record from mightycrap: tries left='+(32-i));
        }
    }
}

The absolute most critical part to the whole approach is the use of the XMLHTTP object:
// Create an AJAX object
var ajax=WScript.CreateObject('Microsoft.XMLHTTP');
This is object which started the whole AJAX movement off. Its functions are replicated in FireFox and other major browsers, to it is a very useful object to learn about. Whilst it does have XML parsing functionality built in, we are not actually going to use that part of its interface in this example.

As the comments explain, we use a synchronous request. We have nothing else to do whilst waiting for the response so we may as well just wait. The other thing that is worth learning is that whilst users tolerate web pages that do not load first time (just tolerate) they totally despise applications which fail. The HTTP protocol talking to web services can fail, and does, a lot. So it is well worth placing web service requests in a loop and trying again if they fail. Just make sure the loop does exist eventually if the request never succeeds!

Once the data has come back from the service we pipe it to Excel. The script at the part does several things that might cloud the issue. So, as you can download that and look at the comments, here is a cut down version to discuss here:

    // Create a new Excel in the usual way
    var excel=WScript.CreateObject('Excel.Application');

    ...
    
    // Now we get a workbook and then the first worksheet.
    // We get rind of the other worksheets as they are not required
    var wb=excel.WorkBooks.Add();
    while(wb.WorkSheets.Count>1)
    {
        wb.WorkSheets.Item(1).Delete();
    }
    ws=wb.WorkSheets.Item(1);
    ws.name="Raw Data";
    
    // Get the csv data and place it into Excel. 
    // The trick to making this work
    // quickly is to do it one line at a time rather than one cell
    // at a time.  We can do this in JScript by getting a Dictionary
    // object to create arrays of variants for us.
    var dict = WScript.CreateObject("Scripting.Dictionary");
    var rows = csvData.split("\n");

    ...

    for(var row = 0;row<rows.length;++row)
    {
        // Chop each row into its fields
        var cols=rows[row].split(",");

        ...

        // Empty the dictionary each time we go around
        // the loop
        dict.RemoveAll();
        // Load it with the new fields
        for(var col=0;col<14;++col)
        {
            dict.Add("_" + col, cols[col]);
        }
        
        // Add in the date in a sortable format for Excel to use
        var myDate=cols[0].split(' ');
        myDate=myDate[1].substr(0,myDate[1].length-2)+' '+myDate[3]+myDate[4];
        dict.Add("_14",myDate);
        
        // Offset row by 1 because it is 1 indexed and another 1 because
        // of the headers
        var arr=dict.Items();
        ws.Range(ws.Cells(prow+2,1),ws.Cells(prow+2,15)).Value = arr;
    }

Notice how we make the process of loading data into Excel quicker by doing it one row at a time. Loading data into Excel one field at a time is very very slow indeed! Excel will allow you to feed values in to a range of cells at once using the Range.Value property. To do this you have to pass Excel a COM 'SafeArray' which JScript cannot create directly. However, the Scripting.Dictionary object can, so we use this object as an intermediate step to loading Excel. This approach is still dominated by the speed of Excel, so the using the Dictionary object does not have any noticeable speed impact. Should all this work, you will get a spread sheet that looks like this:

The above image, and many of the others in this post, have been reduced to load faster. If you click on it you will see a bigger version, but still reduced.

Next the script puts in the column headers and autofits the columns. The column headers are CRITICAL because these are what the Pivot Table code in Excel uses to generate the table and chart.

    // Add in the headers
    var heads='Date,URL,Referrer,Browser Id,Browser Version,OS Id,OS Version,Screen Resolution,URL Scheme,URL Host,URL Path,Query,Client IP,Client Address,Date Day';
    heads=heads.split(',');
    for(var i=0;i<15;++i)
    {
        ws.Cells(1,1+i).value=heads[i];
        // Auto Fit the Columns
        ws.Columns(i+1).AutoFit();
    }

Once the headers and fitting have happened the spread sheet should look something like this:

Now we have the raw data in place, we need to create the Pivot Table. The code to do this is below (with comments!):

    // To do this we first add a pivot cache to the work book.
    // The pivot cache is used to hold and computer the data
    var pcs = wb.PivotCaches();
    var pc = pcs.Add
    (
        xlDatabase,
        "'Raw Data'!R1C1:R"+prow+"C15"
    )
    
    // Now we create a new worksheet into which we
    // can place the pivto table
    var ptWs=wb.WorkSheets.Add();
    ptWs.Name='Hits By Day Summary Table';
    
    // Now we create the pivot table, remembering its name
    // as we will use that later
    var ptn='PivotTable1';
    // This range is where the top left hand corner of the table
    // will go
    pc.CreatePivotTable
    ( 
           ptWs.Range('A1'),     
           ptn
       );
       
       // Now we can look up the pivot table be name when ever we want
       with(ptWs.PivotTables(ptn))
    {   
        // This section adds the data to the table
        // in a self explanatory way
        // NB: you cannot rename a field to a name
        // that already exists in the pivot data - even
        // if you are not showing that data. For example
        // you cannot make this field have the name 'Date'
        // as that is taken.         
        with(PivotFields("Date Day"))
        {
            Orientation = xlRowField;
            Name='Day';
        }
        
        with(PivotFields("URL Host"))
        {
            Orientation = xlColumnField;
            Name='Host';
        }

        with(PivotFields("Date"))
        {
            Orientation = xlDataField;
            Name = '# Hits';
        }
    }

You might have noticed that in the above code I have used some Excel constants like xlDataField. All these (and a load more) are defined at the start of the script (which you can download - see the start of the post). I have not included the definitions here as they are boring and just take up loads of space.

The resulting Pivot table should look something like this:

Now we have a Pivot Table we can create a Pivot Chart from it:

    var chart=excel.Charts.Add();
    with(chart)
    {
        // Point the source data to the top left
        // non lable field of the pivto chart
        SetSourceData(ptWs.Range("B4"));
        // Make the chart be a new sheet
        Location(xlLocationAsNewSheet);
         // Column Clusterted
        ChartType = 51;
        // This alchemy sets up the lables are we want
        ApplyDataLabels
        (
            2,
          true,
          false,
          false,
          false,
          false,
          true,
          false,
          false
        );
    }

Cool Things You Can Do With The Pivot Table

It is easy to think that the Pivot Table is just a means of creating the chart. Actually, it is a very powerful analysis tool. If you click on one of the data or total cells, Excel will create a new spreadsheet highlighting where that data came from. This can be seen in the following two images:

Other Charts And Analyses

So far the script just generates one chart and one table. However, once you have data into Excel, you can just keep going, adding new and interesting analyses. The following two charts show the ratio of hits on Nerds-Central (at the time the script was run) of different browsers. You can see some interesting trends: IE7 has a good takeup and for the readers of Nerds-Central, Firefox is nearly as popular as IE!

An example of the output of the script can be found here.

Have Fun!

Developing A Spam Filter For Referral Logs

I am developing a referral tracker for Nerds Central

This uses an unobtrusive piece of JavaScript to send data to one of might web server accounts each time someone views Nerds-Central. The point being that I want to be able to fully control the data that is recorded and how much is recorded but I am hosting Nerds=Central on blogspot, so I don't have access to the server logs.

The system has been working for a few days now, you can see the raw log at http://www.mightycrap.com/Logs/nerds.csv.

The key design goal is to prevent log spam

I really want to put the referral information on the blog as I think this is interesting informations. Readers could, I am sure, benefit from seeing what other readers are looking for. I want to include ideas like 'people who read this post also read this and this'.

All use ideas are kind of trashed by the pernicious advent of log spam. This is where marketing companies make scripts that connect to blogs to fill the blog log with referrals to sites (normally porn) that they are marketing.

So, along with the system for creating and managing logs, I am considering making a spam filter. Maybe it could work by people who are spammed sending in spam referral URL's for submission to a central database, a bit like email spam filtering does now?

Anyone with suggestions, for features to a spam filtering blog log please comment

So if anyone has any thoughts on this, please let me know! Just comment to on this post for now. Should I go for an open source solution? Would anyone be interested in helping code it. Sourceforge maybe. Is it a good idea. Should it include a Google Maps mashup. I'd love to know what you think

Thanks - AJ

OK - You Win: vbscript read binary file!

Several times a day I get someone querying Nerds-Central asking about accessing binary files with VBScript.

So here is a brief look at the subject. It is important to realise though, that just like accessing binary files with JScript the method described here stores the entire file in memory. So, if you expect this to be a good way of moving around DVD iso images, I am sorry to tell you....

The key to accessing binary files from Windows scripts is to use the ADODB library which is supplied by Microsoft for free on all Windows distributions. You will require ADODB 2.5 or later. If you want to know if you have this versio then the following script will tell you:

dim adodb
set adodb=WScript.CreateObject("ADODB.Connection")
dim v
WScript.echo "Your ADODB Version=" & adodb.version

Any thing at 2.5 or later should work fine.

Accessing The Binary Files Is Done Using ADODB.Stream Objects

' This is a simple example of managing binary files in
' vbscript using the ADODB object
dim inStream,outStream
const adTypeText=2
const adTypeBinary=1

' We can create the scream object directly, it does not
' need to be built from a record set or anything like that
set inStream=WScript.CreateObject("ADODB.Stream")

' We call open on the stream with no arguments.  This makes
' the stream become an empty container which can be then
' used for what operations we want
inStream.Open
inStream.type=adTypeBinary

' Now we load a really BIG file.  This should show if the object
' reads the whole file at once or just attaches to the file on
' disk
' You must change this path to something on your computer!inStream.LoadFromFile("d:\Documents and Settings\aturner\Desktop\ScriptingDel\TestData\test.jpg")

' Copy the dat over to a stream for outputting
set outStream=WScript.CreateObject("ADODB.Stream")
outStream.Open
outStream.type=adTypeBinary

dim buff
buff=inStream.Read()

' Write out to a file
outStream.Write(buff)
' You must change this path to something on your computer!
outStream.SaveToFile("d:\Documents and Settings\aturner\Desktop\ScriptingDel\TestData\test_out.jpg")

outStream.Close()
inStream.Close()

In the above example the Stream object is used to load the contents of the file into memory and store it as a byte array. The byte array is then passed to another stream which is written out to disk. Not very memory efficient, but if you want really efficient binary file handling, then you should probably be programming in C!

Another way of achieving the same thing is to use the CopyTo method. This does not allow you access to any of the data, but is more efficient if you are wanting to just copy a file. But then, if all you want to do is copy a file, why not use the Scripting.FileSystemObject CopyFile method?

One problem you might face using this approach in VBScript is that you cannot create a byte array. The following script fails:

dim outStream
const adTypeText=2
const adTypeBinary=1
dim data

' Copy the dat over to a stream for outputting
set outStream=WScript.CreateObject("ADODB.Stream")
outStream.Open
outStream.type=adTypeBinary

dim buff()
redim buff(255)
dim i
for i=0 to 255
  buff(i)=i
next

' Write out to a file - but the script fails here
outStream.Write(buff)
outStream.SaveToFile("d:\Documents and Settings\aturner\Desktop\ScriptingDel\TestData\buff_out.bin")

outStream.Close()
inStream.Close()

The script fails because the array created is a Variant array, not a byte array. To write out arbitary data to a file might require some work around like writing out a text file of the appropreate length, reading it in as a binary file and then changing the byte array you get. This is not very nice. It is posible to use text files and manipulate the data as string using the 437 code page. This is how I did it in the JScript example linked above

OK, here we see that we can read binary files as long as they are not too large. We can read the data in them by getting a byte array from the LoadFromFile method of the Stream object, and we can write to them. The methods are a bit limited, but with imagination we can overcome most problems. Good luck!

ADODB.Stream Summary

Properties

Property Description
CharSet Sets or returns a value that specifies into which character set the contents are to be translated. This property is only used with text Stream objects (type is adTypeText)
EOS Returns whether the current position is at the end of the stream or not
LineSeparator Sets or returns the line separator character used in a text Stream object
Mode Sets or returns the available permissions for modifying data
Position Sets or returns the current position (in bytes) from the beginning of a Stream object
Size Returns the size of an open Stream object
State Returns a value describing if the Stream object is open or closed
Type Sets or returns the type of data in a Stream object

Methods

Method Description
Cancel Cancels an execution of an Open call on a Stream object
Close Closes a Stream object
CopyTo Copies a specified number of characters/bytes from one Stream object into another Stream object - args Stream,[count]
Flush Sends the contents of the Stream buffer to the associated underlying object
LoadFromFile Loads the contents of a file into a Stream object
Open Opens a Stream object
Read Reads the entire stream or a specified number of bytes from a binary Stream object
ReadText Reads the entire stream, a line, or a specified number of characters from a text Stream object
SaveToFile Saves the binary contents of a Stream object to a file
SetEOS Sets the current position to be the end of the stream (EOS)
SkipLine Skips a line when reading a text Stream
Write Writes binary data to a binary Stream object
WriteText Writes character data to a text Stream object

Notes

If the mode of a stream is adTypeText you will get an error if you try to use Read or Write. Equally, if the type is adTypeBinary the ReadText and WriteText will fail. Note also that if you have the streams set to Text and use CopyTo to copy a file, the file will be converted to text even if it did not start out this way. The end result is normally a corrupt file. I would strongly suggest always using adTypeBinary and using the FileSystemObject for all text file manipulations.

Using JScript To Paste Text Into Notepad

In the previous post I showed how to paste the contents of the clipboard into Notepad using VBScript. Here is the same thing in JScript.

// Open notepad 
var WshShell = WScript.CreateObject("WScript.Shell");
WshShell.Run("notepad", 9);

// Give Notepad time to load
WScript.Sleep(500); 

' Do The Paste
WshShell.SendKeys("%e");
WshShell.SendKeys("p");

Notes:

See here for a list of the possible send key escape sequences.

Using VBScript To Paste Text Into Notepad

There is no COM interface to Notepad, so we are going to have to use the old SendKeys method. So here is a quick crib on how that works:

Most keys can be represented by the character of the key itself. E.g, the key sequence FRED can be represented simply by "FRED". Some special keys, such as the control keys, function keys etc are encoded in a string enclosed by {braces}

Key Code
{ {{}
} {}}
[ {[}
] {]}
~ {~}
+ {+}
^ {^}
% {%}
BACKSPACE {BACKSPACE}, {BS}, or {BKSP}
BREAK {BREAK}
CLEAR {CLEAR}
CAPS LOCK {CAPSLOCK}
DEL or DELETE {DELETE} or {DEL}
DOWN ARROW {DOWN}
END {END}
ENTER {ENTER} or ~
ESC {ESC}
HELP {HELP}
HOME {HOME}
INS or INSERT {INSERT} or {INS}
LEFT ARROW {LEFT}
NUM LOCK {NUMLOCK}
PAGE DOWN {PGDN}
PAGE UP {PGUP}
PRINT SCREEN {PRTSC}
RIGHT ARROW {RIGHT}
SCROLL LOCK {SCROLLLOCK}
TAB {TAB}
UP ARROW {UP}
F1 {F1}
F2 {F2}
F3 {F3}
F4 {F4}
F5 {F5}
F6 {F6}
F7 {F7}
F8 {F8}
F9 {F9}
F10 {F10}
F11 {F11}
F12 {F12}
F13 {F13}
F14 {F14}
F15 {F15}
F16 {F16}

To specify keys combined with any combination of SHIFT, CTRL, and ALT keys, precede the key code with one or more of the following:

For SHIFT prefix with +
For CTRL  prefix with ^
For ALT   prefix with %

As promised, here is the way to paste into NotePad, the trick being to open the edit menu then call paste using keyboard short cuts.

' Open notepad 
Set WshShell = WScript.CreateObject("WScript.Shell")
WshShell.Run "notepad", 9

' Give Notepad time to load
WScript.Sleep 500 

' Do The Paste
WshShell.SendKeys "%e"
WshShell.SendKeys "p"

How To Excel VBScript Autofit

Wow - I must get 2 or three searches a day for this. So here is how to do it.

dim excel,wb,ws
set excel=WScriptCreateObject("Excel.Application");
'... get a workbook by add or open
'... get a worksheet from the workbook using the Sheets() collection
'... set ws to that worksheet

' To fit the first 10 columns
for column=1 to 10
   ws.columns(column).AutoFit()
next

' To fit the third row
ws.rows(3).AutoFit()

I know, I could not believe how simple it is either!

Installing Firefox 2 On Ubuntu Linux

Well - not known for my patience, I finally got bored of waiting for FireFox 2 turn up on the Application list for Ubuntu (Dapper Drake). So I went looking for how to install it by hand. If found a post from last year here:

http://everythingelse.wordpress.com/2006/07/15/howto-install-firefox-20-bon-echo-in-ubuntu/

That totally does the job!

The method from the site is to have a simple script that does all the work for you. So, to try and make things even easier I have include a step by step explanation.

  1. First you need to open up gedit by going to 'Applications' on your command bar and then picking 'Accessories' then 'Text Editor'.
  2. Copy the following script (at the end of this post) by selecting it in your browser and then going 'Edit/Copy'.
  3. Go back to your gedit window and go 'Edit/Paste'. You should get something like this:
  4. Now make sure you close every single web browser window!
  5. Now save your new script to the Desktop by (in gedit) going 'File/Save' and saving as installFirefox2.sh in your home directory. To save you should get a dialogue like this (my account name is ajt so my home folder is called ajt:
  6. Getting the end now - patience! On your Ubuntu command bar, go 'Applications/Accessories/Terminal'. One of those scary windows in which you can type commands will open up. But no fear, we don't have to do much, just type:
    cd ~
    . ./installFireFox2.sh
    
    And it should look something like this: Now the script will pop up a dialogue box asking you to put in your password, which you do (your normal login one) and it does the rest!

One Good Reason I Should

I am a dyslexic git! The in-line spell check system in FireFox2 is a God send. There are loads of other nice features as well, but that is enough to sell it to me:-)

OK Here is the script!

#!/bin/bash
echo "Updating repositories list
"
sudo apt-get update
echo "
Making sure libstdc++5 and the old Firefox are installed
"
sudo apt-get -y install firefox libstdc++5
echo "
Backing up old Firefox preferences
"
cp -R ~/.mozilla ~/.mozilla_backup
echo "
Changing to home directory
"
cd
echo "
Downloading Firefox from the Mozilla site
"
wget -c http://releases.mozilla.org/pub/mozilla.org/firefox/releases/2.0/linux-i686/en-GB/firefox-2.0.tar.gz
echo "
Unzipping the .tar.gz file
"
sudo tar -C /opt -x -z -v -f firefox-2.0.tar.gz
echo "
Removing the unzipped .tar.gz
"
rm firefox-2.0.tar.gz
echo "
Linking plugins
"
cd /opt/firefox/plugins/
sudo ln -s /usr/lib/mozilla-firefox/plugins/* .
echo "
Linking launcher to new Firefox
"
sudo dpkg-divert --divert /usr/bin/firefox.ubuntu --rename /usr/bin/firefox
sudo ln -s /opt/firefox/firefox /usr/bin/firefox
sudo dpkg-divert --divert /usr/bin/mozilla-firefox.ubuntu --rename /usr/bin/mozilla-firefox
sudo ln -s /opt/firefox/firefox /usr/bin/mozilla-firefox
echo "
The new Firefox is installed."

Setting Excel Borders From VBScript Or JScript

In response to the following Google search: "script excel .borders createobject" here is how to do it.

Borders are controlled via the Borders property of the Excel Range object, where a range is a bunch of cells. Before we start looking at the borders we need to define a few nice Excel constants:

You can define these as contants or an scripting dictionary or what ever takes your fancy.

These define the style of the border
xlNone = -4142; Note this is the same as xlLineStyleNone
xlContinuous = 1;
xlDash = -4115;
xlDashDot = 4;
xlDashDotDot = 5;
xlDot = -4118;
xlDouble = -4119;
xlSlantDashDot = 13;

These define the weight of the border
xlHairLine = 1;
xlMedium = -4138;
xlThick = 4;
xlThin = 2;

Thise is handy to make borders have the default color index
xlAutomatic = -4105;

These define the placement of border pieces
xlDiagonalDown = 5;
xlDiagonalUp = 6;
xlEdgeLeft = 7;
xlEdgeTop = 8;
xlEdgeBottom = 9;
xlEdgeRight = 10;
xlInsideVertical = 11;
xlInsideHorizontal = 12;

So, if I wanted to put a slant dash dot heavy border on the right hand side of the first cell of the first sheet I could do something like this:

' VBScript
dim excel
set excel=WScript.CreateObject("Excel.Application")
dim wb
set wb=excel.WorkBooks.Add()
dim ws
set ws=wb.Sheets("Sheet1")
dim range
set range=ws.Cells(1,1)
with range.Borders(xlEdgeRight)
 .LineStyle=xlSlantDashDot
 .Weight=xlThick
 .ColorIndex=xlAutomatic
end with

// JScript
var excel=WScript.CreateObject("Excel.Application");
var wb=excel.WorkBooks.Add();
var ws=wb.Sheets("Sheet1");
var range=ws.Cells(1,1);
with(range.Borders(xlEdgeRight))
{
 LineStyle=xlSlantDashDot;
 Weight=xlThick;
 ColorIndex=xlAutomatic;
}

I guess the above is also a nice little demo of how VBScript can be converted to JScript and visa versa! Anyhow - that should get you started at playing with Excel borders.

Nerd Search Has Arrived

Nerds Central is now trying out Google's new 'Site Flavoured Search'. This new technique allows the Google search engine to learn what sort of search results people searching from a site are looking for. The upshot of which is that as most people searching from Nerds-Central are looking for techi script/software stuff, using this search is likely to be a good bet. Let's see if Google have come up with a real innovation, or if it is just hype. So far I have been impressed with the results.

New AI Search For Nerds-Central

I have added a new search tool to Nerds-Central. It uses a new technique by Google to learn to search for stuff of particular interest to Nerds-Central readers. So if you spend lots of time looking for techi stuff on Google, you might well find this service is a big benefit!

Mixing VBScript And JScript, The Magic Of WSFs

How To Do It And Why

If you hunt around the Internet you will probably find lots of examples of how to write wsf files. Here is my experiance of how to write them easily to allow the mixing of VBScript with JScript. You see, no matter how much one might say that VBScript is better than JScript or JScript is better than VBScript, there are times when mixing them is better than being forced to work in just one.

WSF files are XML files that have the file extention wsf. If you no nothing about XML at all, no problem, you can just use the template I provide here.

<?xml version="1.0" encoding="ISO-8859-1"?>
<job id="Run">
<script language="VBScript">
<![CDATA[
    Function WSHInputBox(Message, Title, Value)
        ' Provides an InputBox function for JScript
        ' Can be called from JScript as:
        ' var result = WSHInputBox("Prompt", "Title", "Initial Value");
        WSHInputBox = InputBox(Message, Title, Value)
    End Function
]]>
</script>
<script language="JScript">
<![CDATA[
    var result = WSHInputBox( "Please Enter Something Or Hit Cancel","Script Input Box", "My Name");
    if(typeof(result)=='undefined')
    {
        WScript.echo('You Canceled');
    }
    else
    {
        WScript.echo('You put: ' + result);
    }
]]>
</script>
</job>

Tricks And Gottachs

  1. <![CDATA[ ... ]]>. Putting the actual code inside these funny enclosures means that unless for some odd reason you have the symbol ]]> in your code, you don't need to worry about escaping any of it to make it XML complient.
  2. You can pass simple variables back and forth. By simple I mean, String, Boolean and Number. If you try and pass JScript objects to VBScript, or the reverse then it will almost certainly fail (there could be exceptions, but don't risk it) (Update: See bottom of page, I have found ways...). By using the VBArray object, JScript can read VBScript arrays, but not write them. VBScript cannot read or write JScript arrays.
  3. You can pass COM objects back and forth. This is dead handy for passing complex data as well as actual objects. For example:
    <?xml version="1.0" encoding="ISO-8859-1"?>
    <job id="Run">
    <script language="VBScript">
    <![CDATA[
        Function EchoDictItems(dict)
            For Each i in dict.items
                WScript.echo("" & i)
        End Function
    ]]>
    </script>
    <script language="JScript">
    <![CDATA[
        var objDict=WScript.CreateObject('Scripting.Dictionary');
        for(var i=0;i<10;++i)
        {
            objDict.Add('_' + i,i);
        }
        EchoDictItems(objDict);
    </script>
    </job>
    
    Also, you can pass COM references to applications like Excel.Application.
  4. WSF files can contain many 'Jobs'. In my examples, I have only shown one. The first job in a WSF file is the one run if the file is double clicked on or run from csript without the -job argument. 90% of the time, I only have one job in a wsf file.

Since I wrote this post I have learned loads more about making these languages co-operate, check out the post summary (see top of page) for later posts.

10 JScript (JavaScript) Tricks For VBScript Programmers

If you are used to working in a VBScript environment, the following ten tricks may well help you convert over to working in JScript. Frankly, some are just plain usedful anyway, like a easy way to make sure a variable is a numer.

/* 1) Thou Shalt Be A String */
var myVar = 1;
myVar+='';
WScript.echo(typeof(myVar));

/* 2) Thou Shalt Be A Number */
var myVar = '1';
myVar*=1.0;
WScript.echo(typeof(myVar));

/* 3) Thou Shalt Be An Integer */
var myVar = 1.2;
myVar>>=0;
WScript.echo(typeof(myVar) + '=' + myVar);

/* 4) Doest Thou Exist */
var myVar=new Array();
myVar['a']='some value';
myVar['b']='another value';
// true if exists, false otherwise (true <=> -1, false <=> 0)
WScript.echo(typeof(myVar['a'])!='undefined');
WScript.echo(typeof(myVar['c'])!='undefined');

/* 5) Woops - something went wrong */
try
{
    thisFunctionsDoesNotExist();
}
catch(e)
{
    // Enumerate all the information on the error object
    var es='';
    for(m in e)
    {
        es+=m+': '+e[m]+'\r\n';
    }
    WScript.echo(es);
}

/* 6) Make a COM (VB style) array */
var myVar=WScript.CreateObject('Scripting.Dictionary');
for(var i=0;i<10;++i)
{
    myVar.Add(''+i,i);  // string pseudo keys
}
var myVBArray=myVar.items();

/* 7) Convert a COM (VB Style) array to a JS Array */
var myJSArray=(new VBArray(myVBArray)).toArray();
WScript.echo(myJSArray[7]);

/* 8) Trim the white space from a string */
var myVar='  somestuff  ';
myVar=myVar.replace(/^\s+/,'').replace(/\s+$/,'');
WScript.echo('"'+myVar+'"');

/* 9) Enumerate Collections */
function ShowDriveList(){
   var fso, s, n, e, x;                     //Declare variables.
   fso = new ActiveXObject("Scripting.FileSystemObject");
   e = new Enumerator(fso.Drives);          //Create Enumerator on Drives.
   s = "";
   for (;!e.atEnd();e.moveNext())           //Enumerate drives collection.
   {
         x = e.item();
         s = s + x.DriveLetter;
         s += " - ";
         if (x.DriveType == 3)              //See if network drive.
            n = x.ShareName;                //Get share name
         else if (x.IsReady)                //See if drive is ready.
            n = x.VolumeName;               //Get volume name.
         else
            n = "[Drive not ready]";
         s +=  n + "\r\n";
   }
   return(s);                               //Return active drive list.
}
WScript.echo(ShowDriveList());

/* 10) Pass variable number of arguments to a function*/
function ArgTest(a, b){

   var i, s = "The ArgTest function expected ";
   var numargs = arguments.length;     //Get number of arguments passed.
   var expargs = ArgTest.length;       //Get number of arguments expected.
   s += expargs + " arguments. ";
   if (numargs < 2)
      s += numargs + " was passed.";
   else
      s += numargs + " were passed.";
   s += "\r\n\r\n"
   for (i =0 ; i < numargs; i++){      //Get argument contents.
       s += "  Arg " + i + " = " + arguments[i] + "\r\n";
   }
   return(s);                          //Return list of arguments.
}
WScript.echo(ArgTest('I','II','III','IV'));

Let's Learn JScript (Where to start for VBScripters)

Passing Through The JScript Black Hole

It has been postulated that if one was able to pass through a black hole one would end up in a different universe. Here I propose those of us who script in VBScript try to pass through just such a black hole and end up in a JScript Universe. Maybe you will pop back and fourth and gain the advantages or both. Whether you go for a full conversion or choose to alternate between the two, it will be a major benefit to you to have a look around the JScript side of the singularity!

First Off - Why?

VBScript is a nice little language. It was designed to work well with COM and it does. The snag is that it is getting very old now and lacks a lot of features that are really good to have. For example, VBScript lack bit wise shift operators. You might ask why one would want them? Well, in the increasingly complex work of interoperability, they are needed to convert data from one format (say HEX) to another (say Base64). Further, VBScript only works in three places, the Windows Scripting Host, Windows Scripting Control or in Internet Explorer. So, code developed in VBScript is not very useful in across the board.

JavaScript is a universal language now. Because it has become the language of choice for Web pages, and because Microsoft got a version of it standardized as ECMA script, you can get JavaScript to work on just about any computer anywhere. There is a Java version of it, and so JavaScript will even work on a Java-Phone! Further, it is a good rich language with a lot of operators and 'make my life easy' syntactic short cuts. For example, if you want to ensure a variable contains a string you just put myVar+='';! Finally, there is so much JavaScript example code in on the Internet, you need never get stuck

So How

Given these considerations, I for one have switched most of my Windows scripting from VBScript to JScript (JScript being Microsoft's version of JavaScript). This was not a 100% painless experience, but well worth it! To make the same transition your self, I would highly recommend looking at the Microsoft JScript resource below:

JScript Resource

Should MS choose to move this resource and the link break, please let me know! After having a poke around, how about trying to do some of the stuff you currently do in VBScript using JScript. I have found a couple of things that are harder to do in JScript and in VBScript, so in the next post, I shall show you how to work around these to make life even easier!

Excel: Copy And Paste Cells/Rows/Columns Using JScript/VBScript

In response to the Googel query:

www.google.com/search?hl=en&lr=&q=vbscript%2C copy excel row.

Here is how to do it in JScript and then translated to VBScript

// We start with two workbooks wbA and wbB and we
// are copying from Sheet1 in wbA to Sheet2 in wbB
// Both workbooks are in an instance of Excel we will
// call Excel.

// Example 1, the whole contents of the work sheet
var wsA=wbA.WorkSheets.item('Sheet1');
var wsB=wbB.WorkSheets.item('Sheet2');
// This is critical, you cannot copy from a non active sheet
wsA.Activate();
wsA.Cells.Select();
excel.Selection.Copy();
wsB.Activate();
wsTo.Paste();

// Example 2, just the second row, to the third row
wsA.Activate();
wsA.Rows.item(2).Select();
excel.Selection.Copy();
wsB.Activate();
wsTo.Rows(3).Select();
wsB.Paste();

// Example 3, just the second column, to the third column
wsA.Activate();
wsA.Columns.item(2).Select();
excel.Selection.Copy();
wsB.Activate();
wsTo.Columns(3).Select();
wsB.Paste();

// Example 4, just the cell A1, to cell B2
wsA.Activate();
wsA.Range('A1').Select();
excel.Selection.Copy();
wsB.Activate();
wsTo.Range('B2').Select();
wsB.Paste();

The same is dead easy in VBScript as well

' Example 1, the whole contents of the work sheet
dim wsA,wsB
set wsA=wbA.WorkSheets.item("Sheet1")
set wsB=wbB.WorkSheets.item("Sheet2")
' This is critical, you cannot copy from a non active sheet
wsA.Activate
wsA.Cells.Select
excel.Selection.Copy
wsB.Activate
wsTo.Paste

' Example 2, just the second row, to the third row
wsA.Activate
wsA.Rows.item(2).Select
excel.Selection.Copy
wsB.Activate
wsTo.Rows(3).Select
wsB.Paste

' Example 3, just the second column, to the third column
wsA.Activate
wsA.Columns.item(2).Select
excel.Selection.Copy
wsB.Activate
wsTo.Columns(3).Select
wsB.Paste

' Example 4, just the cell A1, to cell B2
wsA.Activate
wsA.Range('A1').Select
excel.Selection.Copy
wsB.Activate
wsTo.Range('B2').Select
wsB.Paste

In general, it is much faster to move data between worksheets using Copy and Paste than by taking the values into the script and back out to Excel.

JMeter Moderated

I have just started a new experimental blog I call JMeter Moderated. The idea is that I can pick posts to the JMeter email list which have just the solutions to questions to the blog and then the JMeter community can benefit from being about to search for those solutions.

VBScript Arrays Push

In response to the following query which hit Nerds-Central this morning: www.google.co.uk/search?q=vbscript arrays push

Sorry - you cannot push onto VBScript arrays. This is a very good reason for this. VBScript is designed to work well with COM. COM passes arrays around as a datastructure called a SafeArray. As a scripter you will never actually see these SafeArray structures. I see them quite a bit when writing C++ COM controls. The problem being that a SafeArray has a fixed size, it cannot be increased in size once created for lots of horrible nast reasons todo with low level memory management which I am sure you really don't want to know (ask if you do!).

The upshot of all this is that to gain an array structure that can be passed directly to COM, VBScript has sacreifised having flexible arrays. If you want to script on Windows with flexible arrays, then you should use JScript (I do). Alternatives to exist; you can use a Scripting.Dictionary. There is a performance hit for using an COM object, but for most situations, this approach is fast enough. Also, the Dictionary object does not support a true Push/Pop/Shift/UnShift like interface so....

Sometimes I get told that a script has to be able to run in an environment where Scripting.Dictionary might not exist (PE springs to mind) or as above where someone just does not want to use it for a particular taks. To help I have included below a simple pure VBScript linked list class what can also act as a flexible array.


' Copy Right: Dr Alexander J Turner, all rights reserved.
' You can use this code in any way you want as long as
' you mention I wrote it. Thanks
'
' A Simple linked list class for manipulating and accumulating
' data in VBScript
'
' e.g.
' list=new cLinkedList
' for i=1 to 100
'     list.Push i
' next i
'
' while list.size>0
'     WScript.echo list.Unshift()
' wend
'

class cListLink

    public before
    public datum
    public after

end class

class cLinkedList

    private start_
    private end_
    private current_
    private size_
    private index_
    
    Private Sub Class_Initialize()
        start_=null
        end_=null
        current_=null
        size_=0
        index_=0
    end sub
    
    private sub setDatum(ll,datum)
        if isObject(datum) then
            set ll.datum=datum
        else
            ll.datum=datum
        end if
    end sub
    
    private sub getDatum(ll,datum)
        if isObject(ll.datum) then
            set datum=ll.datum
        else
            datum=ll.datum
        end if
    end sub
    
    ' winds index to start quickly
    public sub Rewind()
        index_=1
        set current_=start_
    end sub
    
    ' Winds index to end quickly
    public sub WindToEnd()
        index_=me.size()
        set current_=end_
    end sub
    
    ' Returns current inde
    public function Index()
        index = index_
    end function
    
    ' Returns the current datum (which may be null) or
    ' null if the list is empty.
    ' Warning - this is ambiguous, you should check the
    ' size is bigger than 0 before getting datum if there
    ' is any chance that the list will be empty.
    public function Datum
        if isnull(current_) then
            Datum=null
        else
            Datum=current_.datum
        end if
    end function
    
    ' Moves index on one if it can
    public sub MoveNext()
        if index_<size_ then
            set current_=current_.after
            index_=index_+1
        end if
    end sub
    
    ' Moves index back one if it can
    public sub MovePrevious()
        if index_ > 1 then
            set current_=current_.before
            index_=index_-1
        end if
    end sub
    
    ' true if index is at the end of the list
    ' false otherwise
    public function EOF()
        if size_=index_ then
            EOF=true
        else
            EOF=false
        end if
    end function
    
    ' Retrieves a datum by index without changing
    ' the lists index.  This is slow as it must itterate
    ' through the list, please try and use enumeration
    ' via
    '   Rewind
    '   MoveNext
    ' or
    '   WindToEnd
    '   MovePrevious
    '
    ' if you are traversing all or a large part of the
    ' list
    public function Item(byval indx)
        if index>size_ then
            Item = null
        else
            set c=start_
            for i=2 to indx
                set c=c.after
            next
            'Item=c.datum
            getDatum c,Item
        end if
    end function
    
    public function SetItem(indx,datum)
        if index>size_ then
            Item = null
        else
            set c=start_
            for i=2 to indx
                set c=c.after
            next
            'c.datum=datum
            setDatum c,datum
        end if
    end function
    
    ' Add an item to the end of the list
    public sub Push(datum)
        if isnull(end_) then
            set l=new cListLink
            set start_=l
            set end_=l
            set current_=l
            'l.datum=datum
            setDatum l,datum
            index_=1
            size_=1
        else
            set l=new cListLink
            set end_.after=l
            set l.before=end_
            set end_=l
            'end_.datum=datum
            setDatum end_,datum
            size_=size_+1
        end if
    end sub
    
    ' Add an item at the start of the list
    public sub Shift(datum)
        if isnull(end_) then
            set l=new cListLink
            set start_=l
            set end_=l
            set current_=l
            'l.datum=datum
            setDatum l,datum
            index_=1
            size_=1
        else
            set l=new cListLink
            'l.datum = datum
            setDatum l,datum
            set start_.before=l
            set l.after=start_
            set start_=l
            size_=size_+1
            index_=index_+1
        end if
    end sub
    
    ' Remove an item from the top of the list and return its value
    public function Pop()
        if size_=0 then
            Pop=null
        else
            'Pop=end_.datum
            getDatum end_,Pop
            if size_=1 then
                start_=null
                end_=null
                current_=null
                size_=0
            else
                set end_=end_.before
                end_.after=null
                if index_=size_ then
                    set currnt_=end_
                    index_=index_-1
                end if
                size_=size_-1
            end if
        end if
    end function
    
    ' Remove an item from the end of the list and return its value
    public function Unshift()
        if size_=0 then
            Unshift=null
        else
            'Unshift=start_.datum
            getDatum start_,Unshift
            if size_=1 then
                start_=null
                end_=null
                current_=null
                size_=0
            else
                set start_=start_.after
                start_.before=null
                size_=size_-1
                if index=1 then set current_=start_
            end if
        end if
    end function
    
    ' How big is the list
    public function Size()
        Size=size_
    end function
    
    ' Move the index
    public sub SetIndex(index)
        if index<=size_ then
            me.Rewind
            for i=2 to index
               me.MoveNext
            next
        end if
    end sub
    
    ' Add a list to the end of this list
    public sub AppendList(list)
        if list.size=0 then exit sub
        tmpIndex=list.Index()
        list.Rewind
        while list.Index() <= list.Size()
            me.Push list.Datum()
            list.MoveNext
        wend
        list.SetIndex tmpIndex
    end sub
    
    ' Add a list to the start of this list
    public sub PrependList(list)
        if list.size=0 then exit sub
        tmpIndex=list.Index()
        list.WindToEnd
        while list.Index() >0
            me.Shift list.Datum()
            list.MovePrevious
        wend
        list.SetIndex tmpIndex
    end sub
    
    ' Add all array elements to the end of the list
    public sub AppendArray(arr)
        for each v in arr
            me.Push v
        next
    end sub
    
    ' Add all array elements to the start of the list
    public sub PrependArray(arr)
        for i=ubound(arr) to lbound(arr) step -1
            me.Shift arr(i)
        next
    end sub
    
    ' return an array made of the elements of the list
    public function ToArray()
        dim ret()
        if size_=0 then
            ToArray=null
            exit function
        end if
        if size_=1 then
            redim ret(0)
            'ret(0)=start_.datum
            getDatum start_,ret(0)
            ToArray=ret
            exit function
        end if
        redim ret(size_-1)
        tmpIndex=me.Index
        me.Rewind
        i=0
        do
            'ret(i)=me.Datum
            getDatum current_,ret(i)
            me.MoveNext
            i=i+1
        loop until me.EOF
        ret(i)=me.Datum
        me.SetIndex tmpIndex
        ToArray=ret
    end function

end class

sub RunTests()

    set list=new cLinkedList
    for i=1 to 10
        list.Push i
    next 
    Wscript.echo "Test 1"
    while list.size>0
        WScript.echo ": " & list.Unshift()
    wend
    
    dim ar(9)
    for i=1 to 10
       ar(i-1)=i
    next
     
    list.AppendArray(ar)
    Wscript.echo "Test 2"
    while list.size>0
        WScript.echo ": " & list.Unshift()
    wend
    
    for i=1 to 10
        list.Push i
    next 
    Wscript.echo "Test 3"
    for i=1 to 10
        WScript.echo ": " & list.Item(i)
    next

    arv=list.ToArray()
    Wscript.echo "Test 4"
    for i=1 to 10
        WScript.echo ": " & arv(i-1)
    next
    
    set list=new cLinkedList
    list.Push "Array Before This"
    list.PrependArray arv
    Wscript.echo "Test 5"
    
    while list.size>0
        WScript.echo ": " & list.Unshift()
    wend

    list.Push "Array After This"
    list.AppendArray arv
    Wscript.echo "Test 6"
    
    while list.size>0
        WScript.echo ": " & list.Unshift()
    wend

    
end sub

runTests

Using A GUID In A Script - Exploding GUIDs

This is a sad story of pain which continues until at the eleventh hour a solution is found:

I wanted to find a nice reliable way to create temporary files from a script running on a Windows box. As normal, I was working in JScript. The best solution I came up with was to use a GUID. A GUID is a globally unique identifier. Microsoft software creates them with some nice algorithm that ensures that the probability of any two identical ones ever being created anywhere in the world is so vanishingly small as to be ignorable. So, there is a nice plan, use a GUID as a file name.

Actually getting a GUID is dead simple in the scripting environment. This is because you can get them from the Scriplet COM object:

 var tl=WScript.CreateObject('Scriptlet.TypeLib');
 var guid=new String(tl.Guid); // The new String is probably not required, but plays it safe

But there is a silent problem. The guid string produced has a \0 at the end. This makes some of the string handling functions in JScript misbehave. They do this silently, and you cannot see the problem when you echo the guid to the console :-)

So here is my temp file name getter, with the solution (chop off the last character). In this version I usa regular expression or two to make the GUID more filename like. I do not add in the file extension (e.g. .xls) here, I have left that up to the calling code.

function GetTempFile()
{
 var fso=WScript.CreateObject('Scripting.FileSystemObject');
 var tempf=fso.GetSpecialFolder(2);
 var tl=WScript.CreateObject('Scriptlet.TypeLib');
 var fn=new String(tl.Guid);
 fn=fn.replace(/[{}]/g,'_');
 return tempf+'\\'+fn.substr(0,fn.length-2);
}

So, the GUID tried to explode my script - and managed for a time - but there was (as there always seems to be) a solution.

See Who Is Visiting Your Site - How Cool Is That!

Statcounter is just so cool.

By getting an account with them I can see who is visiting Nerds-Central and what visiters are looking for. This makes it possible for me to make the site better for you!

What I really like is that for start up sites (like mine) it is free. Once you get a lot of traffic, you need to upgrade, but that actually seems fair to me.

Right - that is enough being nice for one day!

Base64 Post Issues - Please Re-read

Somewhere in the process of putting the code for the Base64 codec into its post, all the pipe | symbols vanished :-(

I have now put them all back in and upgraded the decoder to work faster with big files.

JavaScript Hash Tables - The Why And The How

One of the marvellous things about JavaScript (JScript on Windows) is its handling of arrays. There is just no comparison between the power and flexibility of JavaScript arrays and those in VBScript. Since I learned data handling in Perl (far too long ago) I have been very aware of how central powerful array handling is to script based data processing.

JavaScript array tick all the boxes apart from one critical one which I seem to end up needing a lot of the time: Enumeration of keys and/or values. These functionallities are required to perform data aggregation tasks similar to the GROUP BY and HAVING clauses of SQL.

A JavaScript array can be keyed numerically:

var a=new Array();
a[0]='a';
a[1]='b';
a[2]='c';
...

When it is does this way, you can enumerate the array by counting through it:

for(var i=0;i<a.length;++i)
{
  WScript.echo(''+a[i]));
}

JavaScript arrays can also be keyed by string values:

var a=new Array();
a['first']='a';
a['second']='b';
a['third']='c';
...

How can we enumerate the keys and values of this array? Well, I have not found a way yet (if you know one, please let me). So, here is a simple implementation of a HashTable. It uses the native string keying of a JavaScript array to perfom value lookup, and so is veey fast for lookup. However, it add the features of key and value enumeration

function Hash()
{
    var _hash=new Array();
    var _keys=new Array();
    var _count=0;
    
    this.keys=function()
    {
        var ret=new Array();
        var l=_count;
        for(var i=0;i<l;++i)
        {
            ret.push(_keys[i]);
        }
        return ret;
    }
    
    this.values=function()
    {
        var ret=new Array();
        var l=_count;
        for(var i=0;i<l;++i)
        {
            var k=_keys[i];
            ret.push(_hash[_keys[i]]);
        }
        return ret;
    }

    this.add=function(key,value)
    {
        if(typeof(_hash[key])==='undefined')
        {
            _keys.push(key);
            _count++;
        }
        _hash[key]=value;        
    }
    
    this.getItem=function(key)
    {
        return _hash[key];
    }

    this.setItem=function(key,value)
    {
        _hash[key]=value;
    }
    
    this.exists=function(key)
    {
        return typeof(_hash[key])!='undefined';
    }
    
    this.removeAll=function()
    {
        _hash=new Array();
        _keys=new Array();
        _count=0;
    }

    this.count=function()
    {
        return _count;
    }
    
    // very slow for now
    this.remove=function(key)
    {
        var newHash=new Array();
        var newKeys=new Array();
        var l=_keys.length;
        _count=0;
        for(var i=0;i<l;++i)
        {
            var ik=keys[i];
            if(ik!=key)
            {
                newKeys.push(ik);
                newHash[ik]=_hash[ik];
                _count++;
            }
        }
        _hash=newHash;
        _keys=newKeys;
    }
}

In case you were wandering "But why would one want such a thing?". Well, consider a data processing challenge where you have a list of people by name. You want to find out how often each name appears in the list. You do it like this:

Loop over the list
  Does the hash contain the name?
    Yes: Get the value keyed by name and increment it then set the value
    No: Add the name/value pair with the value of 1 to the hash.
End Loop
Loop over all keys in the hash
  print key+'='+value;
End Loop.

Good Luck

Fast Scalable JavaScript And VBScript Base64 Encoder And Decoder

This is a JavaScript Base64 encoder and decoder object. I have found several out in the wild, but non of them scaled properly. This will scale nicely in both time and space. It is linear is space and nLog(n) in time.

/** CopyRight: Dr Alexander J Turner - all rights reserved.
  * Please feel free to use this any way you want as long as you
  * mention I wrote it!
  */
function Base64() {
    this.maxLineLength = 76;

    this.base64chars =  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
   
    this.decode = function(encStr) {
        var base64charToInt = {};
        for (var i = 0; i < 64; i++) base64charToInt[this.base64chars.substr(i,1)] = i;
        encStr = encStr.replace(/\s+/g, "");
        var decStr = "";
        var decArray=new Array();
        var linelen = 0
        var el=encStr.length;
        var bits24;
        for (var i = 0; i < el; i += 4) {
            bits24  = ( base64charToInt[encStr.charAt(i)] & 0xFF  ) <<  18;
            bits24 |= ( base64charToInt[encStr.charAt(i+1)] & 0xFF  ) <<  12;
            bits24 |= ( base64charToInt[encStr.charAt(i+2)] & 0xFF  ) <<   6;
            bits24 |= ( base64charToInt[encStr.charAt(i+3)] & 0xFF  ) <<   0;
            decStr += String.fromCharCode((bits24 & 0xFF0000) >> 16);
            if (encStr.charAt(i + 2) != '=')  // check for padding character =
                decStr += String.fromCharCode((bits24 &   0xFF00) >>  8);
            if (encStr.charAt(i + 3) != '=')  // check for padding character =
                decStr += String.fromCharCode((bits24 &     0xFF) >>  0);
            if(decStr.length>1024)
            {
                decArray.push(decStr);
                decStr='';
            }
        }
        if(decStr.length>0)
        {
            decArray.push(decStr);
        }
        
        var ar2=new Array();
        for(;decArray.length>1;)
        {
            var l=decArray.length;
            for(var c=0;c<l;c+=2)
            {
                if(c+1==l)
                {
                    ar2.push(decArray[c]);
                }
                else
                {
                    ar2.push(''+decArray[c]+decArray[c+1]);
                }
            }
            decArray=ar2;
            ar2=new Array();
        }
        return decArray[0];
    }
   
    this.encode = function(decStr)
    {
        var encArray=new Array();
        var bits, dual, i = 0, encOut = "";
        var linelen = 0;
        var encOut='';
        while(decStr.length >= i + 3){
            bits =    (decStr.charCodeAt(i++) & 0xff) <<16 |
                (decStr.charCodeAt(i++) & 0xff) <<8 |
                decStr.charCodeAt(i++) & 0xff;
            encOut +=
                this.base64chars.charAt((bits & 0x00fc0000) >>18) +
                this.base64chars.charAt((bits & 0x0003f000) >>12) +
                this.base64chars.charAt((bits & 0x00000fc0) >> 6) +
                this.base64chars.charAt((bits & 0x0000003f));
            linelen += 4;
            if (linelen>this.maxLineLength-3) {
                encOut += "\n";
                encArray.push(encOut);
                encOut='';
                linelen = 0;
            }
        }
        if(decStr.length -i > 0 && decStr.length -i < 3) {
            dual = Boolean(decStr.length -i -1);
            bits =
                ((decStr.charCodeAt(i++) & 0xff) <<16) |
                (dual ? (decStr.charCodeAt(i) & 0xff) <<8 : 0);
            encOut +=
                this.base64chars.charAt((bits & 0x00fc0000) >>18) +
                this.base64chars.charAt((bits & 0x0003f000) >>12) +
                      (dual ? this.base64chars.charAt((bits & 0x00000fc0) >>6) : '=') +
                      '=';
        }
       
        encArray.push(encOut);
        // this loop progressive concatonates the
        // array elements entil there is only one
        var ar2=new Array();
        for(;encArray.length>1;)
        {
            var l=encArray.length;
            for(var c=0;c<l;c+=2)
            {
                if(c+1==l)
                {
                    ar2.push(encArray[c]);
                }
                else
                {
                    ar2.push(''+encArray[c]+encArray[c+1]);
                }
            }
            encArray=ar2;
            ar2=new Array();
        }
        return encArray[0];
    }
}

Accessing Binary Files From JScript or VBScript

Here is a nice way of accessing binary files from VBScript or JScript. VBScript is not really powerful enough to do this directly, so I have written it all in JScript. You could do with with binary arrays in VBScript if you want, but I don't find the performance particularly impressive. If you want to access this object from VBScript, then put it in a swf file. This object has been used in anger to upload files to a web service and has proven to be nice and fast for files in the 0-10 MByte region.

/** This is a fairly well optimized object which alows
  * access to binary files from JScript on a Windows
  * operating system.
  *
  * A the end of the file is small set of tests to show how it
  * is used.  You will require ADODB 2.5 or higher installed.
  * this will be so on most 2000 machines and all XP or higher
  * machines.
  *
  * CopyRight: Dr Alexander J Turner - all rights reserved.
  * Please feel free to use this code in any way you like
  * as long as you place a reference in the comments that
  * I wrote it.
  */
function BinaryFile(name)
{
    var adTypeBinary = 1 
    var adTypeText   = 2 
    var adSaveCreateOverWrite = 2
    // The trick - this is the 'old fassioned' not translation page
    // It lest javascript use strings to act like raw octets
    var codePage='437';
   
    this.path=name;
   
    var forward  = new Array();
    var backward = new Array();
   
    // Note - for better performance I should preconvert these hex
    // definitions to decimal - at some point :-) - AJT
    forward['80'] = '00C7';
    forward['81'] = '00FC';
    forward['82'] = '00E9';
    forward['83'] = '00E2';
    forward['84'] = '00E4';
    forward['85'] = '00E0';
    forward['86'] = '00E5';
    forward['87'] = '00E7';
    forward['88'] = '00EA';
    forward['89'] = '00EB';
    forward['8A'] = '00E8';
    forward['8B'] = '00EF';
    forward['8C'] = '00EE';
    forward['8D'] = '00EC';
    forward['8E'] = '00C4';
    forward['8F'] = '00C5';
    forward['90'] = '00C9';
    forward['91'] = '00E6';
    forward['92'] = '00C6';
    forward['93'] = '00F4';
    forward['94'] = '00F6';
    forward['95'] = '00F2';
    forward['96'] = '00FB';
    forward['97'] = '00F9';
    forward['98'] = '00FF';
    forward['99'] = '00D6';
    forward['9A'] = '00DC';
    forward['9B'] = '00A2';
    forward['9C'] = '00A3';
    forward['9D'] = '00A5';
    forward['9E'] = '20A7';
    forward['9F'] = '0192';
    forward['A0'] = '00E1';
    forward['A1'] = '00ED';
    forward['A2'] = '00F3';
    forward['A3'] = '00FA';
    forward['A4'] = '00F1';
    forward['A5'] = '00D1';
    forward['A6'] = '00AA';
    forward['A7'] = '00BA';
    forward['A8'] = '00BF';
    forward['A9'] = '2310';
    forward['AA'] = '00AC';
    forward['AB'] = '00BD';
    forward['AC'] = '00BC';
    forward['AD'] = '00A1';
    forward['AE'] = '00AB';
    forward['AF'] = '00BB';
    forward['B0'] = '2591';
    forward['B1'] = '2592';
    forward['B2'] = '2593';
    forward['B3'] = '2502';
    forward['B4'] = '2524';
    forward['B5'] = '2561';
    forward['B6'] = '2562';
    forward['B7'] = '2556';
    forward['B8'] = '2555';
    forward['B9'] = '2563';
    forward['BA'] = '2551';
    forward['BB'] = '2557';
    forward['BC'] = '255D';
    forward['BD'] = '255C';
    forward['BE'] = '255B';
    forward['BF'] = '2510';
    forward['C0'] = '2514';
    forward['C1'] = '2534';
    forward['C2'] = '252C';
    forward['C3'] = '251C';
    forward['C4'] = '2500';
    forward['C5'] = '253C';
    forward['C6'] = '255E';
    forward['C7'] = '255F';
    forward['C8'] = '255A';
    forward['C9'] = '2554';
    forward['CA'] = '2569';
    forward['CB'] = '2566';
    forward['CC'] = '2560';
    forward['CD'] = '2550';
    forward['CE'] = '256C';
    forward['CF'] = '2567';
    forward['D0'] = '2568';
    forward['D1'] = '2564';
    forward['D2'] = '2565';
    forward['D3'] = '2559';
    forward['D4'] = '2558';
    forward['D5'] = '2552';
    forward['D6'] = '2553';
    forward['D7'] = '256B';
    forward['D8'] = '256A';
    forward['D9'] = '2518';
    forward['DA'] = '250C';
    forward['DB'] = '2588';
    forward['DC'] = '2584';
    forward['DD'] = '258C';
    forward['DE'] = '2590';
    forward['DF'] = '2580';
    forward['E0'] = '03B1';
    forward['E1'] = '00DF';
    forward['E2'] = '0393';
    forward['E3'] = '03C0';
    forward['E4'] = '03A3';
    forward['E5'] = '03C3';
    forward['E6'] = '00B5';
    forward['E7'] = '03C4';
    forward['E8'] = '03A6';
    forward['E9'] = '0398';
    forward['EA'] = '03A9';
    forward['EB'] = '03B4';
    forward['EC'] = '221E';
    forward['ED'] = '03C6';
    forward['EE'] = '03B5';
    forward['EF'] = '2229';
    forward['F0'] = '2261';
    forward['F1'] = '00B1';
    forward['F2'] = '2265';
    forward['F3'] = '2264';
    forward['F4'] = '2320';
    forward['F5'] = '2321';
    forward['F6'] = '00F7';
    forward['F7'] = '2248';
    forward['F8'] = '00B0';
    forward['F9'] = '2219';
    forward['FA'] = '00B7';
    forward['FB'] = '221A';
    forward['FC'] = '207F';
    forward['FD'] = '00B2';
    forward['FE'] = '25A0';
    forward['FF'] = '00A0';
    backward['C7']   = '80';
    backward['FC']   = '81';
    backward['E9']   = '82';
    backward['E2']   = '83';
    backward['E4']   = '84';
    backward['E0']   = '85';
    backward['E5']   = '86';
    backward['E7']   = '87';
    backward['EA']   = '88';
    backward['EB']   = '89';
    backward['E8']   = '8A';
    backward['EF']   = '8B';
    backward['EE']   = '8C';
    backward['EC']   = '8D';
    backward['C4']   = '8E';
    backward['C5']   = '8F';
    backward['C9']   = '90';
    backward['E6']   = '91';
    backward['C6']   = '92';
    backward['F4']   = '93';
    backward['F6']   = '94';
    backward['F2']   = '95';
    backward['FB']   = '96';
    backward['F9']   = '97';
    backward['FF']   = '98';
    backward['D6']   = '99';
    backward['DC']   = '9A';
    backward['A2']   = '9B';
    backward['A3']   = '9C';
    backward['A5']   = '9D';
    backward['20A7'] = '9E';
    backward['192']  = '9F';
    backward['E1']   = 'A0';
    backward['ED']   = 'A1';
    backward['F3']   = 'A2';
    backward['FA']   = 'A3';
    backward['F1']   = 'A4';
    backward['D1']   = 'A5';
    backward['AA']   = 'A6';
    backward['BA']   = 'A7';
    backward['BF']   = 'A8';
    backward['2310'] = 'A9';
    backward['AC']   = 'AA';
    backward['BD']   = 'AB';
    backward['BC']   = 'AC';
    backward['A1']   = 'AD';
    backward['AB']   = 'AE';
    backward['BB']   = 'AF';
    backward['2591'] = 'B0';
    backward['2592'] = 'B1';
    backward['2593'] = 'B2';
    backward['2502'] = 'B3';
    backward['2524'] = 'B4';
    backward['2561'] = 'B5';
    backward['2562'] = 'B6';
    backward['2556'] = 'B7';
    backward['2555'] = 'B8';
    backward['2563'] = 'B9';
    backward['2551'] = 'BA';
    backward['2557'] = 'BB';
    backward['255D'] = 'BC';
    backward['255C'] = 'BD';
    backward['255B'] = 'BE';
    backward['2510'] = 'BF';
    backward['2514'] = 'C0';
    backward['2534'] = 'C1';
    backward['252C'] = 'C2';
    backward['251C'] = 'C3';          
    backward['2500'] = 'C4';
    backward['253C'] = 'C5';
    backward['255E'] = 'C6';
    backward['255F'] = 'C7';
    backward['255A'] = 'C8';
    backward['2554'] = 'C9';
    backward['2569'] = 'CA';
    backward['2566'] = 'CB';
    backward['2560'] = 'CC';
    backward['2550'] = 'CD';
    backward['256C'] = 'CE';
    backward['2567'] = 'CF';
    backward['2568'] = 'D0';
    backward['2564'] = 'D1';
    backward['2565'] = 'D2';
    backward['2559'] = 'D3';
    backward['2558'] = 'D4';
    backward['2552'] = 'D5';
    backward['2553'] = 'D6';
    backward['256B'] = 'D7';
    backward['256A'] = 'D8';
    backward['2518'] = 'D9';
    backward['250C'] = 'DA';
    backward['2588'] = 'DB';
    backward['2584'] = 'DC';
    backward['258C'] = 'DD';
    backward['2590'] = 'DE';
    backward['2580'] = 'DF';
    backward['3B1']  = 'E0';
    backward['DF']   = 'E1';
    backward['393']  = 'E2';
    backward['3C0']  = 'E3';
    backward['3A3']  = 'E4';
    backward['3C3']  = 'E5';
    backward['B5']   = 'E6';
    backward['3C4']  = 'E7';
    backward['3A6']  = 'E8';
    backward['398']  = 'E9';
    backward['3A9']  = 'EA';
    backward['3B4']  = 'EB';                                                              
    backward['221E'] = 'EC';
    backward['3C6']  = 'ED';
    backward['3B5']  = 'EE';
    backward['2229'] = 'EF';
    backward['2261'] = 'F0';
    backward['B1']   = 'F1';
    backward['2265'] = 'F2';
    backward['2264'] = 'F3';
    backward['2320'] = 'F4';
    backward['2321'] = 'F5';
    backward['F7']   = 'F6';
    backward['2248'] = 'F7';
    backward['B0']   = 'F8';
    backward['2219'] = 'F9';
    backward['B7']   = 'FA';
    backward['221A'] = 'FB';
    backward['207F'] = 'FC';
    backward['B2']   = 'FD';
    backward['25A0'] = 'FE';
    backward['A0']   = 'FF';     
   
    var hD="0123456789ABCDEF";
    this.d2h = function(d)
    {
        var h = hD.substr(d&15,1);
        while(d>15) {d>>=4;h=hD.substr(d&15,1)+h;}
        return h;
    }

    this.h2d = function(h)
    {
        return parseInt(h,16);
    }
   
    this.WriteAll = function(what)
    {
        //Create Stream object
        var BinaryStream = WScript.CreateObject("ADODB.Stream");
        //Specify stream type - we cheat and get string but 'like' binary
        BinaryStream.Type = adTypeText;
        BinaryStream.CharSet = '437';         
        //Open the stream
        BinaryStream.Open();
        // Write to the stream
        BinaryStream.WriteText(this.Forward437(what));
        // Write the string to the disk
        BinaryStream.SaveToFile(this.path, adSaveCreateOverWrite);

        // Clearn up
        BinaryStream.Close();
    }
   
    this.ReadAll  = function()
    {
        //Create Stream object - needs ADO 2.5 or heigher
        var BinaryStream = WScript.CreateObject("ADODB.Stream")
        //Specify stream type - we cheat and get string but 'like' binary
        BinaryStream.Type = adTypeText;
        BinaryStream.CharSet = codePage;
        //Open the stream
        BinaryStream.Open();
        //Load the file data from disk To stream object
        BinaryStream.LoadFromFile(this.path);
        //Open the stream And get binary 'string' from the object
        var what = BinaryStream.ReadText;
        // Clean up
        BinaryStream.Close();
        return this.Backward437(what);
    }
   
    /* Convert a octet number to a code page 437 char code */
    this.Forward437 = function(inString)
    {
        var encArray = new Array();
        var tmp='';
        var i=0;
        var c=0;
        var l=inString.length;
        var cc;
        var h;
        for(;i<l;++i)
        {
            c++;
            if(c==128)
            {
                encArray.push(tmp);
                tmp='';
                c=0;
            }
            cc=inString.charCodeAt(i);
            if(cc<128)
            {
                tmp+=String.fromCharCode(cc);
            }      
            else
            {
                h=this.d2h(cc);
                h=forward[''+h];
                tmp+=String.fromCharCode(this.h2d(h));
            }
        }
        if(tmp!='')
        {
            encArray.push(tmp);
        }

        // this loop progressive concatonates the
        // array elements entil there is only one
        var ar2=new Array();
        for(;encArray.length>1;)
        {
            var l=encArray.length;
            for(var c=0;c<l;c+=2)
            {
                if(c+1==l)
                {
                    ar2.push(encArray[c]);
                }
                else
                {
                    ar2.push(''+encArray[c]+encArray[c+1]);
                }
            }
            encArray=ar2;
            ar2=new Array();
        }
        return encArray[0];
    }
    /* Convert a code page 437 char code to a octet number*/
    this.Backward437 = function(inString)
    {
        var encArray = new Array();
        var tmp='';
        var i=0;
        var c=0;
        var l=inString.length;
        var cc;
        var h;
        for(;i<l;++i)
        {
            c++;
            if(c==128)
            {
                encArray.push(tmp);
                tmp='';
                c=0;
            }
            cc=inString.charCodeAt(i);
            if(cc<128)
            {
                tmp+=String.fromCharCode(cc);
            }
            else
            {
                h=this.d2h(cc);
                h=backward[''+h];
                tmp+=String.fromCharCode(this.h2d(h));
            }
        }
        if(tmp!='')
        {
            encArray.push(tmp);
        }

        // this loop progressive concatonates the
        // array elements entil there is only one
        var ar2=new Array();
        for(;encArray.length>1;)
        {
            var l=encArray.length;
            for(var c=0;c<l;c+=2)
            {
                if(c+1==l)
                {
                    ar2.push(encArray[c]);
                }
                else
                {
                    ar2.push(''+encArray[c]+encArray[c+1]);
                }
            }
            encArray=ar2;
            ar2=new Array();
        }
        return encArray[0];
    }
   
}

// Test
/*
var s = '';
for(var i=0;i<256;++i)
{
    s+=String.fromCharCode(i);
}
var bf0=new BinaryFile();
var s1=bf0.Forward437(s);
var s1=bf0.Backward437(s1);
for(var i=0;i<256;++i)
{
    if(s1.charCodeAt(i)!=i)
    {
        WScript.Echo('Error At:',i,bf0.d2h(i));
    }
}

var crFolder = 'C:/Temp/cr'
var bf1=new BinaryFile(crFolder+"/PCDV0026.JPG");
var bf2=new BinaryFile(crFolder+"/PCDV0026_.JPG");
bf2.WriteAll(bf1.ReadAll());
*/

Angler Technology Comes To The UK

Now Angler Technology has a full time UK branch! I happen to know the work of Angler, but possibly more importantly, I know the chap who is running the UK branch. The former is excellent value for money and the latter is a fantastic asset to the UK IT market.

So (and I am getting no payment for this recommendation), if you want anything web doing or need to find some first rate IT resource fast, I 100% whole heartedly recommend Angler UK.

ANGLER Technologies UK [andy@angleritech.com]

Blogger Lost My Pictures

For some strainge reason, blogger lost some of the images on my Strobe Tuning post. I have put the pictures on one of my own servers and relinked the post. What a waist of effort :-(

How Download And Upload Free Images, Video, Music And Software

OK - I keep being asked how to do this so here is what I do to distribute and receive free programs, pictures, movies and music to/from the Internet.

STOP RIGHT THERE

The technology described here can be used to distribute copies of copy-righted material. This is against the law in a lot of places. I am not condoning this use!

The technology can and is also use for a lot of very good reasons. Renting enough web space to publish your content can be very expensive. The new P2P (peer to peer) technologies mean you don't have to rent all that web space. They are a liberating and empowering technology.

Now we have done with that - here is the software you'll be needing:

http://azureus.sourceforge.net/

http://sourceforge.net/projects/frostwire

I have picked these two as they both work, they are both open source and they serve different types of p2p networking. The key thing about them being open source is that everyone can see everything that goes into them. That means, no hidden spyware or viruses!

Azureus uses the BitTorrent http://en.wikipedia.org/wiki/BitTorrent protocol for sharing files between computers. It is designed for and is ideal for the sharing of large files. These include, entire music albums, image libraries and videos.

Frostwire uses Gnutella http://en.wikipedia.org/wiki/Gnutella. This is a more primitive protocol (IMHO) but is more appropriate for sharing smaller files, like single songs you might have produced, individual pictures or small programs.

Over the last year or so I have moved away from the Gnutella/Frostwire almost entirely as I find it is totally taken over by teenagers. I guess that means that if you are a teenager, there is probably just the content you want on there. Whilst I use BitTorrent/Azureus near 100% for file sharing, I will continue to describe both for completeness.

There are other protocols and pieces of software out there. For example - eDonky. I would highly recommend not ever using these under any circumstances. The software for them tends to be less safe and the content more criminal (not just copy-right abuse) in nature :-(

Azureus - Step By Step:

My favourite file sharing program! You just download the appropriate installer and install it. You may well have to have Java installed. This is software framework that both FrostWire and Azureus use. Instructions on how to download and install it are on the download page of the Aureus site: http://azureus.sourceforge.net/download.php.

Unless you are intending to download a truly massive amount of data, the chances are that the default settings for Azureus will be OK. So the next trick is to find a 'torrent' to download. You see, BitTorrent works by having a definition of a torrent that then alows the files associated with that torrent to be shared between many computers (or peers).

If you know the person who has published the content - or they have their own web site - then you can probably just go load the torrent from there. Torrents are files, and when you click on the link to the file, the chances are that your browser will ask if you want to open it in Azureus. If it does not, then download the file to your hard drive, open Azureous and then open the file from the File/Open dialogue of Azureus. Yes - it is that simple, because now Azureous will do all the work for you of finding other computers with bits of the file and sharing it.

Once it has download that entire file for you, it will carry on sharing it with other people. You should let it do so for a reasonable time, this is how the sharing principle works. If you do not let other people share the file what you have downloaded, not only will the file not be as available across the Internet, but other users might set their sharing software to 'lock you out' in the future.

But how do I find general torrent files to download

There are sites out there on the Internet that allow the registration and searching of torrents. My favourite at the time of writing is

Above is the results of me searching to see if it is possible to download the Azureus software it self via BitTorrent searching on Isohunt. The answer is yes. But, be careful, I would not be at all shocked to find copy-right breaking material on there. If this bothers you, be careful.

Above is what happens when I click on the link to the first of the torrents. If you click on the 'Download' link then the torrent file will be downloaded and either automatically opened in Azureus or you can open it in Azureus once it is downloaded (as I explained earlier). There is other useful information in there too. If the list of 'trackers' is all red, then the chances are that you'll no be able to download the file because no-one is sharing it any more. This is not always the case because there is a new fancy technology called DHT, but you are taking a risk. Also, you can look at the 'S' and 'L' columns of the main table, these count known Seeders and Leachers. If there are no Seeders, again, there is little chance you'll be able to download the file. It is not the torrent you'll no be able to download, it is the shared file(s) it connects you to that is probably not available.

One final caution: Not software downloaded this way has any guarantee with it. Make sure you scan it with anti-virus and anti-shareware programs before using it. If in doubt - throw it out.

Frost Wire - Step By Step

Quick note: I see that FrostWire now supports BitTorrent. I have not tried this, and I expect I'll stick to Azureous for BitTorrent, but it might be worth a look. If you like it, please leave a comment to let everyone know

When you install FrostWire, it will ask you if you would like it to start when the OS starts (well on Windows it does, not sure about Mac or Linux). I would highly recommend saying no! I like to stay in control.

Right, back to FrostWire for Gnutella. Gnutella has search built in, so it is in some ways easier to use. In the FrostWire window just click on the search tab. A new tab with the results will appear. You simply double click on the files you are interested in and it will try and download them. If it comes up saying 'needs more sources' you can try right clicking on the file in the downloading area and select 'find more sources', it works sometimes.

Let us put this in perspective though. As a quick test I just searched for images of 'beautiful women'. You might think I would get loads of pictures of porn? No, just a load of spam about me having won a competition or something that I am sure is just a plan to empty my bank account. The moral of this story is, Gnutella is full to bursting with crap! You have to plough through a lot of it to get want you want. But is can be worth it!

Uploading

In a later post I will discuss how to create torrents, but for now - GOOD LUCK