Hi everyone,

I'm implementing a WiX installer for a product that is going to be updated
frequently by most of its users.  After reading A LOT, I decided to only do
MajorUpdates that are scheduled early: afterInstallInitialize (see also:
http://www.joyofsetup.com/2008/12/30/paying-for-upgrades/).

Basically, I have the following in my WiX file:

    <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeErrorMessage)"
                  Schedule="afterInstallInitialize"
                  MigrateFeatures="yes"
                  AllowSameVersionUpgrades="yes"/>

With this strategy, I have run into a number of problems (dealbreakers really).
 I am writing this post to share my solution to these problems, and to ask for
some feedback from the more experienced WiX users out there.

Here we go.

First, the problems I encountered:

 1. If you implement the removal of additional files in the installation
    directory (config files added after the instal in my case) by using:

     <RemoveFile Id="rmf_RmAllFiles" Name="*.*" On="uninstall" />

    on the installation directory, then these files will also be removed with
    each update.  This is not acceptable, since there are config files among
    them, that must not be deleted in case of an update (FYI: I don't know
    their names when creating the installer).

    Similar posts:
     *
http://stackoverflow.com/questions/3287626/wix-installer-remove-files-on-uninstall-but-not-on-upgrade

 2. If you implement the removal of additional registry keys by using
    ForceDeleteOnUninstall on the parent key, as in:

     <RegistryKey Root="HKMU" Key="Software\Company\Product"
ForceDeleteOnUninstall="yes" />

    then these keys will also be removed with each update.  This is not
    acceptable for similar reasons as in point 1: these registry keys must not
    be deleted during an upgrade (and I don't know their names when creating
    the installer).

    Similar posts:
     *
http://stackoverflow.com/questions/8578788/wix-retaining-registry-settings-on-major-upgrade
     *
http://windows-installer-xml-wix-toolset.687559.n2.nabble.com/MSI-upgrade-remove-old-registries-td5639349.html

 3. If you implement shortcuts as described in the official How-To:


    
http://wixtoolset.org/documentation/manual/v3/howtos/files_and_registry/create_start_menu_shortcut.html,

    these shortcuts get deleted and created from scratch at each update. The
    same happens when the shortcuts are created by placing the shortcut
    element inside the <File> element.  The problem with this twofold:
     - Any pinned taskbar icon (pinned by the user himself) will completely
       break.  It becomes a white placeholder icon that does not work, and
       that has to be unpinned and pinned again by the user.
     - Any desktop shortcut created by the installer is going to "jump back"
       to its default place on the desktop, messing up the nice shortcut
       arrangement the user may have made.

    Similar posts:
     *
http://stackoverflow.com/questions/10655931/wix-minor-upgrade-remove-windows-7-taskbar-pinned-shortcut
     *
http://stackoverflow.com/questions/21241828/wix-do-not-remove-pinned-taskbar-on-install
     *
http://stackoverflow.com/questions/10126631/pattern-to-do-a-wix-upgrade-without-messing-with-the-desktop-icons-of-the-users
     *
http://windows-installer-xml-wix-toolset.687559.n2.nabble.com/Do-Not-Remove-Pinned-Taskbar-Shortcuts-on-Install-or-Upgrade-Windows-7-td7591982.html



>From reading through all the other posts and threads, I have come to the
conclusion that there is no nice way around these problems.  The only clean
solution would be to schedule the MajorUpdate late (afterInstallExecute).
 However, I don't want to go down that road since it leads to much more
headaches later on (due to versioning and component rules).

So here is what I've come up with (one point for each of the above problems):

 1. Do not use <RemoveFile Name="*.*"> on the directory.  To still remove the
    files during a uninstall, I use a CustomAction that does

      cmd.exe /C rmdir /S /Q [APPLICATIONFOLDER]

    with a condition REMOVE="ALL" AND NOT UPGRADINGPRODUCTCODE that prevents
    it from running during a normal upgrade.  (APPLICATIONFOLDER is the
    installation directory.)

      <CustomAction Id="ca_CleanupApplicationfolder_Cmd"
Property="ca_CleanupApplicationfolder" Value='"[SystemFolder]cmd.exe" /C rmdir
/S /Q "[APPLICATIONFOLDER]"'/>
      <CustomAction Id="ca_CleanupApplicationfolder" BinaryKey="WixCA"
DllEntry="CAQuietExec" Execute="deferred" Return="ignore" Impersonate="no"/>

      <InstallExecuteSequence>
        <Custom Action="ca_CleanupApplicationfolder"
After="RemoveExistingProducts">REMOVE="ALL" AND NOT
UPGRADINGPRODUCTCODE</Custom>
        <Custom Action="ca_CleanupApplicationfolder_Cmd"
Before="ca_CleanupApplicationfolder">REMOVE="ALL" AND NOT
UPGRADINGPRODUCTCODE</Custom>
      </InstallExecuteSequence>

 2. Same idea: do not use ForceDeleteOnUninstall="yes", instead, add a
    CustomAction that does

      cmd.exe /C reg delete HKLM\Software\Company\Product

    with the same condition as before. Note that this command only removes the
    product-specific key.  For a clean uninstall, the company key has to be
    removed as well (and only if there are no other products left).  For that,
    I use a dummy value under the company key.

    Full code:

      <CustomAction Id="ca_CleanupRegistryHKLM_Cmd"
Property="ca_CleanupRegistryHKLM" Value='"[SystemFolder]cmd.exe" /C reg delete
"HKLM\Software\Company\Product" /f'/>
      <CustomAction Id="ca_CleanupRegistryHKLM" BinaryKey="WixCA"
DllEntry="CAQuietExec" Execute="deferred" Return="ignore" Impersonate="no"/>

      <InstallExecuteSequence>
        <Custom Action="ca_CleanupRegistryHKLM"
After="RemoveExistingProducts">REMOVE="ALL" AND NOT
UPGRADINGPRODUCTCODE</Custom>
        <Custom Action="ca_CleanupRegistryHKLM_Cmd"
Before="ca_CleanupRegistryHKLM">REMOVE="ALL" AND NOT
UPGRADINGPRODUCTCODE</Custom>
      </InstallExecuteSequence>

      <Component Id="cmp_uninstall" Directory="APPLICATIONFOLDER" Guid="*">
        <RegistryValue Root="HKMU" Key="Software\Company" Name="Product"
Value="installed" Type="string" KeyPath="yes" />
        <!-- The following line is related to point 3 in this post (it removes
the Company subfolder of the start menu) -->
        <RemoveFolder Id="rmf_CompanyProgramMenuFolder"
Directory="CompanyProgramMenuFolder" On="uninstall"/>
      </Component>

 3. Here, I follow the WiX How-To for creating the shortcuts, but the
    component with the shortcuts gets Guid="", so that it won't be removed
    during a uninstall:

      <!-- Start menu -->
      <Component Id="cmp_startMenuShortcuts.shortcut" Directory="ShortcutFolder"
Guid="">
        <Shortcut Id="lnk_Launch.shortcut" Name="!(loc.Name)"
Description="!(loc.PackageDescription)" Target="[#file_MainExe]"
WorkingDirectory='APPLICATIONFOLDER' />
        <Shortcut Id="lnk_Uninstall.shortcut" Name="!(loc.UninstallLink)"
Description="!(loc.UninstallDescription)" Target="[System64Folder]msiexec.exe"
Arguments="/x [ProductCode]" />
        <!-- Prevent ICE64 error (installer won't ever remove the directory due
to the empty Guid. -->
        <RemoveFolder Id="rmf_ShortcutFolder" On="uninstall"
Directory="ShortcutFolder" />
        <!-- HKMU is the only sensible choice, but leads to ICE57 which has to
be ignored -->
        <RegistryValue Root="HKMU" Key="HKCU\Software\Company\Product\Installer"
Name="CreatedStartMenuShortcuts" Type="string" Value="[ShortcutFolder]"
KeyPath="yes" />
      </Component>

      <!-- Desktop -->
      <Component Id="cmp_desktopShortcuts.shortcut" Directory="DesktopFolder"
Guid="">
        <Condition>DESKTOPSHORTCUT=1 AND NOT DESKTOPSHORTCUTEXISTS</Condition>
        <Shortcut Id="lnk_Desktop.shortcut" Name="!(loc.Name)"
Description="!(loc.PackageDescription)" Target="[#file_MainExe]"
WorkingDirectory='APPLICATIONFOLDER' />
        <!-- HKMU is the only sensible choice, but leads to ICE57 which has to
be ignored -->
        <RegistryValue Root="HKMU" Key="HKCU\Software\Company\Product\Installer"
Name="CreatedDesktopShortcuts" Type="string"
Value="[DesktopFolder]!(loc.Name).lnk" KeyPath="yes" />
      </Component>

    The location of the shortcuts is saved in two registry keys (during
    install, the user may decide not to create a desktop shortcut). These keys
    are read out so that the files can be deleted with a custom action during
    uninstall.

    Moreover, I prevent the Desktop shortcut from being overwritten, when it
    already exists.  This is necessary because overwriting the shortcut will
    sometimes cause it to jump back to its default position on Windows 8 and
    Server 2012.

    Code:

      <Property Id="CLEANUP_CREATEDSTARTMENUSHORTCUTS">
        <RegistrySearch Id="cleanup_CreatedStartMenuShortcuts_SerachHKLM"
Root="HKLM" Key="Software\Company\InstallDir\Installer"
Name="CreatedStartMenuShortcuts" Type="raw" />
      </Property>

      <Property Id="CLEANUP_CREATEDDESKTOPSHORTCUTS">
        <RegistrySearch Id="cleanup_CreatedDesktopShortcuts_SerachHKLM"
Root="HKLM" Key="Software\Company\InstallDir\Installer"
Name="CreatedDesktopShortcuts" Type="raw" />
      </Property>

      <CustomAction Id="ca_CleanupCreatedStartMenuShortcuts_Cmd"
Property="ca_CleanupCreatedStartMenuShortcuts" Value='"[SystemFolder]cmd.exe" /C
rmdir /S /Q "[CLEANUP_CREATEDSTARTMENUSHORTCUTS]"'/>
      <CustomAction Id="ca_CleanupCreatedStartMenuShortcuts" BinaryKey="WixCA"
DllEntry="CAQuietExec" Execute="deferred" Return="ignore" Impersonate="no"/>

      <CustomAction Id="ca_CleanupCreatedDesktopShortcuts_Cmd"
Property="ca_CleanupCreatedDesktopShortcuts" Value='"[SystemFolder]cmd.exe" /C
del /F /Q "[CLEANUP_CREATEDDESKTOPSHORTCUTS]"'/>
      <CustomAction Id="ca_CleanupCreatedDesktopShortcuts" BinaryKey="WixCA"
DllEntry="CAQuietExec" Execute="deferred" Return="ignore" Impersonate="no"/>

      <InstallExecuteSequence>
        <Custom Action="ca_CleanupCreatedStartMenuShortcuts"
After="RemoveExistingProducts">REMOVE="ALL" AND NOT UPGRADINGPRODUCTCODE AND
CLEANUP_CREATEDSTARTMENUSHORTCUTS</Custom>
        <Custom Action="ca_CleanupCreatedStartMenuShortcuts_Cmd"
Before="ca_CleanupCreatedStartMenuShortcuts">REMOVE="ALL" AND NOT
UPGRADINGPRODUCTCODE AND CLEANUP_CREATEDSTARTMENUSHORTCUTS</Custom>
        <Custom Action="ca_CleanupCreatedDesktopShortcuts"
After="RemoveExistingProducts">REMOVE="ALL" AND NOT UPGRADINGPRODUCTCODE AND
CLEANUP_CREATEDDESKTOPSHORTCUTS</Custom>
        <Custom Action="ca_CleanupCreatedDesktopShortcuts_Cmd"
Before="ca_CleanupCreatedDesktopShortcuts">REMOVE="ALL" AND NOT
UPGRADINGPRODUCTCODE AND CLEANUP_CREATEDDESKTOPSHORTCUTS</Custom>
      </InstallExecuteSequence>

      <!-- The following property is somewhat redundant with
CLEANUP_CREATEDDESKTOPSHORTCUTS (due to it being placed in another corner of my
WiX file) -->
      <Property Id="DESKTOPSHORTCUTEXISTS">
        <RegistrySearch Id="find_DESKTOPSHORTCUTEXISTS_HKLM" Root="HKLM"
Key="Software\Company\Product\Installer" Name="CreatedDesktopShortcuts"
Type="file">
          <FileSearch Name="[DESKTOPSHORTCUTEXISTS]"
Id="find_DESKTOPSHORTCUTEXISTS_HKLM_file" />
        </RegistrySearch>
      </Property>



This solution seems to work (tested on several Windows Versions from XP to
Windows 8).  However, I am aware that it is not exactly best practice.  Any
feedback is therefore welcome.

Cheers,

andreas
------------------------------------------------------------------------------
_______________________________________________
WiX-users mailing list
WiX-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/wix-users

Reply via email to