Skip to content

Managed Setup Model

Oleg Shilo edited this page Feb 15, 2024 · 14 revisions

All samples in this document are from the '<Wix#>/Samples/Managed UI' folder All of the samples are also reflected in the Visual Studio templates available as a "WixSharp Project Templates" extension.

The objective of Wix# is to bring MSI authoring in line with the other software development/programming activities. The initial Wix# development was mainly about integrating the authoring process with the C# syntactical model and C# and WiX compilers. However, it was only a part of the problem that WiX# was trying to solve. A more fundamental task was to change the over-engineered and often outdated MSI programming model. Thus Wix# allows avoiding the definition of the MSI/WiX elements that have low (or no) deployment value and yet can be generated automatically. Hence IDs, the associations between elements are optional for the cases when Wix# engine can establish them without user involvement. MSI Components are even become completely hidden without any exposure to the user. Another dramatic improvement is the ability to define Custom Action implementations directly in the build script.

However, two very distinctive MSI authoring activities for quite some time remained very consistent with the raw MSI development experiences: Custom Action scheduling and standard/custom UI. This gap has been closed with the release of the event-driven custom action scheduling and a complete set of managed (WinForm and WPF based) standard MSI dialogs. From that moment a fully managed WiX/MSI setup definition became possible. These two major features are the essence of Managed Setup programming model.

The access to the Managed Setup programming model is implemented via a dedicated Wix# project type WixSharp.ManagedProjects. It's important to note that ManagedProjects doesn't replace WixSharp.Projects. It only extends it with new functionality without affecting the existing one.

Note that all code samples in this document are from the "Managed Setup" samples included in the product downloadables.

Building and running MSI

The diagrams below show how WixSharp builds msi files and how they are executed:

image

image

Event-Driven Custom Actions

When it comes to scheduling Custom Actions MSI allows an extremely high level of granularity. A custom action can be scheduled at any of the ~160 possible "execution steps" (before/after ~80 standard actions). Why such an extreme resolution?

The answer is in the limited nature of MSI runtime. Every custom action is completely isolated not only from other custom actions but also from the MSI runtime. There is no way for the custom action to become "aware" of its position on the execution timeline or about any other action outcome. Every custom action is invoked in the individual rundll32.exe process. The only way to interact with any other installation component is by writing and reading string values into the session properties collection. Though Deferred Actions don't even have direct access to the MSI session at all. Concurrency control is ultimately simplistic: either blocking a single action at a time or fully asynchronous. Thus MSI API needs to leverage these limitations by offering a high resolution scheduling.

However, Wix# is different. While inheritably exhibiting all MSI limitations it hides (thanks to the power of .NET) them behind Wix# API layer and by doing so dramatically simplifies the whole authoring process. And this is exactly what the Managed Setup concept is all about.

Managed Setup is a "lean setup", which delivers event-driven runtime behavior instead of action-scheduling normally enforced by MSI. Instead of dealing with ~80 execution steps, Managed Setup recognizes only the three most important installation events: Load, BeforeInstall and AfterAinstall. The diagram below demonstrates the "placement" of these events concerning the major installation stages.

image

Thus ManagedProject class allows developers to use an event-driven programming model so common in the mainstream application development.

Note that ManagedProject also has other three events: WixSourceGenerated, WixSourceFormated and WixSourceSaved. The use of the prefix 'Wix' indicates that these events are raised not at runtime (msi execution) but at compile-time (generation of WiX sources). See the 'XML Injection' article for more details.

The following code illustrates how to use the runtime events:

project.Load += msi_Load;
project.BeforeInstall += msi_BeforeInstall;
project.AfterInstall += msi_AfterInstall;
...
static void msi_Load(SetupEventArgs e)
{
    MessageBox.Show(e.ToString(), "Load");
}

static void msi_BeforeInstall(SetupEventArgs e)
{
    MessageBox.Show(e.ToString(), "BeforeInstall");
}

static void msi_AfterInstall(SetupEventArgs e)
{
    MessageBox.Show(e.ToString(), "AfterExecute");
}

With the event handlers, you are no longer required to have the CustomActionAttribute placed in your code. It all will be done for you by the Wix# compiler. The event handlers will be invoked at runtime according to the following scheduling:

Load          -> When.Before, Step.AppSearch
BeforeInstall -> When.Before, Step.InstallFiles
AfterInstall  -> When.After,  Step.InstallFiles

Inside of the event handlers you have access to all session objects via SetupEventArgs argument:

static void msi_AfterInstall(SetupEventArgs e)
{
    if (!e.IsUninstalling && e.UILevel > 2)
    {
        string readme = io.Path.Combine(e.InstallDir, @"Docs\readme.txt");

        if (io.File.Exists(readme))
            Process.Start(readme);
        else
            MessageBox.Show("Readme.txt is not installed.\n"+
                            "You need to download it from the product website.",
                             e.ProductName);
    }
}

Scheduling of AfterInstall is slightly different to the other two events. It is actually a deferred Custom Action. This means that it will always run elevated. This implies that the actual MSI session object (WixSharp.SetupEventArgs.Session) is already closed and the MSI properties are no longer available (deferred actions limitation). But Wix# preserves all important properties so they are still available for use. The WixSharp.SetupEventArgs.ToString gives a good idea of what session properties can be accessed even when they are already unavailable with the native MSI session object:

image

Note that not all properties of the SetupEventArgs are populated by MSI runtime. This is because some of them are only available after the installation (e.g. session["Installed"]).

Handling runtime exceptions - Unhandled Exception event

There is also a special runtime event that is not related to the deployment scenario but rather to CLR runtime event. Similar. Similarly to the AppDomain.UnhandledException event you can subscribe for a ManagedProject.UnhandledException event, which is fired by WixSharp runtime when user uncaught exception is thrown either from Managed UI or from one of the ManagedProject events (wrappers around CA).

This is how you can use it:

project.UnhandledException += Project_UnhandledException;

. . .

void Project_UnhandledException(ExceptionEventArgs e)
{
    MessageBox.Show("Project_UnhandledException");
    Debug.Assert(false);
}

Note, that raw custom actions defined with the [CustomAction] attribute, cannot take benefit of UnhandledException mechanism. It is because these CA are invoked by WiX runtime directly wo it is bypassing WixSharp exception monitor.

Managed UI

When it comes to UI Wix# offers a few ways of simplifying the development of MSI UI:

You can define an ordinary WinForm dialog and indicate when in the UI sequence MSI runtime should display it. This model is simple to use though it is somewhat limited as it doesn't allow modifying any of the standard UI dialogs.

Wix# provides a binding mechanism for integrating a standalone managed UI application with the MSI runtime. This is the most flexible UI development approach though it relies on the complex interop interface. The distribution model is a pair of the exe (UI application) and the msi file. Alternatively, msi can be embedded into the exe and extracted upon execution.

WiX/MSI also supports embedding an external UI directly into the msi file. Wix# makes embedding the UI as simple as a single line of code: project.EmbeddedUI = new EmbeddedAssembly("<assembly path>");. Embedded UI is nothing else but an External UI assembly stored in the MSI. The interop interface between the UI and MSI runtime is completely hidden within WiX abstraction layer. The distribution model is the same as with any MSI installation - a single msi file.

Wix# recognizes Embedded UI as a major API model and now it is a recommended way for any UI customization. Starting from v1.0.22.3 Wix# extends the Embedded UI model even further by reimplementing all standard MSI dialogs in C# (WinForms and WPF). It means that now UI can be as customizable as any .NET UI.

Wix# Standard Dialogs support is implemented as a main window shell stepping through the sequence of the user-defined or standard dialogs and displaying a single dialog at the time:

image

The easiest way of customizing the UI is to create the VS project from one of the Wix# VS templates:

image The "Custom Dialog" template will create a project with an empty custom dialog already integrated into the UI sequence. You will only need to customize the dialog according to your requirements.

The "Custom UI" project will create a project with the source code for all standard dialogs forming a typical standard MSI UI everyone is well familiar with. You will note that some of the usual UI features as (disk space estimation, countless confirmation message boxes, feature item context menus .etc) are completely omitted due to the low practical value. But should you require them (or any new feature) you can easily implement them by yourself. For example, Wix# implements a slightly different standard Exit Dialog. It allows an immediate inspection of the setup log file without restarting the *.msi/msiexec.exe with extra command line arguments:

image

When customizing UI it can be beneficial to be able to preview the dialog or the whole dialogs sequence without executing the actual msi. Wix# allows this by running UI dialogs in a demo mode.

//play (run) the whole install UI sequence
UIShell.Play(ManagedUI.Default.InstallDialogs); 

//play (show) a single Exit dialog
UIShell.Play(Dialogs.Exit); 

.NET Dependency

Of course by implementing your setup as a "Managed Setup" you are introducing a new dependency - CLR dependency. For quite some time it was the subject of some hot discussions in the "WiX house". Some believed that any MSI setup should be native and 'pure', the way it was created in 1999. Others believed it should reflect and embrace the changes in the programming landscape introduced this century. For quite some time any integration of MSI runtime with CLR was only an experimental effort. But today CLR is a part of any Windows OS. Even the older versions of Windows are getting the CLR present thanks to the system update policies. WiX eventually recognized that and now it comes with plenty of CLR-dependant features. Thus the "war" is over (at least for the majority of developers). It is no longer a question if it is OK to depend on CLR. The question is how to deal with this dependency.

The first step for handling dependency is to define a proper LaunchCondition (see 'LaunchConditions' sample):

project.SetNetFxPrerequisite("NETFRAMEWORK45 >= '#378389'", 
                             "Please install .Net 4.5 First");

However, you can be even more proactive and bundle your application with the .NET setup (or a downloader/installer). The bundle (bootstrapper) doesn't even have to have its own UI. You can make it silent thus it will only serve the purpose of automatically ensuring the presence of the required .NET version. The following code is a complete WixBootstrapper_NoUI sample from the Wix# suite:

var productProj =
    new Project("My Product",
        new Dir(@"%ProgramFiles%\My Company\My Product",
            new File("readme.txt"))) { InstallScope = InstallScope.perUser };
        
productProj.GUID = new Guid("6f330b47-2577-43ad-9095-1861bb258777");
string productMsi = productProj.BuildMsi();

var bootstrapper =
        new Bundle("My Product",
            new PackageGroupRef("NetFx40Web"),
            new MsiPackage(productMsi) { DisplayInternalUI = true });

bootstrapper.Version = new Version("1.0.0.0");
bootstrapper.UpgradeCode = new Guid("6f330b47-2577-43ad-9095-1861bb25889b");
bootstrapper.Application = new SilentBootstrapperApplication();

bootstrapper.Build();

And when it comes to the compatibility of the CLR required by your (or Wix#) assembly and the CLR present on the target system you can handle it the same way as with any managed assembly/application: via the app.config file. Read more about compatibility in this section.