Upgrade individual feature in WIX feature-tree without uninstalling/upgrading other feature(s)
Asked Answered
E

2

9

I'm trying to create a setup project using WIX that will allow me to install multiple features of a single product. How can I update one of the installed features (which is independent of the other installed features) without having to reinstall the other features in the feature-tree?

For example, I want to be able to have a project (going back to HelloWolrd) called HelloWolrd, which (surprise) prints "Hello world!" on the screen. Let's say that I have three of these hello world applications, Hello World 1, Hello World 2, and Hello World 3. Each of which prints on the screen Hello World 1, 2, or 3, respectfully. What I would like is to create an MSI which by default installs all three of these "features" but also allows upgrading of each feature individually at a later time.

Here is my layout of my solution:

Solution Explorer http://img12.imageshack.us/img12/5671/solutionexplorerm.jpg

My WIX Product.wxs file looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
    <Product Id="ca484210-c719-4b2e-b960-45212d407c11" Name="HelloWorldInstaller" Language="1033" Version="1.0.0.0" Manufacturer="HelloWorldInstaller" UpgradeCode="68eeb8cb-9ef3-443c-870c-9b406129f7ff">
        <Package InstallerVersion="200" Compressed="yes" />

        <Media Id="1" Cabinet="media1.cab" EmbedCab="yes" />

        <!-- Create Directory Structure -->
        <Directory Id="TARGETDIR" Name="SourceDir">
            <Directory Id="ProgramFilesFolder">
                <Directory Id="INSTALLLOCATION" Name="Hello World" />
            </Directory>
            <Directory Id="DesktopFolder" Name="Desktop"/>
        </Directory>

        <DirectoryRef Id="INSTALLLOCATION">
            <Component Id="HelloWorld1" Guid="6D1D9D33-DA17-4db3-8132-C39F32200C3A">
                <RegistryKey Root="HKCU" Key="Software\HelloWorldInstaller\HelloWorld1\Install" Action="createAndRemoveOnUninstall">
                    <RegistryValue Name="DTSC" Value="1" Type="integer" KeyPath="yes" />
                </RegistryKey>

                <File Id="HelloWorld1.exe" Name="$(var.HelloWorld1.TargetFileName)" Source="$(var.HelloWorld1.TargetPath)" DiskId="1" Checksum="yes">
                    <Shortcut Id="HelloWorld1ApplicationDesktopShortcut" Name="Hello World 1" Description="Hello World Application 1" Directory="DesktopFolder" WorkingDirectory="INSTALLLOCATION" />
                </File>

            </Component>
            <Component Id="HelloWorld2" Guid="B2D51F85-358B-41a7-8C45-B4BB341158F8">
                <RegistryKey Root="HKCU" Key="Software\HelloWorldInstaller\HelloWorld2\Install" Action="createAndRemoveOnUninstall">
                    <RegistryValue Name="DTSC" Value="1" Type="integer" KeyPath="yes" />
                </RegistryKey>

                <File Id="HelloWorld2.exe" Name="$(var.HelloWorld2.TargetFileName)" Source="$(var.HelloWorld2.TargetPath)" DiskId="1" Checksum="yes">
                    <Shortcut Id="HelloWorld2ApplicationDesktopShortcut" Name="Hello World 2" Description="Hello World Application 2" Directory="DesktopFolder" WorkingDirectory="INSTALLLOCATION" />
                </File>
            </Component>
            <Component Id="HelloWorld3" Guid="A550223E-792F-4169-90A3-574D4240F3C4">
                <RegistryKey Root="HKCU" Key="Software\HelloWorldInstaller\HelloWorld3\Install" Action="createAndRemoveOnUninstall">
                    <RegistryValue Name="DTSC" Value="1" Type="integer" KeyPath="yes" />
                </RegistryKey>

                <File Id="HelloWorld3.exe" Name="$(var.HelloWorld3.TargetFileName)" Source="$(var.HelloWorld3.TargetPath)" DiskId="1" Checksum="yes">
                    <Shortcut Id="HelloWorld3ApplicationDesktopShortcut" Name="Hello World 3" Description="Hello World Application 3" Directory="DesktopFolder" WorkingDirectory="INSTALLLOCATION" />
                </File>
            </Component>
        </DirectoryRef>

        <Feature Id="HelloWorld1Feature" Title="Hello World 1" Level="1">
            <ComponentRef Id="HelloWorld1"/>
        </Feature>
        <Feature Id="HelloWorld2Feature" Title="Hello World 2" Level="1">
            <ComponentRef Id="HelloWorld2"/>
        </Feature>
        <Feature Id="HelloWorld3Feature" Title="Hello World 3" Level="1">
            <ComponentRef Id="HelloWorld3"/>
        </Feature>

    </Product>
</Wix>

Now, when this is built, it installs the features as you would expect. However, when you make a modification to HelloWorld1.vb and recompile, I would like it to be able to reinstall (upgrade) only that feature, not all of them.

When I update one file, and rebuild the solution, then try to install the msi, i get this error:

MSI Error http://img696.imageshack.us/img696/849/anotherversionisinstall.jpg

I updated my code to allow for uninstalling of the features and allow the use of upgrade codes, but that un-installed all of the features, and re-installed all of them.


-- Real world application --

The real world application to this is a large software package that needs multiple support applications that run as services/scheduled tasks on a regular basis. I would like to get the install of these supporting apps into one MSI allowing us to not have such a nightmare of rolling out each exe individually. I know that if we have an update to one of the exe's that we could just manually compile that exe and roll it out, but I'd like to do this in a completely reproducible manner.

Any help would be appriciated,

Thank you!

EDIT:

I added the source for download from Google Code. Thanks again!

Ellanellard answered 16/11, 2009 at 18:52 Comment(4)
Why is it important to avoid a reinstall for features? Isn't the end result the same? I'd think there's no way to tell the difference except by looking at an installation log.Extramundane
@wcoenen - The reason that I'd like to avoid the reinstall of other features is the scenario where we find a bug in FeatureA, and need to fix it. If the bug doesn't affect FeatureB or FeatureC, then their processing shouldn't need to be stopped to fix FeatureA.Ellanellard
@Ellanellard I know its been a couple years since you posted this, but I was curious if you still had your sample code available? I checked out the link above to google code, but don't see this question number in the list. Thanks.Thrift
@Thrift - I may have it somewhere, I will have a look around and see if I can't dig it up for you. I'll comment here if I find it. Thanks!Ellanellard
E
15

I got this figured out and thought I would post the answer here for future reference for others. So I have fully explained the problem, I will go in to more depth of the real world scenario.

We have a moderately large piece of software that requires us to have multiple supporting applications that run on a number of different servers. Our current progression of upgrades makes it moderately difficult to upgrade code in a reliable fashion. Currently we use self extracting exe's to rollout our code to the different servers. The problem arises when we have such a large number of supporting applications that it becomes hard to make sure that the applications got installed correctly with the correct configuration settings, etc. To solve this problem we are looking into the ability to instead of compressing each of the supporting applications, we create a single installer (MSI) that will allow the infrastructure team to install a specific set of supporting applications to each given machine. When we have a major change (for example from 1.0 to 2.0) we will do a full upgrade install (meaning all services/processes will need to be stopped, un-installed, installed, and started.) When we have a minor change, we would like to only have to stop and reinstall the affected services/processes, without touching other applications. Now, enough of me rambling, lets get to the solution:

I modified the WIX Product.wxs to remove the shortcuts as we don't really need them in our scenario. Here is the updated wxs file:

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
 <Product Id="13C373D3-5C27-487e-A020-C2C89E4607B1" Name="HelloWorldInstaller" Language="1033" Version="1.0.0.0"
      Manufacturer="HelloWorldInstaller" UpgradeCode="E7CB3C76-4D51-48a8-BFB4-6D11B2E2E65B">

  <Package InstallerVersion="200" Compressed="yes" />

  <Media Id="1" Cabinet="product.cab" EmbedCab="yes" />
  <FeatureRef Id="HelloWorld1Feature" />
  <FeatureRef Id="HelloWorld2Feature" />
  <FeatureRef Id="HelloWorld3Feature" />
 </Product>

 <Fragment>
  <Directory Id="TARGETDIR" Name="SourceDir">
   <Directory Id="ProgramFilesFolder">
    <Directory Id="INSTALLLOCATION" Name="Hello World" />
   </Directory>
   <Directory Id="DesktopFolder" Name="Desktop"/>
  </Directory>
 </Fragment>

 <Fragment>
  <DirectoryRef Id="INSTALLLOCATION">
   <Directory Id="HelloWorld1Directory" Name="Hello World 1">
    <Component Id="HelloWorld1Component" Guid="6D1D9D33-DA17-4db3-8132-C39F32200C3A">
     <File Id="HelloWorld1.exe" Name="HelloWorld1.exe" Source="HelloWorld1.exe" DiskId="1" Checksum="yes" />    
    </Component>
   </Directory>
   <Directory Id="HelloWorld2Directory" Name="Hello World 2">
    <Component Id="HelloWorld2Component" Guid="B2D51F85-358B-41a7-8C45-B4BB341158F8">
     <File Id="HelloWorld2.exe" Name="HelloWorld2.exe" Source="HelloWorld2.exe" DiskId="1" Checksum="yes" />
    </Component>
   </Directory>
   <Directory Id="HelloWorld3Directory" Name="Hello World 3">
    <Component Id="HelloWorld3Component" Guid="A550223E-792F-4169-90A3-574D4240F3C4">
     <File Id="HelloWorld3.exe" Name="HelloWorld3.exe" Source="HelloWorld3.exe" DiskId="1" Checksum="yes" />
    </Component>
   </Directory>
  </DirectoryRef>
 </Fragment>

 <Fragment>
  <Feature Id="HelloWorld1Feature" Title="Hello World 1" Level="1">
   <ComponentRef Id="HelloWorld1Component"/>
  </Feature>
 </Fragment>
 <Fragment>
  <Feature Id="HelloWorld2Feature" Title="Hello World 2" Level="1">
   <ComponentRef Id="HelloWorld2Component"/>
  </Feature>
 </Fragment>
 <Fragment>
  <Feature Id="HelloWorld3Feature" Title="Hello World 3" Level="1">
   <ComponentRef Id="HelloWorld3Component"/>
  </Feature>
 </Fragment>
</Wix>

Now along with this, for our minor upgrades, we will be looking at releasing patches for our components.

For example, let's say we have a ProductA, which has three components - 1,2, and 3. These three components must run either as services, or scheduled tasks. The nature of our product, we cannot shut down all of our services to update or fix one of our components. So, if after we've installed version 1.0, we find a bug in component 2, but we don't want 1 or 3 to be affected by the fix being applied to this bug, we will be releasing a patch for component 2, thus only component 2 will be affected.

For our quick example above, we are using HelloWorld1, HelloWorld2, and HelloWorld3 as our 3 components in our software application. The thought is that we should be able to install all three with one MSI, but then update each one independently without it affecting any of the other installed components.

So, to demonstrate this, I have created the three console applications above that will display "Hello World 1!", "Hello World 2!", and "Hello World 3!". Then after we release the initial MSI, lets say we find a "bug" that requires us to have HelloWorld1 say "Hello World 1! Updated." instead. Here is what we will do to simulate this:

  1. Create the Product.wixobj by executing this command at the command prompt:
    candle.exe Product.wxs
    Please remember that in order to call the candle.exe or any of the WIX commands, the Wix install directory should be in your PATH variable. (Short tutorial on updating PATH environment variable) Also, please perform the commands in the same directory as your Product.wxs file.
  2. Create the first version of your product (lets say 1.0):
    light.exe Product.wixobj -out ProductA-1.0.msi
  3. Now find a bug (change the output of HelloWorld1 to say "Hello World 1! Updated.") then update the assembly version and file version. This is important as this is how WIX can tell the exe's are different.
  4. Run the same command as step one (for good measure):
    candle.exe Product.wxs
  5. Run nearly the same command as step two:
    light.exe Product.wixobj -out ProductA-1.1.msi
    Notice that this is version 1.1 instead of 1.0 (this is the msi with our updated code). However, we don't want to just install this, keep reading.
  6. Here is the fun part, we get the difference in the two MSIs with the following command:
    torch.exe -p -xi ProductA-1.0.wixpdb ProductA-1.1.wixpdb -out Diff.WixMst
  7. Now we create the patch file from this (Patch.wxs will be explained below):
    candle.exe Patch.wxs
  8. We will now create the WixMsp file with this command:
    light.exe Patch.wixobj -out Patch.WixMsp
  9. And now, the fun part. Create the MSP file with this command:
    pyro.exe Patch.WixMsp -out Patch.msp -t RTM Diff.Wixmst

Now, if everything went according to plan, you should have two msi's and one msp file. If you install the first msi (ProductA-1.0.msi) and run HelloWorld1.exe, you should see the message, "Hello World 1!". Just for fun (and example), run both the other applications and leave them running (I built in a stop to keep them open). Close HelloWorld1.exe as we are now going to apply the update for that exe, but in doing so we will not affect HelloWorld2.exe or HelloWorld3.exe. If you now install the msp (Patch.msp) file, and then run HelloWorld1.exe, you will see the updated message, "Hello World 1! Updated."

Now, for the magical Patch.wxs file:

<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
 <Patch
   AllowRemoval="yes"
   Manufacturer="Dynamo Corp"
   MoreInfoURL="http://www.dynamocorp.com/"
   DisplayName="Sample Patch"
   Description="Small Update Patch"
   Classification="Update"
        >

  <Media Id="5000" Cabinet="RTM.cab">
   <PatchBaseline Id="RTM"/>
  </Media>

  <PatchFamilyRef Id="SamplePatchFamily"/>
 </Patch>

 <Fragment>
  <PatchFamily Id='SamplePatchFamily' Version='1.0.0' Supersede='yes'>
   <ComponentRef Id="HelloWorld1Component"/>
  </PatchFamily>
 </Fragment>
</Wix>

Doesn't look like much, does it? Well, the most interesting parts are these:

  1. <PatchBaseline Id="RTM"/> - This if you recall is used in our creation of the patch msi. The "RTM" is referred to in the last step above: -t RTM - These have to match.
  2. <ComponentRef Id="HelloWorld1Component"/> - This points the patch to the correct component to update, in our case HelloWorld1Component.

If you've been doing any searching around, the above code may seem familiar because it came from Peter Marcu's Blog: WiX: Building a Patch using the new Patch Building System - Part 3

I also relied heavily on Alex Shevchuk's Blog: From MSI to WiX, Part 8 - Major Upgrade

If you're wondering, "Wow, that's a lot of steps, why would anyone do this many steps?", please remember that once the hard work (above) is done, you need to move this into your integration routine. Thats right, integrate, integrate, integrate! How do you do this? Well, thats a bit more research, and maybe a blog post? - Probably. To get you off on the right foot, here is an awesome article on Automate Releases With MSBuild And Windows Installer XML.

Wow, I hope you read all of this (all two of you), and learned a lot. I hope this helps someone other than myself.

Thank you!

Ellanellard answered 18/11, 2009 at 0:16 Comment(2)
This is very interesting, I'm bookmarking your answer for future reference. Could you please clean it up a little though? You repeated a LOT of information that is already present in the question itself. By removing the redundancy here and rephrasing it as a real related answer, it would be a lot easier to read. It was very confusing and tedious to me because I had just read your question and then had to re-read all the contextual information (present in the question) again.Diocesan
Great real world example. One thing to add - torch.exe always failed with error code TRCH0001 on my machine, which seems to be a known bug with this workaround. This error was fixed in Wix v4.Bahner
D
0

Sounds like you figured out the upgrade scenario, now you just need to figure out Where to place RemoveExistingProducts in a major MSI upgrade so that features aren't reinstalled if they haven't changed :)

Damnatory answered 16/11, 2009 at 22:51 Comment(1)
I don't think this actually answers my question since it still removes my features during an upgrade. I have however found a solution and will be posting that shortly.Ellanellard

© 2022 - 2024 — McMap. All rights reserved.