Peter Hinchley

Creating a Custom MSI to Integrate Twhirl with an Internally Hosted StatusNet Deployment

Tagged: msi, microblogging

In this article, which follows on from my previous post, I explain how to configure Twhirl to integrate with a standalone instance of StatusNet. I also explain how to package Twhirl so that it can be silently deployed across an enterprise using a tool such as Microsoft SCCM 2007. A significant section of the article will focus on Windows Installer packaging techniques, and in particular, the steps required to create a custom MSI using Microsoft Orca.

Pre-requisites

To perform the tasks described in this tutorial you will need the following tools:

  • Orca. Used to edit MSI files and generate MSI transforms. It is officially available as part of the Windows Server 2008 SDK. The only component of the SDK that needs to be installed to access Orca is Win32 Development Tools (listed under Windows Development Tools, under Development Tools in the installation wizard). Installing the Win32 Development Tools will copy Orca.msi to C:\Program Files\Microsoft SDKs\Windows\v6.1\Bin. Orca must then be manually installed using the MSI. This will create Orca.exe under C:\Program Files\Orca. If this all seems a bit long winded, you can just download Orca.msi from here.

  • Resource Hacker. A freeware utility used to view, modify, rename, add, delete and extract resources in 32-bit Windows executables and resource files. It is available as a free download.

  • Adobe AIR. A cross-platform runtime environment developed by Adobe for building and executing rich internet applications that are written in Flash, Flex, HTML or Javascript. The Twhirl client is an AIR application and requires the AIR runtime environment. You can download the Adobe AIR installer from the Adobe web site.

    As we intend to deploy Adobe AIR silently, it is important that we delete the NTFS stream that may get associated with the executable when it is downloaded. I won't digress and launch into a discussion of streams, but if Windows displays a security warning dialog when launching the executable, it is probably due to the presence of zone information stored in an NTFS stream linked to the installer. You can delete the NTFS stream using a utility named Streams from Microsoft. After downloading the utility, run the extracted streams.exe as follows:

    streams -d AdobeAIRInstaller.exe

    If an NTFS stream was deleted, you will see the following message:

    Deleted :Zone.Identifier:$DATA

    You will now be able to launch the installer without the intrusion of a security warning dialog.

  • Twhirl. The desktop client we will use to connect to StatusNet. You can download the client for free from the Twhirl web site. Note: This article assumes version 0.9.4.

  • An empty MSI. A "starter" MSI that provides the mandatory structure of an MSI without installing an application. You can download the empty MSI that I used for this project. The MSI will be built upon to apply the Twhirl customisations required to integrate with StatusNet.

To ensure we don't encounter any unexpected surprises, do not install any of the tools listed above until instructed within the article.

Integration with StatusNet

The Twhirl client supports 5 different microblogging services, including Laconica (the precursor of StatusNet). When you first start Twhirl, you are asked to select the service you will access. Unfortunately, when you select Laconica, it is assumed that you will be logging into the public instance of the service available at http://identi.ca. This is not what we want. Instead, we want the client to talk to our private instance of StatusNet (refer to the previous post for further details).

To understand how and where the URL to identi.ca was specified within the Twhirl client, I used Sothink Flash Decompiler. This software can be used to view the source code of a compiled flash (swf) application. Upon reviewing the code I could see that the developers did two things:

  1. Hard coded the identi.ca URL in only a single location. This acted as the default address for the Laconica service.
  2. Designed the application to read the identic.ca URL from a properties file. This suggested that the default value was used to populate a properties files, and that once set, the properties file could be overwritten to force Twhirl to use a different URL.

This is perfect. It means that we don't need to modify and recompile the Twhirl application (which should be avoided at all cost, for the change would need to be reapplied, and the software recompiled, with each new release of the client).

The next step was to discover the location of the Twhirl properties file. You could use a tool like Process Monitor to guide you to the right location, but in this instance I took the gamble that it was located within the user's roaming profile, and sure enough, it was: C:\Users\user\AppData\Roaming\de.makesoft.twhirl.0EA062BC275E7ED1E6EC3762EFFD73C7158ADF33.1\LocalStore\accounts.xml (where user is to be replaced with the account name of the logged on user).

I opened the file, and searched for laconicaSrv, the property that I saw within the decompiled source code. The search yielded the following line:

identi.ca

I replaced identi.ca with status.network (which is the name of my stand-alone instance of StatusNet), saved the file, restarted Twhirl, and surprise, surprise, the setting was retained, and the client connected to the local StatusNet service. Perfect.

At this point it is worth noting that the Twhirl client communicates with StatusNet through an API. Furthermore, Twhirl expects the API to be accessible in the form http://server/api. As I explained in the previous article, to access the API via this URL require the implementation of an .htaccess rule. Refer to the previous post for further instruction.

Deployment Overview

The previous sections provide an overview of the steps I followed to manually configure Twhirl to talk to StatusNet. I now want to explain the steps required to silently deploy the Twhirl client so that it is preconfigured to point to a stand-alone StatusNet instance. This effectively involves three steps:

  1. Install Adobe AIR.
  2. Install Twhirl.
  3. Replace the Twhirl properties file with a custom version that includes the URL to the internal instance of StatusNet.

In my case at least, these tasks were complicated by the fact that in our enterprise, software is targeted at computers, not users. This means that software is installed under the context of the local system account instead of the context of the logged on user. This is an important distinction because the properties file that we need to deploy must be written into a user's roaming profile, which isn't easily achieved when the software isn't installed by an end-user. There may not even be a user logged on when the software is installed, so how is the installer to know what roaming profile to modify, especially in a corporate environment where multiple users may share the same computer? It's all a bit daunting. However, the solution comes in the form of an advertised shortcut.

Windows Installer Advertised Shortcuts

What is an advertised shortcut? It's a Windows Installer construct that is used to check the health of an application when clicked, and if necessary, force the installation/repair of the application immediately prior to the point of execution. That was a mouthful. So what does it mean for us? Well, let me put it like this:

  1. We install Twhirl, and then we use a custom MSI to create an advertised shortcut to the new application in the Start Menu. We specifically configure the advertised shortcut to be dependent upon the existence of the Twhirl properties file.
  2. When a user clicks on the advertised shortcut, if the properties file does not exist (and it won't, for it is only created by Twhirl after the application is launched), Windows Installer will initiate a repair of the MSI under the context of the active user account, and the MSI will copy the custom properties file to the user's profile.
  3. Immediately after the properties file is deployed, Twhirl will start. When the user selects the Laconica service, the client will read the URL of the internal StatusNet instance from the custom properties file that was just deployed.

There are three benefits to the use of an advertised shortcut:

  1. Just-in-time deployment of the custom properties file.
  2. The custom properties file is self-healing. If the properties file should ever be deleted, when Twhirl is launched via the advertised shortcut, the MSI will be invoked, and the properties file reinstated.
  3. Immediate availability to end-users. If Twhirl installs in the background whilst a user is logged on, the user does not need to log out and log back in for the MSI to be invoked, and hence for the properties file to be deployed. This would be required if we used Active Setup as a means of repairing the application (you can read more about Active Setup in this earlier post).

Creating the Installation Script

Now that we've discussed the background of this project, let's begin by creating a VBScript named setup.vbs that we will use to install Adobe AIR, Twhirl, and the custom MSI that will deploy our tailored properties file:

Set oShell = CreateObject("WScript.Shell")
Set oFSO = CreateObject("Scripting.FileSystemObject")
sPath = Replace(WScript.ScriptFullname, "\" & WScript.ScriptName, "")

oShell.Run Chr(34) & sPath & "\AdobeAIRInstaller.exe" & Chr(34) & " -silent -eulaAccepted " & Chr(34) & sPath & "\twhirl-0.9.4.air" & Chr(34), 0, 1
oFSO.CopyFile sPath & "\accounts.xml", "C:\Program Files\Twhirl\accounts.xml"
oFSO.CopyFile sPath & "\config.msi", "C:\Program Files\Twhirl\config.msi"
oShell.Run "msiexec.exe /i " & Chr(34) & "C:\Program Files\Twhirl\config.msi" & Chr(34) & " /qb", 0, 1

The script installs Adobe AIR and Twhirl silently. It then copies a preconfigured accounts.xml file (which contains the path to our standalone instance of StatusNet), and our custom MSI (which has been named config.msi) to the Twhirl installation folder. Finally, it installs the MSI, which creates the advertised shortcut necessary to copy the accounts.xml file to the user's roaming profile on demand.

A couple of other points worth noting include:

  1. The script defines a variable named sPath and sets it to the fully qualified path of the source media. This ensures that the script is not dependent on the exact location of the source (i.e. it is not hard-coded). It also ensures that the media is accessible even when the installation script is executed from a different working directory.
  2. The Chr() method is used to insert ASCII character 34 (double quotation marks) around folder paths that may include spaces.
  3. The 0, 1 at the end of the Run method ensures the command shell window associated with the executing script is hidden, and the script pauses execution until the running program completes.

For the installation process to complete successfully, the install script (setup.vbs), AdobeAirInstaller.exe, twhirl-0.9.4.air, accounts.xml, and config.msi, must all be located within a single directory. In a corporate environment, this may be a network share accessible to your software deployment toolset.

Custom Properties File

Before we can use the setup script, we must create accounts.xml and config.msi. Let's start with the accounts.xml properties file. As previously discussed, this is equivalent to the default properties file created when first launching Twhirl and creating a Laconica account, with two exceptions. Firstly, identi.ca is replaced with status.network (where status.network is the name of our internal StatusNet server). The second change requires you to set the StatusNet account name that will be used when logging into Twhirl. This is necessary as you cannot specify a destination server without also including a username. This poses a small problem, for the username will be different for each user of the software. To resolve this issue, we can write a small VBScript that will parse a pre-staged accounts.xml file, replacing a dummy username with the username of the logged on user that has clicked the advertised Twhirl shortcut. The script would only be called during the MSI repair process (i.e. if accounts.xml did not exist within the user's profile). The script below, which I've named parse.vbs, will perform this task:

sProperties = Session.Property("CustomActionData")
aProperties = Split(sProperties, ",")

sUserName = aProperties(0)
sDestPath = aProperties(1)
sSourcePath = aProperties(2)

Set oFSO = CreateObject("Scripting.FileSystemObject")

Set oInputFile = oFSO.OpenTextFile(sSourcePath & "\accounts.xml")
Set oOutputFile = oFSO.OpenTextFile(sDestPath & "\accounts.xml", 2, True)

sInputFile = oInputFile.ReadAll
sOutputFile = Replace(sInputFile, "REPLACEME", sUserName, 1, -1, 1)
oOutputFile.Write sOutputFile

oOutputFile.Close
oInputFile.Close

The script looks for the text REPLACEME within the accounts.xml file we copy to the Twhirl installation folder. It then replace this keyword with the username of the logged on user, and writes the modified file into the user's profile.

To summarise, the modified accounts.xml file is equivalent to the default properties file created when launching Twhirl and connecting to a Laconica service, with the exception of two changes: the laconicaSrv property has been set to status.network and the username property has been set to REPLACEME.

I will discuss the script in further detail toward the end of the article, explaining for instance, why I use a property named CustomActionData.

Twhirl Shortcut

Before we create our MSI, we need to think about the shortcut that the MSI will create in the Start Menu. We want it to look good, and that means the shortcut should display the Twhirl icon. So how do we get it? Well, you need to extract it from the Twhirl executable (or you can cheat, and just download the icon I've already extracted). To extract the icon, install both Twhirl and Resource Hacker on a test computer, and then using Resource Hacker, open C:\Program Files\Twhirl\Twhirl.exe. Expand the Icon folder, expand 104, select the icon, and from the Action menu select Save [Icon: 104: 0], and provide a name of Twhirl.ico. The saved resource file will only include the Twhirl icon and it will be used to import the icon into our MSI.

Creating the Custom MSI

It's now time to create our custom MSI. To start the process, grab the empty MSI MSI that I've prepared, and copy it to the folder containing your other source media, renaming it to config.msi. Next, install Microsoft Orca (using the steps previously described). Now proceed with the steps outlined below:

  1. Open config.msi with Orca.

  2. Navigate to the Property table and change ProductName to Twhirl. Add a new row and set Property to ALLUSERS and Value to 1. This will ensure that when the MSI is installed, the Twhirl shortcut will be added to the all users Start Menu, instead of the user-specific Start Menu of the local system account (which is the account that will be used to install the MSI). This step is particularly important, for if ALLUSERS is not set, the MSI will be deployed as a "per-user" deployment, and the shortcut that is created will not only be invisible to all other users (as it will be hidden away in the Start Menu of the local system account), but equally important, the shortcut will not be advertised to other users, and hence will fail to launch when clicked.

    Whilst we are in the Property table, it is worth nothing that the value of ProductCode should be unique for each MSI. So if you plan to reuse the MSI template that I've provided for other projects, make sure you change the GUID. In fact, while you are at it, also change the GUID of each component in the Component table, and the Package Code GUID that is accessible in Orca via the Summary Information option of the View menu.

    Before leaving the Properties table, add another new row, setting Property to ParseAccountsXML and Value to NULL. I'll explain the purpose of this property in a later step.

  3. Add a new row to the Directory table and set Directory to ProgramFilesFolder, Directory_Parent to TAGRETDIR and DefaultDir to . (a period). Repeat this step to create another row, but this time, set Directory to AppDataFolder. The first directory item acts as a reference to C:\Program Files. The second is a reference to C:\Users\user\AppData\Roaming, where user is replaced with the account name of the user performing the installation.

    Add another row to the table and set Directory to TwhirlInstallFolder, Directory_Parent to ProgramFilesFolder and DefaultDir to Twhirl:.. This will instruct the MSI to create a new folder under C:\Program Files named Twhirl. The DefaultDir property includes two components separated by a colon. The first component is the name of the new directory (i.e. the target directory). The period after the colon reflects the source directory used by the installer when copying files to the target directory. By setting it to a period, we instruct the installer to use the folder containing the MSI as the source folder for any content copied to the Twhirl target folder.

    Add another row and set Directory to TwhirlAppDataFolder, Directory_Parent to AppDataFolder and DefaultDir to de.makesoft.twhirl.0EA062BC275E7ED1E6EC3762EFFD73C7158ADF33.1:.:.. This rather long directory name will be created under the user's roaming profile, and forms part of the path pointing to the Twhirl properties file.

    Add another row and set Directory to LocalStore, Directory_Parent to TwhirlAppDataFolder and DefaultDir to Local Store:.. This row will force the creation of a folder named Local Store as a sub-directory of the previous folder. This is the directory that will hold the Twhirl properties file.

  4. Add a new row to the File table. Set File to Twhirl.exe, Component_ to DEFAULTCOMPONENT, FileName to Twhirl.exe, FileSize to 95232 (which is the size in bytes of Twhirl.exe), Attribute to 0, and Sequence to 1. This configuration item is actually rather tricky. It's necessary because an advertised shortcut must point to a file that is installed by the MSI. This is fine in most situations, but Twhirl.exe is not actually installed by this MSI; it is deployed when we install Twhirl-0.9.4.air. So what's going on here? Well, by copying the MSI to the Twhirl installation directory (as we do in setup.vbs), when the MSI is launched, it references the pre-existing instance of Twhirl.exe within C:\Program Files\Twhirl, which it then wants to deploy to the very same directory. The end result is that it does nothing, and we don't need to redundantly store Twhirl.exe inside our MSI. Nice.

    Add another row to the File table and set the File property to accounts.xml, Component_ to CONFIGCOMPONENT, FileName to accounts.xml, FileSize to 3956 (the size in bytes of the default accounts.xml), Attributes to 0, and Sequence to 1.

  5. Navigate to the Media table and increment the LastSequence number of the existing row from 1 to 2. This is required as we reference two files in the File table.

  6. Navigate to the Component table. Edit DEFAULTCOMPONENT and set Directory_ to TwhirlInstallFolder, Attributes to 18 and KeyPath to Twhirl.exe. We change the Directory_ property so that the MSI attempts to deploy Twhirl.exe to the Twhirl installation folder, which as we've already discussed, is where it already resides. Setting the Attributes to 18 ensures that the file does not get removed if we uninstall the MSI. This is important; since the MSI doesn't deploy Twhirl.exe, it should not remove the file when we uninstall the MSI.

    Add a new row to the Component table. Set Component to CONFIGCOMPONENT, set the ComponentId to be the same as that of the DefaultComponent, but change at least one hexadecimal digit to make it unique. Set Directory_ to LocalStore, Attributes to 2, and KeyPath to accounts.xml.

  7. To link the new component with the existing feature, add a new row to the Feature table. Set Feature_ to DEFAULTFEATURE and Component_ to CONFIGCOMPONENT.

  8. Add an Icon table to the MSI. Add a new row to the Icon table and set the Name property to Twhirl.exe and load Twhirl.ico (which we created earlier) into the Data property. It's essential that you set the Name property to Twhirl.exe, as it must exactly match the name of the shortcut target.

  9. Add a Shortcut table to the MSI. Add a new row to the Shortcut table and set Shortcut to 1 and Directory_ to ProgramMenuFolder. This will ensure the shortcut is added to the all users Programs folder on the Start Menu. Set Name to Twhirl and Component to DEFAULTCOMPONENT. The KeyPath property of the DEFAULTCOMPONENT will be used as the target of the shortcut. Set Target to DEFAULTFEATURE. When the shortcut is clicked, Windows will ensure that all elements of the DEFAULTFEATURE are installed. If any element is missing (e.g. accounts.xml) an application repair will be initiated (which will force the customised accounts.xml file to be deployed). Finanlly, set the Icon property to Twhirl.exe (which is the name of the icon in the Icon table).

  10. Add a Binary table to the MSI. Add a new row to the Binary table. Set the Name property to ParseAccountsXML and load parse.vbs (which we prepared earlier) into the Data field. This embeds the parse.vbs script into the MSI.

  11. Add a CustomAction table to the MSI. Add a new row to the CustomAction table and set Action to ParseAccountsXML and Type to 1030. This value is formed by adding 6 to 1024. The 6 indicates the custom action references a VBScript file stored in the Binary table and the 1024 forces the deferred execution of the script, a topic I will return to shortly. Finally, set Source to ParseAccountsXML. This is a pointer to the script in the Binary table.

    Add another row to the table. Set Action to SetCustomActionProperties, Type to 51, Source to ParseAccountsXML and Target to [LogonUser],[LocalStore],[TwhirlInstallFolder].

    These steps require a little explanation. The purpose of the ParseAccountsXML custom action is to call parse.vbs, which as we've previously discussed, is required to insert the calling user's name into the Twhirl properties file. Now there is something you need to know about custom actions: they can either execute immediately, or the execution can be deferred. What does this mean? Well, in very simple terms, an MSI is processed in two phases. The first phase is a preparation phase, where all the changes required by the installer are determined. The second phase is where the changes are actually applied to the system. By default, custom actions are configured for immediate execution, and are applied in the first phase, where no system changes are permitted. If we want our script to actually write changes to the system, we need to force it to be executed during the second phase. Simple enough; but there is a further complication. When the execution of a custom action is deferred, it executes in a process space that has significantly reduced access to the properties that are available to the installer. For instance, properties we set in our MSI, such as the LocalStore directory path, are not visible to parse.vbs when the execution of the script is deferred. Fear not, there is a way around this dilemma.

    To expose properties to a deferred custom action, it is necessary to create a new property with the same name as the custom action that requires the properties. As we've called our custom action ParseAccountsXML, we need to create a property named ParseAccountsXML, which is exactly what we did in step 2. We then need to create another custom action that assigns the properties required by our script to the new property. This is why we created a second custom action named SetCustomActionProperties. Setting the action type to 51 informs the MSI that it should view the item assigned to the Source column as the name of a property in the Properties table, and that it should assign to the property the value in the Target column. By setting the Target column to [LogonUser],[LocalStore],[TwhirlInstallFolder] we are effectively assigning three existing properties, comma separated, to a new property. What makes it special, is that the new property has the same name as our custom action, and this allows us to access the new property from our custom action script. If you take a look at parse.vbs above, you will see that we use the following line to extract the value of the new property:

    sProperties = Session.Property("CustomActionData")

    This sets sProperties to a comma separated string, which we then split into three separate components which are accessed as needed by the script. It's all pretty nifty.

  12. The final step in creating our MSI is to force the execution of the custom actions. This step will ensure that the accounts.xml file is updated with the logged on user's name. Navigate to the InstallExecuteSequence table and add a new row. Set Action to SetCustomActionProperties and Sequence to 6149. Add another new row, and set Action to ParseAccountsXML and Sequence to 6150. As the sequence number for the SetCustomActionProperties custom action is lower than that of ParseAccountsXML, the action will execute first, and initialise the property that is required by parse.vbs. Both actions are assigned a sequence number greater than 6100 so they are processed toward the very end of the execution sequence (although, as previously discussed, the execution of ParseAccountsXML is deferred; in fact it only runs inside the last step in the installer sequence: InstallFinalize).

  13. You can now save and close the MSI.

Assembled Product

At the end of this rather long-winded process, we should have all of the components required to assemble our setup package (which you can also download). This includes:

  • AdobeAIRInstaller.exe
  • twhirl-0.9.4.air
  • Config.msi
  • accounts.xml
  • setup.vbs

Copy the files to a folder of your choosing, open a command prompt as an administrator, or better yet, as the system account (refer to my previous article for instructions on opening a command prompt as the local system account), and then install the package using the following command:

cscript setup.vbs

The script should silently install the necessary components, and after a short delay, a shortcut to Twhirl should appear in your Start Menu. If you click on the shortcut, a Windows Installer window will briefly appear, as an application repair is initiated, and the custom properties file is deployed to your roaming profile. Shortly after, Twhirl will start, and with any luck, it will attempt to log you into status.network with an account name that matches your logged on username.

That's it. I hope you found this article useful. Let me know if you've any questions.

Your Say