Major Upgrades with Shared Components

Major Upgrades with Shared Components

  • Comments 11

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.

Leave a Comment
  • Please add 4 and 4 and type the answer here:
  • Post
  • Thanks for the good post, Heath!

    A lot of things get clearer now of what strategy to use in certain situations.

    Thanks again!

  • Hello Heath,

    Thanks for this wonderful explanation. I have now run into the exact same problem.

    Let me explain:

    I have a product A with version 1.0, creating registry entries as follows:

    HKLM\CLSID\InProcServer32\<ProductVersion>

    These registry entries are associated with a particular component which has a .dll file as its keypath.

    Now, for the next version of our product, we use a major upgrade. The newer version of the product is 1.5. Here, we sequence RemoveExistingProducts  after InstallFinalize.

    However, this time, the registry entries are updated again to reflect the new Product version. They however, continue to be part of the same component as was in Product A

    Example: Product A: HKLM\CLSID\InProcServer32\1.0

    Product B:HKLM\CLSID\InProcServer32\1.5

    When i install Product B, it uninstalls Product A and installs Produt B(major upgrade). However, Product B has no idea of  the registry entry in the older version of the product i.e HKLM\CLSID\InProcServer32\1.0 . Hence, this registry entry is not cleaned up.

    Consquently, after i uninstall Product B, the registry entry is left on the system.

    How do i overcome this scenario?

    One possible solution :

    I am using Wix to build my installer.

    In Product B, put the registry entries in it's own component  with a componet GUID of  *. For this component, designate  HKLM\CLSID\InProcServer32\<Product Version> as the key path.

    So effectively, every newer version of the product should have a new GUID generated for this component as the <ProductVersion> in the registry key changes. From what i understand, Wix dynamically generates component guids by using a combination of  key path, key file name etc. So , in my case, i should have a new GUID associated with this component every time the registry key changes. This should take care of my scenario.

    Do you have any other suggestions,other than moving RemoveExistingProducts?

    Thanks,

    Kiran Hegde

  • @Kiran, those are different components because they write to different locations as intended (i.e. not user redirectable), and should have separate GUIDs. They won't be ref-counted so when A is uninstalled as part of the major upgrade (during RemoveExistingProducts), that 1.0 subkey will be removed as desired.

  • Hello,

    We have a msi package which installs assemblies into the GAC. We use major upgrades to update our products. In the updated package, we sequence the RemoveExistingProducts standard action , right after InstallInitialize.

    However, this is resulting in a problem where the files are missing from the GAC after an upgrade. This problem has been described here:

    support.microsoft.com/.../905238

    To overcome this issue, there are multiple approaches:

    i)Increment the assembly version of the files which go into the GAC with every upgrade.

    This is not an option for us , as these files are sourced from Merge Modules, which are owned by completely different teams. Additionally, the merge modules need a rebuild for the assembly version to be incremented. Since these files do not change across releases, rebuilding Merge Modules just for the sake of incrementing Assembly versions is not an option.

    ii)Sequence RemoveExistingProducts after InstallFinalize

    However, this comes associated with a risk that if the uninstall of the older product fails, we will be left with 2 products registered on the system. hence, we would like to avoid this option.Additionally, doing this is also resulting in numerous uninstallaiton issues with shared components as described in :blogs.msdn.com/.../archi...omponents.aspx

    A lot of resources are not cleaned up after an uninstall.

    iii)Change the component GUID of the components which install into the GAC with every upgrade.

    This though seems to work, i am not sure if this is a good solution. As per the MSDN recommendation, you should change the component GUID only when the location or the key path changes. Since this is not the case here, i am a bit hesitant to adopt this approach.

    iv)Have the REINSTALLMODE=emus property set in the property table.

    I wanted to know as to what is the recommended solution here?

    I am hesitant to use solutions ii) and iii).

    Are there any other safe solutions which we could make use of?

    Any help would be very much appreciated.

    Thanks,

    Kiran Hegde

  • @Kiran,

    Re: Change the component GUID of the components which install into the GAC with every upgrade.

    You can do this if and when you change the assembly version. If you don't change the assembly version, you should not do this. Changing the file version - which is different from the assembly version and does not change binding in the CLR nor require a GUID change - is the best approach. I understand that these files come from a different team, so in that case you should actually get MSIs from them and use a chainer to install a modular product architecture. That's what most products in Microsoft do and often for that very reason.

    Re: Have the REINSTALLMODE=emus property set in the property table.

    You should never set REINSTALL, REINSTALLMODE, or other such properties (properties that affect installation) in the Property table. The default of "omus" should suffice if you have your product componentized correctly (i.e. follow component rules, where components are very granular) and have key paths set (another important component rule).

  • Thanks Heath.

    I guess you have answered my question.

    On another note, i am quite confused when you state that the value of REINSTALLMODE  should not be set in the property table. Is there a rule anywhere stating that this property should not be over ridden?

    I thought you could over ride the default value of REINSTALLMODE  based upon your requirments, either on the command line or in the property table based upon your **requirements**.

    Please clarify as to what are the disadvantages with over riding the REINSTALLMODE  property.

    Thanks,

    Kiran Hegde

  • @Kiran, that properties and others like it (like REINSTALL) are user/caller preferences. You don't typically hardcode those in anywhere, and should be making sure that your product repairs correctly with the default REINSTALLMODE of "omus" anyway.

  • Thanks for the quick response.

  • Hello Heath,

    I am reigniting this thread again w.r.t to the GAC issue which i had mentioned earlier,  as i had 2 additional questions:

    i)Would  sequencing  RemoveExistingProducts before CostInitialize be a good solution?

    I have tried it and am observing that i am not seeing MigrateFeatureStates being invoked. RemoveExistingProducts gets invoked and runs fine.

    I see the following message: Skipping Migratefeaturestates action: not run in maintenance mode.

    Our installer has a single feature at the moment. So it is not an issue.

    This seems to have been recommended earlier on the web.

    Do you see any issues with this?

    ii)I have also figured out that if you can populate the FileVersion attribute for assemblies which go into the GAC in the MsiAssemblyName table, this problem would be resolved.

    Is there something i need to do to get the FileVersion attribute populated?

    Thanks in advance,

    Kiran Hegde

  • @Kiran, if that works, great. But it's not a documented sequence of RemoveExistingProducts so I can't really recommend it, nor can I recommend against it.

    To populate the fileVersion "attribute" in the MsiAssemblyName table, it depends on your build environment. If using WiX, pass the -fv switch. In a .wixproj, add the following to a PropertyGroup:

    <SetMsiAssemblyNameFileVersion>True</SetMsiAssemblyNameFileVersion>

  • Hello Heath,

    You have already  spoken about sequencing    "RemoveExistingProducts"  before  CostInitiliaze in your last response in the discussion.  However, i  am now facing another problem for which  i need some suggestion of yours.

    A  higher version installer contains lower version of certain binaries . In the higher versioned installer,  sequencing   "RemoveExistingProducts"  after  "InstallInitialize"  results in missing files.  This is an issue with Windows  Installer service and has been around  since 2002.

    To solve this, i have come across the following suggestions:

    -Schedule  "RemoveExistingProducts" earlier  in the   sequence, even before costing i.e before CostInitialize.However, doing that violates  the  guidelines laid out by MSDN. MSDN suggests a sequencing between  InstallValidate and Install Initialize as one of the positions.  InstallValidate is sequenced after costing. Hence, even though this solution might work,  this is a violation of  Microsoft rules

    -Use REINSTALLMODE = emus

    -Force the file to be always overwritten -  Not feasible for Wix. Only exists in InstallShield

    -Version - Handle the versions properly in the higher versions of the installer.

    I agree that  having higher versions of the files in the higher versioned installer is the easiest and safest approach.  However, there could be genuine cases where you  might want to include lower versions of certain binaires in a highver verison of your product.  This is common with third party binaries.  

    So how do you think that this should be handled?

    How does Microsoft handle this scenario?

    As usual, any assistance is very much appreciated.

Page 1 of 1 (11 items)