Bundling an unknown number of MSIs into a bootstrapper

Nov 5, 2015 at 4:06 PM
Edited Nov 6, 2015 at 11:14 AM
Hi there

I've been trying a more complicated use case whereby I have an unknown number of MSIs that I want to bundle into one bootstrapper. So far I have attempted the following (edit for brevity):
    static void Main()
    {
        //Get list of projects to build from app.config
        msiFiles = Dictionary<string, string> from config;
    
        ChainItem[] chainItems = { };
    
        //Loop through list and build each MSI - this works
        foreach(var kvp in msiFiles)
        {
            chainItems.Add(new MsiPackage(CreateMsiFile(kvp)));
        }
    
        BuildBoostrapper(chainItems);
    }

    //This method works fine.
    static string CreateMsiFile(KeyValuePair<string>, <string> projectInfo)
    {
        ...
        ...
        ...
    }
       
    static void BuildBoostrapper(ChainItem[] msiFiles)
    {
        var bootstrapper = 
            new Bundle("MyBundle", msiFiles)
            {
                Version = new Version(),
                UpgradeCode = new Guid("MYGUID"),
                OutDir = "Path\To\Dir",
                Manufacturer = "MyCorp",
                Compressed = true,
                OutFileName = "Mysetup.exe"
            };
    
        bootstrapper.Build();
    }
Trouble is, I get "A Chain element must have at least one child element of type MsiPackage, ExePackage, or PackageGroupRef." error on build. Can I not pass an array of ChainItem as I am doing to the new Bundle? If not, how can I include a variable number of MSI's in the bootstrapper without hard coding them in?

Many thanks

Steve
Coordinator
Nov 6, 2015 at 12:49 AM
Edited Nov 6, 2015 at 8:07 AM
Passing the array of ChaiItem to the constructor is the correct way to do this.
The constructor doesn't actually do much:
public Bundle(string name, params ChainItem[] items)
{
    WixExtensions.Add("WiXNetFxExtension");
    WixExtensions.Add("WiXBalExtension");
    Name = name;
    Chain.AddRange(items);
}
Anyway, I just modified the Samples\Bootsrapper\WixBootsrapper sample as below and it works just fine:
ChainItem[] msiFiles  = new ChainItem[] { new MsiPackage(crtMsi) { DisplayInternalUI = true },
                                          new MsiPackage(productMsi) { DisplayInternalUI = true }};

var bootstrapper = new Bundle("MyBundle", msiFiles)
                       {
                           Version = new Version(),
                           UpgradeCode = new Guid("6f330b47-2577-43ad-9095-1861bb25889b"),
                           Manufacturer = "MyCorp",
                           Compressed = true,
                           OutFileName = "Mysetup"
                       };
Ensure you set bootstrapper.PreserveTempFiles = true; before calling Build() and it will preserve the generated wxs file. The clue to your problem will be there.
Nov 6, 2015 at 11:12 AM
Good to know I'm not going mad, I was certain what I was doing was right. However, changing the sample app as above did not work for me, but I have solved it.
I'm not really sure why, but using
    ChainItem[] items = { };
and pasing that ended up empty, but using
    var chainItems = new List<ChainItem> {new PackageGroupRef("NetFx40Web")};
    chainItems.AddRange(msiFiles.Select(kvp => new MsiPackage(CreateMsiFile(kvp))));

     //Create the bootstrapper
     BuildBootstrapper(chainItems.ToArray());
That worked like a charm. In the end I referenced your source directly instead of the NuGet packages, added debug statements and worked backwards to solve it, and I can now send a number of MSIs to the bootstrapper which is only known at run time.

Just as an FYI, chaning the sample as you did does not work directly - you get an argument exception, but it's the same as you'd get if you called
    var chainItems[] chainItems = { new MsiPackage("package1"), new MsiPackage("package2") };
    var bootstrapper = new Bundle("MyProject", new PackageGroupRef("NetFx40Web"), chainItems);
Many thanks for the help again

Steve
Coordinator
Nov 6, 2015 at 11:45 AM
Hi Steve. I'm glad you solved it.

The sample I gave you should work. I tested the change before suggesting it to you.

You probably didn't notice that that in the code in my post I didn't include PackageGroupRef. Thus my code was
//should work
var bootstrapper = new Bundle("MyBundle", msiFiles) 
but not
//shouldn't work
var bootstrapper = new Bundle("MyBundle", new PackageGroupRef("NetFx40Web"), msiFiles)
Though by skipping the PackageGroupRef I actually defined a bundle that is slightly different to the unmodified sample. Fortunatelly it didn't stop you from solving the problem. :)

Cheers,
Oleg