Skip to content

Tips'n'Tricks

Oleg Shilo edited this page Aug 16, 2024 · 14 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;
    }

See an interesting relevant discussion here: https://github.com/oleg-shilo/wixsharp/issues/1554

Azure pipeline build

Based on #1526:

task: DotNetCoreCLI@2
displayName: 'Install wix as a dotnet tool -- dotnet tool install --global wix'
inputs:
command: custom
custom: tool
arguments: 'install --global wix'

How to skip a dialog based on a runtime condition

If you are using managed UI then it's easy. You just need to remove the not needed dialog from the runtime dialog sequence.Just iterate through the all dialogs, find the one you want to skip and remove it.

If you want to do it before UI is displayed then you can do it from the UI Initialized event:

project.UIInitialized += e =>
{
    if (needToSkip)
        e.ManagedUI.Shell.Dialogs.RemoveAt(1); // remove Licence dialog
};

If you want to do it from a specific dialog then you do the same but from the dialog event handler. IE on next button click of a previous dialog

// WPF 
public void GoNext()
{
    if (needToSkip)
        this.shell.Dialogs.RemoveAt(1); // remove Licence dialog
}

// WinForm
void next_Click(object sender, EventArgs e)
{
    if (needToSkip)
        this.Shell.Dialogs.RemoveAt(1); // remove Licence dialog
}

How to sign all WixSharp DLLs

All WixSharp SDK assemblies are already signed. However, any other files included in the msi package may not be as normally it is a developer's responsibility to sign them. This can be assisted with WixSharp though. Thus by setting project.SignAllFiles to true you can trigger signing the files included in the MSI just before assembling the msi file. And you can control some aspects of the signing process (e.g. which files to sign) via Compiler.SignAllFilesOptions settings. See signing sample.

Compiler.SignAllFilesOptions.SkipSignedFiles = true;
...
project.DigitalSignature = new DigitalSignature
                {
                    PfxFilePath = "wixsharp.pfx",
                    Password = "my_password",
                    Description = "MyProduct",
                    TimeUrl = new Uri("http://timestamp.verisign.com/scripts/timstamp.dll")
                }
project.SignAllFiles = true;