Updates

It is a common scenario to use installers to upgrade previously installed applications. An upgrade can be divided in two basic parts:

  1. Common to all installations: Activities such as copying files, upgrading Add/Remove programs settings or upgrading the uninstaller. InstallBuilder provides support for most of this automatically.

  2. Unique to each installation: Activities such as backing up an existing database, populating it with new data, etc.

When most installers refer to upgrade functionality, they refer to a), when in reality the most critical part for a successful upgrade tends to be b), which cannot be easily automated.

What differentiates upgrade installers from normal installers?

Upgrade installers do not create a new uninstaller. Instead, the new installed files will be appended to the existing uninstaller. Additionally, on Windows an upgrade installer will not create a new entry on the Programs and Features settings. Instead, it will update the "version" field for the existing entry of the application. Also, it will not create a new entry into the Start Menu.

Setting the installer to upgrade installation mode

It is currently possible to create an upgrade installer by setting the <installationType> project property (which defaults to "normal") to "upgrade" as follows:

   <project>
      ...
      <installationType>upgrade</installationType>
      ...
   </project>

Another approach is to switch the installer to upgrade mode at run time, using a <setInstallerVariable> action to set the "installationType" installer variable to "upgrade". This approach allows you to create a smart installer which starts in normal installation mode and is capable of switching to upgrade mode under certain conditions, such as detecting an existing installation:

<project>
   ...
   <installationType>normal</installationType>
   ...
   <parameterList>
      <directoryParameter>
         <name>installdir</name>
         <description>Installer.Parameter.installdir.description</description>
         ...
         <!-- If we found an existing installation in the selected
         directory we set installationType=upgrade -->
         <postShowPageActionList>
           <setInstallerVariable>
             <name>project.installationType</name>
             <value>upgrade</value>
             <ruleList>
               <fileTest condition="exists" path="${installdir}"/>
             </ruleList>
           </setInstallerVariable>
         </postShowPageActionList>
        </directoryParameter>
   </parameterList>
   ...
</project>

The following example detects an existing installation by checking the existence of the ${installdir} directory, using a <fileTest> rule.

<project>
   ...
   <preInstallationActionList>
      <!-- detect existing installation, then switch to
      upgrade mode and display a note. -->
      <actionGroup>
        <actionList>
           <showInfo>
              <text>An existing installation has been detected in ${installdir}.</text>
           </showInfo>
           <setInstallerVariable name="project.allowComponentSelection" value="0"/>
           <setInstallerVariable name="project.installationType" value="upgrade"/>
           ...
           <!-- it also is possible to enable/disable components here: -->
           <componentSelection select="customcomponentname"/>
           <componentSelection deselect="customcomponentname"/>
           <!-- or to perform additional actions related to the upgrade installer. For
           example, hiding he ${installdir} page, as we already detected the installation-->
           <setInstallerVariable name="project.parameter(installdir).ask" value="0"/>
           ...
        </actionList>
        <!-- Assume an existing installation if ${installdir} directory exists -->
        <ruleList>
           <fileTest condition="exists" path="${installdir}"/>
        </ruleList>
      </actionGroup>
      ...
   </preInstallationActionList>
   ...
</project>

Other approaches can be used to detect an existing installation, such as reading a Windows registry key with <registryGetKey> or checking if the value of a system environment variable (${env(PATH)}, for instance) contains a particular value: this can be done using the <compareText> rule.

In addition to detecting the installation directory, you can also compare the installed version with the bundled one in case the user is trying to install an outdated version. On Windows, you could use the built-in registry key HKEY_LOCAL_MACHINE\SOFTWARE\${project.windowsSoftwareRegistryPrefix}\Version, and check for an .ini file located in the old installation directory when working in other platforms:

<project>
  ...
  <preInstallationActionList>
     <!-- Retrieve the old version -->
     <registryGet>
       <!-- By default, InstallBuilder stores the installation
       directory in this key -->
       <key>HKEY_LOCAL_MACHINE\SOFTWARE\${project.windowsSoftwareRegistryPrefix}</key>
       <name>Version</name>
       <variable>oldVersion</variable>
       <ruleList>
          <platformTest type="windows"/>
       </ruleList>
     </registryGet>
     <iniFileGet>
       <file>${oldInstalldir}/info.ini</file>
       <section>Main</section>
       <key>version</key>
       <variable>oldVersion</variable>
       <ruleList>
          <platformTest type="windows" negate="1"/>
       </ruleList>
     </iniFileGet>
     <!-- Validate if the version bundled is valid for the update -->
     <throwError>
       <text>The existing installation is newer or equal that the bundled. Aborting...</text>
       <ruleList>
          <compareVersions>
             <logic>greater_or_equal</logic>
             <version1>${oldVersion}</version1>
             <version2>${project.version}</version2>
          </compareVersions>
       </ruleList>
     </throwError>
  </preInstallationActionList>
  ...
</project>

Selecting the files to upgrade

By default an upgrade installer (as well as a regular installer) will overwrite existing files on the disk. You can customize this global behavior by using the project property overwritePolicy, which can take the following values:

  • always : an existing file on the disk will always be overwritten.

  • never : an existing file on the disk will never be overwritten.

  • onlyIfNewer : an existing file on the disk will only be overwritten if it has an older timestamp than the file being installed.

   <project>
      ...
      <overwritePolicy>onlyIfNewer</overwritePolicy>
      ...
   </project>

Separating the upgrade functionality from the regular behavior in a smart installer

A good approach to separate the regular and update functionality is to include all of the update-related actions and files in a separate component, which will be disabled for normal installations and enabled for upgrade installations. You can enable and disable components inside an action list using the <componentSelection> action:

   <project>
     ...
     <preInstallationActionList>
        ...
        <!-- For an upgrade installation -->
        <componentSelection>
           <select>upgradecomponent</select>
           <deselect>default,datacomponent</deselect>
           <ruleList>
              ...
           </ruleList>
        </componentSelection>

        <!-- For a normal installation -->
        <componentSelection>
           <select>default,datacomponent</select>
           <deselect>upgradecomponent</deselect>
           <ruleList>
              ...
           </ruleList>
        </componentSelection>
        ...
     </preInstallationActionList>
     ...
   </project>

Using built-in functionality to check for newer versions of the product on startup

You can make your installers check for the latest version at a specified URL. For that, you will need to include the following tags in your xml project file:

<project>
    ...
    <!-- versionId should be a positive integer number, and less than the
         version number you will use in the update.xml file below described -->
    <versionId>100</versionId>
    <checkForUpdates>1</checkForUpdates>
    <updateInformationURL>http://www.example.com/updates/update.xml</updateInformationURL>
    ...
</project>

The <updateInformationURL> points to a remote XML file in the server with the update information and should match the following structure:

<installerInformation>
    <versionId>2000</versionId>
    <version>4.0.1</version>
    <platformFileList>
        <platformFile>
            <filename>program-4.0.1.exe</filename>
            <platform>windows</platform>
            <md5></md5>
        </platformFile>
        <platformFile>
            <filename>program-4.0.1.run</filename>
            <platform>linux</platform>
            <md5></md5>
        </platformFile>
    </platformFileList>
    <downloadLocationList>
        <downloadLocation>
            <url>http://www.example.com/updates/download/</url>
        </downloadLocation>
        <downloadLocation>
            <url>ftp://www.example.com/updates/download/</url>
        </downloadLocation>
    </downloadLocationList>
</installerInformation>

The <versionId> will be compared with the current installer <versionId>. You can also specify a list with the download URL where the full download URL will be: downloadLocation + filename.

Detecting the previous installation directory

On Windows, InstallBuilder automatically creates a registry entry for your program. You can use the <registryGet> action (for instance during the <initializationActionList>) to get the location in which your software has been installed.

      <registryGet>
          <key>HKEY_LOCAL_MACHINE\Software\${project.windowsSoftwareRegistryPrefix}</key>
          <name>Location</name>
          <variable>installdir</variable>
          <ruleList>
              <platformTest type="windows"/>
          </ruleList>
      </registryGet>

Where <windowsSoftwareRegistryPrefix> is a project property that defaults to ${project.vendor}\${project.fullName}

Using normal mode when upgrading

The upgrade installation has a limitation: although it upgrades the installed files and the variables in the old uninstaller, it does not allow adding new actions to the <preUninstallationActionList> and <postUninstallationActionList>. In addition, as mentioned above, the Start Menu entry won’t be modified. Because of these restrictions, sometimes it is desirable to update an existing installation using the regular mode.

In these scenarios, the simpler approach is to use the default <overwritePolicy> (always) so the uninstaller will be fully recreated for each installation, as all of the files will be reinstalled and registered. Another alternative is to add to the uninstaller the existing files before performing the update installation, which will just install new components or will use the onlyIfNewer or never <overwritePolicy>:

  <project>
     ...
     <installationType>normal</installationType>
     ...
     <parameterList>
        <directoryParameter>
           <name>installdir</name>
           <description>Installer.Parameter.installdir.description</description>
           ...
           <!-- If we found an existing installation in the selected
           directory we configure the installer to perform the update but
           do not set the upgrade mode  -->
           <postShowPageActionList>
             <actionGroup>
                <actionList>
                  <!-- This is custom flag to set we are performing an upgrade
                  but do not modify the 'installationType' of the project -->
                  <setInstallerVariable name="isUpgradeMode" value="1"/>
                  <componentSelection>
                    <select>upgradecomponent</select>
                    <deselect>default,datacomponent</deselect>
                  </componentSelection>
                  <setInstallerVariable name="project.overwritePolicy" value="onlyIfNewer"/>
                </actionList>
                <ruleList>
                 <fileTest condition="exists" path="${installdir}"/>
                </ruleList>
             </actionGroup>
           </postShowPageActionList>
        </directoryParameter>
     </parameterList>
     <readyToInstallActionList>
        ...
        <!-- Add the files installed by the previous
        installation to the uninstaller -->
        <addDirectoriesToUninstaller>
          <addContents>1</addContents>
          <matchHiddenFiles>1</matchHiddenFiles>
          <files>${installdir}/data;${installdir}/core</files>
          <ruleList>
             <isTrue value="${isUpgradeMode}"/>
          </ruleList>
        </addDirectoriesToUninstaller>
        ...
     </readyToInstallActionList>
     ...
  </project>

In addition, if you are creating a Windows installer, you need to include some additional actions to clean old registry keys, the Start Menu shortcuts and the ARP (Add/Remove Programs) menu:

  <project>
     ...
     <installationType>normal</installationType>
     ...
     <readyToInstallActionList>
        ...
        <actionGroup>
          <actionList>
             <!-- Delete old Start Menu entries if needed -->
             <deleteFile path="${windows_folder_common_startmenu}/${previousStartMenuName}"/>
             <deleteFile path="${windows_folder_startmenu}/${previousStartMenuName}"/>

             <!-- Remove the old ARP Entry -->
             <!-- Get the old version -->
             <registryGet>
               <key>HKEY_LOCAL_MACHINE\Software\${project.windowsSoftwareRegistryPrefix}</key>
               <name>Version</name>
               <variable>oldVersion</variable>
             </registryGet>
             <!-- Delete the old ARP registry keys -->
             <registryDelete>
               <key>HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\${project.fullName} ${oldVersion}</key>
             </registryDelete>
             <registryDelete>
               <key>HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Management\ARPCache\${project.fullName} ${oldVersion}</key>
             </registryDelete>
          </actionList>
          <ruleList>
             <platformTest type="windows"/>
             <isTrue value="${isUpgradeMode}"/>
          </ruleList>
        </actionGroup>
        ...
     </readyToInstallActionList>
     ...
  </project>