My blog was pretty quiet for a while because of a problem we ran into regarding Windows Installer type 35 custom actions. These custom actions change the target location for a directory.

Developer Division uses type 35 custom actions for vertical integration with other editions of our Visual Studio products, so that installing products with shared features such as Visual Basic .NET don't install into two separate locations. It's not enough to use shared components since some editions contain a superset of components, so if another product with the same general feature is installed, type 35 custom actions are used to set the directory for all related components.

As the documentation states, type 35 custom actions should not be used during maintenance installations. We found out under the right circumstances why: when type 35 custom actions run after the CostFinalize action as they are supposed to - or fail ICE12 validation - they force a re-costing of all components to be installed under that directory. What does that mean for servicing?

CostFinalize determines the size requirements for installation based upon directory structure, component state, and component action state. During the first costing in a maintenance installation for a component that is already installed, the component requested state is Reinstall, so that when CostFinalize is complete the requested state for the component and the action state are set to Local. During the second costing the action state and request state are both Local, so Windows Installer thinks there is nothing that needs to be done for the component and the action state is set to Null. That means the resources contained within that components are not updated. As Hemchander Sannidhanam from the Windows Installer team depicts for a scenario to update an installed component:

Description Installed Request Action
Before CostFinalize is complete Local Reinstall Null
After CostFinalize is complete Local Local Local
After a type 35 custom action Local Null Null

When using type 35 custom actions you should be careful to only use them on a per-component basis, even conditioning them to run if the component state is not installed and the action is local, such as in the following example. If a shared component is already installed by another product and does not require updating, its component action will be Null but the destination directory will have already been registered.

InstallExecuteSequence

Action Condition Sequence
CostFinalize   1000
CA_SetComp1Dir ?Comp1 = 2 AND $Comp1 > 2 1010
CA_SetComp2Dir ?Comp2 = 2 AND $Comp2 > 2 1020

Another option is to instead use type 51 custom actions to set directory properties before CostFinalize. Due to concerns with type 35 custom actions, this is the preferred action for Developer Division going forward where not all components can be shared between different editions of products.

Given that this problem affected only a small SKU set in unusual scenarios, our patches add the condition "NOT Installed" to instances of type 35 custom actions in the sequence tables so that the re-costing of components installed under directories set by type 35 custom actions doesn't result in files not being updated. However, recall from How Patching Works that when a patch is uninstalled it is removed from view and the product is repaired. This means the fix from the patch is not present and the files are not reverted due to the same problems encountered during installation of the patches without the fix. When 2 or more patches with the fix are applicable on the system, uninstall will work correctly.

We are working hard to eliminate this and other problems in future products and are doing what we can given that products have already shipped with these problems and there is only so much that can be fixed in servicing.