15

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?

Wim Coenen
  • 66,094
  • 13
  • 157
  • 251

5 Answers5

8

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.

Rob Mensching
  • 33,834
  • 5
  • 90
  • 130
  • 1
    This worked for a special case I had once, but no guarantees - useful one for the oddness of the solution though: http://stackoverflow.com/a/12291807/129130 . I wonder how this affects the MSI database - it seems to re-register the product per machine and unregister a per user install, but does it do so for all users? I don't recall. I think we used this as a one-time fix for systems with primarily one user. – Stein Åsmul Mar 31 '14 at 19:15
  • This answer is incorrect. You *can* check both install scopes for existing products. See my answer. – Mike Fuchs Jan 28 '16 at 14:42
6

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.

Mike Fuchs
  • 12,081
  • 6
  • 58
  • 71
  • Works great the other way around too =) – Nilaksha Perera Sep 26 '16 at 12:32
  • 3
    Would it be possible to perform these actions without a UI? (I don't have a Control to place the Publish elements in), what could be a good workaround? – Jesús Otero Dec 05 '17 at 13:31
  • A possible issue with this is later when you release a second per-machine install. If you leave this workaround in, it will not upgrade the first per-machine to the second per-machine version. If you take it out, then users that want to upgrade the per-user version to the second per-machine version (jumping a version) will hit the same problem where the uninstall is skipped – Slack Groverglow Jan 21 '21 at 16:27
  • @JesúsOtero you can create custom actions to set, reset the property: `` and then call them in the install seequence: `` – Slack Groverglow Jan 21 '21 at 16:36
2

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

Ilya Serbis
  • 21,149
  • 6
  • 87
  • 74
  • +1 interesting hack. That mailing list thread doesn't have confirmation that it would actually work though. I'm no longer concerned with this issue, so I can't test it quickly. If anyone can confirm that it works, please comment. – Wim Coenen Jun 02 '10 at 12:55
  • The doesn't work. In my opinion it has never been tested because it doesn't even compile... the id "24INSTALLPERUSER" is not a legal identifier for example. The sequences 199 and 201 are not right eiter. I corrected the hack to make it work, checked with orca that the two custom actions would fit just before and after FindRelatedProducts, but at setup execution time both custom actions are still executed after FindRelatedProducts. You get a "Skipping FindRelatedProducts action: already done on client side". Too bad, it would have been a nice idea ... – Sébastien Nussbaumer Mar 28 '12 at 12:41
  • I have done something similar and it works, see my answer. – Mike Fuchs Jan 28 '16 at 14:41
1

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>
Robin Johnson
  • 357
  • 2
  • 11
1

I implemented Mike Fuchs approach, and while it did technically work (aka install functioned as expected), the program did not appear in APR for other users.

In thinking this over, it makes sense because how would the installer know that all per-user installs were removed/updated to the new perMachine version.

Russ R
  • 61
  • 1
  • 7