Click here to Skip to main content
Click here to Skip to main content
Go to top

Uninstall a Previous Application Installation When Upgrading an Application with Setups Created in VS.NET - Part 2

, 22 Oct 2005
Rate this:
Please Sign up or sign in to vote.
Extend the configuration of your upgrade setups to to work properly for both per-user and per-machine previous installs.

Introduction

This is the second part of a two part article. This article assumes you have read and understood the techniques and concepts described in Part 1.

In the previous article you saw how properly defining an UpgradeCode could be used to upgrade an existing application while preserving legacy files if needed.

We also saw in the previous article that an upgrade does not work if you change the per-machine or per-user level between setups. We will look at one of several possible solutions to this potential problem, as well as look a little deeper into how upgrades function in general.

What You'll Need:

You need to read and work through Part 1 first - this article assumes you have prior knowledge of the concepts from Part 1. You'll also need Orca to manually edit the MSI package. The Custom Action DLL required is included in the Zip.

What the Zip Contains:

The Zip file contains the two simple Hello World setups from Part 1, but configured to use a Custom Action DLL to synchronize per-user/per-machine upgrade context.

The Custom Action DLL and source is also included as a VS.NET 2002 project so it is compatible with both 2002 and 2003. The Custom Action has been tested on Win98, Win2K, and WinXP.

Note: I have never worked with WinME. The Custom Action behavior may need to be modified to work on WinME. The code currently assumes WinME behaves like Win98. If WinME has Users and Admins like WinNT, the code will need to be modified to work correctly.

More About Install Design and Upgrades

Windows Installer organizes a setup in terms of Features, Components, and Resources. Features are the view of the product presented to the user, for example, "Typical", "Custom", "Complete" options.

At the other end of the spectrum is a Resource. A Resource is for example a file to be installed. But the scope of Resources extends to registry entries, fonts, and covers basically all changes made on the target machine.

A Component is a logical grouping of Resources associated with one or more Features, and is the atomic unit which the Windows Installer recognizes when it makes decisions about whether to install or uninstall various resources. Components are organized by the setup developer to ensure the integrity of the Feature(s) installed. Component definitions represent the developer's strategy to enforce the installation "business rules" if you will. For example, if an application executable has a shortcut, that shortcut should be installed if the executable is installed. Similarly, the shortcut should be removed if the executable is removed. Proper organization of Resources into logical Components is the mechanism that enforces these behaviors.

Components are also used to enforce rules across multiple setups that are related products, i.e., setups that share an UpgradeCode. There are a number of Component rules that must be enforced to maintain application integrity over multiple version installs/upgrades.

The most basic of these is that a given file with a specific target location must have a unique identifier. This unique identifier must also be preserved across multiple setups. For example, a version 1 setup installs a file named HelloWorld.exe to the application folder. A version 2 setup installs a newer version of HelloWorld.exe to the application folder. The ComponentID for the file HelloWorld.exe must be the same in both setups for the Component rules to be properly enforced during an upgrade. If the two IDs are not the same, the Component rules are broken - a big no-no. Component rules also apply to all Resources.

You may get away with it depending on a lot of factors, but breaking Component rules means you have possibly corrupted the integrity of the application, and doing so may also give rise to a host of anomalous behaviors. Among the common symptoms of broken Component rules are applications which leave behind artifacts upon uninstalling, shortcuts which trigger the setup to run in repair mode, and possibly even application failure if registry entries are corrupted.

Simply setting the UpgradeCodes in two setups to relate them is only a small part of the picture. The UpgradeCode only allows a Windows Installer to determine that two setups are related. But in order to actually perform a proper upgrade, Component rules must be enforced across the two setups.

Let's look at how the VS.NET IDE helps us in this process.

VS.NET IDE Setup Build Behaviors

The VS.NET Setup IDE does not support the concept of multiple Features. VS.NET also applies a very simplified form of Component assignment - one Resource per Component. That means a unique Component and ComponentID is created for each file, each shortcut, each registry entry that you author into the setup using the VS.NET IDE.

And a very important function the setup IDE performs for you which is not documented anywhere is that when you set the UpgradeCode in the VS.NET IDE, the wizard that builds the actual MSI uses some whiffle-dust to enforce Component rules for you automatically across the two setups. Basically what it does is it sets the ComponentIDs in the new setup to the same ones found in the old setup for all resources common to both setups.

If you build two related setups on different machines, or presumably if you have reinstalled VS.NET between builds on the same machine, the "magic" no longer works.

I cannot cover all the possibilities in a reasonable length article, but I encourage you to study the SDK documentation on Components and Resources carefully.

And be aware that if your setups are built under conditions where the VS.NET IDE cannot enforce the Component rules for you, you will have to synchronize them manually by changing the ComponentIDs in your upgrade MSI using Orca. This is a tedious but necessary step which requires a careful, detailed comparison of the two setups.

You've been warned. You must synchronize the ComponentIDs for the two versions for all resources which have the same name and target for the upgrade to work properly under all conditions.

The rest of this article will assume that your upgrade version setup is properly configured to enforce the Component rules.

Configuring an Upgrade to Work When Per-User/Per-Machine Is Unknown

In the previous Part 1 article, you saw how properly defining an UpgradeCode could be used to upgrade an existing application while preserving legacy files if needed. We also saw in the previous article that an Upgrade does not work if you change the per-machine or per-user level between setups.

This is because per-machine installs save their information in the HKLM tree, while per-user installs save their information in the HKCU tree. The UpgradeCode is used by the Windows Installer to find a previously installed related product. If the Windows Installer cannot find the UpgradeCode because it is not looking in the right tree, i.e., because the per-user or per-machine levels of the two installs differ, then the upgrade action is essentially ignored.

When you set the ALLUSERS property explicitly to 2 in the MSI, it will default to the highest level possible. This means if the user has admin privileges, the setup will start in what amounts to "per-machine" mode. The Windows Installer runs an action named FindRelatedProducts to search for a related setup on the target machine. But this action only searches the registry tree that matches the mode the version 2 setup is running in. This means that if the new version setup is running in per-machine mode, a previous per-machine install will be found and upgraded. But a per-User install will not be found because the FindRelatedProducts action will only search the HKLM tree. You cannot configure a Windows Installer to look both places directly.

What you can do is create a Custom Action for an upgrade setup that can look both places, and then configure the upgrade so it works in all cases. So basically the Custom Action is used to override the built-in FindRelatedProducts action, and to dynamically configure the running setup to match the per-machine or per-user mode of the previously installed version.

Custom Actions

You probably already know about Installer classes, but this type of a Custom Action does not help us in this scenario because Installer based custom actions only run at the end of the install sequence.

But there are other types of custom actions that allow you more control over the execution sequence. They can be written in script, as an executable, or as a DLL embedded in the MSI. We'll use the last type, and I suggest you use the DLL format for all custom actions you create other than Installer classes. Although it is more work to create a DLL, they are much more stable and versatile than VBScript.

DLLs used as Custom Actions must include at least a couple of required MSI headers, link the msi.lib standard library, must export their function calls as standard C calls, not COM calls, either by declaration or by a definition file, and generally should be created so that they only use resources that already exist on the target machine - typically Win32 or MSI API calls.

You can debug DLLs from a running install but it is a hassle. I find it easiest to work out the basic functionality in a regular project so it can be debugged easily in the IDE, then convert the code to conform to Windows Installer requirements.

Finally, a Custom Action should never throw up a message box. This interferes with running the install in unattended or silent mode. You should process all messages through the MSI API to allow the install to both log and process the messages according to the mode it is running in.

You can find out more about DLL Custom Actions from the SDK as well as by searching other articles on Code Project.

About the Custom Action Logic

The Custom Action DLL performs the following operations to essentially override the intrinsic Windows Installer FindRelatedProducts action so that it works for all cases:

  1. Determine system and user information. If the platform does not support per-user/per-machine we do not take any action - the setup will always work.
  2. Retrieve the previous ProductCode from the new setup as a custom property.
  3. Search the HKLM tree for the ProductCode to see if the previous version is installed at all.
  4. Assuming the previous version is installed, determine if it was per-user or per-machine by searching for the UpgradeCode in the HKLM tree. Searching both trees is redundant because we first determine if the product is installed at all, and if that is true, then failure to find an UpgradeCode in the HKLM tree implies the existing install must be per-user.
  5. Compare the previous install to the current user's permissions. If the previous install is per-machine, and the current user does not have per-machine install permissions, we cannot properly upgrade the existing application so we display a message and abort.
  6. If the permissions are OK, we set a Windows Installer property - PREVIOUSVERSIONSINSTALLED - to the old ProductCode so the upgrade works. This property is set internally by the FindRelatedProducts action, which we are overriding so the upgrade works regardless of the previous install level.
  7. This particular Custom Action also enforces a couple of additional behaviors. If the previous install was per-user, we will allow the user to choose per-user or per-machine. But if the previous install was per-machine, we will force the upgrade to install per-machine, and we do this by setting the FormFolder_AllUsers property to ALL, and also hiding the controls that allow the user to change the option. We could allow an admin user to change from per-machine to per-user, but we would probably want to display a warning that doing so would remove the application for all other users. That involves a lot of control event modifications that I think deviates from the basic task at hand just for the sake of allowing a behavior which is possible but that has little real merit.

So instead we'll add and set a custom property that simply hides the option.

About the Code

The code is included in the Zip, along with the compiled DLL. The project is in VS 2002 so it is compatible for all.

If you are not a C++ programmer, you can find the particulars of how to set the includes and other configuration properties to compile a Custom Action DLL discussed elsewhere, including a previous article I wrote on PID Validation.

The code is a little verbose, but I wanted to make it as simple as possible for people (like me) who don't do a lot of C++ programming to understand and modify. So I did not put strings in a Resource table, I used an extern function call rather than a definition file, and so on and so forth. The code also has minimal error handling - I allocate strings on the heap and assume that memory is available etc.

The code is well commented, and since I have already described its behavior we will not discuss it in any further detail.

Configuring the Upgrade Install to Apply the Custom Action

Develop and compile your version 2 setup, taking care to set the UpgradeCode in the IDE to match your previous version, as described in Part 1 of this article. But do not make any modifications to the MSI - we'll use a different approach here.

Open the release build of the MSI in Orca.

  1. Embed the DLL in the MSI as a binary resource:

    The DLL can be embedded directly into the MSI itself. Open the binary table and add a new row.

    • Name: SetUpgradeContextDLL
    • Data: Use the file browser that appears to browse to the compiled SetUpgradeContext.DLL file and select it. Click OK.
  2. Define the Custom Action:

    This tells the Windows Installer how to call the DLL. Open the CustomAction table, and add a new row. Set the values to:

    • Action: SetUpgradeContext
    • Type: 1
    • Source: SetUpgradeContextDLL
    • Target: The function call prototype string exactly as shown below.
     _SetUpgradeContext@4

    The Action value is used to identify the custom action. The Type value of 1 specifies the action is in the form of a DLL. The Source property directs the setup to use the binary resource we embedded. The Target property prototypes the syntax for the exported function call. When you are done, you should see something like this:

  3. Sequence the Custom Action execution:

    Now that we have embedded our custom action source and defined how to call it, we need to sequence the call in order to direct the Windows Installer on when and under what conditions to call the action. The logical sequence is some time after the FindRelatedProducts action, since that is the behavior we plan to override. We also want to apply a condition - the custom action should only be called during an install, not during a repair or remove action.

    We also need to add the same sequence to both the InstallExecuteSequence and the InstallUISequence tables.

    Open the InstallExecuteSequence, click on the sequence header to order the table in the order of action execution. In my setups I put the custom action call after FindRelatedProducts and the action that checks for a newer version. I used a Sequence value of 202. Your setup may be different.

    Add a new row and set the values to:

    • Action: SetUpgradeContext
    • Condition: NOT Installed
    • Sequence: 202 (or as required by your setup sequence)

    The result should look like this:

    Repeat exactly for the InstallUISequence table:

    Let's take a minute and review. So far we have added the CA resource to our upgrade setup as a binary embedded DLL. We have defined the CustomAction table entry that tells the Windows Installer how to call the DLL. We have also sequenced the actual function call in our install execution action tables. We have done everything we need to allow the Windows Installer to find and call our function.

    But we need to add some custom properties to support the custom action function call.

  4. Add the Additional Property Entries required by the Custom Action

    We need a custom property that the DLL will read to determine the ProductCode of our previous install. The DLL will use this value to search the registry, and if the previous version is found, it will also set an intrinsic property, PREVIOUSVERSIONSINSTALLED, to the previous version ProductCode. This will instruct the Windows Installer to remove the existing product.

    First find the ProductCode GUID for the version you are upgrading from. You can get this from the version 1 VS.NET Setup project, or you can open the version 1 MSI in Orca and find it in the property table. Copy this GUID exactly.

    My ProductCode GUID from version 1 was: {05BE32C8-423F-4A2A-9162-B79CAD08866D}

    Open the property table in the version 2 install. Add a new row, and set the values to:

    • Property: OldProductCode
    • Value: {05BE32C8-423F-4A2A-9162-B79CAD08866D}

    Use your GUID, not mine!!!!

    We also set a second custom property to allow hiding the AllUsers option controls. This property will be set by the DLL to enforce a per-machine upgrade if appropriate. Otherwise the property will be left at a default value of 0 which will allow the AllUsers options to be displayed.

    Again we are in the property table in the version 2 install. Add a new row and set the values to:

    • Property: HideAllUsers
    • Value: 0

    When you are done, you should have something like this:

    Do not add the ALLUSERS entry we used in part 1, and do not change the FormFolder_AllUsers property either. Our CA will now configure all this for us.

    The CA is now configured to run properly. But we still need a change to conditionally hide the AllUsers option controls.

  5. Apply new conditions to the AllUsers controls:

    We will be working with a table we have not seen yet: ControlCondition.

    As you probably already know, the Windows Installer hides the AllUsers option group in the dialog interface if you are on a Win9x system, or if the user does not have admin privileges on an NT system. This is accomplished by a control condition that is evaluated at runtime.

    We can also change control conditions to implement our own behaviors.

    In this case we want to evaluate the custom HideAllUsers property. If the DLL has set it to 1 we hide the option controls. This requires a simple conditional expression change.

    Open the ControlCondition table in Orca. Find the entries for the dialog named FolderForm. Then find the entries for the AllUsersRadioGroup option controls. Then find the Hide action. Make sure you have the right entry! In Orca you can double click on the cell to edit an existing entry.

    The original condition will be:

    NOT (VersionNT>=400 AND Privileged=1)

    Change this to read:

    (NOT (VersionNT>=400 AND Privileged=1) OR HideAllUsers=1)

    When you are done it should look like this:

    Now, if we hide the option group, we need to hide the label associated with it.

    Find the entries for AllUsersText. Find the Hide action. Change the condition to the same as the option group.

    When you are done it should look like this:

    Now our CA is fully functional and also the Windows Installer applies the appropriate control conditions.

  6. Set the RemoveExistingProducts sequence:

    OK, at this point our Custom Action implementation is complete. But all the CA does is it configures the upgrade version 2 setup to set the per-user/per-machine context, and it also finds a previous version install even if the initial contexts were different. But the CA does not actually cause the existing version to be removed.

    This still happens when the RemoveExistingProducts action runs. So as in Part 1, again you need to decide what is appropriate for your situation - either uninstall first-install-last or install first-uninstall last - and configure the sequence value for the RemoveExistingProducts action. This was described in detail in Part 1.

  7. Save and Test

Now we are done! Save the modified MSI, close it from Orca, and test carefully!

History

None, so far ....

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

rwestgraham
Web Developer
United States United States
I've worked in the chemical industry, nuclear power industry, and now healthcare (in that order).
 
My specialized area of IT interest is installer technologies.

Comments and Discussions

 
QuestionUpgradeCode instead of ProductCode PinmemberRuben Jönsson6-Nov-08 20:25 
AnswerRe: UpgradeCode instead of ProductCode PinmemberRuben Jönsson12-Nov-08 0:26 
GeneralAutomatic MSI build problem Pinmembertushkan18-Oct-08 23:48 
Questionhow to upgrade more than one version Pinmembervsuchde11-Oct-07 3:47 
AnswerRe: how to upgrade more than one version Pinmemberrwestgraham19-Oct-07 8:45 
General2 Entries in Add/Remove Programs Pinmemberdisruptor1081-Jun-07 6:19 
GeneralRe: 2 Entries in Add/Remove Programs Pinmemberrwestgraham19-Oct-07 8:52 
I could not reproduce this problem.
QuestionDose this feature work on Windows Vista ? PinmemberJaydeepSawant31-May-07 2:40 
GeneralNot able to use property value given in one userinterface dialog box PinmemberAnhad15-May-07 5:13 
GeneralAlways uninstall existing version Pinmembermfer5-Feb-07 22:59 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140916.1 | Last Updated 23 Oct 2005
Article Copyright 2005 by rwestgraham
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid