by on September 11, 2014, in

Real-World Example: WiX/MSI Application Installer

Creating an installer that does not suck is hard. The tools available are expensive, inadequate, overly complicated and/or poorly documented (pick any combination). WiX is one of the better choices. It is free and it is used for some Microsoft products. But WiX is just a wrapper around MSI and as such is unnecessarily difficult to use. In an effort to make life better for fellow developers I am publishing the full source code of uberAgent’s installer here.

uberAgent setup

What it Does

The WiX installer presented here can be compiled into two separate MSI packages: one for 32-bit and the other for 64-bit. The unfortunate fact that both are required is due to Windows Installer’s inability to write to the 64-bit Program Files directory from a 32-bit MSI package.

Features:

  • Puts an executable and a config file in %ProgramFiles%
  • Installs and starts a Windows system service
  • Custom dialog asks for a server name and adds that to the config file
  • Install location and custom server name are persisted across upgrades
  • Install location and server name can be specified on the msiexec command line
  • Displays a license agreement in RTF format
  • Easily localizable (including the license agreement), all relevant strings in per-language WXL files
  • Uses the upgrade logic introduced with WiX 3.5
  • Prevents multiple installations of the same product (same or different versions)
  • Checks for a minimum required OS version
  • Checks to prevent installing the 32-bit version on a 64-bit OS and vice versa

The source code is heavily commented. Please study if carefully. It consists of the following files:

  • Product.wxs: Main installer file
  • WixUI_HK.wxs: Custom UI sequence definition, required since we are adding dialogs
  • Product_en-us.wxl: Localization, English strings
  • LicenseAgreementDlg_HK.wxs: Custom dialog displaying an RTF license agreement
  • ServerDlg.wxs: Custom dialog asking for server name(s)

Directory Structure

The following files and directories are referenced by the installer:

  • images\app.ico: icon used in Programs and Features
  • images\BannerTop.bmp: Top banner for the installer UI
  • images\Dialog.bmp: Full background image for installer UI
  • Eula-en.rtf: English EULA

Compiling

In order to build this installer, you need to install WiX. I am using it with and compiling from Visual Studio 2013. I have tested it with WiX v3.8.

The MajorUpgrade option AllowSameVersionUpgrades=”yes” unfortunately produces the validation error ICE61 which you need to suppress in the project settings.

Source Code

Product.wxs

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">

   <!--
   Unattended setup: The following variables can be set:
   -  INSTALLDIR: Full path to the installation directory
   -  SERVERS: Splunk servers to send data to
   -->

   <!--
      ====================================================================================
      Defines & Variables
   -->

   <!-- Full version number to display -->
   <?define VersionNumber="!(bind.FileVersion.uberAgentExe)" ?>

   <!--
   Upgrade code HAS to be the same for all updates.
   Once you've chosen it don't change it.
   -->
   <?define UpgradeCode="YOUR-GUID-HERE" ?>

   <!-- The URL for add/remove programs -->
   <?define InfoURL="https://helgeklein.com/" ?>

   <!-- 32-bit / 64-bit variables -->
   <?if $(var.Platform) = x64 ?>
      <?define Win64 = "yes" ?>
      <?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
      <?define uberAgentExeSourcePath = "$(var.ProjectDir)..\uberAgent\x64\Release\uberAgent.exe" ?>
   <?else ?>
      <?define Win64 = "no" ?>
      <?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
      <?define uberAgentExeSourcePath = "$(var.ProjectDir)..\uberAgent\Win32\Release\uberAgent.exe" ?>
   <?endif ?>

   <!--
      ====================================================================================
      Package start
   -->

   <!-- The upgrade code must never change as long as the product lives! -->
   <!-- Product IDs must be autogenerated (*) or else major upgrades will not work -->
   <Product Id="*" Name="!(loc.ApplicationName)" Language="!(loc.Language)" Version="$(var.VersionNumber)" Manufacturer="!(loc.ManufacturerFullName)" UpgradeCode="$(var.UpgradeCode)" >

      <!-- Package IDs are valid for a single package version only - they are autogenerated by WiX -->
      <!-- Let's require Windows Installer 4.0 (included in Vista) -->
      <!-- And ALWAYS install per machine!!! -->
      <Package Id="*" InstallerVersion="400" Compressed="yes" InstallScope="perMachine"  Description="!(loc.ProductDescription)" Comments="!(loc.Comments) $(var.VersionNumber)" />

      <!-- License agreement text: dummy. Real text is set in WXS file -->
      <WixVariable Id="WixUILicenseRtf" Value="dummy" />
      
      <!-- UI customization -->
      <WixVariable Id="WixUIBannerBmp" Value="images\BannerTop.bmp" />
      <WixVariable Id="WixUIDialogBmp" Value="images\Dialog.bmp" />

      <!-- Define icons (ID should not be longer than 18 chars and must end with ".exe") -->
      <Icon Id="Icon.exe" SourceFile="images\app.ico" />

      <!-- Set properties for add/remove programs -->
      <Property Id="ARPPRODUCTICON" Value="Icon.exe" />
      <Property Id="ARPHELPLINK" Value="$(var.InfoURL)" />
      <Property Id="ARPNOREPAIR" Value="yes" Secure="yes" />      <!-- Remove repair -->
      <Property Id="ARPNOMODIFY" Value="yes" Secure="yes" />      <!-- Remove modify -->

      <!-- Upgrade logic -->
      <!-- AllowSameVersionUpgrades -> Always upgrade, never allow two versions to be installed next to each other -->
      <!-- AllowSameVersionUpgrades causes ICE61 which must be ignored -->
      <MajorUpgrade DowngradeErrorMessage="!(loc.NewerInstalled)" AllowSameVersionUpgrades="yes" />
      
      <!-- This is the main installer sequence run when the product is actually installed -->
      <InstallExecuteSequence>
      
         <!-- Determine the install location after the install path has been validated by the installer -->
         <Custom Action="SetARPINSTALLLOCATION" After="InstallValidate"></Custom>

      </InstallExecuteSequence>

      <!-- Set up ARPINSTALLLOCATION property (http://blogs.technet.com/b/alexshev/archive/2008/02/09/from-msi-to-wix-part-2.aspx) -->
      <CustomAction Id="SetARPINSTALLLOCATION" Property="ARPINSTALLLOCATION" Value="[INSTALLDIR]" />

      <!-- 
         Launch conditions
         
         1. Check minimum OS version 
            If not, the installation is aborted.
            By doing the (Installed OR ...) property means that this condition will only be evaluated if the app is being installed and not on uninstall or changing
         
            Note: Under a Product element, a condition becomes a LaunchCondition entry. 
      -->
      <Condition Message="!(loc.OS2Old)">
         <![CDATA[Installed OR (VersionNT >= 600)]]>
      </Condition>

      <!-- 
         2. Check OS bitness
            Unfortunately 32-bit MSI packages cannot write to 64-bit ProgramFiles directory. That is the only reason we need separate MSIs for 32-bit and 64-bit.
      -->
      <?if $(var.Platform) = x64 ?>
         <Condition Message="!(loc.x86VersionRequired)">
            <![CDATA[VersionNT64]]>
         </Condition>
      <?endif?>
      <?if $(var.Platform) = x86 ?>
         <Condition Message="!(loc.x64VersionRequired)">
            <![CDATA[NOT VersionNT64]]>
         </Condition>
      <?endif?>
      <!-- 
         Launch conditions end
      -->
      
      <!-- Save the command line value INSTALLDIR and restore it later in the sequence or it will be overwritten by the value saved to the registry during an upgrade -->
      <!-- http://robmensching.com/blog/posts/2010/5/2/the-wix-toolsets-remember-property-pattern/ -->
      <CustomAction Id='SaveCmdLineValueINSTALLDIR' Property='CMDLINE_INSTALLDIR' Value='[INSTALLDIR]' Execute='firstSequence' />
      <CustomAction Id='SetFromCmdLineValueINSTALLDIR' Property='INSTALLDIR' Value='[CMDLINE_INSTALLDIR]' Execute='firstSequence' />
      <InstallUISequence>
         <Custom Action='SaveCmdLineValueINSTALLDIR' Before='AppSearch' />
         <Custom Action='SetFromCmdLineValueINSTALLDIR' After='AppSearch'>
            CMDLINE_INSTALLDIR
         </Custom>
      </InstallUISequence>
      <InstallExecuteSequence>
         <Custom Action='SaveCmdLineValueINSTALLDIR' Before='AppSearch' />
         <Custom Action='SetFromCmdLineValueINSTALLDIR' After='AppSearch'>
            CMDLINE_INSTALLDIR
         </Custom>
      </InstallExecuteSequence>

      <!-- Save the command line value SERVERS and restore it later in the sequence or it will be overwritten by the value saved to the registry during an upgrade -->
      <!-- http://robmensching.com/blog/posts/2010/5/2/the-wix-toolsets-remember-property-pattern/ -->
      <CustomAction Id='SaveCmdLineValueSERVERS' Property='CMDLINE_SERVERS' Value='[SERVERS]' Execute='firstSequence' />
      <CustomAction Id='SetFromCmdLineValueSERVERS' Property='SERVERS' Value='[CMDLINE_SERVERS]' Execute='firstSequence' />
      <InstallUISequence>
         <Custom Action='SaveCmdLineValueSERVERS' Before='AppSearch' />
         <Custom Action='SetFromCmdLineValueSERVERS' After='AppSearch'>
            CMDLINE_SERVERS
         </Custom>
      </InstallUISequence>
      <InstallExecuteSequence>
         <Custom Action='SaveCmdLineValueSERVERS' Before='AppSearch' />
         <Custom Action='SetFromCmdLineValueSERVERS' After='AppSearch'>
            CMDLINE_SERVERS
         </Custom>
      </InstallExecuteSequence>

      <!-- Determine the directory of a previous installation (if one exists). If not INSTALLDIR stays empty -->
      <Property Id="INSTALLDIR">
         <RegistrySearch Id="DetermineInstallLocation" Type="raw" Root="HKLM" Key="Software\!(loc.ManufacturerName)\InstalledProducts\!(loc.ApplicationName)" Name="InstallLocation" />
      </Property>
      <!-- Determine the servers of a previous installation -->
      <Property Id="SERVERS">
         <RegistrySearch Id="DetermineServers" Type="raw" Root="HKLM" Key="Software\!(loc.ManufacturerName)\InstalledProducts\!(loc.ApplicationName)" Name="Servers" />
      </Property>
      <!-- Set default value if registry search comes up empty -->
      <SetProperty Before='InstallInitialize' Sequence='execute' Id='SERVERS' Value='localhost:19500'>NOT SERVERS</SetProperty>

      <!--
         ====================================================================================
         Start to build directory structure
      -->

      <!-- We do not have more than one medium (Floppy, CD, ...). Everything in one file. -->
      <Media Id="1" Cabinet="media1.cab" EmbedCab="yes" />

      <!-- Outermost folder (kind of virtual). Fixed entry. -->
      <Directory Id="TARGETDIR" Name="SourceDir">

         <!-- We start building our directory structure here -->
         <!-- "ProgramFilesFolder" is a variable containing the absolute path. -->
         <!-- For a list of folder variables, see: http://msdn.microsoft.com/en-us/library/aa372057%28VS.85%29.aspx -->
         <Directory Id="$(var.PlatformProgramFilesFolder)">

            <!-- All folders from here on are relative to their parent. -->

            <Directory Id="ProgramFilesHK" Name="!(loc.ManufacturerName)">

               <!-- INSTALLDIR is a property name. We need it later for the UI (to be able to change the install dir. -->
               <Directory Id="INSTALLDIR" Name="!(loc.ApplicationName)">

                  <!-- Define components, the building blocks of MSIs. -->
                  <!-- Rule: A component should only contain items that belong together so strongly that they always need to be installed or removed together. -->
                  <!-- If this means a single file, then your components will contain a single file each. This is not only normal but exactly what you're -->
                  <!-- to do. Don't be afraid, Windows Installer can efficiently handle thousands of components or more, if needed. -->

                  <!-- Installation directory as a component so it can be emptied during uninstall (by default files added by someone other than Windows Installer are not removed) -->
                  <Component Id="INSTALLDIR_comp" Guid="YOUR-GUID-HERE">
                     <CreateFolder />
                     <RemoveFile Id="RemoveFilesFromAppDirectory" Name="*.*" On="uninstall" />
                  </Component>
                  
                  <!-- Main program file -->
                  <Component Id="uberAgent.exe_comp" Guid="*" Win64="$(var.Win64)">
                     
                     <File Source="$(var.uberAgentExeSourcePath)" Id="uberAgentExe" KeyPath="yes" />

                     <ServiceInstall Id="ServiceInstaller" Account="LocalSystem" Description="!(loc.ServiceDescription)" DisplayName="!(loc.ServiceDisplayName)" ErrorControl="normal"
                                     LoadOrderGroup="NetworkProvider" Name="uberAgentSvc" Start="auto" Type="ownProcess" Vital="yes" />
                     <ServiceControl Id="ServiceControl" Name="uberAgentSvc" Start="install" Stop="both" Remove="uninstall" />
                     
                  </Component>

                  <!-- Configuration file -->
                  <Component Id="uberAgent.conf_comp" Guid="YOUR-GUID-HERE">

                     <File Source="$(var.ProjectDir)..\uberAgent\Config file\Standalone mode\uberAgent.conf" Id="uberAgentConf" KeyPath="yes" />

                     <IniFile Id="ConfigFile" Action="addLine" Directory="INSTALLDIR" Name="uberAgent.conf" Section="Receiver" Key="Servers" Value="[SERVERS]" />
                     
                  </Component>

               </Directory>
            </Directory>
         </Directory>

         <!-- Registry entries -->
         <Component Id="RegValInstallLocation_comp" Guid="YOUR-GUID-HERE">
            <!-- Do NOT use the application's default registry key here, because THIS key will be removed on uninstall
                 (important when installing a newer version, because that is uninstall followed by install) -->
            <RegistryKey Root="HKLM" Key="Software\!(loc.ManufacturerName)\InstalledProducts\!(loc.ApplicationName)">
               <RegistryValue Name="InstallLocation" Value="[INSTALLDIR]" Type="string" KeyPath="yes" />
               <RegistryValue Name="Servers" Value="[SERVERS]" Type="string" />
            </RegistryKey>
         </Component>

      </Directory>

      <!--
         End of directory structure
         ====================================================================================
      -->

      <!-- Features define which parts of the application can be installed in a custom installation -->
      <Feature Id="Complete" Title="!(loc.ApplicationName)" Description="!(loc.FeatureCompleteDescription)" Display="expand" Level="1" ConfigurableDirectory="INSTALLDIR">

         <!-- A feature block for the main (GUI) program and all its dependencies -->
         <Feature Id="MainProgram" Title="!(loc.FeatureMainProgramTitle)" Description="!(loc.FeatureMainProgramDescription)" Level="1">
            <ComponentRef Id="INSTALLDIR_comp" />
            <ComponentRef Id="uberAgent.exe_comp" />
            <ComponentRef Id="uberAgent.conf_comp" />

            <!-- Registry entries -->
            <ComponentRef Id="RegValInstallLocation_comp" />
         </Feature>

      </Feature>

      <UI>
         <!-- Define the installer UI -->
         <UIRef Id="WixUI_HK" />
      </UI>
      
      <Property Id="WIXUI_INSTALLDIR" Value="INSTALLDIR" />
      <Property Id="WIXUI_SERVERS" Value="SERVERS" />

   </Product>

</Wix>

LicenseAgreementDlg_HK.wxs

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
   <Fragment>
      <UI>
         <Dialog Id="LicenseAgreementDlg_HK" Width="370" Height="270" Title="!(loc.LicenseAgreementDlg_Title)">
            <Control Id="LicenseAcceptedCheckBox" Type="CheckBox" X="20" Y="207" Width="330" Height="18" CheckBoxValue="1" Property="LicenseAccepted" Text="!(loc.LicenseAgreementDlgLicenseAcceptedCheckBox)" />
            <Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Text="!(loc.WixUIBack)" />
            <Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="!(loc.WixUINext)">
               <Publish Event="SpawnWaitDialog" Value="WaitForCostingDlg">CostingComplete = 1</Publish>
               <Condition Action="disable"><![CDATA[LicenseAccepted <> "1"]]></Condition>
               <Condition Action="enable">LicenseAccepted = "1"</Condition>
            </Control>
            <Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="!(loc.WixUICancel)">
               <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
            </Control>
            <Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="!(loc.LicenseAgreementDlgBannerBitmap)" />
            <Control Id="LicenseText" Type="ScrollableText" X="20" Y="60" Width="330" Height="140" Sunken="yes" TabSkip="no">
            <!-- This is the original line -->
            <!--<Text SourceFile="!(wix.WixUILicenseRtf=$(var.LicenseRtf))" />-->
            <!-- To enable EULA localization we change it to this: -->
            <Text SourceFile="$(var.ProjectDir)\!(loc.LicenseRtf)" />
            <!-- In each of the localization files (wxl) put a line like this:
            <String Id="LicenseRtf" Overridable="yes">EULA_en-us.rtf</String>-->
            </Control>
            <Control Id="Print" Type="PushButton" X="112" Y="243" Width="56" Height="17" Text="!(loc.WixUIPrint)">
               <Publish Event="DoAction" Value="WixUIPrintEula">1</Publish>
            </Control>
            <Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
            <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
            <Control Id="Description" Type="Text" X="25" Y="23" Width="340" Height="15" Transparent="yes" NoPrefix="yes" Text="!(loc.LicenseAgreementDlgDescription)" />
            <Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Transparent="yes" NoPrefix="yes" Text="!(loc.LicenseAgreementDlgTitle)" />
         </Dialog>
      </UI>
   </Fragment>
</Wix>

Product_en-us.wxl

<?xml version="1.0" encoding="utf-8"?>
<WixLocalization Culture="en-us" Codepage="1252" xmlns="http://schemas.microsoft.com/wix/2006/localization">
   <String Id="Language">1033</String>         <!-- Supported language and codepage codes can be found here: http://www.tramontana.co.hu/wix/lesson2.php#2.4 -->
   
   <String Id="ApplicationName">uberAgent</String>
   <String Id="ManufacturerName">Helge Klein</String>
   <String Id="ManufacturerFullName">Helge Klein GmbH</String>
   <String Id="ProductDescription">Monitoring agent</String>
   <String Id="Comments">Installs uberAgent</String>

   <String Id="LicenseRtf" Overridable="yes">Eula-en.rtf</String>

   <String Id="OS2Old">This product requires at least Windows Vista / Server 2008.</String>
   <String Id="NewerInstalled">The same or a newer version of this product is already installed.</String>
   <String Id="x64VersionRequired">You need to install the 64-bit version of this product on 64-bit Windows.</String>
   <String Id="x86VersionRequired">You need to install the 32-bit version of this product on 32-bit Windows.</String>

   <String Id="FeatureCompleteDescription">The complete package.</String>
   <String Id="FeatureMainProgramDescription">The main version including all dependencies.</String>
   <String Id="FeatureMainProgramTitle">Main program</String>

   <String Id="ServiceDisplayName">uberAgent</String>
   <String Id="ServiceDescription">Monitors the system's performance, applications and user experience.</String>

   <String Id="ServerDlgTitle">{\WixUI_Font_Title}Receiver Configuration</String>
   <String Id="ServerDlgDescription">Target servers for uberAgent's data</String>
   <String Id="ServerDlgServerLabel">Specify to which Splunk Indexers or Forwarders uberAgent should send its data.&#13;&#10;If multiple targets are specified, uberAgent load-balances between them.&#13;&#10;&#13;&#10;Format:&#13;&#10;    server1:port1, server2:port2, ...&#13;&#10;Default:&#13;&#10;    localhost:19500&#13;&#10;    (requires locally installed Universal Forwarder)</String>
</WixLocalization>

ServerDlg.wxs

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
   <Fragment>
      <UI>
         <Dialog Id="ServerDlg" Width="370" Height="270" Title="!(loc.InstallDirDlg_Title)">
            <Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="!(loc.WixUINext)" />
            <Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Text="!(loc.WixUIBack)" />
            <Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="!(loc.WixUICancel)">
               <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
            </Control>

            <Control Id="Description" Type="Text" X="25" Y="23" Width="280" Height="15" Transparent="yes" NoPrefix="yes" Text="!(loc.ServerDlgDescription)" />
            <Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Transparent="yes" NoPrefix="yes" Text="!(loc.ServerDlgTitle)" />
            <Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="!(loc.InstallDirDlgBannerBitmap)" />
            <Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
            <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />

            <Control Id="ServerLabel" Type="Text" X="20" Y="60" Width="290" Height="90" NoPrefix="yes" Text="!(loc.ServerDlgServerLabel)" />
            <Control Id="Server" Type="Edit" X="20" Y="150" Width="320" Height="18" Property="WIXUI_SERVERS" Indirect="yes" />
         </Dialog>
      </UI>
   </Fragment>
</Wix>

WixUI_HK.wxs

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
   <Fragment>

      <UI Id="WixUI_HK">
         <TextStyle Id="WixUI_Font_Normal" FaceName="Tahoma" Size="8" />
         <TextStyle Id="WixUI_Font_Bigger" FaceName="Tahoma" Size="12" />
         <TextStyle Id="WixUI_Font_Title" FaceName="Tahoma" Size="9" Bold="yes" />

         <Property Id="DefaultUIFont" Value="WixUI_Font_Normal" />
         <Property Id="WixUI_Mode" Value="InstallDir" />

         <DialogRef Id="BrowseDlg" />
         <DialogRef Id="DiskCostDlg" />
         <DialogRef Id="ErrorDlg" />
         <DialogRef Id="FatalError" />
         <DialogRef Id="FilesInUse" />
         <DialogRef Id="MsiRMFilesInUse" />
         <DialogRef Id="PrepareDlg" />
         <DialogRef Id="ProgressDlg" />
         <DialogRef Id="ResumeDlg" />
         <DialogRef Id="UserExit" />
         
         <!--   Make sure to include custom dialogs in the installer database via a DialogRef command, 
               especially if they are not included explicitly in the publish chain below -->
         <DialogRef Id="LicenseAgreementDlg_HK"/>
         <DialogRef Id="ServerDlg" />

         <Publish Dialog="BrowseDlg" Control="OK" Event="DoAction" Value="WixUIValidatePath" Order="3">1</Publish>
         <Publish Dialog="BrowseDlg" Control="OK" Event="SpawnDialog" Value="InvalidDirDlg" Order="4"><![CDATA[WIXUI_INSTALLDIR_VALID<>"1"]]></Publish>

         <Publish Dialog="ExitDialog" Control="Finish" Event="EndDialog" Value="Return" Order="999">1</Publish>

         <Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="LicenseAgreementDlg_HK">NOT Installed</Publish>
         <Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg">Installed AND PATCH</Publish>

         <Publish Dialog="LicenseAgreementDlg_HK" Control="Back" Event="NewDialog" Value="WelcomeDlg">1</Publish>
         <Publish Dialog="LicenseAgreementDlg_HK" Control="Next" Event="NewDialog" Value="InstallDirDlg">LicenseAccepted = "1"</Publish>

         <Publish Dialog="InstallDirDlg" Control="Back" Event="NewDialog" Value="LicenseAgreementDlg_HK">1</Publish>
         <Publish Dialog="InstallDirDlg" Control="Next" Event="SetTargetPath" Value="[WIXUI_INSTALLDIR]" Order="1">1</Publish>
         <Publish Dialog="InstallDirDlg" Control="Next" Event="DoAction" Value="WixUIValidatePath" Order="2">NOT WIXUI_DONTVALIDATEPATH</Publish>
         <Publish Dialog="InstallDirDlg" Control="Next" Event="SpawnDialog" Value="InvalidDirDlg" Order="3"><![CDATA[NOT WIXUI_DONTVALIDATEPATH AND WIXUI_INSTALLDIR_VALID<>"1"]]></Publish>
         <Publish Dialog="InstallDirDlg" Control="Next" Event="NewDialog" Value="ServerDlg" Order="4">WIXUI_DONTVALIDATEPATH OR WIXUI_INSTALLDIR_VALID="1"</Publish>
         <Publish Dialog="InstallDirDlg" Control="ChangeFolder" Property="_BrowseProperty" Value="[WIXUI_INSTALLDIR]" Order="1">1</Publish>
         <Publish Dialog="InstallDirDlg" Control="ChangeFolder" Event="SpawnDialog" Value="BrowseDlg" Order="2">1</Publish>

         <Publish Dialog="ServerDlg" Control="Back" Event="NewDialog" Value="InstallDirDlg">1</Publish>
         <Publish Dialog="ServerDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg">1</Publish>

         <Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="ServerDlg" Order="1">NOT Installed</Publish>
         <Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="MaintenanceTypeDlg" Order="2">Installed</Publish>

         <Publish Dialog="MaintenanceWelcomeDlg" Control="Next" Event="NewDialog" Value="MaintenanceTypeDlg">1</Publish>

         <Publish Dialog="MaintenanceTypeDlg" Control="RepairButton" Event="NewDialog" Value="VerifyReadyDlg">1</Publish>
         <Publish Dialog="MaintenanceTypeDlg" Control="RemoveButton" Event="NewDialog" Value="VerifyReadyDlg">1</Publish>
         <Publish Dialog="MaintenanceTypeDlg" Control="Back" Event="NewDialog" Value="MaintenanceWelcomeDlg">1</Publish>
      </UI>

      <UIRef Id="WixUI_Common" />
   </Fragment>
</Wix>

Previous Article What is Splunk and How Does it Work?
Next Article What's New in uberAgent 2.0