How do I detect the currently installed features during a MajorUpgrade using WiX Burn MBA Bundles?
Asked Answered
P

2

10

I'm using WiX 3.7's Burn/Managed Bootstrapper Application features to create a custom MBA-based installer. For each of the packages in my bundle's chain, when performing a MinorUpdate, I can easily detect which of the package features are already installed to ensure I maintain those feature selections during the upgrade by using these events provided in the WiX base class for the bootstrapper: DetectPackageComplete, DetectMsiFeature, DetectRelatedBundle, DetectRelatedMsiPackage, DetectComplete.

However, during a MajorUpgrade, I'm only seeing a way to determine which package(s) are installed, but am not seeing how to determine which features are installed, as the DetectMsiFeature event does not fire. I tried using the MigrateFeatures flag on the product's configuration, but that doesn't seem to work (or I'm not using it right).

What is the correct way to detect/migrate existing features when performing a MajorUpgrade using a Custom Managed Bootstrapper Application in WiX?


Some file snippets:

Note: I can provide a fully working VS Solution with all code if that is helpful.

Bundle.wxs:
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:bal="http://schemas.microsoft.com/wix/BalExtension">
    <Bundle Name="Bootstrapper1"  Version="1.1.0.0" Manufacturer="Knights Who Say Ni" UpgradeCode="e6fbf160-d1d9-4b38-b293-94d60eae876f" Compressed="yes">    
        <BootstrapperApplicationRef Id="ManagedBootstrapperApplicationHost" >
          <Payload SourceFile="$(var.ManagedBootstrapperApplication.TargetPath)" />
          <!-- other files here -->
        </BootstrapperApplicationRef>
        <Chain>      
          <PackageGroupRef Id="NetFx40Web" />
          <MsiPackage SourceFile="$(var.SetupProject1.TargetPath)" EnableFeatureSelection="yes" Vital="yes"  Compressed="yes" />
        </Chain>
    </Bundle>
</Wix>

Product.wxs:

<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
    <Product Id="*" Name="SetupProject1" Language="1033" Codepage="1252"
           Version="1.1.0.0" Manufacturer="Knights Who Say Ni" 
           UpgradeCode="5fcd463a-3287-4fdf-bf00-d5d74baeccda">

        <Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
        <MajorUpgrade AllowSameVersionUpgrades="no" AllowDowngrades="no" MigrateFeatures="yes" DowngradeErrorMessage="Bring me a shrubbery!" />
        <MediaTemplate EmbedCab="yes" />

        <Feature Id="feature_one" Title="Primary Feature" Level="1">      
          <Component Id="CMP_emptyFile1" Guid="{1740AFA6-A98F-482A-B319-A153AA1BEF10}" Directory="INSTALLFOLDER">
            <File Id="file_emptyFile1" Checksum="yes" KeyPath="yes" Source="TextFile1.txt" />
          </Component>      
        </Feature>
        <Feature Id="feature_Two" Title="Optional Feature" Level="2">
          <Component Id="CMP_emptyFile2" Guid="{F0831C98-AF35-4F5E-BE9A-2F5E3ECF893C}" Directory="INSTALLFOLDER">
            <File Id="file_emptyFile2" Checksum="yes" KeyPath="yes" Source="TextFile2.txt"  />
          </Component>
        </Feature>    
    </Product>
</Wix>

CustomBootstrapper.cs

public class CustomBootstrapperApplication : BootstrapperApplication {        
    protected override void Run() {
            DetectPackageComplete += HandlePackageDetected;
            DetectMsiFeature += HandleFeatureDetected;
            DetectRelatedBundle += HandleExistingBundleDetected;
            DetectRelatedMsiPackage += HandleExistingPackageDetected;
            DetectComplete += HandleDetectComplete;
            this.Engine.Detect();
            //blocks here until DetectComplete fires...
    }

    private void HandleExistingPackageDetected(object sender, DetectRelatedMsiPackageEventArgs e) {
        Log(string.Format("Detected Related Package {2} ({1}) at version {3} which is a {0}",
            e.Operation, e.PackageId, e.ProductCode, e.Version));
    }

    private void HandleExistingBundleDetected(object sender, DetectRelatedBundleEventArgs e) {
        Log(string.Format("Detected Related {2} Bundle {0} at version {1} which is a {3}",
            e.ProductCode, e.Version, e.RelationType, e.Operation));
    }

    private void HandleFeatureDetected(object sender, DetectMsiFeatureEventArgs e) {
        Log(string.Format("Feature {0} from Package {1} detected in state {2}",
            e.FeatureId, e.PackageId, e.State));
    }

    private void HandlePackageDetected(object sender, DetectPackageCompleteEventArgs e) {
        Log(string.Format("Package {0} Detected in State {1}",
            e.PackageId, e.State));
    }

    private void HandleDetectComplete(object sender, DetectCompleteEventArgs e)
    { /* release the main thread to continue with work */ }

}

Output on upgrade:

Note that the package and two features were both installed at v1.0.0 and detected in state Absent. The Related Package was detected, but no feature details are included.

Detected Related Upgrade Bundle {5eff0a3c-4b0d-4fd9-875f-05117c07f373) at version 1.0.0.0 which is a MajorUpgrade
Package NetFx4OWeb Detected in State Present
Detected Related Package {540AE32D-75C0-4BF3-A72D-ADBE97FSFF3E} (SetupProject1.msi) at version 1.0.0.0 which is a MajorUpgrade
Feature feature_one from Package SetupProjectl.msi detected in state Absent
Feature feature_Two from Package SetupProjecti .msi detected in state Absent
Package SetupProject1.msi Detected in State Absent
Pusan answered 9/7, 2013 at 16:4 Comment(0)
C
9

DetectMsiFeature is telling you the state of features for the new package; it's not installed, so neither are the features. DetectRelatedMsiPackage gives you the data you need to query the feature states of the installed version using the (native) MSI API functions MsiEnumFeatures and MsiGetFeatureState/MsiGetFeatureUsage.

Chokefull answered 9/7, 2013 at 19:5 Comment(2)
Thanks Bob! Since the WiX SDK provides some managed wrappers to gather that data, and I'd prefer to let someone else (ie: you guys on the WiX team) handle the native API marshaling, I added a separate answer detailing that route. Marking yours as the formal answer, since you lead me down the path ;)Pusan
I rarely use DTF so I didn't know what the DTF equivalents were named. :)Chokefull
P
17

I'm marking Bob Arnson's response as the answer, since it gave me what I needed to push this along, but for others who come across this post, I thought I'd give a little more detail about how you can gather the feature states using the WiX-provided ProductInstallation class (found in the Microsoft.Deployment.WindowsInstaller.dll assembly, which is in the WiX SDK), thus removing the need to make your own direct calls to native MSI API.

Here's an example of a method that could be registered to the DetectRelatedMsiPackage event. Note that you'll need to store off the information you gather so that you can set the appropriate states during the Plan phase.

private void DetectRelatedMsiPackageHandler(object sender, DetectRelatedMsiPackageEventArgs e)
{
    var existingPackageProductCode = e.ProductCode;
    var actionToBeAppliedToExistingPackage = e.Operation;
    var existingPackageId = e.PackageId;
    var existingPackageVersion = e.Version;

    Log(string.Format("Detected existing related package {0} (product: {1}) at version {2}, which will be {3}",
                      existingPackageId, existingPackageProductCode, existingPackageVersion,
                      actionToBeAppliedToExistingPackage));

    if (actionToBeAppliedToExistingPackage == RelatedOperation.MajorUpgrade)
    {

        //requires reference to WiX Toolset\SDK\Microsoft.Deployment.WindowsInstaller.dll
        var installedPackage = new Microsoft.Deployment.WindowsInstaller.ProductInstallation(existingPackageProductCode);
        if (!installedPackage.IsInstalled) {
            Log(string.Format("Migrating Package {0}, which is not installed, so marking it and it's features as Absent", existingPackageId));
            //TODO: add logic to store state so that during Plan phase can set package with package with product code = existingPackageProductCode to PackageState.Absent
        } else {
            Log(string.Format("Migrating features for MajorUpgrade of Package {0}", existingPackageId));

            foreach (var currentInstallFeature in installedPackage.Features) {                        
                if (currentInstallFeature.State == InstallState.Local) {
                    Log(string.Format("Migrating feature {1} of Package {0} - marking as Present", existingPackageId, currentInstallFeature.FeatureName));
                    //TODO: add logic to store state so that during Plan phase can set package and feature states based on this info
                } else {
                    Log(string.Format("Migrating feature {1} of Package {0} - marking as Absent", existingPackageId, currentInstallFeature.FeatureName));
                    //TODO: add logic to store state so that during Plan phase can set package and feature states based on this info
                }
            }
        }
    }
}
Pusan answered 9/7, 2013 at 22:42 Comment(2)
I struggled with this for a long time before realizing that you need to include Microsoft.Deployment.WindowsInstaller.dll as a payload in the bundle. Before I added that, I wasn't even getting the DetectRelatedMsi callbacks, and attempting to call a method in that dll manually resulted in the thread seemingly dying without even an exception being thrown.Conducive
@DaveAndersen Thanks a lot for the additional information. You saved me :)Kathe
C
9

DetectMsiFeature is telling you the state of features for the new package; it's not installed, so neither are the features. DetectRelatedMsiPackage gives you the data you need to query the feature states of the installed version using the (native) MSI API functions MsiEnumFeatures and MsiGetFeatureState/MsiGetFeatureUsage.

Chokefull answered 9/7, 2013 at 19:5 Comment(2)
Thanks Bob! Since the WiX SDK provides some managed wrappers to gather that data, and I'd prefer to let someone else (ie: you guys on the WiX team) handle the native API marshaling, I added a separate answer detailing that route. Marking yours as the formal answer, since you lead me down the path ;)Pusan
I rarely use DTF so I didn't know what the DTF equivalents were named. :)Chokefull

© 2022 - 2024 — McMap. All rights reserved.