1
votes

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?

3
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
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
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
Did you manage to resolve this issue?Stein Åsmul
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

3 Answers

0
votes

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.

0
votes

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.

0
votes

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.