WiX based Installer– Boilerplate

By Mirek on (tags: Installer, WiX, categories: tools)

WiX Toolset is a technology to create Windows installers for your software. It is open source and free for use, but as many of free things is not super user friendly. The creation of the installer is XML based. In this post I will try to present a kind of boilerplate template which you can use to quickly create most of the need installer.

Once you have the Wix Toolset installed you can add a Wix setup project to your application solution. You should find it under Windows Installer XML section. Assuming you application project name is MyApp go to setup project references and add a reference to MyApp project. Now open or add installer file Product.wxs and replace its content with following.

   1: <?xml version="1.0" encoding="UTF-8"?>
   2: <?define ProductVersion="!(bind.FileVersion.MainAppEXE)"?>
   3: <?define AppManufacturer="YOUR-MANUFACTURER-NAME-HERE"?>
   4: <?define AppTitle="YOUR-APP-NAME-HERE"?>
   5: <?define AppProjectName="YOUR-APP-PROJECT-NAME-HERE"?>
   6: <?define AppTargetDir=$(var.YOUR-APP-PROJECT-NAME-HERE.TargetDir)?>
   7:  
   8: <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
   9:      xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
  10:  
  11:   <Product
  12:     Id="*"
  13:     Name="$(var.AppTitle)"
  14:     Language="1033"
  15:     Version="$(var.ProductVersion)"
  16:     Manufacturer="$(var.AppManufacturer)"
  17:     UpgradeCode="PUT-NEW-GUID-HERE">
  18:  
  19:     <!--Install package per machine-->
  20:     <Package
  21:       Id="*"
  22:       InstallerVersion="200"
  23:       InstallScope="perMachine"
  24:       Compressed="yes"
  25:       Keywords="Installer"
  26:       Description="$(var.AppManufacturer) $(var.AppTitle) ver. $(var.ProductVersion) Installer"
  27:       Comments="(c) 2014 $(var.AppManufacturer)"
  28:       Manufacturer="$(var.AppManufacturer)"/>
  29:  
  30:     <MajorUpgrade DowngradeErrorMessage="A newer version of $(var.AppManufacturer) $(var.AppTitle) is already installed. If you are sure you want to downgrade, remove the existing installation via Programs and Features." />
  31:  
  32:     <!--Embed cab file inside the setup.msi-->
  33:     <MediaTemplate EmbedCab="yes" />
  34:  
  35:     <!--Check if there is .Net framework 4.5 installed-->
  36:     <PropertyRef Id="NETFRAMEWORK45"/>
  37:     <Condition Message="This application requires .NET Framework 4.5. Please install the .NET Framework then run this installer again.">
  38:       <![CDATA[Installed OR NETFRAMEWORK45]]>
  39:     </Condition>
  40:  
  41:     <!--Create directory structure-->
  42:     <Directory Id="TARGETDIR" Name="SourceDir">
  43:       <Directory Id="ProgramFilesFolder">
  44:         <Directory Id="OwnerDir" Name="$(var.AppManufacturer) ">
  45:           <Directory Id="INSTALLDIR" Name="$(var.AppTitle)" >
  46:           </Directory>
  47:         </Directory>
  48:       </Directory>
  49:  
  50:       <Directory Id="ProgramMenuFolder" Name="Programs">
  51:         <Directory Id="ProgramMenuDir" Name="$(var.AppManufacturer) $(var.AppTitle)" />
  52:       </Directory>
  53:  
  54:     </Directory>
  55:  
  56:     <!--Copy main exec file into target directory-->
  57:     <Component Id="cMainExecutable" Guid="PUT-NEW-GUID-HERE" Directory="INSTALLDIR" >
  58:       <File Id="MainAppEXE" Name="$(var.AppProjectName).exe" DiskId="1" Source="$(var.AppTargetDir)$(var.AppProjectName).exe" KeyPath="yes" >
  59:         <Shortcut Id="startmenuShortcut" Directory="ProgramMenuDir" Name="$(var.AppTitle)"
  60:                  WorkingDirectory="INSTALLDIR" Icon="main_icon.ico" IconIndex="0" Advertise="yes" />
  61:       </File>
  62:       <RemoveFolder Id="RemoveProgramMenuDir" Directory="ProgramMenuDir" On="uninstall"/>
  63:     </Component>
  64:  
  65:     <!--Create registry key in HKLM with the install path-->
  66:     <Component Id="cInstallPathRegKey" Guid="PUT-NEW-GUID-HERE" Directory="TARGETDIR" >
  67:       <RegistryKey Root="HKLM"
  68:                    Key="Software\$(var.AppManufacturer)\$(var.AppTitle)" >
  69:         <RegistryValue Type="string" Name="InstallPath" Value="[INSTALLDIR]" KeyPath="yes" />
  70:       </RegistryKey>
  71:     </Component>
  72:  
  73:     <!--Copy config file into target directory-->
  74:     <Component Id="cMainExecutableConfig" Guid="PUT-NEW-GUID-HERE" Directory="INSTALLDIR">
  75:       <File DiskId="1" Source="$(var.AppTargetDir)$(var.AppProjectName).exe.config" KeyPath="yes" />
  76:     </Component>
  77:  
  78:     <!--Copy additional libraries-->
  79:     <Component Id="cExternalDLL" Guid="PUT-NEW-GUID-HERE" Directory="INSTALLDIR">
  80:       <File  DiskId="1" Source="$(var.AppTargetDir)core.dll" KeyPath="yes" />
  81:     </Component>
  82:  
  83:     <!--Construct complete installation as a set of all components-->
  84:     <Feature Id="Complete" Level="1" ConfigurableDirectory="INSTALLDIR" >
  85:       <ComponentRef Id="cMainExecutable" />
  86:       <ComponentRef Id="cInstallPathRegKey" />
  87:       <ComponentRef Id="cMainExecutableConfig" />
  88:       <ComponentRef Id="cExternalDLL" />
  89:     </Feature>
  90:  
  91:     <!--Set icon on installer row in Add/Remove programs-->
  92:     <Icon Id="main_icon.ico" SourceFile="YOUR_ICON_HERE.ico"/>
  93:     <Property Id="ARPPRODUCTICON" Value="main_icon.ico" />
  94:  
  95:     <Property Id="WIXUI_INSTALLDIR" Value="INSTALLDIR" />
  96:  
  97:     <!--Set custom setup flow -> Only select the target directory-->
  98:     <UIRef Id="WixUI_InstallDirNoLicence" />
  99:     <UIRef Id="WixUI_ErrorProgressText" />
 100:  
 101:   </Product>
 102:  
 103: </Wix>

Description:

  1. In line 3 put your manufacturer company name.
  2. In line 4 put long title of your application like “My first WPF application”
  3. Lines 5 and 6 must include the name of the project of your application, so in our example it should be
    <?define AppProjectName="MyApp"?>
    <?define AppTargetDir=$(var.MyApp.TargetDir)?>
  4. Then you have to put new unique guids in every PUT-NEW-GUID-HERE placeholder. Those guids allows the windows installer to identify the components of your application and properly handle them. Once you set guids you should not change them between your application releases.
  5. You can adjust the installation be adding or removing unnecessary components. Just remember to update the feature compilation section in lines 84-89.
  6. For example you can remove the cInstallPathRegKey component if you don’t need one.
  7. Provide correct additional library name in line 80 or remove whole component if you do not have any extra libraries. The best practices say you should have one component with unique guid for each extra file you want to include into the installer. Each of components must be listed in feature compilation (line 84-89)
  8. Include your icon (.ico) file into the installer project and put its name in line 92 in SourceFile attribute
  9. In line 98 we use a customize UI mode. This only shows up the target directory selection to the user. No license or feature selection is shown. To have that in place you must include the modified WixUI_InstallDir  template. Just add new .wxs file, name it WixUI_InstallDirNoLicence.wxs and copy paste following xml in it
    <?xml version="1.0" encoding="UTF-8"?>
    <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
      <Fragment>
        <!--
        This is a custom install template which shows up only a target directory selection.
        No licence is displayed to the user
        -->
        <UI Id="WixUI_InstallDirNoLicence">
          <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" />
     
          <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="InstallDirDlg">NOT Installed</Publish>
          <Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg">Installed AND PATCH</Publish>
     
          <Publish Dialog="InstallDirDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg">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="VerifyReadyDlg" 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="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="InstallDirDlg" Order="1">NOT Installed</Publish>
          <Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="MaintenanceTypeDlg" Order="2">Installed AND NOT PATCH</Publish>
          <Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg" Order="2">Installed AND PATCH</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>
     
          <Property Id="ARPNOMODIFY" Value="1" />
        </UI>
     
        <UIRef Id="WixUI_Common" />
      </Fragment>
    </Wix>

 

And now you can compile your solution and you should have your application .msi installer under the installer project output directory.

 

Useful links

WiX Toolset main page, Wix Tutorial, WiX Toolset Manual