Skip to content

DetectPhase Class

Richard Martin edited this page Mar 25, 2024 · 2 revisions

Bootstrapper.Phases.DetectPhase

For this tutorial, we'll use the detect phase to determine if the bundle has been previously installed, and if so, what version is installed. WiX will use the bundle's upgrade and product codes for this.

OnDetectBegin method

In the OnDetectBegin method, we determine if the currently running bundle is registered or not. We'll save this to a field for later use.

if (e.RegistrationType == RegistrationType.Full)
    _bundleDetectedState = DetectionState.Present;
else
    _bundleDetectedState = DetectionState.Absent;

OnDetectRelatedBundle method

The DetectRelatedBundle event is triggered when WiX finds an installed bundle that is related to the currently running bundle, but isn't the currently running bundle. For this installer, we can keep things simple. We only care if Windows Installer detected a major upgrade relationship. That means the upgrade code matches, but the product code and versions differ.

If an upgrade is detected, we'll save off the version of the installed bundle and determine if it's newer or older.

if (e.RelationType == RelationType.Upgrade)
{
    _bundleDetectedState = DetectionState.Present;
    if (string.IsNullOrWhiteSpace(_model.State.RelatedBundleVersion))
        _model.State.RelatedBundleVersion = e.Version;

    if (_model.Engine.CompareVersions(_model.State.BundleVersion, e.Version) > 0)
    {
        if (_model.State.RelatedBundleStatus <= BundleStatus.Current)
            _model.State.RelatedBundleStatus = BundleStatus.OlderInstalled;
    }
    else if (_model.Engine.CompareVersions(_model.State.BundleVersion, e.Version) == 0)
    {
        if (_model.State.RelatedBundleStatus == BundleStatus.NotInstalled)
            _model.State.RelatedBundleStatus = BundleStatus.Current;
    }
    else
        _model.State.RelatedBundleStatus = BundleStatus.NewerInstalled;
}

After determining what type of upgrade was detected, we can add the detected bundle to the chain of packages in the running bundle. This detected bundle will be scheduled for uninstall.

if (!_model.State.Bundle.Packages.ContainsKey(e.ProductCode))
    _model.State.Bundle.AddRelatedBundleAsPackage(e);

OnDetectComplete method

This is where we can finalize the bundle's status on the machine. Before we do that, we should look at the final HRESULT of the detect phase to make sure everything went as expected.

_model.State.PhaseResult = e.Status;

If the detect phase failed for any reason, we'll log the results and, if the bundle is running silently, call the UI facade's ShutDown method. This stops the Windows message loop and allows the BA to exit.

if (ErrorHelper.HResultIsFailure(e.Status))
{

    ...

    if (!_model.UiFacade.IsUiShown)
        _model.UiFacade.ShutDown();
    return;
}

With that out of the way, next check to see if a related bundle was detected. If it wasn't, then the app state's RelatedBundleStatus property will be unknown, so let's sort out whether the currently running bundle is installed or not based on the detected state we saved in OnDetectBegin.

if (_model.State.RelatedBundleStatus == BundleStatus.Unknown)
{
    if (_bundleDetectedState == DetectionState.Present)
    {
        _model.State.RelatedBundleStatus = BundleStatus.Current;
        if (string.IsNullOrWhiteSpace(_model.State.RelatedBundleVersion))
            _model.State.RelatedBundleVersion = _model.State.BundleVersion;
    }
    else
        _model.State.RelatedBundleStatus = BundleStatus.NotInstalled;
}

At this point, we know if the bundle is installed and which version. Set the BA status to waiting. The UI will know it's safe to ask the user for info when the BA is waiting.

_model.State.BaStatus = BaStatus.Waiting;

Tip

This tutorial doesn't cover how to handle user configurable options very well, like install folder. But if your UI asks the user for info before allowing them to install or update and your installer supports a silent mode with command line parameters, then this tip is for you. Now would be a good time to verify all command line options before allowing the install to proceed. If validation fails, you should set the app state's PhaseResult property to an appropriate HRESULT failure code. This is converted to a win32 error and returned when the BA exits. The easiest way to handle this is to use the HRESULT from a .NET exception. Native exceptions all have HRESULTS which convert nicely to win32 errors.

_model.State.PhaseResult = new InvalidOperationException().HResult;

Now to figure out how to proceed with the install. If it should be automated, then start the plan phase, otherwise, clean up and let the UI take over. In the sample solution, I perform a check to see if the user selected the Uninstall option from Add/Remove Programs. If they did, then I start an automatic uninstall.

if (_model.CommandInfo.Action == LaunchAction.Uninstall && _model.CommandInfo.Resume == ResumeType.Arp)
{
    _model.Log.Write("Starting plan for automatic uninstall");
    followupAction = _model.CommandInfo.Action;
}

If the user selected the -quiet or -passive command line switch, or the bundle is running embedded, let's start the plan phase.

if (_model.State.Display != Display.Full)
{
    _model.Log.Write("Starting plan for silent mode.");
    followupAction = _model.CommandInfo.Action;
}

Before starting the plan phase, notify the UI that detect has completed. Let it know what action is planned as well.

NotifyDetectComplete(followupAction);

Finally, if an action has been selected for planning, start the plan phase. Otherwise, the detect phase is done and the UI can take things from here.

if (followupAction != LaunchAction.Unknown)
    _model.PlanAndApply(followupAction);

Previous: Phases Overview || Next: PlanPhase Class

Clone this wiki locally