Setting scriptmaps: automation

The articles below describe how a Web Setup Project can be modified to set scriptmaps for the website it installs.  This article describes the script used to automate this modification.  The script is attached to "How to set scriptmaps in a Web Setup Project" in "Scripts.zip" and is named "AddSetScriptMaps.wsf."

This began as a simple JScript for experimenting with the Installer Automation Interface and was later adapted to add MSI entries to a short-term project.  When I prepared to add it to this posting, it looked like one of those awful examples which leave error handling as an "exercise," so I spent some time fleshing it out and making it more versatile.  The result is a script that can easily be modified to make additions to any MSI file.  Deletions and modifications are error conditions, but if you refer to the Automation Interface Documentation these would be easy to add.  Be careful; removing an entry from an MSI should be done only with great care.

If you examine AddSetScriptMaps.wsf, you'll see that it's a JScript file packaged in Windows Scripting Host format.  I've ID'd each section of the script primarily to make it easier to talk about; this provides an unambiguous means of referring to a segment of a script without having to break it up into multiple files.

The first section, <runtime>, is simply documentation.  If you run the script with a "/?" parameter this information will be neatly printed.  Any script included in a build process should implement "/?"; it provides a quick check to ensure that the tool does what you think it will.

The first script section, ErrorCodes simply defines the different values that could be returned in the command shell's "errorlevel" by running the script.  Each failure point returns a unique error code to make it simple to detect what went wrong.  Documenting these errors at the top of the script is logical since it's the one thing users are likely to be searching for after initial configuration is complete.

The Constants script section is just a hold-over from C/C++ coding style.  Unfortunately JScript does not support constants, but the section ID helps clarify the intent.

The "UtilityFunctions" script section is also self-explanatory.  It provides two routines for reporting errors and exiting the script, and one for returning the path to the directory the script resides in.  This is similar to the "%~f" parameter modifier in batch files.

The TableUpdater script section defines a JScript object which is instantiated with an MSI table name, a list of field names, and an Array of Arrays of strings defining the rows to be added.  (If you didn't know, or had forgotten, that JScript can be object oriented, check the docs.  It's not Ruby, but it's better than VBScript.)  The Table Updater includes two member functions; QueryString (which would be protected if JScript supported this) and UpdateTable, which adds each row defined in the Additions Array to the specified table.  While the parameter which describes the fields could have been eliminated, I decided to leave it in place.  Updating is done through a View, and it's entirely possible that future uses of this script might include updating a table with many fields, most of which are left empty.  Specifying the fields to use in the view makes this much simpler.  Let me know if you find that this was a poor choice.

The QueryString function simply constructs the query required for the call to OpenView.  Windows Installer uses a syntax similar, but not identical, to SQL.  It's documented in the Automation Interface Reference.  The format of a typical query would be similar to:

INSERT INTO 'AppSearch` (`Property_`, `Signature`) VALUES (?, ?)

UpdateTable opens a View with the query constructed above.  Then for each addition defined in its Array it creates a record with the appropriate number of fields, sets the value of each field, and calls Execute on the View with the record as a parameter.  This executes the parameterized query used in opening the View and inserts the record into the current transaction; it's not written to the MSI until committed later in the process.  After the last record is added, the View is closed.

The BinaryUpdater script section handles additions to the Binary table, so no table designation is required.  The value of the Name field and a name of a file containing the binary data are required for each addition.  The path name is considered to be relative to the location of the script itself, so if they are both in the same directory this field would be only the file name.  The UpdateBinaryTable method pushes the addition to the MSI.

UpdateBinaryTable creates a record with a single field and then calls SetStream on this field with the full path to the file name provided.  Note that the ScriptPath utility function was used in the constructor to generate this.  A View is opened on the binary table with the query:

INSERT INTO `Binary` (`Name`, `Data`) VALUES (this.Name, ?)

View.Execute is called with the record created above, which reads the stream from the file into to MSI file's pending transaction.  Finally the View is closed.

The MsiUpdater script section implements the main worker object.  It's constructed with the MSI name, and the single "public" method PerformUpdate takes an Array of TableUpdaters and Array of BinaryUpdaters, opens the MSI, calls each TableUpdater and BinaryUpdater to add their changes, and then commits the changes.

Opening the MSI requires creating a WindowsInstaller.Installer ActiveX object.  We open the database by calling the OpenDatabase method on this object, passing the path to the MSI and the mode parameter; in this case "transaction mode," which permits write access and transaction batching.  After the additions have been processed, database.Commit writes them to the MSI.  Strangely, there is no database.Close in the API, but the MSI is closed when the ActiveX object is released.

The UpdateDataSection contains only data, and is of particular interest because this is the section you'd change to modify the script's behavior.  I've broken out the Component GUID for aspnet_regiis.exe because you might be interested in updating to a different set of script maps in the future, requiring a different GUID.  I've also define the sequence #s of the three Custom Actions in the InstallExecuteSequence because these are dependant on how the default InstallExecuteSequence is produced by the Web Setup Project, and it's conceivable that this could require modification under some circumstances or in future releases.  The rest of the section is simply the definition of a tableUpdates Array and a binaryUpdates Array.  Each is an Array of the appropriate constructors.  The example provided should make it simple to define additional tables if you have similar but different needs.

Finally, the MainSection script section checks the script parameter, creates an MsiUpdater, calls PerformUpdate on it passing the data we defined in the previous section, then prints a completion message and returns a non-error result.

As stated in the first article in this series, if you add something like:

CScript "..\scripts\AddSetScriptMaps.wsf" "$(BuiltOuputPath)".

...to the PostBuildEvent property of your Web Setup Project, you'll have your MSI modified to install scriptmaps automatically when built.  You can also modify the script to make other additions.  If you're considering making additional modifications to Web Install Projects, I'd be interested in hearing about them (and whether this approach is helpful in making them.)

Published 13 September 06 09:04 by vank

Comments

No Comments
Anonymous comments are disabled

Search

Go

This Blog

Syndication

Page view tracker