An Improper Update Experience

One of the changes being implemented along with the new packaging system I’ve designed for my work deals with how updates to our product are applied. Before I was even aware of WiX, I knew what Windows Installer patches (.msp’s) were, and that they would be an ideal choice for providing updates to our customers.

While the efficiency and practicality offered by the *.msp technology make it an ideal choice, it would all be for naught if the user experience was also not as positive. If you are in the business of providing software to folks who pay money for it, like I am, you will agree with me that the default user experience offered by *.msp’s is not acceptable. In this article, I will go over how to correct that using WiX.

This article isn’t meant to be an introduction to *.msp’s, or even *.msp creation using WiX by any means. Some good introductory text on *.msp’s can be found on the MSDN. Concerning WiX, Peter Marcu has an adequate posting on patch creation using WiX.

To start things off, let’s examine what the update experience is like when running a stock *.msp that was made using WiX. Here’s the scenario:

You’ve just been hired by a secret, black budgeted government organization to design a software interface to a newly developed time machine. Being the genius that you are, you had a working version of the interface up and running in only four days time! At first the product was met with only skepticism from the scientific community, being that it was a critical component that only ran on Windows. Eventually, however, the skepticism gave way to impatience, which allowed the product’s use to proceed to production.

After installing the software on the Windows-operated time machine, you realized a few days later that you made a critical error! Due to the pressure incurred by stringent deadlines, a mistake was made in the section of code that handled input from the operator of the time machine, a mistake that would have inevitably caused the machine to travel backwards in time instead of forwards; or conversely, forwards in time instead of backwards. You need to update your software immediately! How do you go about accomplishing this?

(Note about the following: There used to be some pictures here showing the default images that would be displayed when trying to run a stock .msp file made in WiX, however those pictures got lost with the server that died with them. In the interest of my time, I’m not reproducing them.
The first picture displayed here shows a very confusing looking dialog that’s identical to what you get when first installing the application, for the most part.)

Well heck, that doesn’t look like it’s going to patch an existing installation. At first glance, the admin/user may confuse this dialog with the one that appears when first installing the application, which is not good, and may cause the admin/user to begin worrying about the whole deal.

If you look closely, you will notice that this appears to be the lead-in dialog to the Change/Repair/Uninstall screen. This is an accurate assumption, as clicking the Next button brings you to this screen:

(Note: The second picture here displayed a dialog displaying the three choices (Change, Repair, Uninstall) to the user, again identical to what you would get when clicking the Change button in the Add/Remove Programs folder.)

Not good! The user is presented with three choices here. That means the user has a 2 in 3 chance of making the wrong choice (well, he’d have to be an idiot to hit Remove frankly). Even a 1 in 2 chance of making the correct choice is not good enough, we need 100% certainty.

To proceed with the patching, the user would have to click on the Repair button, which leads to this screen:

(Note: The final picture used to show a stock a dialog with a progress bar indicating that it was repairing the installation, or something similar to that. Something not ideal.)

Disgusting. Unless the user was quite experienced with *.msp’s, they would undoubtedly be scratching their heads at this point. A user who lacked experience would have probably made a mistake (it’s always a good idea to assume the worst in people), or given up and called support by this point!

Besides, even if the user did knew what to do, the above looks shabby and unprofessional. It should involve less steps and clearly present itself as an update to the user.

Three dialogs are way too many; fortunately, we can easily cut down on the amount of cruft here if we start the patching process by executing msiexec like so:

msiexec /update patch.msp REINSTALL=ALL REINSTALLMODE=omus

Obviously, you cannot simply double click on the *.msp in some odd fashion in order for this to occur. This requires explicit calling either via the command line or some other program. Your best bet for a friendly update experience is to embed the *.msp in an executible that provides the command shown above to the *.msp upon extraction. There are many ways to achieve this. Good luck.

Going back to our walk through the update experience: The command listed above will result in us starting immediately in REINSTALL mode, which presents us with the screen shown below.

(Note: There used to be a picture here showing a dialog indicating that it was “Resuming installation…” and required the user to hit an “Install” button. Again, this picture was lost with the server, it should be very easy to duplicate yourself. The point here was that the dialog did not look like something you would show to someone informing them they were about to begin a patching process.)

Resuming? An Install button? Dear Lord, that is horrid.

Making the Improper Proper

Enough games, we need to make it so our installer actually appears as if it is going to update our system when we happen to run a *.msp through msiexec.

How to do this? The solution is simple: we must make use of the PATCH property, a property most obviously set during the upgrade of an install initiated by a *.msp. Using this property, we can conditionally show and hide various parts of the installation dialog in order to provide the user with an installer that has a look and feel that properly reflects what its current mode of operation is.

We can tell from the screen shown above that the dialog we are going to want to override is the one that deals with resuming installations. With WiX, this is the ResumeDlg dialog. With that in mind, remove the reference to ResumeDlg in your custom UI set and add a new dialog entitled ResumeAndUpdateDlg.

Use the code found in ResumeDlg.wxs as a base for the new dialog. Modifications to already defined controls within the dialog are going to apply to their conditional elements. In addition to the modifications, we are going to add a few new controls; specifically, two buttons and a few labels.

Product.wxs

.
.
<Dialog Id="ResumeAndUpdateDlg" Width="370" Height="270" Title="!(loc.ResumeDlg_Title)">
        <!-- First, we will modify the existing Install w/o ElevationShield button -->
        <Control Id="Install" Type="PushButton" ElevationShield="yes" X="236" Y="243"
                    Width="56" Height="17" Default="yes" Text="!(loc.ResumeDlgInstall)"
                    Hidden="yes">
            .
            .
            <!-- Keep the rest the same until... -->
            .
            .
            <Condition Action="show">ALLUSERS AND NOT PATCH</Condition>
        </Control>
        <!-- We then modify the Install w/ ElevationShield button -->
        <Control Id="InstallNoShield" Type="PushButton" ElevationShield="no" X="236" Y="243"
                    Width="56" Height="17" Default="yes" Text="!(loc.ResumeDlgInstall)"
                    Hidden="yes">
            .
            .
            <!-- Again, keep the rest the same until... -->
            .
            .
            <Condition Action="show">NOT ALLUSERS AND NOT PATCH</Condition>
        </Control>
        <!-- Next, we will add a new button: -->
        <Control Id="Update" Type="PushButton" ElevationShield="yes" X="236" Y="243"
                    Width="56" Height="17" Default="yes" Text="Update"
                    Hidden="yes">
            <Publish Event="SpawnWaitDialog" Value="WaitForCostingDlg">
                    CostingComplete = 1
            </Publish>
            <Publish Event="EndDialog" Value="Return">
                    <![CDATA[OutOfDiskSpace <> 1]]>
            </Publish>
            <Publish Event="SpawnDialog" Value="OutOfRbDiskDlg">
                    OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0
                    AND
                    (PROMPTROLLBACKCOST="P" OR NOT PROMPTROLLBACKCOST)
            </Publish>
            <Publish Event="EndDialog" Value="Return">
                    OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0
                    AND
                    PROMPTROLLBACKCOST="D"
            </Publish>
            <Publish Event="EnableRollback" Value="False">
                    OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0
                    AND
                    PROMPTROLLBACKCOST="D"
            </Publish>
            <Publish Event="SpawnDialog" Value="OutOfDiskDlg">
                    (OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 1)
                    OR
                    (OutOfDiskSpace = 1 AND PROMPTROLLBACKCOST="F")
            </Publish>
            <Condition Action="show">ALLUSERS AND PATCH</Condition>
        </Control>
        <!-- And another new button, this one without ElevationShield... -->
        <Control Id="UpdateNoShield" Type="PushButton" ElevationShield="no" X="236" Y="243"
                    Width="56" Height="17" Default="yes" Text="Update"
                    Hidden="yes">
            <Publish Event="SpawnWaitDialog" Value="WaitForCostingDlg">
                CostingComplete = 1
            </Publish>
            <Publish Event="EndDialog" Value="Return">
                <![CDATA[OutOfDiskSpace <> 1]]>
            </Publish>
            <Publish Event="SpawnDialog" Value="OutOfRbDiskDlg">
                OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0
                AND
                (PROMPTROLLBACKCOST="P" OR NOT PROMPTROLLBACKCOST)
            </Publish>
            <Publish Event="EndDialog" Value="Return">
                OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND PROMPTROLLBACKCOST="D"
            </Publish>
            <Publish Event="EnableRollback" Value="False">
                OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 0 AND PROMPTROLLBACKCOST="D"
            </Publish>
            <Publish Event="SpawnDialog" Value="OutOfDiskDlg">
                (OutOfDiskSpace = 1 AND OutOfNoRbDiskSpace = 1)
                OR
                (OutOfDiskSpace = 1 AND PROMPTROLLBACKCOST="F")
            </Publish>
            <Condition Action="show">NOT ALLUSERS AND PATCH</Condition>
        </Control>
        <!-- No changes until we hit the Description and Title controls -->
        <Control Id="Description" Type="Text" X="135" Y="80"
                    Width="220" Height="60" Transparent="yes" NoPrefix="yes"
                    Text="!(loc.ResumeDlgDescription)">
            <Condition Action="hide">PATCH</Condition>
        </Control>
        <Control Id="Title" Type="Text" X="135" Y="20"
                    Width="220" Height="60" Transparent="yes" NoPrefix="yes"
                    Text="!(loc.ResumeDlgTitle)" >
            <Condition Action="hide">PATCH</Condition>
        </Control>
        <!-- Finally, we add our own update-only textual controls. -->
        <Control Id="UpdateDescription" Type="Text" X="135" Y="80"
                    Width="220" Height="60" Transparent="yes" NoPrefix="yes"
                    Text= "Click on the Update button to proceed with updating your [ProductName] installation to version [ProductVersion]."
        >
            <Condition Action="hide">NOT PATCH</Condition>
        </Control>
        <Control Id="UpdateTitle" Type="Text" X="135" Y="20"
                    Width="220" Height="60" Transparent="yes" NoPrefix="yes"
                    Text="{\WixUI_Font_Bigger}Update [ProductName] to [ProductVersion]"
        >
            <Condition Action="hide">NOT PATCH</Condition>
        </Control>
      </Dialog>
      <InstallUISequence>
        <Show Dialog="ResumeandUpdateDlg" Before="ProgressDlg">
            Installed AND (RESUME OR Preselected)
        </Show>
      </InstallUISequence>
.
.

Contrary to what you see in the code provided above, you should always use localized strings. Because the above code was written for demonstrative purposes only, I hope you will excuse me from not putting forth the effort required by the use of localized strings.

After you add this code, recompile your *.msi and then rebuild your patch. Everything will look the same until you load your *.msp, which gives you the following screen:

(Note: As I’ve already mentioned in this article, the pictures have gone missing. What used to be displayed here was a proper looking patch dialog informing you of the upgrade that was about to occur, as well as the “Install” button being replaced with a button named “Upgrade”, among other enhancements that greatly improve user experience with the whole ordeal).

Now that’s an update screen, and one that leaves no room for confusion. Clicking on the Update button will direct the user to a screen sporting only a progress bar where the actual patching will occur.
The above interface changes, in combination with a single double-click update process via an executable with the embedded *.msp, will deliver unto your users an update experience that is much more acceptable in this humble writer’s opinion.

 

EDIT (9/26/2011) : This article is a little bit old. The dates are misleading because they reflect when I uploaded my articles to this new server; they don’t reflect the true creation date. A much simpler solution (one that is used in practice actually) for integrating your build scripts with WiX can be found at: http://badecho.com/2011/09/automating-wix-with-msbuild/

I’ve recently been working on designing and implementing a new automated build process for our main products at the company I work for. This challenging, yet interesting, problem manifests itself to be one of many distinct facets: the tie into versioning/source control, the various differing development technologies utilized in the product’s development, and the actual packaging stage, among others.

My most current endeavor relating to my work with developing a new build process corresponds to the packaging stage. The previous solution for this step was accomplished via Installshield; a packaging tool that would find itself being used again by the new build process.

However, soon after implementing it into the new process, I found it to be severely lacking in what I envisioned the process to eventually be capable of. To be brief, it suffers in the field of automation and would prove to only be an inhibitor to the build system’s ideal functionality. That’s when I stumbled upon WiX. As of version 3.0, the project files fully support MSBuild; additionally, the toolset also offers a MSBuild targets file as well as a MSBuild tasks library.

The task library is frequently advertised and used as a response to prospective users’ queries regarding automation capability. Despite this, examples of their use are not readily available online. Even worse, the documentation fails to mention even half of the available functionality within the task file. This posts hopes to alleviate these two problems.

Continue reading »

 

The ability to discover all local and network based SQL data source instances can be a useful feature, especially when a user has to supply SQL database information on the spot. More than a few applications sport this feature, such as Microsoft’s own SQL Management Studio, where a user can choose what server to log on to via a graphical representation of SQL servers on the network.

The graphical rendering of representations for the data sources is trivial; the core component here is the discovery mechanism itself. There exists a multitude of ways to go about achieving this sort of functionality. Which one is the most sensible way to gather a collection of SQL server instance information, is the question you should be asking.

In order to answer this question, this post covers four different approaches to solving the data source instance discovery problem.?Each approach also has some rudimentary performance analysis included, allowing us to truly decide which method takes the cake for most general scenarios.

Continue reading »

 

In order to take full advantage of the services offered by the Team Foundation platform, one must build upon it by using the various mechanisms set in place for purposes of customization by the creators themselves. It is doubtful that the stock offerings provided by TFS will meet your organization’s needs, and this really comes to light with Team Build.

Unless the ideal build process for your organization’s product is extremely simple, you will most likely have to build upon the capabilities of Team Build through the creation of custom build tasks.

If you’ve stumbled across some code examples online detailing how to accomplish this, you may have found yourself scratching your head regarding the references assemblies required.

Continue reading »

© 2012-2013 Matt Weber. All Rights Reserved. Terms of Use.