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