About Windows Installer, the .NET Framework, and Visual Studio.
Continuing the series on the perils and necessity of ARPSYSTEMCOMPONENT, it's time to explain one solution for having your cake an eating it too.
If you have determined that you need to define ARPSYSTEMCOMPONENT=1 in your product RTM, remember that your product nor patches will not show up in the Add/Remove Programs (ARP) control panel unless you write entries for each to the registry yourself. But because obsolescing, superseding, or removing a patch removes it from the view before the reinstallation of your product, those registry keys will never be removed because they've been orphaned.
To define ARP keys for your patch, you should add them to a new component that is associated with entries in the Registry table to define your ARP keys. If you're targeting the Windows Installer runtime prior to 2.0, you must add this component to a new feature. If you have the luxury of targeting Windows Installer 2.0 or newer you can add that component to an existing feature. This can't be just any normal component, though — you should make it a transitive component by adding msidbComponentAttributesTransitive (0x0040) to the Attributes column in the Component table for your component. This way, when the component condition evaluates to true, the component is installed; and when the condition evaluates to false, the component is uninstalled. The condition is evaluated each time the parent feature is reinstalled. Now when one patch is to replace another patch, that condition should be made to evaluate to false so that the ARP registry keys component is uninstalled the ARP keys are removed. To do that, each patch should define a unique property — like a KB number — in the Property table and use that as a prefix, for example, to define a condition for future patches to set.
So, let's consider that patch A (KB001) defines a property like KB001. In the Condition column of the Component table, you should add NOT KB001.Replaced. Now patch B (KB002) is authored to replace the same or a superset of the files in patch A, so it defines a property KB002 as well as the property KB001.Replaced. Since that property is now defined, the component condition for the ARP component in patch A evaluates to false and the component is removed, thus removing the ARP registry keys. You've basically defined your own supersedence feature.
But speaking of supersedence, you now have to consider that if you obsolete or supersede a patch the patch is removed from view and so are all of its components; thus, the ARP keys are still orphaned. For this reason, you must never set the msidbPatchSequenceSupersedeEarlier (1) bit in the Attributes column of the MsiPatchSequence table or specify the previous patch codes in the Revision Number summary property in the summary information stream of the MSP. That means that patches will always be in view until removed using Windows Installer 3.0 or newer if the MsiPatchMetadata table exists in the MSP and defines AllowRemoval=1.
There's still another problem, though. Typically transforms contained in a patch target a specific ProductVersion for the product they're patching. If a minor patch — a service pack — is installed the ProductVersion is changed and all those patches are removed from the view. To workaround that you'll have to update your transform validation bits so that the patch transforms target a specific or newer ProductVersion by adding MSITRANSFORM_VALIDATE_NEWGREATEREQUALBASEVERSION (0x0200) to the validation flags when creating your transform with the MsiCreateTransformSummaryInfo function, or in the ProductValidateFlags column of the TargetImages table of your PCP file. Now your patches will always be in view. This may increase initialization when lots of patches are installed.
Don't stop reading — we're not done yet! If you've added your ARP components to new features, you've now got another problem. In order for the transitive components to be re-evaluated, those features that define those transitive components must be reinstalled in addition to the features you're patching and the feature you're adding as the parent of your new ARP component. To do that, you must first set your new feature in the ADDLOCAL property and then set REINSTALL to your new ARP feature, the feature(s) you're patching, and all other ARP features already installed. If you don't add your new feature to ADDLOCAL, it won't be reinstalled because it wasn't installed in the first place.
After setting ADDLOCAL, you must call the ProcessComponents, PublishFeatures, and InstallExecute standard actions again. After setting REINSTALL, you must call the CostFinalize and InstallValidate standard actions again. This makes sure that your feature is treated as a local feature when being reinstalled, and that all features specified in the REINSTALL property are reinstalled. If you add your new transitive components to an exist feature such as a top-most parent feature that must be installed, you can avoid these problems.
There are several ways of setting these properties from within a custom action. The REINSTALL property could, for example, be set to a comma-delimited list of features by enumerating your Feature table looking for a common prefix or a pattern that each patch defines. Using this approach makes it easy to figure out which features to reinstall. You could also set REINSTALL=ALL so that all local features are reinstalled, but keep in mind that if REINSTALLMODE contains "a" that all files — not just those files being patched — will be installed and source may be required if not cached.
Setting ARPSYSTEMCOMPONENT=1 can be perilous and lead to a tedius exercise in defining your own supersedence feature, but it is possible and it's what we've had to implement in patches for the .NET Framework, Visual Studio, and other products' patches to enable a good customer experience with localized dialogs when installing or uninstalling developer products, or when viewing information about such products and patches in the Add/Remove Programs control panel.