1

We have a wix setup project that installs and several COM dlls and a service using ServiceInstall. The COM dlls also have associated registry keys extracted with heat.exe to avoid issues with SelfRegCost.

However, the two seem to have conflicting requirements:

  • The COM dlls registry keys need 'RemoveExistingProducts After="InstallInitialize"' to avoid wiping the registry information if uninstalling after installing, e.g. if a dll path is modified on an upgrade.
  • The service need 'RemoveExistingProducts After="InstallExecute"' (or later) to avoid loosing the service account credentials on an upgrade.

I have read a ton of related questions/answers about msi, wix, services and COM but did not find a solution.

What is the correct way to solve this?

EDIT:

The installer uses 'automagic' generated component GUIDs, and has only one file per component. The exception is the COM dll components which are as generated by heat, i.e.:

<Component ...>
    <File ...>
        <TypeLib ...>
            <Class ...>
            ...
    </file>
    <RegistryValue ... HKCR...>
    ...
</Component>

It has 2 custom actions, which registers and un-registers a COM server (exe) because I could not figure out what else to do, as heat could not extract it.

It does write registry keys, to HKCR and HKLM, but none to HKCU.

It installs ~20 third party files COM files (.ocx), and currently installed into System32. It also install a number of third party files in our own folder.

Then it installs ~15 proprietary COM dlls and a number of non-COM files (incl. the service) to our own folder.

The service is installed using the Wix 'ServiceInstall' with a default account 'LocalSystem', but the user changes this after the first installation. The account information is not known by us. Unfortunately, in many cases the service needs access to network share(s) to read large images, so I do not see how this can work with a built in account.

As far as I know, no shared files.

I agree that RemomveExisting AfterInstallFinalize is preferable, so if we can get it to work with the COM registrations, that would be great.

Including help files (chm and pdf 177MB) it ends at 250MB.

UPDATE

The service issue is fixed if we use 'AfterInstallFinalize'. However, this leaves us with the COM dll issue.

We have created a test installer which installs just one COM dll and its corresponding registry keys (TypeLib...).

As expected, when upgrading it works fine IF the component is not modified. I.e. both the dll path and the auto-generated component-guid are unchanged.

But, if the dll path IS modified, effectively we install a new component, then the associated COM registry keys are removed after the installation, probably by RemoveExistingProducts. We tried but using the auto generated guid, and hard-coded to the same guid as the previously installed guid.

The issue seems to be that the dll path changes but most of the registry key do not. E.g. all the 'class' keys are missing. This is what I meant when I said 'wiping the registry information'. A repair of the installation brings back the COM registry keys.

So I guess my question boils down to: How do we correctly install/update COM dlls, so the COM registry keys are not un-installed if the file path is changed? Is this possible using REP=AfterInstallFinalize?

Kim
  • 37
  • 7
  • Thank you all so much for your help. There is a lot of information that I need to consume, I will get back when have tried it. – Kim Dec 22 '17 at 13:33
  • Judging from the above, your installer should work correctly already to be honest. I must be missing something. Are you sure all COM servers have auto-GUID enabled? Are you sure the custom actions for the COM EXE registration are working correctly? How are these conditioned and sequenced? What are they implemented as? Scripts? Batch files? DLL? EXEs? That is a normal-size setup - sounds quite vanilla. Any .NET COM Interop? Any GAC installs? I do believe the built-in accounts can access a network share, but I have never tried it. NetworkService could be tried - then you access as the machine. – Stein Åsmul Dec 22 '17 at 14:44
  • You state that dll paths for a COM dll may be modified on an upgrade. Why does this happen, and how often? If these are auto-GUID components, it should actually still work - at least after a self-repair. Is this what happens? You update and then self-repair is invoked on instantiation for that COM component? If this is the case, then I wish I could see the source or at least the two compiled MSI files (version 1 and 2) to determine what is actually going on. – Stein Åsmul Dec 22 '17 at 16:01
  • Did you manage to resolve this issue? – Stein Åsmul Dec 28 '17 at 22:48
  • Sorry, I have not had a chance to try it yet, as I have been away during the holidays. I look forward to try your suggestions next week. – Kim Dec 30 '17 at 17:18
  • I have now tried several of the suggestions and updated the question with my findings. – Kim Jan 04 '18 at 12:06
  • Hi Kim, I will look at this in a little while. In the mean time, [**please have a look here**](https://stackoverflow.com/questions/48035990/registry-issue-when-upgrading/48037265). Read the comments about the **msiexec.exe repair command**. Not great, but I will comment on the silent problem later. Apart from that a setup.exe launcher uninstalling the old version and then installing the new one would work if it wasn't for your service issue. Also generating new COM progids and classids would work, and I suppose a permanent COM component might work, but i haven't tested it. More later. – Stein Åsmul Jan 04 '18 at 12:31
  • Hi again. I also thought about new COM progids, which could work, but it is a lot of work, and works only for our own files. We also have third party COM files. Of the options available I have decided to go with the 'auto-repair' option, executed if the previous version requires it. Thanks again for all you input. So if you can add the 'repair' suggestion to your answer then I can set that as the solution. – Kim Jan 16 '18 at 15:16
  • Have you thought about COM isolation? Or registration-less COM with a manifest in the local installation folder? No registry impact at all. I have limited experience with this, but I know certain teams use it. There are several answers here on stackoverflow that describe it. – Stein Åsmul Jan 17 '18 at 15:08
  • That sounds like an interesting idea. I have no experience with it either, but will look into it later. It would be great to get rid of the registry entries. However, for now the repair-fix will have to suffice. – Kim Jan 19 '18 at 10:49
  • I never have had the opportunity to really use reg-free COM, but I know several teams use it. There are complexities, and generally it seems only people with source code access to the solution in question succeed. Due to the nature of COM, you have to have a 100% binary match of file versions to get this working correctly and obviously the manifests have to be very carefully made. Some MSDN info: [Registration-Free Activation of COM Components: A Walkthrough](https://msdn.microsoft.com/en-us/library/ms973913.aspx). Maybe [have a look here](https://stackoverflow.com/q/465882/129130) too. – Stein Åsmul Jan 19 '18 at 12:19

3 Answers3

0

You should be able to use static GUIDs for the com components then it shouldn't matter where they are installed by the installer since the GUID would be the same as in the older installer.

I believe this way the GUID will have two product references for it and when your old installation goes to uninstall, it will not remove the registry settings associated with the COM components since they will have another reference.

You'll have to go into the old MSI and see what GUIDs were auto generated for each COM component though. Probably the easiest way to do this would be decompiling your old MSI with dark.exe or using ORCA.

I think this would work but you'll have to test it out. Try with just a single COM component first as a proof of concept. I'm not sure if it will leave behind the old COM component in the old install location.

Brian Sutherland
  • 4,698
  • 14
  • 16
0

You need to be more precise about "wiping the registry information". If the COM Dll installer component ids are different, then an upgrade after InstallExecute will result in that older Dll's reference count (by installer id) counting down to zero (if there are no other client products), so that would normally result in the component (and therefore BOTH the Dll and the registry entries) being removed after they've been installed (because REP is after everything is installed). This also gets complicated because a repair might notice that the newer Dll is now missing from the upgrade product and try to re-install. (If the registration path has changed then Heat.exe might generate a new installer component id - not sure on that, sorry.)

If the COM Dll installer component ids are the same, they will be shared, but if you move the COM Dll to another location the registry path is required to be different but it may still refer to the older location. If this is the case, you may need to author a RemoveRegistryValue to get rid of the old registration before the new registry information is written (the RemoveRegistryValues action is before WriteRegistryValues). This is the approach I'd try, with the caveat that I'm not clear exactly what you see in the registry or after a repair.

So as Brian says, check the installer component ids for the COM Dlls, and do the afterInstallexecute upgrade with a verbose MSI log.

For the service credentials, it would help to know the background. If these credentials are supplied at install time (WiX ServiceInstall) and never changed it's common (if not too secure) to preserve the credentials somewhere secure and apply them at upgrade time. As an experiment, does a Repair of the installed product lose the service credentials? Any potential application of uninitialized ServiceInstall credentials could cause that problem.

PhilDW
  • 20,260
  • 1
  • 18
  • 28
  • I am not sure I understand. So if the path of the installed COM dll has changed, I shall still use the original installer component guid to avoid the refcount going to 0? However, after a test it still removes the COM registry keys when upgrading. – Kim Jan 04 '18 at 09:08
  • The issue is that there are two resources here, the Dll and the registry entry for the path. You can't share these, which is what you're doing with an afterInstallExecute upgrade. Also there's not enough info about service credentials to know if there is a solution; for example, if they are entered as part of the install and go in the ServiceInstall element then they could be saved and restored, as I mentioned previously. – PhilDW Jan 05 '18 at 01:20
  • Sorry if it was not clear. The service credentials are not know, we just use the default credentials. Then the user manually sets up the service with an account appropriate to their set up. Yes, the problem must be that the com registry entries cannot be shared. It look like the only option is to run a repair if the dll path has changed, because REP after installFinalize has a lot of good properties, such a not overwriting registry values modified after installation. – Kim Jan 05 '18 at 10:33
  • It would be nice it you could 'decouple' the dll from it registry entries so they could be upgraded independently, then I could change the dll path without modifying the registry part :) – Kim Jan 05 '18 at 10:48
0

Short summary:

All I am saying below is essentially: break the link to the old, erroneous state where the new and old setups are confused about files pointed to erroneously by multiple GUIDs. Use one file per component to solve all kinds of reference counting issues. Keep GUIDs stable across releases going forward - use WiX's auto-GUID feature to do this automagically. Enforce late uninstall of existing product during major upgrades. Your problems should be solved, but read the suggested steps for a snag or two.

Suggested steps:

  • Enforce one file per component. This solves all kinds of problems. There are a few exceptions with multi-file assemblies and some fringe cases.
  • At the same time apply the WiX auto-GUID feature where you set the GUID to "*". This should effectively take care of component reference counting for you keeping them stable until the installation location for the component's key file / registry key changes (it generally shouldn't, no need to).
  • Install to a new location (new folder name). This is to "start fresh" and eliminate any interference from older versions. This is not always possible for components going to shared locations. If you go into the system32 folder, just set the component permanent. If you go somewhere else, let us know where.
  • Move RemoveExistingProduct after InstallFinalize.
  • For your future releases you should have a working solution.
  • Downside: your service won't survive this without a "reinstall" (re-applied service credentials and a new install location), but from this point forward it should survive any upgrade. You should still test what happens during repair though - which I am not sure of (that problem already exists though).
  • Keep in mind that installing services with user credentials is generally a deployment anti-pattern as described in section 12 here: How do I avoid common design flaws in my WiX / MSI deployment solution?. Any chance you could remove this and make it run as LocalSystem or NetworkService, LocalService or some other standard account? (built-in account info - worth a quick read? See this as well).

Below are some more long-winded musings. Worth a skim, I hope. I'll leave it in, but I hope this step-by-step description is easier to digest.


Good advise already here. Basically the uninstall of the old version can happen before or after the uninstall of the new version. This is obviously clear already.

Now if your component GUIDs follow best practice and remain stable across versions, none of the problems you describe above should occur if you put RemoveExistingProducts after InstallFinalize, but you have to follow the component rules 100% (or you may see missing files after upgrade and things like that).

It is important to understand what the component rules really mean. Essentially there is a 1:1 mapping between an absolute installation location (key path) and a GUID. If the key-path changes, the GUID must change. If the GUID changes, the key-path should change. Otherwise all of this should be stable across releases. For simplicity and reliability I like to use one file per component for all files - this makes upgrades much easier to implement reliably. Maybe this explanation is better: Change my component GUID in wix?

Late placement of RemoveExistingProducts after InstallFinalize makes the major upgrade essentially work as a "patch" - installing only what is new, and removing only what is obsolete. This ensures that your service component won't be uninstalled and reinstalled (which could wipe out your credentials), but rather any higher version files it contains will be installed.

  • There is a similar description of upgrade behavior here: MSI Major Upgrade overwriting rules.
  • I wrote up a summary of common problems seen in WiX and MSI packages. It is a bit messy, but here it is: How do I avoid common design flaws in my WiX / MSI deployment solution?
  • Here is a discussion showing what usually happens if you don't use one file per component: https://www.symantec.com/connect/forums/upgrade-problem-and-removeexistingproducts . The solution for his problem would have been to change the component GUID AND the file name - to break the link to the old, erroneous state. His setting of a new key-path to a different file from the previous version (and the deletion of a previous key file) has broken the 1:1 link between absolute path (key path) and GUID. Renaming and assigning a new GUID gives the file "a new identity" - it is no longer entangled with the old state. There is a similar explanation here: Safely resolving duplicate component GUIDs in Wix.
  • A more extreme version would be to change the name of the installation folder from the previous version. This would require that you re-generate all component GUIDs installing into that (sub)directory (since all absolute paths have changed). I tested this back in the day and it worked, but haven't dealt with it in years since I adopted a one-file-per component strategy. It really solves all kinds of problems.

I would ask some questions to clarify things:

  • How big is this installer in number of files, and in megabytes? Just to get a feel for its complexity and ease of maintenance.
  • How many COM files are there, are they your own, proprietary files, or are they shared files installed to shared locations?
  • Do you use one file per component, or do you install multiple files per component?
  • Do you write a lot of registry data? If so, how do you write it? Multiple components? Do you write to HKCU, HKLM? I recommend writing all HKCU settings from the application itself, and not from the setup. This will decouple it from any deployment entanglement. It will be very much more reliable.
  • How do you set the service credentials? Do you install the service via a custom action or by means of the normal MSI tables? As Phil states, this is important for repair operations among other things.
  • Are there many custom actions overall, if so, what do they do?

If it isn't obvious, the conclusion is that I would follow the component rules 100% accurately and put RemoveExistingProduct after InstallFinalize to make the upgrade behave as a patch. For huge packages this can be a lot faster too. The problems you describe should not occur if this is done right.

By following the component rules you will also be able to deliver a minor upgrade for your product - if needed. This can be crucial for a commercial product if you discover half a dozen of files that you want to "hotfix" after having installed a package with thousands of files. Extreme care is actually needed to make this work, but it is possible.

If you haven't followed the component rules so far, the easiest way to start is in my opinion to install to a different folder than before in ProgramFiles (to break the link to any past sins), and set the component guids to auto-generate and use a single file per component. WiX's auto-generate guids are meant to remain stable based on the installation location under ProgramFiles. In other words if you later change the installation location all guids will be different from before, but until then they remain stable. Automagic. This even allows patching and minor upgrades if you are very thorough.

Stein Åsmul
  • 39,960
  • 25
  • 91
  • 164