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>