Messenger Plus Live Script updater

This is a tutorial on Script version checking and updating
tutorial by -dt- (Matt Labrum)

So you’ve made a script, and you want to make sure your users have the most current version of your script.
Well you’ve come to the right place. This tutorial is about exactly that!

Now I’m going to assume a few things
1) you’re not a complete noob
2) you’re not segosa
3) you have a script (If you don’t just create a new one)
4) the script must have a ScriptInfo.xml file
5) somewhere online where you can upload files

Now that thats out of the way, we can get to the fun part :D

A few reference documents for later:
XMLHttpRequest – http://msdn2.microsoft.com/en-us/library/ms535874.aspx
XMLDomDocument – http://msdn2.microsoft.com/en-us/library/ms757878.aspx

Now open up your ScriptInfo.xml file it should look something like the one shown below

		updateExample
		This script is an example script to test update checking
		http://blog.thedt.net
		1.00

You should notice the Version tag, this is one of the core tags needed in our update checker. You should also notice that there aren’t any tags to specify an update location.
This is a problem since our update checker needs to find the new version information somewhere, so lets add a section in our ScriptInfo.xml and add some updater tags

		updateExample
		This script is an example script to test update checking
		http://blog.thedt.net
		1.00
 
			http://yoursite.com/updateExample/ScriptInfo.xml
http://yoursite.com/updateExample/updateExample.plsc
 
			- First release
 
			10/11/2007

Ok so now we have alot more information for our script to use, but I bet you’re looking at me like “dt what the hell are all those tags” well :( I’ll explain each one

ScriptInfoUrl – the location of the updated scriptInfo.xml (our checker will download this file and compare it to the current one)
PlscLocation – Plsc to download if our update checker detects that we need updating
ReleaseNotes – optional, the changelog to display to the user
DateReleased – optional, the date the new version was released

Now i bet you’re wondering how the update checker will work. Well, inside your script’s OnEvent_Initialize you’ll trigger UpdateChecker.Check(), which will then download the update xml (gets this value from Update/ScriptInfoUrl).
Once the updateXML is downloaded it will trigger UpdateChecker.parseUpdate() which then compares the local ScriptInfo.xml tag with the downloaded ScriptInfo.xml tag.
If it’s a newer version then the script will display a dialog to the user asking them if they want to update, if they say yes then your script will download the plsc file and run it.

Diagram of how all this fits together
(click for a bigger image)

Now to make the javascript for the above process

Create a new file called UpdateChecker.js
Inside that file we’ll create an Object called UpdateChecker, with the functions we need

var UpdateChecker = function(){
	this.Check();
};
 
UpdateChecker.prototype = {
	"Check" : function(){}, //This function will parse the local XML file and trigger the download the update ScriptInfo.xml
	"ParseScriptInfo" : function(xml){}, //returns an object with the parsed ScriptInfo information
	"AskUser" : function(){},
	"GetNewPlsc" : function(){}
}

Now we need to start filling the functions and making it do something :D

We will start with the checking code

	"Check" : function(){ //This function will parse the local XML file and trigger the download the update ScriptInfo.xml
		var scriptInfo = this.ParseScriptInfo(); //will return an object in the form of {"Version" : 1.0, "UpdateUrl" : "http://.../scriptInfo.xml", "ReleaseNotes" : "", "DateReleased" : ""}
		this.details = scriptInfo; //add the details to the object, incase the script wants to use this for something
 
		Interop.Call("Wininet.dll", "DeleteUrlCacheEntryW", scriptInfo.UpdateUrl); //make sure theres no cached version of the update
 
		var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); // Create a new XMLHttp request object to download our update ScriptInfo.xml
 
		// see http://msdn2.microsoft.com/en-us/library/ms536648.aspx for details on the open method
		xmlhttp.open("GET", scriptInfo.UpdateUrl, true) //open a new xmlhttp request to the updateUrl and set it to asynchronous operation
 
		var thisd = this; //create a reference to the current scope to use in the callback
 
		//see http://msdn2.microsoft.com/en-us/library/ms534308.aspx for more information on the onreadystatechange callback
		xmlhttp.onreadystatechange = function(){
			if(xmlhttp.readyState == 4){
				var updateInfo = thisd.ParseScriptInfo(xmlhttp.responseXML); //parses the update scriptInfo and returns an object
 
				if(updateInfo.Version > scriptInfo.Version){ //if version is newer
					Debug.Trace("Update is newer");
				}else{
					Debug.Trace("Update Is older");
				}
			}
		}
		xmlhttp.send();
	}

Now this can’t do anything on its own because it requires the object from ParseScriptInfo, so we will implement that and then test the script

	"ParseScriptInfo" : function(xml){ //returns an object with the parsed ScriptInfo information
		if(typeof(xml) == "undefined"){ //if no xml is passed, we will load the local script info
			var xml =  new ActiveXObject("Microsoft.XMLDOM");
			xml.async = false;
			xml.load(MsgPlus.ScriptFilesPath + "\\" + "ScriptInfo.xml");
		}
 
		//object to return
		var ob = {
			"Version" : 0,
			"UpdateUrl" : "",
			"PlscLocation" : "",
			"ReleaseNotes" : "",
			"DateReleased" : ""
		};
 
		//get the Update node values
		ob.UpdateUrl = xml.selectSingleNode("//Update/ScriptInfoUrl").text;
		ob.PlscLocation = xml.selectSingleNode("//Update/PlscLocation").text;
		ob.ReleaseNotes = xml.selectSingleNode("//Update/ReleaseNotes").text;
		ob.DateReleased = xml.selectSingleNode("//Update/DateReleased").text;
 
		var version = xml.selectSingleNode("//Version").text;
		//convert the version to a real number, parseInt chops off the decimal points so we cheat and * it by 1
		ob.Version = version * 1;
 
		return ob; //return our object
	}

The full code so far

var UpdateChecker = function(){
	this.Check();
};
 
UpdateChecker.prototype = {
	"Check" : function(){ //This function will parse the local XML file and trigger the download the update ScriptInfo.xml
		var scriptInfo = this.ParseScriptInfo(); //will return an object in the form of {"Version" : 1.0, "UpdateUrl" : "http://.../scriptInfo.xml", "ReleaseNotes" : "", "DateReleased" : ""}
		this.details = scriptInfo; //add the details to the object, incase the script wants to use this for something
 
		Interop.Call("Wininet.dll", "DeleteUrlCacheEntryW", scriptInfo.UpdateUrl); //make sure theres no cached version of the update
 
		var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); // Create a new XMLHttp request object to download our update ScriptInfo.xml
 
		// see http://msdn2.microsoft.com/en-us/library/ms536648.aspx for details on the open method
		xmlhttp.open("GET", scriptInfo.UpdateUrl, true) //open a new xmlhttp request to the updateUrl and set it to asynchronous operation
 
		var thisd = this; //create a reference to the current scope to use in the callback
 
		//see http://msdn2.microsoft.com/en-us/library/ms534308.aspx for more information on the onreadystatechange callback
		xmlhttp.onreadystatechange = function(){
			//because this refers the function we have to use thisd when refering to our class
			if(xmlhttp.readyState == 4){
				var updateInfo = thisd.ParseScriptInfo(xmlhttp.responseXML); //parses the update scriptInfo and returns an object
 
				if(updateInfo.Version > scriptInfo.Version){ //if version is newer
					Debug.Trace("Update is newer");
				}else{
					Debug.Trace("Update Is older");
				}
			}
		}
		xmlhttp.send();
	},
 
	"ParseScriptInfo" : function(xml){ //returns an object with the parsed ScriptInfo information
		if(typeof(xml) == "undefined"){ //if no xml is passed, we will load the local script info
			var xml =  new ActiveXObject("Microsoft.XMLDOM");
			xml.async = false;
			xml.load(MsgPlus.ScriptFilesPath + "\\" + "ScriptInfo.xml");
		}
 
		//object to return
		var ob = {
			"Version" : 0,
			"UpdateUrl" : "",
			"PlscLocation" : "",
			"ReleaseNotes" : "",
			"DateReleased" : "",
			"Name" : ""
		};
 
		//get the Update node values
		ob.UpdateUrl = xml.selectSingleNode("//Update/ScriptInfoUrl").text;
		ob.PlscLocation = xml.selectSingleNode("//Update/PlscLocation").text;
		ob.ReleaseNotes = xml.selectSingleNode("//Update/ReleaseNotes").text;
		ob.DateReleased = xml.selectSingleNode("//Update/DateReleased").text;
		ob.Name = xml.selectSingleNode("//Name").text;
 
		var version = xml.selectSingleNode("//Version").text;
		//convert the version to a real number, parseInt chops off the decimal points so we cheat and * it by 1
		ob.Version = version * 1;
 
		return ob; //return our object
	},
	"AskUser" : function(){},
	"GetNewPlsc" : function(){}
}

Now if you want to test the above code, add the following to your OnEvent_Initialize

function OnEvent_Initialize(){
	var uc = new UpdateChecker();
}

Make sure you have a copy of your ScriptInfo.xml uploaded at the location specified at ScriptInfoUrl, otherwise the code will fail.

Now run the script, if your uploaded ScriptInfo.xml file has a greater version than the local ScriptInfo.xml it should display “Update is newer”

Ok, so now we need to do something if the update is newer, we will call the UpdateChecker.AskUser() method to ask the user if they want to update.
We will just display a messagebox with yes or no buttons, in your own script I recommend that you create a fancy GUI :)

	"AskUser" : function(updateInfo){
		//we will just display a simple yes/no messagebox, in your own version you could take this a step futher and display a pretty plus GUI for it, but thats outside the scope of this tutorial
 
		var message = "A new Script Update has been detected for " + updateInfo.Name + " Do you want to update?";
		var result = Interop.Call("User32", "MessageBoxW", 0, message, "Script Update", 4 /* MB_YESNO */);
 
		if(result == 6){ //a result of 6 means that the user pressed "yes"
			this.GetNewPlsc(updateInfo.PlscLocation); //call the GetNewPlsc function to download the file
		}
	}

And finally we need to create the GetNewPlsc function to open the Plsc file.
Now in your own scripts you should use MsgPlus.DownloadFile to download the plsc and then use

	new ActiveXObject("WScript.Shell").Run('"downloadlocation"');

to run it
but in this example we will simply execute the url

	"GetNewPlsc" : function(url){ //function to download the plsc
		//execute the url
		new ActiveXObject("WScript.Shell").Run('"' + url + '"');
	}

Ok so lets look at the code so far.
It should download the new ScriptInfo.xml and compare the versions, if theres an update then it will open the PlscLocation

var UpdateChecker = function(){
	this.Check();
};
 
UpdateChecker.prototype = {
	"Check" : function(){ //This function will parse the local XML file and trigger the download the update ScriptInfo.xml
		var scriptInfo = this.ParseScriptInfo(); //will return an object in the form of {"Version" : 1.0, "UpdateUrl" : "http://.../scriptInfo.xml", "ReleaseNotes" : "", "DateReleased" : ""}
		this.details = scriptInfo; //add the details to the object, incase the script wants to use this for something
 
		Interop.Call("Wininet.dll", "DeleteUrlCacheEntryW", scriptInfo.UpdateUrl); //make sure theres no cached version of the update
 
		var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); // Create a new XMLHttp request object to download our update ScriptInfo.xml
 
		// see http://msdn2.microsoft.com/en-us/library/ms536648.aspx for details on the open method
		xmlhttp.open("GET", scriptInfo.UpdateUrl, true) //open a new xmlhttp request to the updateUrl and set it to asynchronous operation
 
		var thisd = this; //create a reference to the current scope to use in the callback
 
		//see http://msdn2.microsoft.com/en-us/library/ms534308.aspx for more information on the onreadystatechange callback
		xmlhttp.onreadystatechange = function(){
			//because this refers the function we have to use thisd when refering to our class
			if(xmlhttp.readyState == 4){
				var updateInfo = thisd.ParseScriptInfo(xmlhttp.responseXML); //parses the update scriptInfo and returns an object
 
				if(updateInfo.Version > scriptInfo.Version){ //if version is newer
					Debug.Trace("Update is newer");
 
					thisd.AskUser(updateInfo);
				}else{
					Debug.Trace("Update Is older");
				}
			}
		}
		xmlhttp.send();
	},
 
	"ParseScriptInfo" : function(xml){ //returns an object with the parsed ScriptInfo information
		if(typeof(xml) == "undefined"){ //if no xml is passed, we will load the local script info
			var xml =  new ActiveXObject("Microsoft.XMLDOM");
			xml.async = false;
			xml.load(MsgPlus.ScriptFilesPath + "\\" + "ScriptInfo.xml");
		}
 
		//object to return
		var ob = {
			"Version" : 0,
			"UpdateUrl" : "",
			"PlscLocation" : "",
			"ReleaseNotes" : "",
			"DateReleased" : "",
			"Name" : ""
		};
 
		//get the Update node values
		ob.UpdateUrl = xml.selectSingleNode("//Update/ScriptInfoUrl").text;
		ob.PlscLocation = xml.selectSingleNode("//Update/PlscLocation").text;
		ob.ReleaseNotes = xml.selectSingleNode("//Update/ReleaseNotes").text;
		ob.DateReleased = xml.selectSingleNode("//Update/DateReleased").text;
		ob.Name = xml.selectSingleNode("//Name").text;
 
		var version = xml.selectSingleNode("//Version").text;
		//convert the version to a real number, parseInt chops off the decimal points so we cheat and * it by 1
		ob.Version = version * 1;
 
		return ob; //return our object
	},
	"AskUser" : function(updateInfo){
		//we will just display a simple yes/no messagebox, in your own version you could take this a step futher and display a pretty plus GUI for it, but thats outside the scope of this tutorial
 
		var message = "A new Script Update has been detected for " + updateInfo.Name + " Do you want to update?";
		var result = Interop.Call("User32", "MessageBoxW", 0, message, "Script Update", 4 /* MB_YESNO */);
 
		if(result == 6){ //a result of 6 means that the user pressed "yes"
			this.GetNewPlsc(updateInfo.PlscLocation);
		}
	},
	"GetNewPlsc" : function(url){ //function to download the plsc
		//now in your own scripts you should use MsgPlus.DownloadFile to download the plsc and new ActiveXObject("WScript.Shell").Run('"downloadlocation"'); to run it
		//but in this example we will simply execute the url
		new ActiveXObject("WScript.Shell").Run('"' + url + '"');
	}
}

and your OnEvent_Initialize

function OnEvent_Initialize(){
	var uc = new UpdateChecker();
}

This is a basic updater, which works but it could be improved quite a bit. One such modification could be to only check for a new version every 24 hours. Let’s modify the above code to add such a feature.
On first update we store the time into the registry, on the next update if(time() > stored_time + timeToWait) we will check for an update and rewrite the time else we do nothing.

That sounds simple enough right?

Below are the links to the functions that we will be using to read and write to the registry
RegRead – http://msdn2.microsoft.com/en-us/library/x05fawxd.aspx
RegWrite – http://msdn2.microsoft.com/en-us/library/yfdfhz1b.aspx

First we need to know how to read and write registry keys before we can continue.

Checking if a key exists, reading it and writing to the registry.

try{
	//RegRead will throw an error if it can't read the path
	var lastUpdate = shell.RegRead(path) * 1; //convert it to a number
 
	//if curtime is less than lastUpdate + timeToWait then we return and dont check
	if(curTime < lastUpdate){
		Debug.Trace("no update");
		return;
	} else{
		shell.RegWrite(path, curTime + timeToWait); // if it is, we write the new time and let it check for an update
	}
 
}catch(e){
	//key doesn't exist so we will write it for the first time
	shell.RegWrite(path, curTime + timeToWait); //write time() + timeToWait to the registry
}
 
Debug.Trace("Update");

Now we can implement this into the Check function of our UpdateChecker

	"Check" : function(){ //This function will parse the local XML file and trigger the download the update ScriptInfo.xml
		var scriptInfo = this.ParseScriptInfo(); //will return an object in the form of {"Version" : 1.0, "UpdateUrl" : "http://.../scriptInfo.xml", "ReleaseNotes" : "", "DateReleased" : ""}
		this.details = scriptInfo; //add the details to the object, incase the script wants to use this for something
 
		// we check if we need to update here after the local script info is stored inside the object
		var shell = new ActiveXObject("WScript.Shell");
		var path = MsgPlus.ScriptRegPath + "lastUpdateTime";
		var curTime = new Date().getTime();
		var timeToWait = 24 * 60 * 60 * 1000; //time to wait in milliseconds
		try{
			//RegRead will throw an error if it can't read the path
			var lastUpdate = shell.RegRead(path) * 1; //convert it to a number
 
			//if curtime is less than lastUpdate + timeToWait then we return and dont check
			if(curTime < lastUpdate){
				Debug.Trace("no update");
				return;
			} else{
				shell.RegWrite(path, curTime + timeToWait); // if it is, we write the new time and let it check for an update
			}
		}catch(e){
			//key doesn't exist so we will write it for the first time
			shell.RegWrite(path, curTime + timeToWait); //write time() + timeToWait to the registry
		}
 
		Interop.Call("Wininet.dll", "DeleteUrlCacheEntryW", scriptInfo.UpdateUrl); //make sure theres no cached version of the update
 
		var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); // Create a new XMLHttp request object to download our update ScriptInfo.xml
 
		// see http://msdn2.microsoft.com/en-us/library/ms536648.aspx for details on the open method
		xmlhttp.open("GET", scriptInfo.UpdateUrl, true) //open a new xmlhttp request to the updateUrl and set it to asynchronous operation
 
		var thisd = this; //create a reference to the current scope to use in the callback
 
		//see http://msdn2.microsoft.com/en-us/library/ms534308.aspx for more information on the onreadystatechange callback
		xmlhttp.onreadystatechange = function(){
			//because this refers the function we have to use thisd when refering to our class
			if(xmlhttp.readyState == 4){
				var updateInfo = thisd.ParseScriptInfo(xmlhttp.responseXML); //parses the update scriptInfo and returns an object
 
				if(updateInfo.Version > scriptInfo.Version){ //if version is newer
					Debug.Trace("Update is newer");
 
					thisd.AskUser(updateInfo);
				}else{
					Debug.Trace("Update Is older");
				}
			}
		}
		xmlhttp.send();
	},

And the complete code

var UpdateChecker = function(){
	this.Check();
};
 
UpdateChecker.prototype = {
	"Check" : function(){ //This function will parse the local XML file and trigger the download the update ScriptInfo.xml
		var scriptInfo = this.ParseScriptInfo(); //will return an object in the form of {"Version" : 1.0, "UpdateUrl" : "http://.../scriptInfo.xml", "ReleaseNotes" : "", "DateReleased" : ""}
		this.details = scriptInfo; //add the details to the object, incase the script wants to use this for something
 
		// we check if we need to update here after the local script info is stored inside the object
		var shell = new ActiveXObject("WScript.Shell");
		var path = MsgPlus.ScriptRegPath + "lastUpdateTime";
		var curTime = new Date().getTime();
		var timeToWait = 24 * 60 * 60 * 1000; //time to wait in milliseconds
		try{
			//RegRead will throw an error if it can't read the path
			var lastUpdate = shell.RegRead(path) * 1; //convert it to a number
 
			//if curtime is less than lastUpdate + timeToWait then we return and dont check
			if(curTime < lastUpdate){
				Debug.Trace("no update");
				return;
			} else{
				shell.RegWrite(path, curTime + timeToWait); // if it is, we write the new time and let it check for an update
			}
		}catch(e){
			//key doesn't exist so we will write it for the first time
			shell.RegWrite(path, curTime + timeToWait); //write time() + timeToWait to the registry
		}
 
		Interop.Call("Wininet.dll", "DeleteUrlCacheEntryW", scriptInfo.UpdateUrl); //make sure theres no cached version of the update
 
		var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); // Create a new XMLHttp request object to download our update ScriptInfo.xml
 
		// see http://msdn2.microsoft.com/en-us/library/ms536648.aspx for details on the open method
		xmlhttp.open("GET", scriptInfo.UpdateUrl, true) //open a new xmlhttp request to the updateUrl and set it to asynchronous operation
 
		var thisd = this; //create a reference to the current scope to use in the callback
 
		//see http://msdn2.microsoft.com/en-us/library/ms534308.aspx for more information on the onreadystatechange callback
		xmlhttp.onreadystatechange = function(){
			//because this refers the function we have to use thisd when refering to our class
			if(xmlhttp.readyState == 4){
				var updateInfo = thisd.ParseScriptInfo(xmlhttp.responseXML); //parses the update scriptInfo and returns an object
 
				if(updateInfo.Version > scriptInfo.Version){ //if version is newer
					Debug.Trace("Update is newer");
 
					thisd.AskUser(updateInfo);
				}else{
					Debug.Trace("Update Is older");
				}
			}
		}
		xmlhttp.send();
	},
 
	"ParseScriptInfo" : function(xml){ //returns an object with the parsed ScriptInfo information
		if(typeof(xml) == "undefined"){ //if no xml is passed, we will load the local script info
			var xml =  new ActiveXObject("Microsoft.XMLDOM");
			xml.async = false;
			xml.load(MsgPlus.ScriptFilesPath + "\\" + "ScriptInfo.xml");
		}
 
		//object to return
		var ob = {
			"Version" : 0,
			"UpdateUrl" : "",
			"PlscLocation" : "",
			"ReleaseNotes" : "",
			"DateReleased" : "",
			"Name" : ""
		};
 
		//get the Update node values
		ob.UpdateUrl = xml.selectSingleNode("//Update/ScriptInfoUrl").text;
		ob.PlscLocation = xml.selectSingleNode("//Update/PlscLocation").text;
		ob.ReleaseNotes = xml.selectSingleNode("//Update/ReleaseNotes").text;
		ob.DateReleased = xml.selectSingleNode("//Update/DateReleased").text;
		ob.Name = xml.selectSingleNode("//Name").text;
 
		var version = xml.selectSingleNode("//Version").text;
		//convert the version to a real number, parseInt chops
<pre lang="LANGUAGE"> off the decimal points so we cheat and * it by 1
		ob.Version = version * 1;
 
		return ob; //return our object
	},
	"AskUser" : function(updateInfo){
		//we will just display a simple yes/no messagebox, in your own version you could take this a step futher and display a pretty plus GUI for it, but thats outside the scope of this tutorial
 
		var message = "A new Script Update has been detected for " + updateInfo.Name + " Do you want to update?";
		var result = Interop.Call("User32", "MessageBoxW", 0, message, "Script Update", 4 /* MB_YESNO */);
 
		if(result == 6){ //a result of 6 means that the user pressed "yes"
			this.GetNewPlsc(updateInfo.PlscLocation);
		}
	},
	"GetNewPlsc" : function(url){ //function to download the plsc
		//now in your own scripts you should use MsgPlus.DownloadFile to download the plsc and new ActiveXObject("WScript.Shell").Run('"downloadlocation"'); to run it
		//but in this example we will simply execute the url
		new ActiveXObject("WScript.Shell").Run('"' + url + '"');
	}
}

and your OnEvent_Initialize

function OnEvent_Initialize(){
 
	var uc = new UpdateChecker();
 
}

There you have it kids :P, a complete update checker that only checks for a new version every 24 hours.

You can download the complete plsc here

http://random.thedt.net/tutorial/scriptUpdating/updateExample.plsc

I recommend that you expand on this and replace the MessageBox in UpdateChecker.AskUser with a nice Plus GUI and in GetNewPlsc replace that with MsgPlus.DownloadFile

I might create another tutorial later to explain how to implement those :)

This is my first time writing a tutorial like this, I would love feedback on it :P did you like/hate it?

If you want to repost this somewhere please contact me (either by leaving a comment or some other crazy way) so we can work out the details :)

A special thanks to Sunshine for helping me with the grammar and spelling issues :D

Leave a comment :)

One thought on “Messenger Plus Live Script updater”

  1. I read a lot of interesting articles here. Probably you spend
    a lot of time writing, i know how to save you a lot of time, there is an online tool that creates readable, google friendly posts in seconds, just type in google – laranita free content

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>