Paying for upgrades

No, this isn’t a post on the costs of proprietary software but an amplification/clarification to my previous post. On wix-users, there’s a thread on the pains of automating upgrades.

If your product consists of a large number of files and the file set changes regularly – files being added and removed during the product lifetime – it’s only natural to want to avoid the cost of hand-authoring setup changes to match. (A little over a year ago, I shipped a product with over 7000 files, so I’m familiar with the pain and desire for automation.)

Unfortunately, our frenemy the component rules makes such automation difficult. The closest we-the-WiX-team has gotten to a complete solution is to create components with one file each, which makes the component eligible to have its component GUID automatically generated (using an asterisk in the Component element’s Guid attribute value).

What doesn’t work is when files are removed. Windows Installer doesn’t let you remove components in a minor upgrade, so using one file per component doesn’t immediately solve the automation problem: Your automatically-generated minor upgrade will be missing a component, which is a mortal component-rule sin.

Nor can you avoid the problem by using multiple files per component because component rules say that components must be immutable: You can’t add or remove resources from a component.

So automating the creation of minor upgrades has additional costs:

  • Additional tooling to try to support removing files without blatantly violating component rules.
  • Additional coding in your product to tolerate “obsolete” files without being able to remove them.

For many types of apps, but especially Web apps with many content files, that’s a huge cost. Being able to ship minor-upgrade patches from an automated build might be a benefit worth the cost. It’s really a decision your team needs to make.

I’ll add that if you don’t anticipate shipping patches on a regular schedule, you might just automate the authoring of your RTM product and pay the price of manually tweaking your setup authoring when you need to ship a patch. Again, it’s a cost-benefit decision your team needs to think about.

Cheap and easy

If you don’t absolutely need to ship patches, you can avoid the costs of minor upgrades by simply using major upgrades. You can remove files without worrying about component-rule violations if you use an “early” scheduling of the RemoveExistingProducts standard action – before or immediately after the InstallInitialize action.

The MSI SDK notes that scheduling RemoveExistingProducts early is “inefficient” because the files that are same between the two product versions are removed and then reinstalled. But that inefficiency is what lets you remove files and components. If you schedule RemoveExistingProducts immediately before or after InstallFinalize, MSI implements the major upgrade by installing the new version of the product “on top of” the previous version, upgrading files with newer versions, then removing the previous version. MSI increments the reference count of components in both packages during the installation of the newer version, then decrements it during the removal of the previous version. Component reference counting works only if component rules are followed, so it’s pretty much the same as minor upgrades.

If you have a large product, the size and install-time benefits of minor upgrade patches might be worth the development effort. Otherwise, major upgrades scheduled early are a great solution. Your call.

6 Replies to “Paying for upgrades”

  1. I can’t help but wonder whether this entire issue is caused by questionable design choices in Windows Installer. Windows Installer fails in certain situations to deal correctly with component reference counting, patch files are difficult to do correctly and on top of that, it’s ungodly slow.

  2. Thanks for a very useful post. I’ll stick to major upgrades only with RemoveExistingProducts before InstallInitialize!

  3. There are some other solutions to defunct files and components in an upgrade scenario. one of the tricks we have used is to add a condition to a component that always evaluates to false, e.g., “1”=”0″ and make sure the attribute to reevaluate conditions is set. This will have the effect of ‘deleting’ the files from the user’s computer.

    We then create a new component with the changed files and a new component GUID since I can create new components in a minor upgrade scenario.

    This scenario above is not practical if you have a single component packed with hundreds of files and only one file has changed though.

    Currently we are working on a system that will handle automation for patching. We do have patches built every night and I have perl scripts that evaluate the patch validation logs for errors and uses the errors to add entries to the Remove Files and Remove Registry Tables.

    We still have a ways to go to have a fully automated system and it is a lot of work, but I think in the end it will be worth the effort.

  4. Transitive components (to reevaluate the condition and remove the file) and one file per component is the solution that I am going for too.

    My ‘scripts’ are C# console apps that use LINQ to XML on *.wxs and *.wxi and produce patches for each build. All is tied up by one batch file invoking msbuild.

    The most tedious thing for me is keeping a good, complete account of what is going on, logging the changes and applying them correctly to *.wxs or *.wxi.

    We also allow to do the things that normally break the component rules, like creating a new folder. This is done in code, in CA DLL, so we don’t break the rule ‘officially’ and still can create a minor update patch.

  5. I’ve already done a system like what Daniel suggests with some of the ideas Tony brings up.

    Goodness is it a pain in the butt! We skirted so many of the rules and hacked up the MSI to such an extent that I feel shame. Dead transitive components add so much cruft to the MSI. And if the install has > 40k files, the thing is ungodly slow.

Comments are closed.