Bootstrapper Installer Sample with Embedded .NET 4.0 or 4.5

May 17, 2016 at 10:34 PM
Edited May 18, 2016 at 2:58 PM
It took me far too long to put this together, so I'm sharing it here.

My installer needs to work without access to the internet.

Starting with the WixSharp Setup - Bootstrapper Visual Studio template, here is one working method for building an installer that contains the .NET 4.5 (or 4.0) redist file.
using System;
using System.Xml.Linq;
using WixSharp;
using WixSharp.Bootstrapper;

namespace SetupBootstrapper {
    class Program {
        static void Main() {

            string productMsi = BuildMsi();

            // To make a self contained installer, it isn't enough to switch from
            //    new PackageGroupRef("NetFx40Web")
            // to
            //    new PackageGroupRef("NetFx40Redist")
            // because the installer still won't contain the redistributable file.
            // It will be downloaded when the installer runs.
            //
            // This sample was constructed by taking the values for variables, registry searches and the ExePackage properties from the NetFxExtension source found here:
            // http://wix.codeplex.com/SourceControl/latest#src/ext/NetFxExtension/wixlib/NetFx4.5.wxs
            //
            // You can follow a link like the one in this line to download the actual file (50MB):
            // <?define NetFx45RedistLink = http://go.microsoft.com/fwlink/?LinkId=225702 ?>
            //
            // If you can arrange for the file to be in the "redist" folder next to your installer when it runs,
            // then you can simply stick with new PackageGroupRef("NetFx40Redist"), as it will look there before dowloading
            // from the URL.
            //
            // I couldn't figure out an easy way to arrange for this to happen, so I replicated the Fragment containing "NetFx45Redist".
            // There are three parts to it:
            //   1. The RegistrySearch: Defined below by bootstrapper.AddWixFragment.
            //   2. The ExePackage: Defined below in the Bundle instead of the PackageGroupRef.
            //   3. The WixVariables: Added below in a bootstrapper.WixSourceGenerated event handler.
            //
            // If the variables WixMbaPrereqPackageId and WixMbaPrereqLicenseUrl aren't assigned you'll get two build errors.

            var bootstrapper =
              new Bundle("MyProduct"
                    /* Only .NET 4 is required by the bootstrapper. I needed 4.5 so that's enabled in this sample.
                          , new ExePackage {
                            InstallCommand="/q /norestart ", // /ChainingPackage \"[WixBundleName]\"",
                            RepairCommand="/q /norestart /repair", //  /ChainingPackage \"[WixBundleName]\"",
                            UninstallCommand="/uninstall /q /norestart", //  /ChainingPackage \"[WixBundleName]\"",
                            PerMachine=true,
                            DetectCondition="MYNETFRAMEWORK40",
                            Id="MyNetFx40Redist",
                            Vital=true,
                            Permanent=true,
                            //Protocol="netfx4",
                            SourceFile = @"redist\dotNetFx40_Full_x86_x64.exe",
                            Compressed=true,
                            Name=@"redist\dotNetFx40_Full_x86_x64.exe"
                          }
                          */
                    , new ExePackage {
                        InstallCommand = "/q /norestart ",
                        RepairCommand = "/q /norestart /repair",
                        UninstallCommand = "/uninstall /q /norestart",
                        PerMachine = true,
                        DetectCondition = "MYNETFRAMEWORK45 >= 378389",
                        Id = "MyNetFx45Redist",
                        Vital = true,
                        Permanent = true,
                        //Protocol="netfx4",
                        SourceFile = @"redist\dotnetfx45_full_x86_x64.exe",
                        Compressed = true,
                        Name = @"redist\dotnetfx45_full_x86_x64.exe"
                    }
                    , new MsiPackage(productMsi) { DisplayInternalUI = true });

            bootstrapper.AddWixFragment("Wix/Bundle"
                , new UtilRegistrySearch {
                    Root = Microsoft.Win32.RegistryHive.LocalMachine,
                    Key = @"SOFTWARE\Microsoft\Net Framework Setup\NDP\v4\Full",
                    Value = "Install",
                    Result = SearchResult.value,
                    Variable = "MYNETFRAMEWORK40",
                }
                , new UtilRegistrySearch {
                    Root = Microsoft.Win32.RegistryHive.LocalMachine,
                    Key = @"SOFTWARE\Microsoft\Net Framework Setup\NDP\v4\Full",
                    Value = "Release",
                    Result = SearchResult.value,
                    Variable = "MYNETFRAMEWORK45",
                }
            );

            bootstrapper.IncludeWixExtension(WixExtension.Util);
            bootstrapper.Version = new Version("1.0.0.0");
            bootstrapper.UpgradeCode = new Guid("6f330b47-2577-43ad-9095-1861bb25844b");
            bootstrapper.Application = new SilentBootstrapperApplication();

            bootstrapper.PreserveTempFiles = true;

            bootstrapper.WixSourceGenerated += document => {
                var root = document.Root;
                root.Select("Bundle").AddElement("WixVariable", @"Id=WixMbaPrereqPackageId;Value=MyNetFx45Redist");
                root.Select("Bundle").AddElement("WixVariable", @"Id=WixMbaPrereqLicenseUrl;Value=http://go.microsoft.com/fwlink/?LinkID=260867");
            };

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

        static string BuildMsi() {
            var project = new Project("MyProduct",
                             new Dir(@"%ProgramFiles%\My Company\My Product",
                                 new File("Program.cs")));

            project.GUID = new Guid("6fe30b47-2577-43ad-9095-1861ba25889b");
            //project.SourceBaseDir = "<input dir path>";
            //project.OutDir = "<output dir path>";

            return project.BuildMsi();
        }
    }
}
Coordinator
May 18, 2016 at 4:13 AM
Thank you for sharing.

Since you are becoming a more regular Wiki contributer :) you may like to know that you can even further improve your code samples by using 'C#' tag, which triggers C# syntax highlighting:

Image
May 18, 2016 at 2:59 PM
Much nicer :-)
May 18, 2016 at 3:01 PM
Is there a way to add a binary file to a Bundle or Bootstrapper and extract it to a local "redist" folder prior to starting on the Chain items?
Coordinator
May 19, 2016 at 1:53 AM
I don't think so. You can do this (add binary) for msi but I don't know any similar technique for bundle.

Though if you are using managed Bootstrapper application (Custom BA) then you can just embed your binary into the application resources and extract them at startup.