Major Upgrades with Shared Components

Major upgrades are Windows Installer products that can be installed like any other product with the added benefit of removing one ore more related products. For example, version 2 of a product can be installed on a clean machine, or on a machine with version 1 already installed and will remove version 1.

When another version of a product is removed depends on where you schedule RemoveExistingProducts. If you schedule the action late – either before or after InstallFinalizeshared components are a potential factor when developing your product installation. But before going into detail, let’s first discuss what major upgrades really are.

A common misconception is that major upgrades occur when the UpgradeCode is the same. The fact is that the UpgradeCode is only a minor detail for major upgrades. It’s really more of a related or family code. One product may upgrade any number of products regardless of whether their UpgradeCodes are the same. The Upgrade table is used to list all of the UpgradeCodes for products that may be upgraded, while the FindRelatedProducts action is responsible for using that information to gather all related products identified by their ProductCodes. RemoveExistingProducts uses that list of ProductCodes and runs a nested uninstall for each.

As the FindRelatedProducts action name denotes, the UpgradeCode is mainly used to relate different products. This is also evident with the MsiEnumRelatedProducts function. But without scheduling the RemoveExistingProducts action, that’s really all those products are even if the UpgradeCode is the same: related products.

Scheduling RemoveExistingProducts early is a safe way to perform a major upgrade. All the older products are removed first and you have flexibility to do things in your product deployment that may not be allowed in small updates or major upgrades. But doing so is also inefficient if the product composition really doesn’t change much or many of the files aren’t updated. You’ll be uninstalling one or more products in full and laying bits back onto the machine. If you have many of the same file versions, it may be more efficient to schedule RemoveExistingProducts later.

If you schedule RemoveExistingProducts later, however, you must consider the impact of shared components. This is because until RemoveExistingProducts actually executes, two or more products with the same components are installed. And since you should never change the GUID of a component that shares the same installation location, that means there are now shared components – components with a reference count of two or more (depending on how many products have installed those components to the same locations).

Imagine that you ship a component with GUID {788FAC73-431F-41AF-BF72-9AB2AE74DB4E} that installs files foo.dll and bar.dll. Already this is not the best component design since you’re installing two files – two versioned files to be exact – in the same component, but it helps illustrate the point. If another product installs the same component with GUID {788FAC73-431F-41AF-BF72-9AB2AE74DB4E} to the same location then that component is now a shared component with a reference count of two. If foo.dll wasn’t actually upgraded, the Windows Installer does not copy the file again but does install the component only to increase the reference count. This is why scheduling RemoveExistingProducts late is more efficient: potentially less disk I/O.

But this also raises the key point: because the other product – a newer version of the previous product – did not contain bar.dll in the composition of the component with GUID {788FAC73-431F-41AF-BF72-9AB2AE74DB4E}, bar.dll will be orphaned. This is because the component has a reference count of two before the previous product is uninstalled. When that product is actually uninstalled, the component is not removed from disk but instead the reference count is simply decremented to one. Because the new product does not know about bar.dll for the component in this scenario, bar.dll will be left behind when this newer product is removed. It’s now an orphaned file.

So while scheduling RemoveExistingProducts later may be more efficient, it’s also less safe that scheduling it early. Of course, if you follow good component authoring rules foo.dll and bar.dll would be in their own components. If this were the case and the newer product in this scenario still didn’t contain bar.dll, bar.dll and its parent component would be removed from disk because the reference count would only be one when that older product is removed.