How to create a self-updating Max Script or Macro Script

17 votes
How to create a self-updating Max Script or Macro Script

**DISCLAIMER:
This method can be used to place potentially very dangerous script code into your downloaded script without the users knowledge. It is the responsibility of the authors and users of the scripts to make sure they are aware of the script they are running comes from a reliable source.

ANY SCRIPTS you run on your machine whether they are Max scripts, Mel or VisualBasicScripts (VBS) can potentially contain malicious code which could damage your system.

I take no responsibility for what this method is used for.
DISCLAIMER OFF**

This system I suspect would work for any scripting host that lets you integrate ActiveX controls. I'm sure this method could be applied to both Mel and XSI scripts.

I came across a problem where I wanted to update everyone using one of my scripts with a newer (bug free) version of the script but I didn't want them to bother the users with re-downloading and installing the script from scratch. I thought there must be a way to imbed a update mechanism in a script to allow it to check for updates and download them if requested by the user.

Basically what I wanted to create was an 'update Tab' for my scripts that could ping a web server and check for an update and then download it.

The first component to the problem is finding a method to send a query to a website and convert the result into data you can use.
When using the ActiveX Internet Explorer control in maxscript you'll quickly notice that it's very difficult to access the raw data returned from the server. I found it almost impossible to get the contents of a webpage beyond the title and location.

There is however an activeX control called "Microsoft.XMLHTTP" which gives you complete access to the raw data returned from a http query.

So lets try applying this to a functional script (below is a breakdown of each stage):

---------------------------------------------------------------------------------------------------------
/*
# This script will ping scriptspot and get the
# raw data from the server.
*/

/* Http socket open */
rollout httpSock "httpSock" width:0 height:0
(
activeXControl port "Microsoft.XMLHTTP" setupEvents:false releaseOnClose:false
);
createDialog httpSock pos:[-100,-100];
destroyDialog httpSock;

/* HTTP socket connect */
httpSock.port.open "GET" "http://www.scriptspot.com" false;
httpSock.port.setrequestheader "If-Modified-Since" "Sat, 1 Jan 1900 00:00:00 GMT";
httpSock.port.send();

/* HTTP socket return */
print (httpSock.port.responsetext);
---------------------------------------------------------------------------------------------------------

This block of script simply creates a rollout definition with one "Microsoft.XMLHTTP" control object (port). The "releaseOnClose:false" will keep the control loaded even when the rollout is destroyed.

'rollout httpSock "httpSock" width:0 height:0
(
activeXControl port "Microsoft.XMLHTTP" setupEvents:false releaseOnClose:false
);'


The next line of script establishes the destination of where the http request will be sent:

'httpSock.port.open "GET" "http://www.scriptspot.com" false;'

The next line makes sure the script gets the latest version of the file from the web and not from the local internet cache:

'httpSock.port.setrequestheader "If-Modified-Since" "Sat, 1 Jan 1900 00:00:00 GMT";'

The next line attempts the actual connection:

'httpSock.port.send();'

Finally the last line of the script will print what data was returned through the http request:

'print (httpSock.port.responsetext);'

The second problem is then to make a function that can query a website for the latest version number. therefore we need to interpret the "responsetext" from the website into usable response. We're not going to get any version information from the www.scriptspot.com server so we need to create a page which can handle update queries:

---------------------------------------------------------------------------------------------------------
/*
# This script will ping a server and request
# what is the latest version number for itself.
*/

/* Http socket open */
rollout httpSock "httpSock" width:0 height:0
(
activeXControl port "Microsoft.XMLHTTP" setupEvents:false releaseOnClose:false
);
createDialog httpSock pos:[-100,-100];
destroyDialog httpSock;

/* HTTP socket connect */
httpSock.port.open "GET" "http://www.rodgreen.com/mxs/updater/rdt_update.php?itemID=936310564" false;
httpSock.port.setrequestheader "If-Modified-Since" "Sat, 1 Jan 1900 00:00:00 GMT";
httpSock.port.send();

/* HTTP socket return */
latestVersion = (execute (httpSock.port.responsetext))[2];
print latestVersion;
---------------------------------------------------------------------------------------------------------

Other then the URL the only change to this script is the line:

'latestVersion = (execute (httpSock.port.responsetext))[2];'

What this does is attempts to execute the responseText which will execute the string like a script. Since the result from the server is

#("936310564","5" as integer,"http://www.rodgreen.com/mxs/rdt_selectByNameV3.mcr", "rdt_selectByNameV3.mcr","$max/rdt_selectByName.ini ")

this therefore creates an array. The second entry in the array is the version integer '5'. From this query I now know the latest version of the script is 5 and if my current version is less then 5 then I'll need to perform an update.

On the server the PHP script I created looked like this:

---------------------------------------------------------------------------------------------------------
<?php
if ( $itemID == 936310564 )
{
echo("#(\"$itemID\",");
echo("\"5\" as integer,");
echo("\"http://www.rodgreen.com/mxs/rdt_selectByNameV3.mcr\",");
echo("\"rdt_selectByNameV3.mcr\",");
echo("\"\$max/rdt_selectByName.ini\")");
}
?>
---------------------------------------------------------------------------------------------------------

From there I created a much more sophisticated structure of functions for handling the requests and version queries as well as saving the 'responseText' data into a file then executing it.

---------------------------------------------------------------------------------------------------------
rollout httpSock "httpSock" width:0 height:0 (activeXControl port "Microsoft.XMLHTTP" setupEvents:false releaseOnClose:false);
createDialog httpSock pos:[-100,-100];destroyDialog httpSock;

struct rdt_updateObj
(
itemID,
version,
url,
path,
ini
);

struct rdt_updateModule
(

function geturlData inputURL =
(
if httpSock == undefined then
(
rollout httpSock "httpSock" width:0 height:0 (activeXControl port "Microsoft.XMLHTTP" setupEvents:false releaseOnClose:false);
createDialog httpSock pos:[-100,-100];destroyDialog httpSock;
);

httpSock.port.open "GET" inputURL false;
httpSock.port.setrequestheader "If-Modified-Since" "Sat, 1 Jan 1900 00:00:00 GMT";
httpSock.port.send();
return (httpSock.port.responseText);
),
function downloadObj sourceObj outFile =
(
local outfileStream = createFile outFile;
if outfileStream == undefined then return undefined;
format (rdt_updateModule.geturlData sourceObj.url) to:outfileStream;
close outfileStream;

return outFile;
),

function performUpdate oldObj newObj =
(
rdt_updateModule.downloadObj newObj oldObj.path;
filein oldObj.path;
),


function getAttrib inputAttrib inputString =
(
local startAttrib = (findString inputString ("<"+inputAttrib as string+">"));
local endAttrib = (findString inputString ("</"+inputAttrib as string+">"));

if startAttrib == 0 or endAttrib == 0 then return undefined
startAttrib += ("<"+inputAttrib as string+">").count;

return (substring inputString startAttrib (endAttrib - startAttrib));
),

function getURLobj inputURL =
(
local collectedXML = (rdt_updateModule.geturlData inputURL);

if collectedXML == "" then return undefined;

local returnUpdateObj = rdt_updateObj();
returnUpdateObj.itemID = execute (rdt_updateModule.getAttrib #itemID collectedXML)
returnUpdateObj.version = execute (rdt_updateModule.getAttrib #version collectedXML)
returnUpdateObj.url = (rdt_updateModule.getAttrib #url collectedXML)
returnUpdateObj.ini = (rdt_updateModule.getAttrib #ini collectedXML)

return returnUpdateObj;
),

function checkUpdate currentObj reEvaluate:false =
(

local updateInfo = (rdt_updateModule.getURLobj ("http://www.rodgreen.com/mxs/updater/rdt_update.php?itemID=" + currentObj.itemID as string));
if updateInfo.version > currentObj.version then rdt_updateModule.performUpdate currentObj updateInfo;
if reEvaluate then (filein currentObj.path);

--if updateInfo.version == currentObj.version then ();
--if updateInfo.version < currentObj.version then ();
);

);

---------------------------------------------------------------------------------------------------------


To see this script in action try downloading and installing Select By Name V3, http://www.rodgreen.com/mxs/rdt_selectByNameV3.mcr