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.

Matt Weber

I'm the the Senior Software Architect at Emergingsoft where I lead the software development team. I am also the owner of this website. I enjoy well-designed code, independent thought, and the application of rationality in general. You can reach me at matt@badecho.com.

  One Response to “Creating a Proper Update Experience with WiX”

  1. Hi Matt,
    An old post but I have a related question. Do you know how to display a UI when uninstalling a Patch from Programs & Features? By default it shows a minimal standard dialog. No customization possible.

    TIA
    Rob

 Leave a Reply

(required)

(required)

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

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