1

I have an installer based on the WixUI_Advanced that allows users to choose their install scope (per user or machine wide).

When upgrading (have an existing app with a lower version installed) I would like to hide the install scope screen and automatically select the install scope they chose last time.

How can I tell what install scope was used for the previous installation?


Edit

Looking at my MSI logs I can see that my existing installation is found:

// Existing user specific installation
FindRelatedProducts: Found application: {C5D3DCD0-4A97-4224-AF22-BDDEB357EEB7}
MSI (c) (C4:F0) [11:11:39:289]: PROPERTY CHANGE: Adding WIX_UPGRADE_DETECTED property. Its value is '{C5D3DCD0-4A97-4224-AF22-BDDEB357EEB7}'.
MSI (c) (C4:F0) [11:11:39:289]: PROPERTY CHANGE: Adding MIGRATE property. Its value is '{C5D3DCD0-4A97-4224-AF22-BDDEB357EEB7}'.

// Existing machine wide installation
MSI (c) (2C:4C) [11:03:19:258]: FindRelatedProducts: current install is per-user.  Related install for product '{C5D3DCD0-4A97-4224-AF22-BDDEB357EEB7}' is per-machine.  Skipping...

I can see the WIX_UPGRADE_DETECTED and MIGRATE properties are set only when the existing installation's scope matches the current installation which makes sense. Perhaps I can use FindRelatedProducts directly?

Felix
  • 3,783
  • 5
  • 34
  • 53

2 Answers2

1

This is not a complete answer. I had to add as an answer because of formatting requirements.


UPDATE: Looked at this, ran out of time again. This really is no answer at all, but just lobbing it to you in case it can help you research it yourself.

Registry Persistence: I assume you have tried to persist ALLUSERS and / or the installation scope in the registry and read it back in the updated MSI? I didn't look at that. For that to work you have to do it in the first release of the package and keep it up later.

MSI API Automation: Here is a little hack to find the previously installed products on the box (this essentially runs similar stuff as "FindRelatedProducts" inside MSI files):

Inside MSI:

Set upgrades = Session.installer.RelatedProducts("INSERT-UPGRADE-CODE")
For Each u In upgrades
    scope = Session.installer.ProductInfo(u,"AssignmentType")
    MsgBox CStr(scope)
Next

Standalone, run script directly (install the MSI with the upgrade code specified first):

Set installer = CreateObject("WindowsInstaller.Installer")
Set upgrades = installer.RelatedProducts("INSERT-UPGRADE-CODE")

For Each u In upgrades
   MsgBox "Product Code: " & u & vbNewLine & "Installation Context: " & installer.ProductInfo(u,"AssignmentType")   
Next

MsgBox "Done"

I was thinking to do something like this in the GUI-sequence, but ran out of time again:

If scope = 1 Then
  Session.Property("ALLUSERS") = "1"
  Session.Property("MSIINSTALLPERUSER") = ""
  Session.Property("WixAppFolder") = "WixPerMachineFolder"
Else
  Session.Property("ALLUSERS") = "2"
  Session.Property("MSIINSTALLPERUSER") = "1"
  Session.Property("WixAppFolder") = "WixPerUserFolder"
End If

WiX Snippets:

<Binary Id='Scope.vbs' SourceFile='Debugging Custom Actions\Scope.vbs' />
<CustomAction Id='Scope.vbs' VBScriptCall='' BinaryKey='Scope.vbs' Execute='immediate' Return='ignore'/>

<..>

<InstallUISequence>
  <Custom Action='Scope.vbs' Before='CostInitialize' />      
</InstallUISequence>

I was going to look at this, but ran out of time. Essentially WIX_UPGRADE_DETECTED will be set in the new setup being installed. See this answer for more. You could use that property to determine whether to hide or show a button. I tested that briefly and it worked, but implementing it in WiX is harder. You need to override the whole dialog I think.

In MSI tables, it would be something like this (Orca screenshot - MSI viewer tools):

Orca


Throwing in some more links:

Stein Åsmul
  • 39,960
  • 25
  • 91
  • 164
  • Overriding the whole dialog isn't an issue, I'm doing that already :P The problem is `WIX_UPGRADE_DETECTED` only gets set if the current install scope matches the install scope of the existing install. Hence I'm trying to figure out the install scope of the existing install. – Felix Jul 10 '19 at 16:11
  • 1
    Sorry, couldn't look properly at this. Added a hacky little VBScript to try to get previous installation scope. Have a look, try the standalone one first maybe? You might need the Win32 / C++ functions to get proper granularity and control. I am not sure. It is very complicated to make a proper dual-scope MSI package. As people discover when they try. – Stein Åsmul Jul 11 '19 at 11:32
  • No worries :) I was hoping to have some magic variables set by MSI/Wix, but it looks like I may have to resort to writing some code :P Ooh, just read your registry suggestion. That could work too. Let me mess around a bit. – Felix Jul 11 '19 at 17:30
  • Yes, I guess I don't need to remind you that this is very high WTF factor - if you pardon my French (or *"resident evil of all things"*). This technology bites back. Be sure to test in many installation modes: `install`, `repair`, `modify`, `self-repair`, `patching`, `upgrades`, etc... There are many cog-wheels in an MSI and they interact so unexpectedly. Hard core testing needed. – Stein Åsmul Jul 11 '19 at 17:34
  • 1
    Ah, almost forgot. Let me send you to [Rob Mensching's Remember Pattern](http://robmensching.com/blog/posts/2010/5/2/the-wix-toolsets-remember-property-pattern/). Maybe you have already seen it. Very important read if you go the registry way. – Stein Åsmul Jul 11 '19 at 17:39
  • I'm trying to get a C# custom action working, but it looks like my `Microsoft.Deployment.WindowsInstaller.Installer` from `WiX Toolset v3.11\SDK\Microsoft.Deployment.WindowsInstaller.dll` doesn't have a `RelatedProducts` property or method :/ – Felix Jul 12 '19 at 15:32
0

I ended up checking for an entry with the DisplayName matching our app name in the registry in (inspired by this answer):

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall

Then I grabbed the content of InstallLocation to determine the install scope:

if (installLocation == string.Empty)
{
    // Installed before we introduced scope => never set install location
    return ExistingInstallation.MachineWide;
}
else if (installLocation.Contains(_programFilesPath))
{
    return ExistingInstallation.MachineWide;
}
else
{
    return ExistingInstallation.UserSpecific;
}
Felix
  • 3,783
  • 5
  • 34
  • 53