Branching Wizard Sequences With WiX

Wizard dialog sequences are common in Windows Installer packages as they lead the user through a series of questions to customize the installation of a product. Welcome to the install, select the installation directory, ready to install and so-on are common dialogs present in the wizard dialog sequence. Sometimes you need to make a choice between two dialogs based on the user’s input, however:

WizardSequence

So you’ll need to branch to different dialogs on the Next button of the Installation Type dialog (and the Back button of the Install Location dialog) based on the user’s input. This post describes the best way to achieve this branching dialog behavior in Windows Installer, using Windows Installer XML (WiX) as the authoring tool.

Windows Installer manages dialog transitions in a Wizard sequence through the NewDialog event published by a push button control on a dialog. The argument to the event names the dialog that should replace the current dialog. The events published for a control on a dialog are listed in the ControlEvent table in the MSI package. Each control event can be associated with a condition, so you might think this is a simple case of using two NewDialog events like this:

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
    <Product Id="E54A2C6A-3206-41ED-BD21-D8063BDA3766"
        Name="SampleProject" Language="1033" Version="1.0.0.0"
        Manufacturer="SampleProject"
        UpgradeCode="08E888B3-AC74-4975-8620-F76F0B121576">
        <!-- product description omitted... -->
        <UI>
            <Dialog Id="SetupTypeDlg" Width="370" Height="270" Title="Setup Type">
                <!-- other controls omitted... -->
                <Control Id="Back" Type="PushButton" Text="Back"
                    X="180" Y="243" Width="56" Height="17" />
                <Control Id="Next" Type="PushButton" Text="Next"
                    X="236" Y="243" Width="56" Height="17" Disabled="yes" />
            </Dialog>
 
            <Publish Dialog="SetupTypeDlg" Control="Next"
                Event="NewDialog" Value="CustomSetupDlg"
                Order="1">SetupType = "Custom"</Publish>
            <Publish Dialog="SetupTypeDlg" Control="Next"
                Event="NewDialog" Value="StandardSetupDlg"
                Order="2">1</Publish>
        </UI>
    </Product>
</Wix>

Unfortunately this is not allowed. Even worse, it works most of the time without error. When it doesn’t work, you’ll be scratching your head and pulling your hair out trying to figure out why your dialogs sometimes work and sometimes they don’t. The constraint being violated is listed in the description of the ControlEvent table (emphasis mine):

The installer starts each event in the order specified in the Ordering column. For example, a push button control can publish events to initiate a transition to another dialog box, exit the dialog box sequence, and begin file installation. The exception is that each control can publish at most one NewDialog or one SpawnDialog event.

Fortunately, the change required to satisfy this constraint is simple and straightforward. Instead of sequencing a series of NewDialog control events, use a series of set property events to store the target of the NewDialog control event in a property. Then use this property value as the target of the single NewDialog control event. This works because the Argument column of the ControlEvent table is of type Formatted. This means we can use the full range of text substitution capabilities of Windows Installer to construct the target of the NewDialog event. Our example from above now becomes:

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
    <Product Id="E54A2C6A-3206-41ED-BD21-D8063BDA3766"
        Name="SampleProject" Language="1033" Version="1.0.0.0"
        Manufacturer="SampleProject"
        UpgradeCode="08E888B3-AC74-4975-8620-F76F0B121576">
        <!-- product description omitted... -->
        <UI>
            <Dialog Id="SetupTypeDlg" Width="370" Height="270" Title="Setup Type">
                <!-- other controls omitted... -->
                <Control Id="Back" Type="PushButton" Text="Back"
                    X="180" Y="243" Width="56" Height="17" />
                <Control Id="Next" Type="PushButton" Text="Next"
                    X="236" Y="243" Width="56" Height="17" Disabled="yes" />
            </Dialog>
 
            <Publish Dialog="SetupTypeDlg" Control="Next"
                Property="SetupTypeDlg_Next" Value="{}"
                Order="1">1</Publish>
            <Publish Dialog="SetupTypeDlg" Control="Next"
                Property="SetupTypeDlg_Next" Value="CustomSetupDlg"
                Order="2">SetupType = "Custom"</Publish>
            <Publish Dialog="SetupTypeDlg" Control="Next"
                Property="SetupTypeDlg_Next" Value="StandardSetupDlg"
                Order="3">NOT SetupTypeDlg_Next</Publish>
            <Publish Dialog="SetupTypeDlg" Control="Next"
                Event="NewDialog" Value="[SetupTypeDlg_Next]"
                Order="4">SetupTypeDlg_Next</Publish>
        </UI>
    </Product>
</Wix>

There are a couple of things to note in this changed control event sequence. First, the property that will contain the name of the new dialog is always initialized to empty before we start conditionally assigning values into it. This guarantees that we always start in the same state when we build this property value. Second, the conditional set property events are executed to assign the appropriate dialog name into the property based on the desired dialog transition logic.

Note carefully the use of ‘NOT SetupTypeDlg_Next’ as the condition on the second set property event in our transition logic. This makes sure that we don’t overwrite a previously set value for the property with this event. You can use an approach like this or a series of mutually exclusive conditions to set your property value. For a simple binary choice, mutually exclusive conditions are probably fine, but for complex N-way conditions, accumulating the mutually excusive conditions leads to large unwieldy condition clauses. Using NOT SetupTypeDlg_Next amounts to an if … else if … style for the constructing the property value.

Third, the NewDialog event is issued using the value of the property with the condition that the transition is only taken if the associated property is non-empty. Forcing the property to empty first and then branching to a new dialog only if the property is non-empty will help make sure that your transition logic is coded properly. When you get it wrong, clicking on the button won’t do anything and you can investigate further with a verbose log showing the property changes.

The final observation is to remember that users can go backwards through a wizard sequence as well as forwards. In our example, it means that transition logic will need to be added to the Back button of the Install Location dialog as we’ve added to the Next button of the Installation Type dialog. You’ll notice in my example that I used a property named SetupTypeDlg_Next to hold the dialog target for the Next button on the SetupTypeDlg dialog. I recommend that you use distinct properties for all your dialogs so that the verbose log shows a property dump that can be used to reconstruct the dialog path taken through your branching wizard sequence.

It may seem like the advice in this blog post is only of academic concern, since lots of dialogs are coded with multiple NewDialog control events and they seem to work. (Even the template UI dialogs in the WiX 3.0 distribution got this wrong; I’ll contribute a patch that fixes the problem.) However, there is a difference between working by accident and working by design. Multiple NewDialog control events may work by accident, but the technique I describe here works by design. While working on a large installation with many dialogs in the wizard sequence, we found that some dialog transitions just weren’t working properly. It was through this real-world experience that I found the constraint on the NewDialog control events and created this technique as the solution. Once we changed our dialogs to use this technique, we never had any problems again with our dialogs once we implemented the correct transition logic.

There is a design choice you might face with a particularly complex and long branching wizard sequence. At some point managing the complex conditions between dialogs may become overwhelming, particularly since they must appear on both Next and Back buttons of different dialogs within the same sequence. Sometimes it can be simpler to manage the complexity of the transitions if dialogs are duplicated along parallel tracks with unconditional transitions between the dialogs in the track. On the large installation mentioned above, we found it worthwhile to replicate dialogs along parallel tracks to minimize the complexity of the conditions on the dialogs within each track. While dialogs were duplicated in the separate tracks, not all dialogs were present in each track. Some dialogs would be skipped depending on which track you were using. It was the conditional skipping of dialogs that made the conditions inordinately complex when they were all within a single wizard sequence. The tradeoff was that if any of those duplicated dialogs were changed, then they needed to be changed on each track containing a copy of the dialog.

7 Responses to “Branching Wizard Sequences With WiX”

  1. UI Custom Action Guidelines « Legalize Adulthood! Says:

    […] not recognizing that the property has changed. Subsequent control events use the property in a branching Wizard dialog sequence to navigate to the next dialog based on the value of the property. Posted in Computers, […]

    Like

  2. SWPalmer Says:

    I wonder if your original example has a bug only because the condition on both NewDialog events could be true at the same time. Is it still an error if the conditions are for certain mutually exclusive?

    Like

    • legalize Says:

      Its true that both conditions could be true, which is on purpose. This example is intended to illustrate a common mistake.

      Yes, it is still an error if the conditions are mutually exclusive. I’ve experienced dialog transition failures due to multiple NewDialog events being published, even when the conditions were mutually exclusive.

      Like

  3. Stefan Krueger Says:

    The next sentence in the MSDN documentation for the ControlEvent table seems to allow multiple NewDialog events, if their conditions are mutually exclusive:

    “If you need to author multiple NewDialog and SpawnDialog control events in this table, also include conditional statements in the Condition fields that ensure at most one event is published.”
    http://msdn.microsoft.com/en-us/library/aa368037(VS.85).aspx

    And I think that’s how most setups are authored.

    Like

    • legalize Says:

      Yes, the documentation does say that, but there are times when it doesn’t work. I have seen this approach fail, even when the conditions were mutually exclusive, but the property approach never fails.

      Like

  4. Allan CM Says:

    Excellent article, it was very helpful for me, and it had saved me a lot of time.
    Thank you very much.

    Like

  5. How to display a non-blocking warning for the operating system in Wix? – w3toppers.com Says:

    […] dialog, but only if there actually is a warning to show. This is a special case of the more general branching wizard sequences […]

    Like


Leave a comment