From MSI to WiX, Part 20 - User Interface - Required Dialog Boxes

The main page for the series is here.

 

Introduction 

We will start with exploring Required Dialog Boxes.  Microsoft Windows Installer uses three special dialogs in response to the following events during product installation:

  • Exit Dialog - shown when installation completed successfully.  Required name for the dialog is ExitDialog.
  • Fatal Error Dialog - shown in response to installation termination due to the fatal error.  Required name for the dialog is FatalError.
  • User Exit Dialog - shown in response to user's request to cancel an installaton.  Required name for the dialog is UserExit.

Each of these dialogs has a special sequence number in both AdminUISequence and InstallUISequence tables:

  • Exit Dialog: -1
  • Fatal Error Dialog: -3
  • User Exit Dialog: -2

There is also one special dialog which Windows Installer uses to show an installation error.

For now, I will leave alone all the bells, whistles, and icing and will use the bare minimum set of WiX elements and their attributes, just enough to explain what needs to be done to make a required dialog.  So, here it goes.

Exit dialog

The Exit dialog is displayed at the end of successful installation.  The only requirement for this dialog is to have a button control which will be used by the user to close the dialog to complete the installation.  It is also advisable to give some sort of message to the user indicating that the installation is completed successfully, but as I said earlier, we will make our dialogs more "civilized" later.

By default, Microsoft Windows Installer is using System font to show text in the user interface.  This can cause the installer to improperly display text strings if the package's code page is different from the user's default UI code page.

The recomendation here is to set the DefaultUIFont property to one of the styles defined in the TextStyle table.  In WiX it is done like this:

<TextStyle Id="DefaultFont" FaceName="Tahoma" Size="8" />

<Property Id="DefaultUIFont" Value="DefaultFont" />

To make the correct implementation of Exit dialog the following must be done:

Here is the very simple implementation of the Exit dialog:

<?xml version="1.0" encoding="UTF-8"?>

<Wix xmlns="https://schemas.microsoft.com/wix/2006/wi">

  <Product Id="732e6d89-5fa5-43a1-b590-88ebed02a05f"

           Name="UIRequiredDialogs"

           Language="1033"

           Version="1.0.0.0"

           Manufacturer="UIRequiredDialogs"

           UpgradeCode="8f2b749f-f563-4a4f-ab66-5a121bf07378">

    <Package InstallerVersion="200" Compressed="yes" />

    <Media Id="1" Cabinet="UIRequiredDialogs.cab" EmbedCab="yes" />

    <UI>

      <Dialog Id="ExitDialog" Width="370" Height="270" Title="Exit Dialog">

        <Control Id="Exit"

                 Type="PushButton"

                 X="236" Y="243" Width="56" Height="17"

                 Default="yes"

                 Cancel="yes"

                 Text="Exit">

          <Publish Event="EndDialog" Value="Return">1</Publish>

        </Control>

      </Dialog>

      <TextStyle Id="DefaultFont" FaceName="Tahoma" Size="8" />

      <Property Id="DefaultUIFont" Value="DefaultFont" />

      <InstallUISequence>

        <Show Dialog="ExitDialog" OnExit="success" />

      </InstallUISequence>

      <AdminUISequence>

        <Show Dialog="ExitDialog" OnExit="success" />

      </AdminUISequence>

    </UI>

    <Directory Id="TARGETDIR" Name="SourceDir">

      <Directory Id="ProgramFilesFolder">

        <Directory Id="INSTALLLOCATION" Name="UIRequiredDialogs">

          <Component Id="ProductComponent" Guid="df47e78b-325c-4371-ba28-10e899dad783">

            <File Id="Readme.txt" Name="Readme.txt" Source="Readme.txt" Vital="yes" KeyPath="yes" />

  </Component>

        </Directory>

      </Directory>

    </Directory>

    <Feature Id="ProductFeature" Title="UIRequiredDialogs" Level="1">

      <ComponentRef Id="ProductComponent" />

    </Feature>

  </Product>

</Wix>

As you can see, all UI-related elements are located inside the <UI> element.

The Id attribute of the <Dialog> element can have any value and must match the value of Dialog attribute of the <Show> element and this value does not have to be "ExitDialog".  What makes this dialog Exit dialog is the value of OnExit attribute of the <Show> element.  By setting OnExit attribute's value to "success" we are telling to WIX that this dialog must have sequence number -1.

It seems to me that X and Y attributes of the <Dialog> element in WIX are coordinates of the center of the dialog on the sceen as a percentage of the screen's width and height.  By default, their values will be set to 50 and dialog will appear at the center of the screen.

Width and Height attributes are the width and height of the dialog window.  These values are in Installer units.

<Control> element's position and size attributes are in Installer unitsDefault attribute set to "yes" means that pressing Enter key will have the same effect as clicking the button control.  By setting Cancel attribute's value to "yes" we are saying that closing the dialog by clicking X button in the right upper corner of the dialog will also have the same effect as clicking the button control.

To raise the EndDialog control event when user click the button, we must add <Publish> element as a chile element of the push button <Control> element.  Event attribute's value must be "EndDialog" (the name of the standard EndDialog control event) and Value attribute's value set to "Return" (means that control returns to the installer with the Success value).

Let's compile our project.  Ignore for now six ICE20 errors.  Install our test product and see how your dialog looks like.  Don't forget to uninstall the test product.

Fatal Error dialog

The FatalError dialog is displayed when installation is terminated because of a fatal error.  Requirements and implementation of this dialog are almost identical to requirements and implementation of Exit dialog.  The only difference is in the <Show> element (only relevant code is shown):

<Property Id="FailureProgram">

<![CDATA[

Function Main()

Main = 3

End Function

]]>

</Property>

<CustomAction Id="FakeFailure"

VBScriptCall="Main"

Property="FailureProgram" />

<InstallExecuteSequence>

<Custom Action='FakeFailure'

Before='InstallInitialize'>TESTFAIL</Custom>

</InstallExecuteSequence>

<UI>

<Dialog Id="FatalError"

Width="370" Height="270"

Title="Fatal Error Dialog">

<Control Id="Finish" Type="PushButton"

X="236" Y="243" Width="56" Height="17"

Default="yes" Cancel="yes"

Text="Exit">

<Publish Event="EndDialog" Value="Exit">1</Publish>

</Control>

</Dialog>

<TextStyle Id="DefaultFont" FaceName="Tahoma" Size="8" />

<Property Id="DefaultUIFont" Value="DefaultFont" />

<InstallUISequence>

<Show Dialog="FatalError" OnExit="error" />

</InstallUISequence>

<AdminUISequence>

<Show Dialog="FatalError" OnExit="error" />

</AdminUISequence>

</UI>

To immitate the fatal error I added custom action which returns a failure exit code. To test it you need to pass the TESTFAIL property in the command line:

Msiexec /I UIRequiredDialogs.msi TESTFAIL=1

User Exit dialog

The UserExit dialog is displayed when installation is terminated at the user's request.  Requirements and implementation of this dialog are identical to requirements and implementation of FatalError dialog:

<Property Id="UserExitProgram">

  <![CDATA[

    Function Main()

      Main = 2

    End Function

    ]]>

</Property>

<CustomAction Id="FakeUserExit"

      VBScriptCall="Main"

      Property="UserExitProgram" />

<InstallExecuteSequence>

  <Custom Action='FakeUserExit'

         Before='InstallInitialize'>TESTUSEREXIT</Custom>

</InstallExecuteSequence>

<UI>

  <Dialog Id="UserExit"

         Width="370" Height="270"

         Title="User Exit dialog">

    <Control Id="Finish" Type="PushButton"

            X="236" Y="243" Width="56" Height="17"

            Default="yes" Cancel="yes"

            Text="Exit">

      <Publish Event="EndDialog" Value="Exit">1</Publish>

    </Control>

  </Dialog>

  <TextStyle Id="DefaultFont" FaceName="Tahoma" Size="8" />

  <Property Id="DefaultUIFont" Value="DefaultFont" />

  <InstallUISequence>

    <Show Dialog="UserExit" OnExit="cancel" />

  </InstallUISequence>

  <AdminUISequence>

    <Show Dialog="UserExit" OnExit="cancel" />

  </AdminUISequence>

</UI>

To immitate the user exit requestI added custom action which returns a UserExit code. To test it you need to pass the TESTUSEREXIT property in the command line:

Msiexec /I UIRequiredDialogs.msi TESTUSEREXIT=1

Error dialog

The Error dialog displays the error message.  To display an Error dialog we must create a dialog and set the ErrorDialog property to the Id of the Error dialog.

The following are requirements for the Error dialog:

  • Dialog must have attribute ErrorDialog set to "yes" .
  • First control in the dialog must be control with Id="ErrorText".
  • Dialog must contain seven buttons.  Each button must publish EndDialog event.  The only two differences between buttons are the value of Id attribute of the Control element and the value of the Value attribute (event argument) of Publish element:
    • Abort button: publishes an event with argument ErrorAbort and must have Control/@Id="A" .
    • Cancel button: publishes an event with argument ErrorCancel and must have Control/@Id="C" .
    • Ignore button: publishes an event with argument ErrorIgnore and must have Control/@Id="I" .
    • No button: publishes an event with argument ErrorNo and must have Control/@Id="N" .
    • OK button: publishes an event with argument ErrorOK and must have Control/@Id="O" .
    • Retry button: publishes an event with argument ErrorRetry and must have Control/@Id="R" .
    • Yes button: publishes an event with argument ErrorYes and must have Control/@Id="Y" .
  • All buttons must have attribute TabSkip set to "yes" .
  • All buttons must have the same position (same value of the X and Y attributes).
  • In Error dialog the values of Cancel and Default attributes of button Control elements are ignored, so there is no need to give them any value.
  • Error dialog can also have an Icon control.  If included, this control must have an Id attribute set to "ErrorIcon" and attribute FixedSize set to "yes" .  Setting Text attribute is not necessary, but not setting it will generate an ICE17.  As a workaround, add an icon to Binary table and set Text attribute to that icon.

Here is the complete source code which includes all four required dialogs.  Compilation of this code still produces ICE20 about missing FilesInUse dialog, but for now we can safely ignore this error.

<?xml version="1.0" encoding="UTF-8"?>

<Wix xmlns="https://schemas.microsoft.com/wix/2006/wi">

  <Product Id="732e6d89-5fa5-43a1-b590-88ebed02a05f" Name="UIRequiredDialogs" Language="1033" Version="1.0.0.0" Manufacturer="UIRequiredDialogs" UpgradeCode="8f2b749f-f563-4a4f-ab66-5a121bf07378">

    <Package InstallerVersion="200" Compressed="yes" />

    <Media Id="1" Cabinet="UIRequiredDialogs.cab" EmbedCab="yes" />

    <Binary Id="ErrorIcon" SourceFile="ErrorIcon.ico" />

    <!-- Fatal Error -->

    <Property Id="FailureProgram">

    <![CDATA[

        Function Main()

          Main = 3

        End Function

        ]]>

    </Property>

    <CustomAction Id="FakeFailure"

          VBScriptCall="Main"

          Property="FailureProgram" />

    <InstallExecuteSequence>

      <Custom Action="FakeFailure"

          Before="InstallInitialize">TESTFAIL</Custom>

    </InstallExecuteSequence>

    <!-- User Exit -->

    <Property Id="UserExitProgram">

      <![CDATA[

        Function Main()

          Main = 2

        End Function

        ]]>

    </Property>

    <CustomAction Id="FakeUserExit"

          VBScriptCall="Main"

          Property="UserExitProgram" />

    <InstallExecuteSequence>

      <Custom Action="FakeUserExit"

          Before="InstallInitialize">TESTUSEREXIT</Custom>

    </InstallExecuteSequence>

    <!-- Error dialog -->

    <CustomAction Id="TestError" Error="25000" />

    <InstallExecuteSequence>

      <Custom Action="TestError" Before="InstallInitialize">TESTERROR</Custom>

    </InstallExecuteSequence>

  <UI>

      <Dialog Id="ExitDialog"

          Width="370" Height="270"

          Title="Exit Dialog">

        <Control Id="Exit" Type="PushButton"

            X="236" Y="243" Width="56" Height="17"

            Default="yes" Cancel="yes"

            Text="Exit">

          <Publish Event="EndDialog" Value="Return">1</Publish>

        </Control>

      </Dialog>

      <Dialog Id="FatalError"

          Width="370" Height="270"

          Title="Fatal Error Dialog">

        <Control Id="Finish" Type="PushButton"

            X="236" Y="243" Width="56" Height="17"

            Default="yes" Cancel="yes"

            Text="Exit">

          <Publish Event="EndDialog" Value="Exit">1</Publish>

        </Control>

      </Dialog>

     

      <Dialog Id="UserExit"

          Width="370" Height="270"

          Title="User Exit dialog">

        <Control Id="Finish" Type="PushButton"

            X="236" Y="243" Width="56" Height="17"

            Default="yes" Cancel="yes"

  Text="Exit">

          <Publish Event="EndDialog" Value="Exit">1</Publish>

        </Control>

      </Dialog>

      <Property Id="ErrorDialog" Value="ErrorDlg" />

      <Error Id="25000">Error message goes here!</Error>

      <Dialog Id="ErrorDlg" Width="270" Height="105" Title="My Error Dialog" ErrorDialog="yes">

        <Control Id="ErrorText" Type="Text" X="48" Y="15" Width="205" Height="60" TabSkip="no" Text="Error text" />

        <Control Id="Y" Type="PushButton" X="100" Y="80" Width="56" Height="17" TabSkip="yes" Text="Yes">

          <Publish Event="EndDialog" Value="ErrorYes">1</Publish>

        </Control>

        <Control Id="A" Type="PushButton" X="100" Y="80" Width="56" Height="17" TabSkip="yes" Text="Abort">

          <Publish Event="EndDialog" Value="ErrorAbort">1</Publish>

        </Control>

        <Control Id="C" Type="PushButton" X="100" Y="80" Width="56" Height="17" TabSkip="yes" Text="Cancel">

          <Publish Event="EndDialog" Value="ErrorCancel">1</Publish>

        </Control>

        <Control Id="I" Type="PushButton" X="100" Y="80" Width="56" Height="17" TabSkip="yes" Text="Ignore">

          <Publish Event="EndDialog" Value="ErrorIgnore">1</Publish>

        </Control>

        <Control Id="N" Type="PushButton" X="100" Y="80" Width="56" Height="17" TabSkip="yes" Text="No">

          <Publish Event="EndDialog" Value="ErrorNo">1</Publish>

        </Control>

        <Control Id="O" Type="PushButton" X="100" Y="80" Width="56" Height="17" TabSkip="yes" Text="OK">

          <Publish Event="EndDialog" Value="ErrorOk">1</Publish>

        </Control>

        <Control Id="R" Type="PushButton" X="100" Y="80" Width="56" Height="17" TabSkip="yes" Text="Retry">

          <Publish Event="EndDialog" Value="ErrorRetry">1</Publish>

        </Control>

        <Control Id="ErrorIcon" Type="Icon" X="15" Y="15" Width="24" Height="24" ToolTip="Error tooltip" FixedSize="yes" Text="ErrorIcon" />

     </Dialog>

     

      <TextStyle Id="DefaultFont" FaceName="Tahoma" Size="8" />

      <Property Id="DefaultUIFont" Value="DefaultFont" />

      <InstallUISequence>

        <Show Dialog="ExitDialog" OnExit="success" />

        <Show Dialog="FatalError" OnExit="error" />

        <Show Dialog="UserExit" OnExit="cancel" />

      </InstallUISequence>

      <AdminUISequence>

        <Show Dialog="ExitDialog" OnExit="success" />

        <Show Dialog="FatalError" OnExit="error" />

        <Show Dialog="UserExit" OnExit="cancel" />

      </AdminUISequence>

    </UI>

    <Directory Id="TARGETDIR" Name="SourceDir">

      <Directory Id="ProgramFilesFolder">

        <Directory Id="INSTALLLOCATION" Name="UIRequiredDialogs">

          <Component Id="ProductComponent" Guid="df47e78b-325c-4371-ba28-10e899dad783">

            <File Id="Readme.txt" Name="Readme.txt" Source="Readme.txt" Vital="yes" KeyPath="yes" />

          </Component>

        </Directory>

      </Directory>

    </Directory>

    <Feature Id="ProductFeature" Title="UIRequiredDialogs" Level="1">

      <ComponentRef Id="ProductComponent" />

    </Feature>

  </Product>

</Wix>

To test the Error dialog you need to pass the TESTERROR property in the command line:

Msiexec /I UIRequiredDialogs.msi TESTERROR=1

 

UIRequiredDialogs.zip