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

Property conditional installation with bootstrapper #1746

Open
Rain36522 opened this issue Feb 13, 2025 · 9 comments
Open

Property conditional installation with bootstrapper #1746

Rain36522 opened this issue Feb 13, 2025 · 9 comments

Comments

@Rain36522
Copy link

Rain36522 commented Feb 13, 2025

Hi,

Thanks for this project.

I want to perform a conditional installation using File condition with a property inside a custom UI managed by a bootstrapper application. Is it possible to do that? If not, how could I achieve it?

Can I store some data inside the property at the build step and retrieve this data at the beginning of the installer application? If not, how can I pass some data from the build step to the execution step without hardcoding the information?

This program is a test. In reality, I have 500 files with different installation conditions for each file, depending on 10 properties.

Main program

    public class Program
    {
        static void Main()
        {
            WixTools.SetWixVersion(Environment.CurrentDirectory, "4.0.6");

            var productMsi = BuildMsi();

            var bootstrapper =
                new Bundle(
                    "MyProduct",
                    new MsiPackage(productMsi) { Id = "msiPackage", DisplayInternalUI = true });

            bootstrapper.Version = new Version("0.0.0.1");
            bootstrapper.UpgradeCode = new Guid("62804baa-f39b-4678-853b-88068d9c5f5e");
            bootstrapper.Application = new ManagedBootstrapperApplication("%this%");

            bootstrapper.Application.Payloads = GetAllDll();

            bootstrapper.Build(@"MyProduct.exe");
        }

        static string BuildMsi()
        {
            var project = new ManagedProject(
                "MyProduct",
                new Dir(
                    @"C:\ProjDotNet\Asile\output",
                    new File(@"Program.cs") { Condition = new Condition("MY_PROPERTY='A' AND MY_PROPERTY2='B'") }));

            project.GUID = new Guid("62804baa-f39b-4678-853b-88068d9c5f5e");
            project.Properties = new[] { new Property("MY_PROPERTY", "A"), new Property("MY_PROPERTY2", "B") };

            return project.BuildMsi();
        }

Custom bootstrapper application

internal class CustomBA : BootstrapperApplication
{
    private IBootstrapperCommand _bootstrapperCommand;

    public CustomBA(IEngine engine, IBootstrapperCommand bootstrapperCommand)
        : base(engine) =>
        _bootstrapperCommand = bootstrapperCommand;

    public IEngine Engine => engine;

    /// <inheritdoc />
    protected override void Run()
    {
        try
        {
            var view = new Window1(engine, _bootstrapperCommand);
            view.ShowDialog();
            engine.Quit(0);
        }
        catch (Exception ex)
        {
            engine.Log(LogLevel.Error, $"< ============= Bootstrapper error ========== >");
            engine.Log(LogLevel.Error, ex.ToString());
            engine.Log(LogLevel.Error, $"< ============= Bootstrapper error ========== >");
            engine.Quit(1);
        }
    }
}

View execution

public partial class Window1 : Window
{
    private readonly IEngine _engine;
    private readonly IBootstrapperCommand _bootstrapperCommand;

    public Window1(IEngine engine, IBootstrapperCommand command)
    {
        InitializeComponent();
        _engine = engine;
        _bootstrapperCommand = command;
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        // ACCESS TO PROPERTY
        try
        {
            label.Text = _engine.GetVariableString("MY_PROPERTY");
        }
        catch (Exception exception)
        {
            Console.WriteLine(exception);
        }
    }
}
@Torchok19081986
Copy link

morning, No, you can't directly use a property set in the bootstrapper (managed UI) for file conditions in the MSI because the MSI session and bootstrapper session are separate. The MSI only knows properties passed to it at launch.
Pass the properties from the bootstrapper to the MSI by using the MsiProperty element inside the Bundle definition in WixSharp:

bootstrapper.MsiProperties = new[] { "MY_CUSTOM_PROPERTY=[BootstrapperApplicationData]" }; 

Alterative

project.Properties = new[] { new Property("MY_PROPERTY", "StoredValue") };

Then, retrieve it inside your MSI:

session["MY_PROPERTY"];

For your case (500 files and 10 properties), you might consider dynamically generating the installation conditions in the build step, storing them in an external file, and having the bootstrapper read and set the properties before launching the MSI.

It not trivial Task as well. I dont tested it, may this helps you.

@Rain36522
Copy link
Author

Thank you for your quick response. If I understand correctly, when the bootstrapper initializes the MSI, it can pass the properties stored in the bootstrapper to the MSI?

My project is for an installer on different machines. I have already created a script that dynamically includes the files in the installer with the conditions.

The conditions are defined during the execution of the installer based on the detected type of machine and the machine's version.

@Torchok19081986
Copy link

If I understand correctly, when the bootstrapper initializes the MSI, it can pass the properties stored in the bootstrapper to the MSI?

Yes. BUT is one way , from BA to MSI, not backwards. You cant reflect msi to BA back.

@Rain36522
Copy link
Author

Okay! 🙃

So how can I get some information (like different machine configurations included in the MSI) inside the bootstrapper? Is it possible to use a JSON file, generated during the build process, included in the EXE, and read the file during the installation directly from the EXE?

@Torchok19081986
Copy link

Torchok19081986 commented Feb 13, 2025

MSI has custom Action where you can do almost everthing in C# code. Remember whole process is controlling by msiexec aka windows installer not BA . BA is ui over msi, because msi has limited or almost nothing , support for multiple instances or installers msi packages. Here comes BA with payloads and all addtionally stuff. BA tunneling all data from exe to msi and msi DO installjob, not exe.
JSON File you can try, but i you have to initialize json from Newtonsoft.Json or System.Text.JSon packages / DLLs as part of your installer in exe, otherwise installer cant find dll to do json-job. Error : source cant be found. MSI do dllimport from GAC and json package it there not present.

@oleg-shilo
Copy link
Owner

Mind you System.Text.Json belongs to the .NET Core family but Bootstrapped BA is a .NET Framework application so you will have difficulties adding this package.

@Rain36522
Copy link
Author

Thanks, guys, for your fast response!

I finally managed to set properties inside the MSI from the custom Bootstrapper Application.

Here is the updated part of the code:

On bootstrapper build

setup.cs

            var bootstrapper =
                new Bundle(
                    "MyProduct2",
                    new MsiPackage(productMsi)
                    {
                        Id = "msiPackage",
                        DisplayInternalUI = true,
                        MsiProperties =
                            "MY_PROPERTY=[MY_PROPERTY];MY_PROPERTY2=[MY_PROPERTY2]"
                    });

During execution

MainWindow.xaml.cs

            bootstrapper.Engine.SetVariableString("MY_PROPERTY", "A", false);
            bootstrapper.Engine.SetVariableString("MY_PROPERTY2", "Hello world", false);

Regarding the JSON file, it was just a suggestion for passing a simple config file from the build part to the execution part. I could also use a simple text file, ...

@Rain36522
Copy link
Author

Rain36522 commented Feb 13, 2025

One last question before closing the issue.

I'm having trouble passing the configuration file from the build phase to the execution phase. Do you have any recommendations on how to accomplish this?

Is it possible to set a property during the build that I can access during execution using bootstrapper.Engine.GetVariableString("PROPERTY_NAME")?

Alternatively, how can I include my file during the build process and access it during installation without actually installing it?

@Torchok19081986
Copy link

Torchok19081986 commented Feb 13, 2025

you can try to add your file as ressource and write it as byte[] stream to temp file at starttime.
Go to Ressouce in your visual studio project and add it as binary. Bootstrapper has many events, see here if you can write this file from BA as embdeded ressource to temp as byte[] or stream. Later you can access it from temp. i done in msi many times, works like charm.

example code

 string tempFilePath = Path.Combine(Path.GetTempPath(), "config.xml");
        string resourceName = "Yournamespace.Filename.xml";

        // Read Stream from embeded Ressource
        using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
        {
            if (stream == null)
            {
                return;
            }

            // Write in Temp-Path
            using (FileStream fileStream = new FileStream(tempFilePath, FileMode.Create, FileAccess.Write))
            {
                stream.CopyTo(fileStream);
            }
        }

you can also add line into Bootstrapper Application

bootstrapper.MsiProperties = new[] { "CONFIG_FILE=" + tempFilePath };

in msi later read file with in your step.

 session["CONFIG_FILE"]  = whatever you want. 

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