Deployment Scenarios

While most of the deployment scenarios reflected in the product samples are self-explanatory some of them may benefit from some extra explanations:


Identities and Naming

Many of the MSI concepts are not very intuitive and over complicated. One of the the biggest MSI blunder is the deployment identities. For example, one would think that MSI identity ProductId uniquely identifies a product. And when you hear 'product' you may immediately think of Notepad++, Visual Studio 2015 or Windows 10... However in MSI domain ProductId is a unique identifier of a single msi file. Thus two msi for Notepad++ v6.7.4 and v6.7.5 have different ProductIds even if they are  deploying the same product Notepad++.

From the other hand, ProductUpgradeCode may seem like something that associated with a version of a Product so upgrades (versions) can be easily identified. But... no, it is the identifier that is shared by all versions of a product. Thus it is in fact something that is associated with the Product as marketing understands it.

MSI is using ProductUpgradeCode implement upgrades. It is in fact a vital component, without which it is impossible to define relationship between different versions of the same product. And yet... MSI considers it as optional. Meaning that MSI built with the default settings  installs the product that is impossible to upgrade ever. You can remove it and install a new version manually but you cannot upgrade it.

Wix# is trying to bring some order in all this. Instead of already taken ProductId and UpgradeCode (which are still available if required) it introduces on ProductGUID (Project.GUID). Wix# compiler uses Project.GUID as a seed for deterministic auto-generation of both product Id and UpgradeCode. Thus you can have your deployment for two versions of the product as follows:

//script AB
project.new Version("1.0.0.0");
project.GUID = new Guid("6f330b47-2577-43ad-9095-1861ba25889b");
 
//script B
project.new Version("2.0.0.0");
project.GUID = new Guid("6f330b47-2577-43ad-9095-1861ba25889b");


The both produced msi files will have the same ProductUpgradeCode and unique ProductId exactly as MSI requires. In fact, if you are building a one off msi you can even skip assigning Project.GUID and Wix# will use a random one. Unconditional initialization of UpgradeCode by Wix# compiler also allows every msi produced with it to be 'upgradable'.

Naming it is another form of identity of the MSI/WiX entities. For example MSI/WiX requires ever directory to have both Name and Id. Id is used as an internal reference and the name is used as the name of the directory created on the target system after deployment. Wix# allows omitting use of Id in C# code:  

new Project("MyProduct",
    new Dir(@"%ProgramFiles%\My Company\My Product",
        new File(@"Files\Bin\MyApp.exe"),
        new Dir("Docs",
            new File(@"Files\Docs\Manual.txt"))));

In the sample above the Ids are auto-generated from the entity names. The Id generation algorithm takes care about possible Id duplications by using special suffixes. The auto-Ids are generated on the first access thus various Id evaluations in your code (e.g. if (file.Id.EndsWith("Services.exe"))) may affect on the final Id allocation outcome.

Thus if you are really need to have 100% deterministic id allocation deterministic you may want to use explicit Ids:

new Project("MyProduct",
    new Dir(new Id("PRODUCT_INSTALLDIR"), @"%ProgramFiles%\My Company\My Product",
        new File(new Id("App_File"), @"Files\Bin\MyApp.exe"),
        new Dir("Manual",
            new File(new Id("Manual_File"), @"Files\Docs\Manual.txt"))));

           
Though you will find that in most cases Wix# Id allocation is just fine. A part from being convenient Id auto-allocation allows avoiding potential id clashes associated with the manual id assignments from initializers. See "Explicit IDs" sample for details.

The target install directory has special importance. The install directory is a the first directory containing the files to be installed. In the code sample above it will be "%ProgramFiles%\My Company\My Product". And if user doesn't specify the Id for the install directory Wix# always assigned it to the "INSTALLDIR".

Working with registry
Sample: Registry

When deploying registry entries you have the option to add the registry values one by one or you can import the values from the registry file:

var project =
    new Project("MyProduct",
        ...
        new RegFile("MyProduct.reg"), 
        new RegValue(RegistryHive.LocalMachine, @"Software\.....         
new RegValue(RegistryHive.LocalMachine @"Software\.....

When importing the registry file it is important to remember that not all the types of the reg file are supported by MSI. Only the following reg file values can be mapped to the MSI registry value types:

DWORD
REG_BINARY
REG_EXPAND_SZ
REG_MULTI_SZ
REG_SZ

By default Wix# compiler will throw an exception if unsupported registry value type is found but this behaviour can be overwritten by setting the RegFileImporter.SkipUnknownTypes flag.

You also have an option to import all RegValues form the regfile manually and then add them to the project either one by one or as the whole collection.

   RegValue[] values = Tasks.ImportRegFile("MyProduct.reg");
   ...
var project = new Project("MyProduct", values.ToWObject(), ...
//or 
project.ImportRegFile("MyProduct.reg");

 

Windows Services
Sample: WinService\With_InstrallUtil; WinService\With_WiX

Installing windows services can be done either by using built-in standard .NET installUtil.exe. Wix# implemented dedicated routines for installing/uninstalling and starting/stopping windows services. If you decide to deploy the service this way you need to call the service control routine from the custom action:

var project =
    new Project("My Product",
        new Dir(@"%ProgramFiles%\My Company\My Product",
            new File(@"..\SimpleService\MyApp.exe")),
        new ElevatedManagedAction("InstallService"
Return.check, 
When.After, 
Step.InstallFiles, 
Condition.NOT_Installed),         new ElevatedManagedAction("UnInstallService"
Return.check, 
When.Before, 
Step.RemoveFiles, 
Condition.BeingRemoved));
... public class CustomActions {     [CustomAction]     public static ActionResult InstallService(Session session)     {         return session.HandleErrors(() =>         {             Tasks.InstallService(session.Property("INSTALLDIR") + "MyApp.exe"true);             Tasks.StartService("WixSharp.SimpleService"false);         });     }
...

Alternatively you can deploy windows service using native WiX functionality. In many cases you would prefer this service deployment model as it is fully consistent with the WiX recommended practices.

File service;
var project =
    new Project("My Product",
        new Dir(@"%ProgramFiles%\My Company\My Product",
            service = new File(@"..\SimpleService\MyApp.exe")));
 
service.ServiceInstaller = new ServiceInstaller
                            {
                                Name = "WixSharp.TestSvc",
                                StartOn = SvcEvent.Install,
                                StopOn = SvcEvent.InstallUninstall_Wait,
                                RemoveOn = SvcEvent.Uninstall_Wait,
                            };

  

.NET prerequisite
Sample: LaunchConditions

There are various ways of validating that a specific version of .NET is present on the target system. The recommended one is by calling SetNetFxPrerequisite of the project. When calling this method you should pass the string expression representing a presence of the desired verion of .NET. This in turn sets the corresponding LaunchConditions based on the corresponding property of the WiX NetFxExtension extension: 

var project = new Project("Setup",
    new Dir(@"%ProgramFiles%\My Company\My Product",
        new File(@"Files\MyApp.exe")));
 
project.SetNetFxPrerequisite("NETFRAMEWORK20='#1'");
 
Compiler.BuildMsi(project);

Though in some cases the conditions can be more complicated:

project.SetNetFxPrerequisite("NETFRAMEWORK45 >= '#378389'", ...);
project.SetNetFxPrerequisite("NETFRAMEWORK30_SP_LEVEL and NOT NETFRAMEWORK30_SP_LEVEL='#0'", ...);

The full list of properties and values for can be found here http://wixtoolset.org/documentation/manual/v3/customactions/wixnetfxextension.html

.NET compatibility

WixSharp.dll, which implements built-in Custom Actions and Standard UI Dialogs, is compiled against .NET v3.5. There is a good reason for this. This version represents a good compromise between functionality and availability on the potential target systems. Of course it doesn't mean that you can now only build msi that targets systems hosting .NET v3.5. No. The by default Wix# will pack any embedded assembly with the app.config file that controls how the assembly will be hosted on the target system during the installation. The same config file also controls CLR compatibility. The below is the default config file content:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup useLegacyV2RuntimeActivationPolicy="true">
        <supportedRuntime version="v[NoRevisionVersionOfBuildSystem]"/>
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/>
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
        <supportedRuntime version="v2.0.50727"/>
        <supportedRuntime version="v2.0.50215"/>
        <supportedRuntime version="v1.1.4322"/>
    </startup>
</configuration>

However if for whatever reason the default config file is not suitable for your case you can specify any config file of your choice:

project.CAConfigFile = "app.config";

Note there was at least one compatibility issue report regarding external UI setup being run on .NET 4.6 target system: http://wixsharp.codeplex.com/discussions/645142

Including files dynamically (working with wild card source location)
Sample: LaunchConditions, Release Folder, WildCard Files

Dealing with the static file structure (to be deployed) is straight forward. However in many cases you may want include in your MSI different sets of files depending on various conditions. For example you create the Wix# project for deploying all files from the Release folder of the Visual Studio project. Once you defined Wix# build script you don't want to update it every time your VS project is changed (e.g. )

Sometimes the set of files to be installed is a subject to the application (to be installed) configuration. For example every new release can contain different set of the dlls or the documentation files. It would very impractical to redefine the Wix# Project every time the new release setup file is to be built. Wix# allows dynamic inclusion of the files in the project by using Files class with the wildcard path definition:

var project =
    new Project("MyProduct",
        new Dir(@"%ProgramFiles%\My Company\My Product",
            new Files(@"Release\*.*"),
            new ExeFileShortcut("Uninstall My Product"
"[System64Folder]msiexec.exe"
"/x [ProductCode]")));

The code sample above shows how to include into project all files from the 'Release' folder and all it's subfolders. The Files constructor accepts exclusion attributes making it a convenient option for processing the Visual Studio build directories:

new Files(@"Release\*.*""*.pdb""*.obj"),

NOTE: It is important to remember that the wild cards are resolved into a collection of files during the Compiler.Build call. Thus if you want to aqccess any of these files before calling Compiler.Build you need to to trigger resolving the wildcard definitions explicitly. The following code snippet shows how to ensure the executable form the release folder will have the corresponding shortcut file created during the installation:  

project.ResolveWildCards()
       .FindFile((f) => f.Name.EndsWith("MyApp.exe"))
       .First()
       .Shortcuts = new[] {
                               new FileShortcut("MyApp.exe""INSTALLDIR"),
                               new FileShortcut("MyApp.exe""%Desktop%")
                           };

When you need to have more control over the project subdirectories generation during resolving the wild cards you may want to use DirFiles class. It is a logical equivalent of Files class except it only collects the files from the top directory:

new Project("MyProduct",
    new Dir(@"%ProgramFiles%\MyCompany\MyProduct",
        new DirFiles(@"\\BuildServer\MyProduct\LatestRelease\*.*"),
        new Dir("Docs", 
            new DirFiles(@"\\DocumentationServer\MyProduct\*.chm"),
 

 

Product upgrades
Sample: MajorUpgrade1,MajorUpgrade2

Upgrading is a relatively simple deployment concept. However its MSI implementation is a bit convoluted and not very intuitive. Wix# allows handling the upgrade scenarion in a relatively simple way. Working with the product identities is described in the 'Identities and Naming' section and this section covers the actual upgrade implementation.

MSI recognizes Major and Minor upgrade scenarios. However the practical application of Minor Upgrades is practically non existent and the latest MS deployment technology ClickOnce does not even allow any equivalent of MinorUpgrad.

Wix# API provides a special class MajorUpgradeStrategy that defines the upgrade model desired for your product. It includes (as per MSI specification):
- Range of the versions allowed to be upgraded by your msi (UpgradeVersions) 
- Range of the versions forbidden to be upgraded by your msi (PreventDowngradingVersions) 
- Error message to be displayed on the restricted downgrade attempt (NewerProductInstalledErrorMessage)

In the wast majority of case upgrades are not concerned about the specific versions and can be expressed by a single statement: "Upgrade if the installed version is older".

This can be codded as follows:

project.MajorUpgradeStrategy = new MajorUpgradeStrategy
{
    UpgradeVersions = VersionRange.OlderThanThis,
    PreventDowngradingVersions = VersionRange.NewerThanThis,
    NewerProductInstalledErrorMessage = "Newer version already installed"
};

In fact MajorUpgradeStrategy already has the code predefined as Default so the code above can be further simplified:

project.MajorUpgradeStrategy = MajorUpgradeStrategy.Default


Note:
Note: MajorUpgradeStrategy yields WiX UpgradeVersion element, which is arguably the most comprehensive upgrade definition. However in the later versions of WiX a simplified upgrade definition has been introduced. It relies on MajorUpgrade WiX element. For most of the upgrade scenarios you will find MajorUpgrade allows achieve the same result with much less effort. Wix# supports MajorUpgrade element via MajorUpgrade member:

project.MajorUpgrade = new MajorUpgrade
{    
    Schedule = UpgradeSchedule.afterInstallInitialize,
   
DowngradeErrorMessage = "A later version of [ProductName] is already installed. Setup will now exit."
};

 

Working with environment variables
Sample: EnvVariables

Wix# support for environment variables includes both handling the build environment variables during the MSI authoring and deployment of the environment variables on the target system.

Compile Time
You can embed environment variables in any path value of the project or Dir/File property:

new Dir(@"%ProgramFiles%\My Company\My Product",
    new File(@"%bin%\MyApp.exe"),
...
project.OutDir = @"%LATEST_RELEASE%\MSI";
project.SourceBaseDir = "%LATEST_RELEASE%";

 
Deploy time
The support for deploying environment variables is fairly consistent with the WiX model for environment variables:

var project =
    new Project("MyProduct",
        ...
        new EnvironmentVariable("MYPRODUCT_DIR""[INSTALLDIR]"),
        new EnvironmentVariable("PATH""[INSTALLDIR]"
{ Part = EnvVarPart.last });

 

Properties
Sample: Properties, PropertyRef, SetPropertiesWith Wix# implementing properties is straight forward:

var project =
    new Project("MyProduct",
        ...
        new Property("Gritting""Hello World!"),
new Property("Title""Properties Test Setup"),

However the code above is only appropriate for setting the properties to the hardcoded values but not to the values that include references to other properties. MSI/WiX prohibits this and requires a custom action instead. With Wix# this problem is solved with the syntax very similar to the code above:

var project =
    new Project("MyProduct",
        ...
        new Property("Gritting""Hello World!"),
new SetPropertyAction("Title""[PRODUCT_NAME] Setup"),

And of course you can always set the property to any value if you do this from the ManagedCustomAction:

[CustomAction]
public static ActionResult OnInstallStart(Session session)
{
    session["Title"] = session["PRODUCT_NAME"] + " Setup";
    return ActionResult.Success; }

Referencing any external property can be achieved with PropertyRef class:

var project = new Project("Setup",
                  new PropertyRef("NETFRAMEWORK20"),

Property "NETFRAMEWORK20" is defined and initialized by the WiX framework outside of the user code.

Deferred actions
Sample: DeferredActions

Deferred Actins, without too much details, are custom actions that are executed in a different runtime context comparing to the regular custom actions and standard actions. Deferred Actins play important role in addressing the MSI restrictions associated with the user security context. For example Deferred Actions are the only way to perform during setup any activity that require user elevation. Though Deferred Actions themselves associated with some serious limitations.

Thus Deferred custom actions cannot access any session property as the session is terminated at the time of the action execution. MSI's standard way of overcoming this limitation is to:

  • create a new custom action for setting the property
  • set the property name to the name of the deferred action
  • set the property value to the specially formatted map
  • schedule the execution of the custom action
  • access the mapped properties only via Microsoft.Deployment.WindowsInstaller.Session.CustomActionData.

With Wix# all this can be done in a single hit. Wix# fully automates creation of the all mapping infrastructure. But before you start using mapped properties you need to understand the MSI property mapping paradigm.

With MSI mapping you are required to declare upfront what standard properties (or property containing values) you are going to use in the runtime context of the Deferred action. Declaring is achieved via specially formatted string:

<deferred_prop1>=<standard_prop1>[;<deferred_propN>=<standard_propN>]

The following is the example of how ElevateManagedAction (it is automatically initialized as differed one) maps standard properties:

new ElevatedManagedAction("OnInstall")
{
    UsesProperties = "D_INSTALLDIR=[INSTALLDIR];[D_PRODUCT]=[PRODUCT]"
}

Though Wix# syntax is more forgiving and you can use coma as a delimiter and you can also skip the assignment if you want to use the same names for the both properties. Thus the following two statements are semantically identical.

UsesProperties = "INSTALLDIR=[INSTALLDIR];[PRODUCT]=[PRODUCT]"
UsesProperties = "INSTALLDIR, PRODUCT"   

In fact all Managed actions have always UILevel and INSTALLDIR mapped by default: 

class ManagedAction : Action
{
public string DefaultUsesProperties = "INSTALLDIR,UILevel";

Consuming the mapped properties isn't straight forward. You cannot use session["INSTALLDIR"] as the session object is already cleared at the time of deferred execution. Thus you have to use an alternative set of properties session.CustomActionData["INSTALLDIR"]. Though Wix# simplifies this by providing an extension method that return the property value transparently regardless of which property collection contains it:

[CustomAction]
public static ActionResult OnInstall(Session session)
{
    session.Log(session.Property("INSTALLDIR"));

 

x64 components
Sample: Install on x64

MSI does not provide any solution for CPU neutral setup. Thus individual setups need to be built for every CPU architecture to be deployed on. Unfortunately the WiX follows the case and the WiX code, which targets x86 has to be heavily modified before it can be used to build x64 setup. This includes a different name for the 'Program Files' directory, special attributes for the Package and all Component elements.

Wix# allows all this to be done with a single Platform assignment statement:

var project =
    new Project("MyProduct",
        new Dir(@"%ProgramFiles%\My Company\My Product",
            new File(@"Files\Bin\MyApp.exe"),
            ...
 
project.Platform = Platform.x64;

Note: while Wix# compiler auto-maps %ProgramFiles% into platform specific path you can still use %ProgramFiles64Folder% (or semantically identical %ProgramFiles64%) if required. 

Managed Custom Actions
Sample: DTF (Managed CA), Different Scenarios\Debugging, Different Scenarios\Embedded, Different Scenarios\EmbeddedMultipleActions, Different Scenarios\External C# file, Different Scenarios\ExternalAssembly, Different Scenarios\STAThread

As opposite to MSI/WiX, which requires external modules for implementing Custom Actions, Wix# allows them to be implemented directly in the build script. And more importantly using the same language, which is used for the setup definition - C#.   

The simplest custom action (ManagedAction) can be defined as follows:

var project = 
new Project("CustomActionTest",         new ManagedAction("MyAction"Return.check, 
When.After, 
Step.InstallInitialize, 
Condition.NOT_Installed)); ...
public class CustomActions {     [CustomAction]     public static ActionResult MyAction(Session session)     {         MessageBox.Show("Hello World!""Embedded Managed CA");         session.Log("Begin MyAction Hello World");         return ActionResult.Success;     } }

The name of the class implementing custom action is irrelevant as long as it is public. Similarly, the actual custom action is any public static method with the ActionResult Method(Session) signature. And the last touch, the method has to be marked with the CustomAction attribute.

What can be done from ManagedAction? Practically anything. The possibilities are limitless. You can completely take advantage the full scale rich runtime, which CLR is. You can interact with the file system, check the registry, connect to the online resource, interact with the user. You have the full power of .NET at your disposal. And of course you can interact with the MSI runtime via the session object passed into the action method as a parameter. The most common use of the session object is to read and write MSI properties values:

[CustomAction]
public static ActionResult OnInstallStart(Session session)
{
    session["Title"] = session["PRODUCT_NAME"] + " Setup";
 
    return ActionResult.Success;
}

See Properties section for details.

If you need elevated privileges then you need to use ElevatedManagedAction class, which schedules the custom action as deferred one. See Deferred actions section for details.

Sometimes instead of the build script you may want to implement ManagedAction as a separate assembly. In such case you need to specify the location of this assembly as a second string parameter in the ManagedAction  constructor:

new ManagedAction("MyAction"@"\bin\myCustomActionAssembly.dll")
new ManagedAction("MyAction""%this%")

Note: a special value %this% is reserved for the build script assembly. If no assembly path is specified then %this% is assumed.

As any C# routine ManagedAction may depend on some external assemblies. Thus for the ManagedAction to run correctly on the target system the dependency assemblies need to be packaged into MSI setup.

new ManagedAction("CustomAction")
{
    RefAssemblies = new []{"DispalyMessage.dll"}
}

If you need to define multiple custom actions then you may want to define a common set of assemblies to be packaged with all ManagedActions: 

project.DefaultRefAssemblies.Add("CommonAsm.dll");   

Thanks to the fact that ManagedAction is implemented in a plain .NET assembly it can be debugged as easy as any other managed code. The most practical way of debugging is to put an assert statement and to attach the debugger when prompted:

[CustomAction]
public static ActionResult MyAction(Session session)
{
    Debug.Assert(false);
    ...

 

Skipping UI Dialogs
Sample: Skip_UIDialog

Modifying one of the predefined the UI dialogs sequences is relatively simple with WiX. For example skipping the Licence Agreement dialog can be achieved with the following WiX statement:

<UI>
    <Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" 
Value="InstallDirDlg"  Order="5">1</Publish>     <Publish Dialog="InstallDirDlg" Control="Back" Event="NewDialog" 
Value="WelcomeDlg"  Order="5">1</Publish> </UI>

The equivalent Wix# statement semantically is very similar. The DialogSequence is a dedicate class for re-wiring UI dialogs sequence.  You can use it to connect ("sort circuit") the dialogs by associating OnClick actions of Next and Back buttons of the dialogs to be connected:

project.CustomUI = 
new DialogSequence()          .On(Dialogs.WelcomeDlg, Buttons.Next, 
new ShowDialog(Dialogs.InstallDirDlg))            .On(Dialogs.InstallDirDlg, Buttons.Back, 
new ShowDialog(Dialogs.WelcomeDlg));

 

Injecting Custom WinForm dialog
Sample: Custom_UI/CustomCLRDialog

It is also possible to inject a dialog in the predefined UI dialogs sequences. There are two possibilities for that:

  • Native MSI UI Dialog
  • Managed WinForm UI Dialog

Support for building native a MSI UI is was available on in the earlier releases of Wix# as an experimental feature and no longer supported. It has been superseded by the "Embedded UI" feature available in v1.0.22.0 and higher. Though Wix# suite contains CustomMSIDialog sample that demonstrates the technique.

Injecting a managed WinForm dialog from other hand represents more practical value as functionality wise WinForms are far superior to native MSI UI and much easier to work with. The image below is the custom dialog from the CustomCLRDialog sample:

Strictly speaking user defined managed dialog is not injected in the UI sequence as MSI cannot understand managed UI. Instead a special managed custom action is scheduled as a first "start up action" of a native dialog. This action hides the native dialog and instead displays the managed dialog. When user presses "Next" button the managed dialog becomes invisible and the native one displayed instead.

  

This technique is relatively simple. But it does require a special care when implementing the "injection":

project.InjectClrDialog("ShowCustomDialog"
NativeDialogs.InstallDirDlg, 
NativeDialogs.VerifyReadyDlg);

Arguable the "Embedded UI" (particularly Managed Standard UI Managed Standard UI) offers a much simpler development model with even greater functionality.

Complete Custom External UI
Sample: External_UI/ConsoleSetup, External_UI/WinFormsSetup, External_UI/WpfSetup 

However the ultimate UI power comes with the External UI MSI architecture. MSI allows delegating interaction to the external process while maintaining the runtime communication between the external UI and MSI runtime via dedicated MSI Interop interface.

This is the most flexible UI technique available as there is no restriction at all on how your UI implemented and how it is hosted at runtime. It can be anything. It can be a WPF, WinForm or even a console application for that matter. The topic is covered in details in this CodeProject article. The following is a custom WPF UI from the WpfSetup sample:



WixSharp provides a lean interface for integrating the UI application. This is the fragment of the WinForm based setup  sample:

public partial class MsiSetupForm : Form
{
MyProductSetup session;

public MsiSetupForm(string msiFile)     {         InitializeComponent();         session = new MyProductSetup(msiFile);         ...        session.ProgressChanged += session_ProgressChanged;         session.ActionStarted += session_ActionStarted;         session.SetupComplete += session_SetupComplete;    }

void installBtn_Click(object sender, EventArgs e)     {         DisableButtons();         session.StartInstall();     }

It is important to note that the MSI External UI model separates completely UI, msi file and MSI runtime. That is why a typical distribution model for an external UI setup is an msi file accompanied with the UI executable (often setup.exe). 

Note that Wix# provide an extremely thin abstraction layer between the user routine and MSI runtime API thus in many cases you may have to deal with the MSI Interop directly. 

Burn bootstrapper
Sample: Bootstrapper/WixBootstrapper* 

Wix# allows defining the setup bundle for building a singe file bootstrapper for installing multiple products:

var bootstrapper =
        new Bundle("My Product",
            new PackageGroupRef("NetFx40Web"),
            new MsiPackage(crtMsi),
            new MsiPackage(productMsi) { DisplayInternalUI = true } );
 
bootstrapper.AboutUrl = "https://wixsharp.codeplex.com/";
bootstrapper.IconFile = "app_icon.ico";
bootstrapper.Version = new Version("1.0.0.0");
bootstrapper.UpgradeCode = new Guid("6f330b47-2577-43ad-9095-1861bb25889b");
bootstrapper.Application.LogoFile = "logo.png";
bootstrapper.Application.LicensePath = "licence.html";   

bootstrapper.Build();

With Wix# you can also easily specify what type of the bootatrapper UI you want to use:

//Standard WiX LicenseBootstrapperApplication
bootstrapper.Application = new LicenseBootstrapperApplication();
 
//Wix# CustomBA with no UI
bootstrapper.Application = new();
 
//User defined CustomBA implemented in the same Wix# project 
bootstrapper.Application = new ManagedBootstrapperApplication("%this%");

It is important to note that when you use custom (non WiX)  like SilentBootstrapperApplication or even your own. WiX is no longer responsible for detecting (at startup) is the bootstrapper needs to be initialized in the Install or Uninstall mode. Any BA implements a routine responsible for this. The example of such a routine can be found in the WixBootstrapper_NoUI sample.

However if you are using Wix# pre-built SilentBootstrapperApplication then you need to indicate what package in the bundle is the primary one. The presence of this package on the target system will trigger initialization of the bootstrapper in Install or Uninstall mode: 

var bootstrapper =
        new Bundle("My Product",
            new PackageGroupRef("NetFx40Web"),
            new MsiPackage(crtMsi),
            new MsiPackage(productMsi) 
{
            Id = "MyProductPackageId",
DisplayInternalUI = true
});
bootstrapper.Application = new SilentBootstrapperApplicationrsion("MyProductPackageId");

If primary package Id is not defined then the last package will be treated as the primary one.

Some user feedback on Burn integration has been captured and reflected in this discussion. It is highly recommended that you read this discussion particularly if you are planing to build a custom msi and/or bootstrapper UI. However if you are using SilentBootstraper then it is a must-reading.

Embedded UI
Sample: Custom_UI/EmbeddedUI, Custom_UI/EmbeddedUI_WPF 

Embedded UI is nothing else but an external UI embedded into MSI so there is no need to distribute separately. A single msi file is fully sufficient for installing the product. Thanks WiX team the integration is simple and straight forward. You just need to build the assembly implements Microsoft.Deployment.WindowsInstaller.IEmbeddedUI interface and then assign Wix# EmbeddedUI member to that assembly:

project.EmbeddedUI = new EmbeddedAssembly(sys.Assembly.GetExecutingAssembly().Location);

Wix# suite comes with the EmbeddedUI and EmbeddedUI_WPF samples , which are based on practically unmodified sample from the WiX toolset samples:

 

Embedded UI limitations
Note that Embedded UI runs instead of the InstallUISequence, so results of standard actions like AppSearch and costing cannot be used in your embedded UI assembly. This is the nature of the MSI Embedded UI architecture. The following blog contains a good description of this constrain: Fun with MSIEmbeddedChainer.  

Note that MSI support for EmbeddedUI is available beginning with Windows Installer 4.5. Thus any OS with earlier version of Windows Installer services (e.g. WinXP) will ignore any EmbeddedUI elements (see this MSDN article). Though you can still do Injecting Custom WinForm dialog.

Thus all InstallUISequence standard and custom actions (if any) will need to be implemented in the user assembly of the class implementing IEmbeddedUI interface. The good candidate for the placement is the IEmbeddedUI.Initialize(...) method.

Managed UI allows simplified mechanism for implementing InstallUISequence actions. You can implement the required actions in the UIInitialized event handler:

project.UIInitialized += UIInitialized;
...
static void UIInitialized(SetupEventArgs e)
{
    if (e.IsInstalling)
    {
        var conflictingProductCode = "{1D6432B4-E24D-405E-A4AB-D7E6D088C111}";
 
        if (AppSearch.IsProductInstalled(conflictingProductCode))
        {
            var msg = string.Format(
                        "Installed '{0}' is incompatible with this product.\n" +
                        "Setup will be aborted.",
                        AppSearch.GetProductName(conflictingProductCode));
 
            MessageBox.Show(msg, "Setup");
 
            e.Result = ActionResult.UserExit;
        }
    }
}

Wix# also implements C# 'assistance' routines that can laverage the well known MSI AppSearch functionality: 

bool keyExist = AppSearch.RegKeyExists(Registry.LocalMachine,
                                        @"System\CurrentControlSet\services");
 
object regValue = AppSearch.GetRegValue(Registry.ClassesRoot, ".txt"null);
 
string code = AppSearch.GetProductCode("Windows Live Photo Common")
                       .FirstOrDefault();
 
string name = AppSearch.GetProductName("<GUID>");
 
bool installed = AppSearch.IsProductInstalled("<GUID>");
 
string[] products = AppSearch.GetProducts();

 

Managed (Standard) UI
Sample: Managed Setup/CustomUIDialog, Managed Setup/CustomUISequence 

Managed UI is a recommended Wix# UI customization approach. It is a part of the  Managed Setup API model. It is highly recommended that you read the Managed Setup documentation before choosing your MSI authoring development direction. 

Managed UI is a Wix# abstraction API layer combined with the set of predefined standard MSI dialogs implemented as WinForms classes. It is the most powerful and straight forward Custom UI model available with Wix#. The combines the strengths of other UI models ad yet eliminates their shortcomings. The conceptual differences between available UI models are described in the Managed UI section of Managed Setup.

A typical Managed UI project (e.g. created form Wix# VS project templates) defines the deployment logic in the  ManagedProject instance: 

var project = new ManagedProject("ManagedSetup",
                    new Dir(@"%ProgramFiles%\My Company\My Product",
                        new File(@"..\Files\bin\MyApp.exe"),
                        new Dir("Docs",
                            new File("readme.txt"))));
 
project.ManagedUI = new ManagedUI();
 
project.ManagedUI.InstallDialogs.Add(Dialogs.Welcome)
                                .Add(Dialogs.Licence)
                                .Add(Dialogs.SetupType)
                                .Add(Dialogs.Features)
                                .Add(Dialogs.InstallDir)
                                .Add(Dialogs.Progress)
                                .Add(Dialogs.Exit);
 
project.ManagedUI.ModifyDialogs.Add(Dialogs.MaintenanceType)
                               .Add(Dialogs.Features)
                               .Add(Dialogs.Progress)
                               .Add(Dialogs.Exit);

The project has a special field ManagedUI, which defines two collections of UI dialogs. One is for displaying UI during the product installation (e.g. double-clicking msi file) and another one for the product modification (e.g. clicking 'modify' in the control panel 'Programs and Features' view). Note that 'Restore' and 'Uninstall' scenarios are not associated with any custom UI and the MSI runtime is using its native UI instead.

The code defines a typical/standard UI thus instead of re-defining it again and again you can just use set ManagedUI to the predefined 'Default' instance:

project.ManagedUI = ManagedUI.Default;

If you don't want to have any UI at all then you can use another predefined ManagedUI value:

project.ManagedUI = ManagedUI.Empty;

You can freely change the sequence of dialogs (e.g. remove some) or you can insert your own custom dialog. A custom dialog is jut a ordinary WinForm class that implements IManagedDialog interface. You can derive your form from the WixShar ManagedForm class, which provides the default interface implementation: 

public partial class UserNameDialog : ManagedFormIManagedDialog
{
    public UserNameDialog()
    {
        InitializeComponent();
    }
 
    void dialog_Load(object sender, EventArgs e)
    {
        banner.Image = MsiRuntime.Session.GetEmbeddedBitmap("WixUI_Bmp_Banner");
 
        name.Text = Defaults.UserName;
        password.Text = MsiRuntime.Session["PASSWORD"];
    }
 
    void back_Click(object sender, EventArgs e)
    {
        Shell.GoPrev();
    }
 
    void next_Click(object sender, EventArgs e)
    {
        MsiRuntime.Session["PASSWORD"] = password.Text;
        MsiRuntime.Session["DOMAIN"] = domain.Text;
 
        Shell.GoNext();
    }
 
    void cancel_Click(object sender, EventArgs e)
    {
        Shell.Cancel();
    }
...

Thanks to the MsiRuntime base class member you have the full access to the underlying MSI session. Thus you can extract the embedded MSI resources or access the session properties. MsiRuntime is also responsible for the localization.

Thus if you embed any text surrounded by square brackets in any Control.Text of your UI element MsiRuntile will try to substitute it with the value from the localization file or the runtime MSI property. The substitution will happen when dialog is initialized. Though you can trigger the dialog localization at any time by calling base class Localize() method. Alternatively you can localize an individual string values with MsiRuntime.Localize():

nextButton.Text = "[WixUINext]";
Localize();
//or
nextButton.Text = MsiRuntime.Localize("WixUINext");

And scheduling the new custom dialog is straight forward:

project.ManagedUI = new ManagedUI();
project.ManagedUI.InstallDialogs.Add<WelcomeDialog>()
                                .Add<MyProduct.UserNameDialog>()
                                .Add<ProgressDialog>()
                                .Add<ExitDialog>();

 

It is also possible to change the look and behavior of any standard dialog. This can be achieved by creating the VS project from the "Custom UI" template. The freshly created project will contain a source code for all standard dialogs and the customization will be no different to the customization of any WinForm application:

Note that Wix# default implementation of Features Dialog offers an alternative simplified user experience when interacting with features tree view. Thus the all features in the tree have checkboxes instead of the usual clickable icons with context menus.

 

The user experience is very simple and intuitive. If the feature's checkbox is checked the feature will be installed, otherwise it will not. If the feature is defined as read-only (user cannot change its "to be installed" state) the check box will be disabled and user will not be able to change its checked state.

Limitations: 

Windows WinForms TreeView control doesn't natively indicate read-only mode for nodes. Thus Wix# visually indicates read-only mode with grayed out checkbox and text. However some target systems may not fully support advanced custom rendering (owner draw) for TreeView. Thus Wix# may disable graying out of read only check boxes if it detects that the system is rendering incompatible. Alternatively you can disable advanced rendering unconditionally with project.MinimalCustomDrawing = true. This limitation does not affect feature node text, which will be grayed out always for all read-only nodes.

Read more in this discussion: https://wixsharp.codeplex.com/discussions/656266

Last edited Aug 24, 2016 at 1:34 AM by oleg_s, version 113