We use wix to create setups for our application. For the case where the user has already installed an older version of our application, we do a major upgrade with the MajorUpgrade XML element. This is all working as desired: if an older version is installed, it is upgraded transparently. If a newer version is present, the installer aborts with a clear message.
However, I now want to change the InstallScope from "perUser" to "perMachine". Unfortunately this breaks the upgrade logic. The new installer does not appear to detect and remove the previous "perUser" installation. Instead, it just installs itself on top of the older version in the same ProgramFiles location. The user gets to see two entries in the "add/remove programs" list and sees two identical shortcuts on the desktop (the old user-specific one and the new perMachine one).
How do I transition my installer from the "perUser" to the "perMachine" install scope without breaking the upgrade logic?
Starting out with per-machine configuration.
<Property Id="ALLUSERS" Value="1" />
This will run an automatic per-machine check (if you have the MajorUpgrade element working, I presume), that does not pick up the previous per-user install:
Action start 15:46:35: FindRelatedProducts.
MSI (c) (D0:0C) [15:46:35:496]: FindRelatedProducts: current install is per-machine. Related install for product '{0C6604FB-58EC-48B9-8259-5871EFDADEB9}' is per-user. Skipping...
MSI (c) (D0:0C) [15:46:35:496]: FindRelatedProducts: current install is per-machine. Related install for product '{0C6604FB-58EC-48B9-8259-5871EFDADEB9}' is per-user. Skipping...
So before install, make sure you run another FindRelatedProducts
call for products that have been installed in user scope (e.g. like this):
<!-- temporarily switch to per-user install scope-->
<Publish Dialog="MyWelcomeDlg" Control="Next" Property="ALLUSERS" Value="{}">1</Publish>
<!-- find related products that have been installed per-user -->
<Publish Dialog="MyWelcomeDlg" Control="Next" Event="DoAction" Value="FindRelatedProducts">1</Publish>
<!-- switch back to per-machine install scope-->
<Publish Dialog="MyWelcomeDlg" Control="Next" Property="ALLUSERS" Value="1">1</Publish>
This in turn finds the per-user install:
Action start 15:46:36: FindRelatedProducts.
FindRelatedProducts: Found application: {0C6604FB-58EC-48B9-8259-5871EFDADEB9}
MSI (c) (D0:88) [15:46:36:716]: PROPERTY CHANGE: Adding WIX_UPGRADE_DETECTED property. Its value is '{0C6604FB-58EC-48B9-8259-5871EFDADEB9}'.
MSI (c) (D0:88) [15:46:36:716]: PROPERTY CHANGE: Adding MIGRATE property. Its value is '{0C6604FB-58EC-48B9-8259-5871EFDADEB9}'.
Existing products will be removed no matter in which check they are found.
Action start 15:46:41: RemoveExistingProducts.
RemoveExistingProducts: Application: {0C6604FB-58EC-48B9-8259-5871EFDADEB9}
On a side note: This does not circumvent a basic difficulty that arises when you have dual-purpose installers: User1 on the machine might install in per-user scope, then later User2 installs per-machine. User1 will see both installs in his programs/features table, and I do not know which one takes precedence. So consider going with per-machine installs only.
Sadly, the Windows Installer doesn't support that. Some process outside your package (a bootstrapper/chainer?) will have to manage the upgrade from per-user to per-machine.
You can use this technique to detect per-user installation from per-machine install: http://www.mail-archive.com/wix-users@lists.sourceforge.net/msg35197.html
This finds both existing perUser and/or perMachine installs. And forces the new install to a perMachine install (obviously logic to make that conditional could be applied as you wished). This works when run as an ordinary install and when installed silently under LocalSystem (silent upgrades). Keep in mind, it can only find a perUser install when running as that user.
Create a custom action (in DLL)
#pragma comment(linker, "/EXPORT:RunFindRelatedProducts=_RunFindRelatedProducts@4")
extern "C" __declspec(dllexport) UINT __stdcall RunFindRelatedProducts(MSIHANDLE a_hInstall)
{
MsiSetProperty(a_hInstall, "ALLUSERS", "1");
MsiDoAction(a_hInstall, "FindRelatedProducts");
MsiSetProperty(a_hInstall, "ALLUSERS", "");
MsiDoAction(a_hInstall, "FindRelatedProducts");
MsiSetProperty(a_hInstall, "ALLUSERS", "1");
return ERROR_SUCCESS;
}//end function
Then "replace" the standard FindRelatedProducts with the custom action
<InstallUISequence>
<FindRelatedProducts>0</FindRelatedProducts>
<Custom Action="RunFindRelatedProducts" Before='FindRelatedProducts'>NOT Installed</Custom>
</InstallUISequence>
<InstallExecuteSequence>
<FindRelatedProducts>0</FindRelatedProducts>
<Custom Action="RunFindRelatedProducts" Before='FindRelatedProducts'>NOT Installed</Custom>
</InstallExecuteSequence>