Wix - third party software reinstalls everytime app is run - wix

My application installs the DesKey Dk2 dongle drivers if they are not installed or are lower than a perticular version. One of the user is complaining that whenever he runs the application from start menu shortcut, DK2 installation starts and then application is launched. Nobody else is facing this issue.
I am indtalling Dk2 as custom action in Wix script as below. lets say DK2 is represented as ABC:
<DirectoryRef Id="TARGETDIR">
<Directory Id="ABCRedistDirectory" Name="ABCDrivers">
<Component Id="ABCRedist" Guid="*">
<File Id="ABC_EXE" Source="$(var.TargetDir)ABC.exe" KeyPath="yes" Checksum="yes"/>
</Component>
</Directory>
</DirectoryRef>
<Property Id="DK2_VERSION">
<RegistrySearch Id="Dk2_Version"
Root="HKLM"
Key="SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\DESkey DK2 Uninstall"
Name="DisplayVersion"
Type="raw" />
</Property>
<Feature Id="ABCRedist" Title="ABC drivers" AllowAdvertise="no" Display="hidden" Level="1">
<ComponentRef Id="ABCRedist"/>
</Feature>
<CustomAction Id="InstallDK2Drivers" FileKey="ABC_EXE" ExeCommand="" Execute="deferred" Impersonate="no" Return="check"/>
<InstallExecuteSequence>
<Custom Action="InstallDK2Drivers" Before="InstallFinalize">
<![CDATA[NOT DK2_VERSION OR DK2_VERSION < "7.34.0.57"]]>
</Custom>
</InstallExecuteSequence>
The warning in Windows Event viewer points to main executable, which is below:
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLLOCATION" Name="FolderName">
<Directory Id ="MyFolder" Name="MyApp">
<Component Id ="MyApp.exe" Guid="*">
<File Id="MyApp.exe" Source ="$(var.TargetDir)MyApp.exe" KeyPath="yes" Checksum ="yes" />
<Shortcut Id="MyAppStartMenuShortcut" Name="My App" Directory="ProgramMenuDir" Icon="MyAppIcon.exe" WorkingDirectory="MyFolder" Advertise="yes"></Shortcut>
<Shortcut Id="MyAppDesktopShortcut" Name="My App" Directory="DesktopFolder" Icon="MyAppIcon.exe" WorkingDirectory="MyFolder" Advertise="yes"></Shortcut>
</Component>
...
Now it is not happening on other machines so I am not able to diagnose. Can anybody point out any obvious mistake? What can I do to diagnose this on customer's machine i.e. how to get logs when installing .exe, .dll, some third party installers like VC100 CRT and VC100 MFC and .Net 4.0 bootstrapper?
Thanks in advance.

I can't tell from your post if the 3rd party ABC product that you're using is the same one that is repairing, or maybe the client doesn't know and you're assuming that it is your ABC thing. There's no indication in that WiX fragment exactly how you're installing the ABC thing, all you show is that it's copied to disk, there's no clue how you are running it to get it installed, and no shortcuts either.
What's happening in general seems to be that the other product is going into repair mode. There should be MsiInstaller entries in the Application Event Log that say something about whatever is wrong, referencing component ids, products, and maybe file names or registry entries.
Your setup may have a conflict with that other install. It's unlikely to be anything to do with your shortcut except that your shortcut is advertised, so it goes off into a component feature check, and is apparently finding that you are sharing something with that other app, and now it needs repairing. If the 3rd party app that repairs is not your ABC thing then you won't be able to reproduce the issue unless you also install that 3rd party thing and find out what you're sharing with it, perhaps in the wrong way.

Is this some sort of bit-torrent component? I see an abc project on sourceforge.net with Python dll's, some visual C++ runtimes included as well as other stuff. This is by no means a properly constructed package, and it could trigger the self-repair problems seen on that particular computer.
To debug this would require to see what entries exist in the Windows Event log - it will specify what MSI component triggered the repair. The error could still be in other packages since a COM call from the embedded components in this abc package, could trigger a repair in any MSI package installed on the system. As could taking over a file association associated with torrent files.

Right click My Computer
Select Manage
Event Log -> Windows Logs -> Programs
In this log you will find entries starting with "Description: Detection of product..." with id 1001 or 1004. Here is a sample:
Event ID: 1001
Description: Detection of product "{4ED0C75A-8BC5-4520-B9C7-76968FD5677F}", feature "Test" failed during request for component "{A7B09747-E527-4E1B-AE51-323CD636210F}"
This information is enough to determine what package triggered the self-repair. Please provide this information and we can take it from there.
I extracted the above sample information from Stefan Krüger's MSI FAG at installsite.org.

You are using advertised shortcuts. In your Shortcut elements, set Advertise="no" or remove the Advertise attribute completely.
For more information, see this SO answer and msdn.

Related

Registry key not updating when value was set to 0 when using WiX Toolset

The registry key value's isn't being updated with its intended data by WiX Toolset for a MSI. If the k:v is missing, it adds it. If the k:v's data is set to 0, it ignores it completely, which is the actual problem here (I think)
The basic goal is to verify this registry key value exists with the intended data-value before installation, and the reboot prompt triggers if the key had to be added/updated.
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<Product Id="*" Name="SampleInstaller" Language="1033" Version="1.0.0.0" Manufacturer="ACME" UpgradeCode="cf6248e9-d7da-4996-9b8e-90072e8510f6">
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" Platform="x64"/>
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
<Feature Id="ProductFeature" Title="SampleInstaller" Level="1">
<ComponentGroupRef Id="ProductComponents" />
</Feature>
</Product>
<Fragment>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder64">
<Directory Id="INSTALLFOLDER" Name="SampleInstaller" />
</Directory>
</Directory>
<Property Id="VKB_QUERY_HKCU" Secure="yes">
<RegistrySearch Id="VkbVisibleHkcu"
Win64="yes"
Type="raw"
Root="HKCU"
Key="Software\Microsoft\TabletTip\1.7"
Name="TipbandDesiredVisibility"/>
</Property>
<ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
<Component Id="ShowVKB_Registry_HKCU" Guid="{97AB4B1D-C9C8-4B34-9328-FF8CA3ED8992}" Directory="INSTALLFOLDER">
<RegistryKey Id="VKB_Registry_Key_HKCU" Root="HKCU" Key="Software\Microsoft\TabletTip\1.7" ForceCreateOnInstall="yes" ForceDeleteOnUninstall="no">
<RegistryValue Id="VKB_Registry_Value_HKCU" Action="write" Type="integer" Name="TipbandDesiredVisibility" Value="1"/>
</RegistryKey>
</Component>
</ComponentGroup>
<InstallExecuteSequence>
<ScheduleReboot After="InstallFinalize">NOT (VKB_QUERY_HKCU = "#1")</ScheduleReboot>
</InstallExecuteSequence>
</Fragment>
</Wix>
Also, it seems like the exit code from MSIEXEC is always returning 0 instead of 3010 of 1641 when checking $LastExitCode and %errorlevel%. I haven't messed with the different reboot behaviors, but I thought having the reboot prompt would have caused my installer to exit non-zero, so any guidance there is also appreciated.
Do that install and create a verbose log with:
msiexec /I [path to msi] /l*vx [path to a text log file]
and look at the property values etc. The most likely reason you're not getting the 3010 exit result is that the ScheduleReboot is conditioned false.
Assuming that everything is otherwise working as intended, the problem might be that you need to set Secure to Yes in your property declaration, otherwise the value won't be transferred from the UI sequence registry search into the execute sequence. If the log shows it gettingthe correct value at the start of the install but losing it later this is most likely the issue.
One of your comments refers to %errorlevel% but it's not clear why this is relevant. If you are initiating this from a batch file or similar then this is useful information to add. Also, if you are installing this in some way that is separate from the current interactive user then this is also useful to know.
The log seems to indicate that everything is fine. The properties have values that look correct, and the ScheduleReboot action is performed. The only issue I see is that Windows Installer did not show the dialog asking the user to do the reboot, so it does the non-interactive thing, which is to return 3010 to tell the caller that a reboot is required. There are no obvious reasons why Windows Installer didn't prompt for the reboot (which is what ScheduleReboot does) but if the install is running in a non-interactive user context then Windows will not show a desktop dialog to another user (or to no user nobody is logged on).
So, the sample works, but you can't test it repeatedly by re-running install. You have to uninstall it first, or stumble upon the reason like I did, and re-compile before re-running.
I noticed that the sample was only working right after a re-compile. So, I'm guessing the installer has a GUID or something tied to it at compile-time, which is then included with your installation. And when re-running the installation, it would just quickly run and close, not ask you to uninstall first or remove the existing product, so I was assuming that it just wasn't evaluating that my installer had to write the keys, and just ended when not having to do more.
So basically it was a test bug/lack of intrinsic WiX knowledge.
So, always uninstall your MSI before re-running it unless you are specifically trying to trigger upgrade behavior.
I'm not sure why I wasn't getting an error about the product already existing. I would have thought for sure that would be the default behavior.

WiX: Managed to not overwrite config file during upgrade, however shortcuts are removed

I have a similar problem like forki23 by bring Wix to not overwrite a configuration file during upgrade. I have a config file that should not be overwritten during upgrade, but it should be removed during uninstall. However every solution I find, breaks something else.
If I set NoOverwrite=yes and move the RemoveExistingProducts to InstallFinalize the config file is handled as I wished. However, in this case the shortcut is removed during upgrade for some reason. If I leave RemoveExistingProducts at InstallInitialize, the config file is actually removed during upgrade, however the shortcuts are present.
Why is this happening and is there are way to fix it?
<InstallExecuteSequence>
<RemoveExistingProducts After="InstallInitialize" />
<!-- InstallInitialize causes config-file to disappear during upgrade -->
<!-- InstallFinalize causes shortcuts to disappear during upgrade -->
...
<Property Id="DISABLEADVTSHORTCUTS" Value="1" />
...
<Directory Id="INSTALLLOCATION" Name="MyApp">
<Component Id="MYAPP.EXE" DiskId="1" Guid="...">
<File Id="MYAPP.EXE" Name="MyApp.exe" Source="..." Vital="yes" KeyPath="yes">
<Shortcut Id="startmenuShortcut"
Directory="ProgramMenuDir"
Name="!(loc.ProductName)"
WorkingDirectory='INSTALLLOCATION'
Icon="Icon.ico"
IconIndex="0"
Advertise="yes" />
</File>
<RegistryValue Root="HKLM"
Name="InstallLocation"
Key="$(var.InstallLocationRegistryKey)"
Type="string"
Value="[INSTALLLOCATION]">
</RegistryValue>
</Component>
<Component Id="MYAPP.EXE.CONFIG" DiskId="1" Guid="..." NeverOverwrite="yes">
<File Id="MYAPP.EXE.CONFIG"
Name="MyApp.exe.config"
Source="..."
KeyPath="yes" />
</Component>
...
</Directory>
...
<Directory Id="ProgramMenuFolder">
<Directory Id="ProgramMenuDir" Name="!(loc.ProductPrefix)">
<Component Id="ProgramMenuDir" Guid="...">
<RegistryValue Root="HKCU" Key="SOFTWARE\MyApp"
Type="string" Value="[INSTALLLOCATION]" KeyPath="yes" />
<RemoveFolder Id="ProgramMenuDir" On="uninstall"/>
</Component>
</Directory>
</Directory>
Note A: The config-file is a machine-wide configuration and should apply for all users.
Note B: I'm using WiX 3.7 and the target plattform is Windows 7 and 8.
Theoretically "NoOverwrite=yes and move the RemoveExistingProducts to InstallFinalize" should work, but it s clear we are losing something from the big picture. The best method to see why Windows Installer removes the shortcuts is to create a verbose log when launching the upgrade setup. You can do that in a cmd.exe with this command: msiexec /i [msi path] /L*V debug.log
The post the a download link for the log and the GUIDs of the components hosting the shortcuts so we can see if the log helps us understand what happens.
Windows installer works very exact in those things, and if anything gets removed in the After="InstallFinalize" case, this means, that a component has been removed, MSI has thought is not needed, because not contained in your new version of the msi file. Be very sure the GUID of the component containing MYAPP.exe and the shortcut has not changed in your new version. (Compare with a tool like Orce or Insted). It seems it has!
MSI removes only full components, not only shortcuts. Really! Maybe you have an update problem of shortcuts in Windows. Sometimes such things happen. Try to reboot to be sure, that this happens, what you think. Maybe there is an error in your test procedure (or it's the above mentioned GUID problem). There are not many other possibilities, if you have not custom actions which remove shortcuts or you try to add shortcuts as files or such dangerous stuff.
Putting a shortcut in the same component as the .exe is common, but not optimal in my eyes! I recommend to separate resources as much as possible, so put it in an own component. This has advantages, if you later want to rename the shortcut. Then you can just change the GUID of this component and the important .exe file is not touched.
There is a small disadvantage of that, loosing the direct connection to the file version of MYAPP.exe in overinstall scenarios, so if MYAPP.exe is a shared file between several different setups, this is not recommended. Perfect solutions for this are possible, but are not in the focus here.
Workaround: If you are still able to change the old (first) msi setup, just mark the component MYAPP.EXE.CONFIG as permanent. Then it will not be uninstalled during Major Upgrade (but not uninstalled at all, what has advantages and disadvantages, in other words, it is mostly acceptable for .config files).
If version 1 of your setup is already shipped, then you could do the same with some tricks too.

Remove registry keys under HKCU on a per machine installation

I build a perMachine installer using WiX 3.6 to install a software I had not developed. Unfortunately the software creates some registry keys under HKCU during execution.
On uninstall, the self created keys should also be removed. It seems not so easy to remove these keys. I am "fighting" with ICE57 and/or ICE38. Both complaining the mix between perUser and perMachine data.
Hopefully you can point me in the right direction on fixing this issue.
To overcome ICEs you should move Per-User registry to separate components and use some registry entry as keyPath for that component, i.e.:
<Component Id='PerUserRegistry' Guid='*'>
<RegistryValue Id="PerUserRegistry_KeyPAth" KeyPath="yes" Root="HKCU" Key="Software\[Manufacturer]\[ProductName]\[ProductCode]\PerUserRegistry" Name="[PackageCode]" Value="[ProductVersion]" Type="string" />
<!--Other Per-user registry goes here-->
</Component>
I completely agree with Christopher: It is common practice to leave per-user data on uninstall, but if removal is necessary, then Active Setup is the only real option.
First I propose you to remove them on Install or Re-Install instead of uninstall, you just need add RemoveRegirty entry and Active Setup, i.e. with this WiX code:
<Component Id='ActiveSetup' Guid='*'>
<RegistryValue Id="ActiveSetup00" Root="HKLM" KeyPath="yes" Key="SOFTWARE\SOFTWARE\Microsoft\Active Setup\Installed Components\[PackageCode]\" Name="StubPath" Value="msiexec /fup [ProductCode] /qb-!" Type="string" />
<RegistryValue Id="ActiveSetup01" Root="HKLM" Key="SOFTWARE\SOFTWARE\Microsoft\Active Setup\Installed Components\[PackageCode]\" Value="[ProductName] [ProductVerion] Configuration" Type="string" />
</Component>
<Component Id='PerUserRegistryCleanup' Guid='*'>
<RegistryValue Id="PerUserRegistry_KeyPath" Root="HKCU" KeyPath="yes" Key="SOFTWARE\SOFTWARE\Microsoft\Active Setup\Installed Components\[PackageCode]\" Name="StubPath" Value="msiexec /fup [ProductCode] /qb-!" Type="string" />
<RemoveRegistryKey Id='PerUserRegCleanup' Root='HKCU' Action='removeOnInstall' Key='Key\To\Be\Removed'/>
</Component>
Note: [PackageCode] use in ActiveSetup is very recommended, so with each new version (build) of MSI package you add separate entry (also see my final note). I used per-user active setup registry as key-path on purpose, so you don't run it for current user twice.
As for removing them after uninstall,
Now, hopefully you need to remove entire key, and not just some values. In either case, I would create custom action to add Registry entry for Active Setup during uninstall (or if there are many such keys/values, create and deploy .CMD file with those and launch it on uninstall, before RemoveFiles action, to add all of them to registry).
Note: that I would strongly recommend adding deleting this registry during install, or you might end up removing per-user values when software is yet installed.
So here's WiX code for all of this:
<CustomAction Id="CA_UninstallRegistryCleanUp" Directory="SystemFolder" ExeCommand="REG.exe ADD "HKLM\SOFTWARE\Microsoft\Active Setup\Installed Components\MySoftName_CleanUp" /v StubPath /d "reg add ^"HKCU\Key\To\Be\Removed^" /va /f" /f" Return="ignore" />
<InstallExecuteSequence>
<Custom Action='CA_UninstallRegistryCleanUp' After='RemoveRegistryValues'>REMOVE~="ALL"</Custom>
</InstallExecuteSequence>
<Component Id='RegCleanup_Remover' Guid='*'>
<RegistryValue Id="PerUserRegistry_KeyPAth" Root="HKLM" KeyPath="yes" Key="SOFTWARE\[Manufacturer]\[ProductName]\[ProductCode]\" Name="DummyKey" Value="[ProductVersion]" Type="string" />
<RemoveRegistryKey Id='RegCleanup_Remover' Root='HKLM' Action='removeOnInstall' Key='SOFTWARE\Microsoft\Active Setup\Installed Components\MySoftName_CleanUp'/>
</Component>
Final notes:
There just two small issues with all this Active Setup stuff: be careful on Windows Terminal Servers; and once active setup was run for one user for current .MSI, it will not run again if you decide to reinstall same package, unless you change its PackageConde or raise version under ActiveSetup registry key. These are topics for another day, let me know if need them clarified.
And don't forget to add all of above Components to some Feature.
The Windows Installer considers this user data and best practice is to not remove it. Either way, it's very difficult to try to remove it anyways since other user profiles are out of scope / context. It's theoretically possible to write a custom action to enumerate profiles and load registry hives but on some versions of Windows ( Vista ) that won't work due to restricted permissions granted to the windows installer service.
If you really, really must be able to remove custom action data on uninstall then take a look at:
Active Setup Explained
You are going to need to leave behind an program (exe for example ) by marking a component as permanent. Then you'll need a custom action to write a registry value during the uninstall (because Windows Installer doesn't support this).
The concept is during the install you lay down an EXE and during the uninstall you leave you. You then write to the ActiveSetup registry key telling it to run your EXE once for each subsequent user to logon to the machine. The EXE then deletes your registry values. Reboot (politely) if needed to unload the extensions from explorer.
But honestly, a better designed application wouldn't need all of this.

How do I install to LocalAppData folder?

Following directory setting works perfectly for me.
<Directory Id='TARGETDIR' Name='SourceDir'>
<Directory Id="ProgramFilesFolder">
<Directory Id='INSTALLDIR' Name='MyApp'/>
</Directory>
</Directory>
However, when I tried changing "ProgramFilesFolder" to "LocalAppDataFolder", I got lots of error when using light to link and generate my msi:
D:\runGroup.wxs(53) : error LGHT0204: ICE38: Component cmpA5561BE36D80EB58252E69DDA0C2FF8C installs to user profile. It must use a registry key under HKCU as its KeyPath, not a file.
D:\main.wxs(38) : error LGHT0204 : ICE64: The directory INSTALLDIR is in the user profile but is not listed in the Remove File table.
Looks like "LocalAppDataFolder" is not acceptable for WiX, while I believe it is one of the system folder properties which defined in here.
What am I supposed to use for LocalAppData folder?
I converted an application from being a perMachine install to be a perUser install. In order to properly convert the install I had to add a registry key for each of the components I have.
Originally I had the following:
<Component Id="C.MyExe">
<File Id="Fi.MyExe" Name="$(var.MyExe.TargetFileName)" Source="$(var.MyExe.TargetPath)" DiskId="1">
<Shortcut Id="SC.StartMenu"
Directory="D.ApplicationMenuDir"
Name="$(var.AppName)"
WorkingDirectory="INSTALLDIR"
Icon="MY_ICON.ico"
IconIndex="0"
Advertise="yes"
/>
...
When I moved the exe component to the user install I had to do something like this:
<Directory Id="LocalAppDataFolder" Name="AppData">
<Directory Id="MyAppDirectory" Name="$(var.AppName)">
<Component Id="C.MyExe" Guid="{MY_GUID}">
<CreateFolder />
<RemoveFolder Id="RemoveMyAppDirectory" On="uninstall" />
<RegistryKey Root="HKCU" Key="Software\MyCompany\MyApp">
<RegistryValue Name="MainExe" Value="1" KeyPath="yes" Type="integer" />
</RegistryKey>
<File Id="Fi.MyExe" Name="$(var.MyExe.TargetFileName)"
Source="$(var.MyExe.TargetPath)" DiskId="1" Checksum="yes">
</File>
</Component>
...
The most important part is that you will have to add a registry key which points to HKEY_CURRENT_USER. I added a registry value for each component which indicates that the component is installed.
I also had to remove the following: Advertise="yes".
I had this problem recently. I wanted to convert my installer from per-machine to a per-user but was getting ICE38. I asked on wix-users and one opinion was that you can ignore ICE38 because that was meant as a check for per-machine installs.
See the discussion at wix-users.
Since that is the case, ICE38 is (in my opinion) incorrect and you will want to ignore it. ICE38 implies you are installing per-user resources in the context of a per-machine installation but never verifies that this is so.
Actually authoring a per-user install requires that you ignore ICE38
because it won't ever be accurate for that world.
[Edit]
Looks like you got help here.
From Peter Shirtcliffe:
This is my own, admittedly inexpert, understanding of per-user installations:
Installing to subdirectory of LocalAppDataFolder is perfectly OK in a
per-user MSI. Because of certain scenarios relating to roaming users, you
need to add components containing elements for any
directories you create under LocalAppDataFolder. That's why ICE64 is
appearing.
The ICE38 error is slightly misleading: since you have a per-user
installation, it's safe to ignore as long as the user cannot pick an
alternative installation location that is common to all users. ICE38 is
checking for the situation where multiple users all install the same
component to the same path.
Just posting to help other people (like me).
Ok, just found that we can do it by overwriting "ProgramFilesFolder":
<SetProperty Id="ProgramFilesFolder" Value="[LocalAppDataFolder]" Before="CostFinalize"><![CDATA[NOT Privileged]]></SetProperty>
Another thing to do is, in <Package> we need to set InstallPrivileges to limited.
Well, I can see no reason why "ProgramFilesFolder" can be used directly while "LocalAppDataFolder" can't.
Are you installing per-user or per-machine? Also, what OS versions are you targetting? You might want to read:
Authoring a single package for Per-User or Per-Machine Installation context in Windows 7

WiX Conditional Feature/Component Orphaned on Uninstall

Edit: Quoting myself because I summarized the issue much better in one of the comments below…
I have a condition that is true when the package is installed, but
not true when it is removed. I expected MSI to remember that it had
installed the conditional component and remove it with the uninstall,
but this is not the case. I am trying to find out A) the proper way to
clean up this orphaned component, and B) the best way to protect
against this problem in the future.
I guess my question boils down to, is it safe to just delete an orphaned feature/component after a product is uninstalled? And is there any way to check what, if anything, is still referencing a component that I believe to be an orphan? And how do I fix my installer to keep this from happening in the future?
We have a wix project to install a library, Foo. This installer puts copies of Foo.dll into the GAC, and a folder, Program Files\Reference Assemblies\Foo\<version> by default. The installer also adds two registry keys, one is a custom key which stores the path of the Foo folder for reuse in future installs, the other tells Visual Studio to include the full <version> folder path in its search for installed libraries so that Foo shows up in the “Add References” dialog. Multiple versions of the Foo library can be installed on the machine at a time, each will be located in the appropriate <version> folder under Foo.
Foo 2.0.0 had a bug that slipped through testing, Foo 2.0.1 contained the bug fix, no other changes. It was decided that since the bug fix was the only change, we would add a policy file to the GAC which would redirected references for Foo 2.0.0 to Foo 2.0.1. This policy file was added to the installer as a new component inside of a new feature. An upgrade tag was added to detect and remove Foo 2.0.0 when Foo 2.0.1 was installed. The installation of the policy feature was made conditional on Foo 2.0.0 being detected. Everything seemed to be working and Foo 2.0.1 was pushed out.
Now, a year later, we discover that we again missed noticing a bug, this time in the installer setup rather than the library code. It turns out that when Foo 2.0.1 replaces 2.0.0, and is then uninstalled, the policy file is orphaned and remains in the GAC while all other files and keys are removed. I have tested this on a clean install of windows (virtual machines can be so useful) and confirmed that the problem can be replicated, i.e. no additional references to the component have snuck in to cause it to stay behind.
All of this was originally done in WiX 3.0 but we have recently moved up to using WiX 3.5. Our WiX code looks like this:
<Product Id="Guid 1" Name="Foo v2.0.1" Language="1033" Version="2.0.1" Manufacturer="My Team" UpgradeCode="Guid 2">
<Package InstallerVersion="300" Compressed="yes" />
<Media Id="1" Cabinet="media1.cab" EmbedCab="yes" />
<Upgrade Id="Guid 2">
<UpgradeVersion Minimum="2.0.0" Maximum="2.0.0" IncludeMaximum="yes" IncludeMinimum="yes" OnlyDetect="no" Property="UPGRADE2X0X0"></UpgradeVersion>
</Upgrade>
<Property Id="FOODIR">
<RegistrySearch Id="FooPath" Type="directory" Root="HKLM" Key="Software\Foo" Name="InstallPath"></RegistrySearch>
</Property>
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="RefAssemb" Name="Reference Assemblies">
<Directory Id="FOODIR" Name="Foo">
<Component Id="FooLibPath" Guid="Guid 3">
<RegistryKey Root="HKLM" Key="Software\Foo" Action="createAndRemoveOnUninstall">
<RegistryValue Name="InstallPath" Type="string" Value="[FOODIR]" KeyPath="yes"></RegistryValue>
</RegistryKey>
</Component>
<Directory Id="FOOVERSION" Name="v2.0.1">
<Component Id="Foo_VSFile" Guid="Guid 4">
<File Id="Foo_DLL" Source="$(sys.CURRENTDIR)2.0.1\Foo.dll" KeyPath="yes"></File>
</Component>
<Component Id="Foo_VSRegKey" Guid="Guid 5">
<RegistryKey Root="HKLM" Key="SOFTWARE\Microsoft\.NETFramework\v3.5\AssemblyFoldersEx\Foo v2.0.1" Action="createAndRemoveOnUninstall">
<RegistryValue Type="string" Value="[FOOVERSION]" KeyPath="yes"></RegistryValue>
</RegistryKey>
</Component>
<Directory Id="FOOGAC" Name="GAC">
<Component Id="Foo_GAC" Guid="Guid 6">
<File Id="Foo" Source="$(sys.CURRENTDIR)2.0.1\Foo.dll" KeyPath="yes" Assembly=".net"></File>
</Component>
<Component Id="Foo_Policy_2x0x1" Guid="Guid 7">
<File Id="Foo_PolicyDLL" Source="$(sys.CURRENTDIR)2.0.1\policy.2.0.Foo.dll" KeyPath="yes" Assembly=".net"></File>
<File Id="Foo_PolicyConfig" Source="$(sys.CURRENTDIR)2.0.1\policy.2.0.Foo.config" CompanionFile="Foo_PolicyDLL"></File>
</Component>
</Directory>
</Directory>
</Directory>
</Directory>
</Directory>
</Directory>
<Feature Id="ProductFoo" Level="1">
<ComponentRef Id="Foo_GAC"/>
<Feature Id="Foo_VSSupport" Level="1">
<ComponentRef Id="FooLibPath"/>
<ComponentRef Id="Foo_VSFile"/>
<ComponentRef Id="Foo_VSRegKey"/>
</Feature>
<Feature Id="Foo_Policy_v2x0x1" Level="0">
<ComponentRef Id="Foo_Policy_2x0x1"/>
<Condition Level="1">UPGRADE2X0X0</Condition>
</Feature>
</Feature>
</Product>
is it safe to just delete an orphaned feature/component after a
product is uninstalled?
No, it's not. If you just delete it, its component registration information is still left on the machine.
And is there any way to check what, if anything, is still referencing
a component that I believe to be an orphan?
Not really. But if there is something referencing one of your components, it's most likely another product developed by you or an older version of your current product which wasn't uninstalled correctly.
It's very unlikely that a random product would reference your component or assembly.
And how do I fix my installer to keep this from happening in the
future?
Use major upgrades which uninstall the old component and install the new one. No special policy files, no conditional installations or removals.
Multiple versions of the Foo library can be installed on the machine
at a time, each will be located in the appropriate folder
under Foo.
Why? If you have a single product, you can use major upgrades. This way the user will have only one version installed with only one version of your assembly.
Versioned assemblies installed side by side make sense only for different products.
It was decided that since the bug fix was the only change, we would
add a policy file to the GAC which would redirected references for Foo
2.0.0 to Foo 2.0.1. This policy file was added to the installer as a
new component inside of a new feature.
This is a hack and most likely this is what is causing the problem. Your new installed should have uninstalled the old version along with Foo 2.0.0.
Major upgrades should always be standalone.