Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic custom UI sequence #1218

Closed
serezlan opened this issue Oct 14, 2022 · 8 comments
Closed

Dynamic custom UI sequence #1218

serezlan opened this issue Oct 14, 2022 · 8 comments

Comments

@serezlan
Copy link

Hi oleg-,

Is there a way to set UI sequence dynamically?
My use case is when user is doing fresh install then custom page is shown requesting additional info.
If user is upgrading then skip custom page.

I'm able to inject custom page but do not know how to do it dynamically.

Thx.

@oleg-shilo
Copy link
Owner

Yes, and it is in fact very easy.
Create, your project from "Custom UI" template (either WinForm or WPF) and then in every dialog you will be able to decide to show or to skip it.
When you create the project investigate the source code of the dialog and it will be obvious how to do it. If not then com back here :)

@serezlan
Copy link
Author

I've experimenting with sample in Custom_UI folder and could not figure out how to do this.
Could you give me some asistance?

Anyway.. just to be clear when I said upgrading, I meant when we install with newer build version.

Thx

@Zerpico
Copy link

Zerpico commented Oct 18, 2022

You can detect if a product is installed via an upgrade code

public class MsiHelper
{

private const uint ERROR_SUCCESS = 0;

private static class MsiWinApi
{
    [DllImport("msi.dll", CharSet = CharSet.Auto, SetLastError = true)]
    internal static extern uint MsiEnumRelatedProducts(string strUpgradeCode, int reserved, int iIndex, StringBuilder sbProductCode);

    [DllImport("msi.dll", ExactSpelling = true)]
    internal static extern IntPtr MsiGetActiveDatabase(IntPtr hInstall);

    [DllImport("msi.dll", CharSet = CharSet.Unicode)]
    internal static extern InstallState MsiGetComponentPath(string szProduct, string szComponent, [Out] StringBuilder lpPathBuf, ref int pcchBuf);

    [DllImport("msi.dll", CharSet = CharSet.Unicode)]
    internal static extern Int32 MsiGetProductInfo(string product, string property, [Out] StringBuilder valueBuf, ref Int32 len);
}

public static bool DetectInstallByUpgradeCode()
{
    try
    {
        string upgradeCode = _session["UpgradeCode"];

        //found related productCode for installation version
        string productCode = GetRelatedProductCode(upgradeCode);
		return !string.IsNullOrEmpty(productCode)
    }
    catch (Exception ex)
    {
        _session.LogMessage(ex.ToString());
    }
    return false;
}

private static string GetRelatedProductCode(string upgradeCode)
{
    StringBuilder productCodeBuffer = new StringBuilder(38);
    uint err = MsiWinApi.MsiEnumRelatedProducts(upgradeCode, 0, 0, productCodeBuffer);
    if (err != ERROR_SUCCESS) throw new ArgumentException($"Can't found related products for UpgradeCode: {upgradeCode}");
    return productCodeBuffer.ToString();
}

}

@serezlan
Copy link
Author

Hi @Zerpico ,

Thx for your reply.
I'm able to detect if product already installed. The problem is how to adjust page sequence when such event happened.

Here's my use case:

  • Fresh install
    Welcome Dialog > License Agreement > Custom page > Install Dir > Verify

  • Update
    Welcomd Dialog > License Agreement > Verify

AFAIK, this sequence is set prior to BuildMSI() and I want to know how to change it dynamically.

Thx

@Zerpico
Copy link

Zerpico commented Oct 19, 2022

@serezlan
I don't know how exactly you made the project. But for a similar situation, I made one customDialog:

//dialogs for install
project.ManagedUI.InstallDialogs
	.Add(WixSharp.Forms.Dialogs.Welcome)
	.Add(WixSharp.Forms.Dialogs.Licence)
	.Add<CustomDialogs.MyCustomDialog>()
	.Add(WixSharp.Forms.Dialogs.InstallDir)                
	.Add(WixSharp.Forms.Dialogs.Progress)
	.Add(WixSharp.Forms.Dialogs.Exit);

//dialogs for modify and uninstall
project.ManagedUI.ModifyDialogs
	.Add(WixSharp.Forms.Dialogs.MaintenanceType)
	.Add(WixSharp.Forms.Dialogs.Features)
	.Add(WixSharp.Forms.Dialogs.Progress)
	.Add(WixSharp.Forms.Dialogs.Exit);

You can create MyCustomDialog and when initializing the form, detect if product already installed and display or skip it

public partial class MyCustomDialog : ManagedForm, IManagedDialog
{
    public MyCustomDialog()
    {
        InitializeComponent();
	this.Load += new System.EventHandler(this.MyCustomDialog_Load);
    }

    void MyCustomDialog_Load(object sender, EventArgs e)
    {
        banner.Image = Runtime.Session.GetResourceBitmap("WixUI_Bmp_Banner");
        ResetLayout();

        if (IsInstalledProduct())
        {
            Shell.GoNext();
        }        
    }
//...
}

But there is one problem with this solution. If you go to the previous step with the InstallDir step, you will open MyCustomDialog and it can immediately execute Shell.GoNext() if it detects that the product is installed

@oleg-shilo
Copy link
Owner

@Zerpico is right, the key algorithm is to detect if you want to skip the dialog in the OnLoad event handler. And if you do then just call Shell.GoNext.

BTW you already have all information about prv version in the event argument:

static void project_Load(SetupEventArgs e)
{
    if (e.IsUpgradingInstalledVersion)
    {
         . . . 
    }

Or in the dialog

static class DialogsHistory
{
    public static Type LastDialog; 
}
... 
public MyCustomDialog()
{
    DialogsHistory.LastDialog = this.GetType(); // should implement it in "License Agreement" and "InstallDir" dialogs too
    InitializeComponent();

    this.Load += new System.EventHandler(this.MyCustomDialog_Load);
}

void MyCustomDialog_Load(object sender, EventArgs e)
{
        banner.Image = Runtime.Session.GetResourceBitmap("WixUI_Bmp_Banner");
        ResetLayout();

        if (this.MsiRuntime.MsiSession.IsUpgradingInstalledVersion())
        {
            if(DialogsHistory.LastDialog = typeof(InstallDir))
                Shell.GoPrev();
            else
                Shell.GoNext();
        }        
    }

As for that problem @Zerpico mentioned, it is enough for you to know what is you prev screen was and you can either call Shell.GoNext() or Shell.GoPrev()

@oleg-shilo
Copy link
Owner

BTW you don't have to do skipping from the dialog. You can use OnCurrentDialogChanged.
This is probably even a simpler solution:

    Type LastDialog; 

    static void Project_UILoaded(SetupEventArgs e)
    {
        e.ManagedUI.OnCurrentDialogChanged += IManagedDialog obj) =>
        {
            var prevDialog =  LastDialog;
            LastDialog = obj.GetType();
             
            if (obj.GetType() == typeof(MyCustomDialog)
            {
                if (prevDialog == typeof(InstallDir))
                    obj.Shell.GoPrev();
                else
                    obj.Shell.GoNext();
            }
        };
    }

@serezlan
Copy link
Author

Hi @oleg-shilo ,

After reading your comment, I searched and found one sample with similar code in wixsharp sample and it works great.

Thank you.

oleg-shilo added a commit that referenced this issue Jan 6, 2023
- Issue #1244: The directory Id generated can be too long
- Issue #1223: Non LegacyDummyDirAlgorithm creates C:\ProgramFilesFolder empty folder
- Issue #1220: ElevatedManagedAction issue
- Feature #1204: Feature - RegisterCom class to ease the registration of COM files
- Issue #1203: SilentBootstrapperApplication
- Issue #182 (extended solution): RegistrySearch has "Win64=no" when building x64 installers
- Added Self-executable_Msi sample
- Added WixBootstrapper_EmbeddedUI to demonstrate how to show managed UI if the bundled MSI
- Added sample for customization of the stock Burn UI. Triggered by #1219
- Added sample for "Issue #1218: Dynamic custom UI sequence"
- Resurrected setting user input from BA UI and passing it to the msi. RegistrySearch input is also retained.
- Added validation for `Issue #1075: [FEAT] Add error if LaunchApplicationFromExitDialog using in common Project` error.
- Fixed problem with RegKey being placed in the x86 root XML dir for the x64 project
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants