3

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>
Rozwel
  • 1,990
  • 2
  • 20
  • 30

1 Answers1

1

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.

Cosmin
  • 21,216
  • 5
  • 45
  • 60
  • In this case Foo is a standalone framework library supporting multiple web applications. If we do not structure it in a way that allows side by side installs of the different versions on the same machine, then every time we update the library we break every app published against a previous version. We are not going to risk introducing breaking changes in 20 applications because app #21 needs the library to support a new feature that was not thought of initially. This is not a hack, this is exactly the scenario that the GAC and policy files were intended to support. – Rozwel Sep 15 '11 at 19:32
  • Do these web applications use MSI installers? If they do, they can share the assemblies. For example, Foo 2.0.0 can be shared between multiple products. This is done by using the same component name and GUID in all MSI packages. In this case Windows Installer will use a reference count to manage the component. Each installer should handle its own components. If you are installing Foo 2.0.1 you shouldn't need to worry about Foo 2.0.0. The old version should be handled automatically by the major upgrade and reference count. – Cosmin Sep 15 '11 at 20:28
  • No, everything we are doing is deployed to internal servers, we are not distributing any code beyond the company. A few of the sites are using a web deployment project, but most are published directly from VS to the development servers. From there the compiled code is copied up to the QA and Production boxes. The only reasons we are even putting the libraries into installer packages is because a few of them have dependencies that need to be accounted for and it makes it easier to make sure everything ends up in the right places on the various servers and developer PCs. – Rozwel Sep 16 '11 at 12:01
  • Maybe they should :). I'm not sure how you could proceed in this case. If you don't want to use the built-in reference counting, Windows Installer may get confused and leave old components behind or remove components which are used. Perhaps another user will have an alternative approach better suited for your scenario. – Cosmin Sep 16 '11 at 12:05
  • The point is that I am trying to rely on the component counters, and it is not working for the conditional component. 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. Moving every project to an MSI deployment is not an option, it just won't happen in this environment. – Rozwel Sep 16 '11 at 13:59