Skip to content

Tips'n'Tricks

Oleg Shilo edited this page Mar 6, 2024 · 8 revisions

How to make the Winform Dialogs look "Nice" (Not Fuzzy) for high res scaled monitors

    public class ManagedBa : BootstrapperApplication
    {
        [System.Runtime.InteropServices.DllImport("user32.dll")]
        private static extern bool SetProcessDPIAware();

        protected override void OnStartup(StartupEventArgs args)
        {
            SetProcessDPIAware();   // https://stackoverflow.com/a/44839778
            Debug.Assert(false);
            base.OnStartup(args);
        }

Performance tips

Dedicated wiki page: https://github.com/oleg-shilo/wixsharp/issues/1173

Debugging

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

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

Note, when attaching the debugger you need to ensure that you have your Visual Studio is running elevated. Otherwise, your VS debugger you may not be able to attach. When attaching, you will need to attach to the msiexec.exe process if you are debugging Managed UI and to rundll32.exe process if you are debugging a custom action.

Note: Normally you will have two msiexec.exe processes: one is the system Windows Installer service and another one is the active MSI session.

image

Checking prerequisite conditions as soon as possible.

Some of the checking can be done by native MSI mechanisms (e.g. LaunchCondition). However, you do not have much control over when the condition is evaluated if you want it to be done before the UI is displayed. You also have rather limited control over CPU architecture context. Thus implementing the condition checking with C# seems like a more versatile option.

It is also important to recognize that you will need to handle both types of installation: with and without UI. It seems to me that your task can be solved in a much easier way than it seems.

For "With UI" scenario place your C# routine in your earliest event which is UIInitialized event. For "Without UI" scenario place your C# reading routine in your earliest event that is Loaded event.

If the target system does not meet the prerequisite conditions one of these events will detect it and prevent further installation. If the target system does meet the prerequisite conditions then both events will do the check and happily pass the execution flow further to the chain.

You can read more about event-driven WixSharp session architecture here. And this is the diagram that can help you to understand the order of the events:

image

Note, if you are using MSI native UI (or a third-party EmbeddedUI) you will not have WixSharp UI events UIInitialized and UILoaded fired. In this case the only way to implement a universal generic checking is to use a bootstrapper with a single item (your msi) and the custom BA where you do your checking.

While it's possible (e.g. MultiLanguageSupport sample) it seems like a very over-engineered solution for such a simple task. Thus you may want to consider a very simple functional equivalent but without a hefty price tag - Self-executable_Msi. It does the same thing but for fraction of the cost.

Showing MSI internal UI from bootstrapper application

If you are using stock BA application(s) then displaying MSI UI is done by setting the MSI package DisplayInternalUI and using an appropriate BA application:

Show both standard BA and MSI UI:

new Bundle("MyProduct",
    . . .
    new MsiPackage(productMsi) { DisplayInternalUI = true });

bundle.Application = new LicenseBootstrapperApplication(. . .);

Show MSI UI but hide standard BA UI:

new Bundle("MyProduct",
    . . .
    new MsiPackage(productMsi) { . . .});

bundle.Application = new WixInternalUIBootstrapperApplication { . . . };

Show custom BA UI and MSI UI:

new Bundle("MyProduct",
    . . .
    new MsiPackage(productMsi) 
    { 
        Id = "MyProductPackageId"
    });

bootstrapper.Application = new ManagedBootstrapperApplication("%this%") { . . . };

. . . 
public class ManagedBA : mba.BootstrapperApplication
{
    public ManagedBA(mba.IEngine engine, mba.IBootstrapperCommand command) : base(engine)
    {
        this.PlanMsiPackage += (s, e) =>
        {
            if (e.PackageId == "MyProductPackageId")
                e.UiLevel = e.Action == ActionState.Uninstall ?
                                INSTALLUILEVEL.ProgressOnly :
                                INSTALLUILEVEL.Full;
        };

        this.Command = command;
    }