Components are the basic unit of installation in a Windows Installer product. They are installed by one or more features, and can contain any number of resources including files, assemblies, registry values, and are recommended for custom resources as well. Examples of custom resources are web sites, virtual directories, SQL tables, and stored procedures.
Components are identified in a product package using a unique ID, but the GUID is used to identify the component across all products on the machine. The unique ID is only scoped to the product. It is a primary key used in many foreign key relationships like in the FeatureComponents table, File table, Registry table, and more.
The same component GUID can be registered by multiple products. This provides for multiple products to install shared resources. However, it is a common misconception that only by defining the same GUID is a shared component defined. Both the GUID and the component key path must be the same. So if one product installs component {26D1EE50-A838-4837-9A02-6801BA2C4867} to C:Program FilesA and another product installs a component with the same GUID to C:Program FilesB, these components are not fully shared components.
Windows Installer registers a component GUID along with its key path and the ProductCode of the product that installed it to that location. This is why functions like MsiGetComponentPath require a ProductCode – to get the path or state of a component installed by a particular product. Without the ProductCode, Windows Installer cannot determine to which component you refer even when the GUID is the same. Windows Installer does define the MsiLocateComponent function to find a component without a ProductCode but even documents, “This function attempts to determine the product using MsiGetProductCode, but is not guaranteed to find the correct product for the caller.”
You can see an example of this in the attached solution. Products A and B install both file and registry resources using shared components, unique components, and even violate component rules as an example. Specifically, components with the same GUID should always have the same composition; and conversely, components that install the same set of resources should have the same GUID.
Product A installs and registers the following shared components.
MSI (s) (F4:4C) [21:47:03:594]: Executing op: ComponentRegister(ComponentId={91507EB3-88C9-45D8-8C48-E52876E4F408},KeyPath=C:UsersheathsAppDataRoamingHeath StewartProduct.wxs,State=3,,Disk=1,SharedDllRefCount=0,BinaryType=0)
MSI (s) (F4:4C) [21:47:03:938]: Executing op: ComponentRegister(ComponentId={A1E54C6D-BDA5-45EB-97BD-818763987E5E},KeyPath=01:SOFTWAREHeath StewartSharedRegSameGuid,State=3,,Disk=1,SharedDllRefCount=0,BinaryType=0)
MSI (s) (F4:4C) [21:47:04:078]: Executing op: ComponentRegister(ComponentId={26D1EE50-A838-4837-9A02-6801BA2C4867},KeyPath=C:UsersheathsAppDataRoamingHeath StewartAProduct.wxs,State=3,,Disk=1,SharedDllRefCount=0,BinaryType=0)
Product B - installed after product A - installs the registers the following shared components.
MSI (s) (F4:18) [21:47:27:031]: Executing op: ComponentRegister(ComponentId={91507EB3-88C9-45D8-8C48-E52876E4F408},KeyPath=C:UsersheathsAppDataRoamingHeath StewartProduct.wxs,State=3,,Disk=1,SharedDllRefCount=0,BinaryType=0)
MSI (s) (F4:18) [21:47:27:093]: Executing op: ComponentRegister(ComponentId={A1E54C6D-BDA5-45EB-97BD-818763987E5E},KeyPath=01:SOFTWAREHeath StewartSharedRegSameGuid,State=3,,Disk=1,SharedDllRefCount=0,BinaryType=0)
MSI (s) (F4:18) [21:47:27:155]: Executing op: ComponentRegister(ComponentId={26D1EE50-A838-4837-9A02-6801BA2C4867},KeyPath=C:UsersheathsAppDataRoamingHeath StewartBProduct.wxs,State=3,,Disk=1,SharedDllRefCount=0,BinaryType=0)
Note that SharedDllRefCount above refers to a legacy reference counting mechanism using only an integer in the registry along with a file path. This provides no identification of what products installed those files, requires that each product correctly increment and decrement the counter, and provides no synchronization between threads. This is not the same as Windows Installer component reference counting.
When product A is uninstalled, you will see in a verbose log that Windows Installer will not uninstall shared components. It will remove files installed to different locations for the same GUID, however.
MSI (s) (F4:A0) [21:47:39:732]: Allowing uninstallation of shared component: {26D1EE50-A838-4837-9A02-6801BA2C4867}. Other clients exist, but installed to a different location
MSI (s) (F4:A0) [21:47:39:763]: Disallowing uninstallation of component: {A1E54C6D-BDA5-45EB-97BD-818763987E5E} since another client exists
MSI (s) (F4:A0) [21:47:39:794]: Disallowing uninstallation of component: {91507EB3-88C9-45D8-8C48-E52876E4F408} since another client exists
...
MSI (s) (F4:A0) [21:47:39:997]: Feature: ProductFeature; Installed: Local; Request: Absent; Action: Absent
MSI (s) (F4:A0) [21:47:40:013]: Component: SharedFileSameGuid; Installed: Local; Request: Absent; Action: Null; Client State: Local
MSI (s) (F4:A0) [21:47:40:059]: Component: SharedRegSameGuid; Installed: Local; Request: Absent; Action: Null; Client State: Local
MSI (s) (F4:A0) [21:47:40:122]: Component: UniqueFileSameGuid; Installed: Local; Request: Absent; Action: FileAbsent; Client State: Local
When product B is uninstalled, those shared components are removed because no remaining clients (products) are installed. The problem is that products A and B also installed the same resource with a different GUID, which is not correctly reference counted and uninstalled when product A is first uninstalled.
MSI (s) (F4:A0) [21:47:40:044]: Component: SharedFileDiffGuid; Installed: Local; Request: Absent; Action: Absent; Client State: Local
MSI (s) (F4:A0) [21:47:40:106]: Component: SharedRegDiffGuid; Installed: Local; Request: Absent; Action: Absent; Client State: Local
There are a lot of implications with shared components and design considerations that will be covered in future posts.
0 comments