Click here to Skip to main content
15,867,141 members
Articles / Programming Languages / C#
Alternative
Article

Creating a Wix# Installer That Includes Prerequisites - Part 2

Rate me:
Please Sign up or sign in to vote.
4.96/5 (9 votes)
17 Mar 2018CPOL4 min read 36.9K   12   14
This is Part 2 of "Creating a Wix# Installer That Includes Prerequisites"

Introduction

This tutorial picks up where Part 1 left off, but if you have a general understanding of C#, it should not be hard to follow. Today, we are going to look at adding the .NET Framework as a prerequisite of the installer.

The parts of this series are:

  1. Creating an MSI installer with Wix#
  2. Creating an EXE Installer that Bundles Prerequisites (this one)
  3. Creating Custom Actions to Simplify Tasks

Background

Installers are great, but directing a user that they need to look up and install a framework is a lot for an average user. Even directing them to the website where they can download it can be overwhelming for some. With Wix#, it becomes easy to add in a list of packages to install that includes everything the user needs to get started.

The WiX Toolset relies on a tool called Burn (continuing with the Candle theme) to build EXE packages that install prerequisites and combine multiple MSIs. This process is known as BootStrapping.

Assumptions

I am going to assume a few things going forward. If these are not true, your experience may differ.

  1. Visual Studio 2017  is your IDE
  2. WiX Toolset is installed
  3. You are coding in C# (VB.NET will have similar principals but will require some translation)
  4. You are using the WixSharp Project Templates Extension for Visual Studio
  5. You have read part 1 and have a basic MSI installer project using Wix#

Building a Bundle with Wix#

The BootStrapper for the Installers in Wix is referred to as a Bundle and uses a WiX Toolset BurnProject. In the WixSharp Project Templates Extension, a Burn Project is referred to as a WixSharp Setup - BootStrapper. It's easiest to just start making a few changes to our starter project if you worked through Part 1.

Start by adding WixSharp.Bootstrapper as a using statement and then restructure your installer as follows:

C#
private static void Main()
{
    string productMsi = BuildMsi();

    var bootstrapper =
      new Bundle("SampleInstaller",
          new MsiPackage(productMsi) { DisplayInternalUI = true, Compressed = true })
      {
          UpgradeCode = new Guid(<Insert Your Bundle GUID>),
          Version = new Version(1, 0, 0, 0),
      };
    bootstrapper.Build("SampleBootStrapper.exe");
}

private static string BuildMsi()
{
    var TestDir = new Dir(@"%ProgramFiles%\My Company\My Product");
    TestDir.Files = System.IO.Directory.GetFiles
     (@"D:\Checkouts\WixSharp Tutorial\WixSharp Tutorial\SampleApp\bin\Release").Where
     (file => file.EndsWith(".dll") || file.EndsWith(".exe")).Select(file => new File(file)).ToArray();

    var project = new Project("MyProduct", TestDir);

    project.GUID = new Guid(<Insert Your MSI GUID>);

    return project.BuildMsi();
}

As you notice, we are still building the MSI for the installer in essentially the same way, but then we return the path to the freshly built MSI and build a bundle wrapped around it.

Including Prerequisites with Bundle

Now that we have a basic bundle put together, we realize that our application requires .NET 4.7 to be installed and the user may not have it installed. With the PackageRefGroups available through the Wix Toolkit, we can check for and have installed a number of runtimes.

private static void Main()
{
    string productMsi = BuildMsi();

    var bootstrapper =
     new Bundle("SampleInstaller",
       new PackageGroupRef("NetFx462Web"),
       new MsiPackage(productMsi) { DisplayInternalUI = true, Compressed = true })
     {
         UpgradeCode = new Guid(< Insert Your Bundle GUID >),
         Version = new Version(1, 0, 0, 0),
     };

    bootstrapper.IncludeWixExtension(WixExtension.NetFx);
    bootstrapper.Build("SampleBootStrapper.exe");
}

The key lines above are new PackageGroupRef("NetFx462Web") and bootstrapper.IncludeWixExtension(WixExtension.NetFx);

NetFx462Web tells WiX to install .NET 4.62 by bundling in the .NET Web Installer. This keeps your installer size small but requires an internet connection with access to the URL.  You could also use NetFx462Redist which tells WiX to bundle the standalone installer from. WiX also has variables that can be used to check if .NET 4.62 is already installed instead of dumping the user into an installer needlessly.

Packaging .NET 4.7 (or any .exe)

If your application needs 4.7+ versions of the .NET Framework, there is at the time of this writing no support in the NetFx extension from WiX. However, the base WiX tools contain everything needed to accomplish this. As noted under Points of Interest, Microsoft has an in depth guide on how to detect what versions of the framework are installed. But to do this in WiX, we need to add a fragment. This fragment combined with another extension (Util) essentially allows us to store the result of a registry search.

bootstrapper.IncludeWixExtension(WixExtension.Util);

bootstrapper.AddWixFragment("Wix/Bundle",
    new UtilRegistrySearch
    {
        Root = Microsoft.Win32.RegistryHive.LocalMachine,
        Key = @"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full",
        Value = "Release",
        Variable = "NetVersion"
    });

The value found by this result is stored as NetVersion and is used in conditions to determine if we need to install the framework or not. This is a good first step and difficult to translate from direct WiX examples.

private static ExePackage Net471()
        {
            string currentN2t471webinstaller = 
              @"https://download.microsoft.com/download/8/E/2/8E2BDDE7-F06E-44CC-A145-56C6B9BBE5DD/
              NDP471-KB4033344-Web.exe";
            string Net471Installer = "Net471-web.exe";
            using (var client = new WebClient())
            {
                client.DownloadFile(currentN2t471webinstaller, Net471Installer);
            }
            ExePackage Net471exe = new ExePackage(Net471Installer)
            {
                Compressed = true,
                Vital = true,
                Name = "Net471-web.exe",
                DetectCondition = "NetVersion >= 460798",
                PerMachine = true,
                Permanent = true,
            };

            return Net471exe;
        }

Now we have declared a static method that generates an ExePackage for our installer to use. There are a few key things to obtain from this code sample:

  • WebClient is from the System.Net namespace and is used here to download the file at build time. If the link you are using may not always be obtainable, you will want to store it locally and replace this with logic to copy the file into the working directory.
  • Compressed if true causes the executable to be compressed and stored in the resulting installers instead of distributed as a separate file.
  • Vital causes the install to fail and roll back if this EXE fails.
  • DetectCondition is where we use the variable from above. Per Microsoft, 460798 is the min version (depends on OS) that can be .NET 4.7. In this case, my app is built in 4.7 but we are installing 4.71 with the installer to prevent needless framework installs if we decide to upgrade in the near future.

Putting It All Together - Complete Code

using System;
using System.Linq;
using System.Net;
using WixSharp;
using WixSharp.Bootstrapper;

namespace WixSharp_Setup2
{
    class Program
    {
        private static void Main()
        {

            string productMsi = BuildMsi();


            var bootstrapper =
              new Bundle("SampleInstaller",
              Net471(),
                  new MsiPackage(productMsi) { DisplayInternalUI = true, Compressed = true })
              //new PackageGroupRef("NetFx47Web"))
              {
                  UpgradeCode = new Guid(<Insert Your Bundle GUID>),
                  Version = new Version(1, 0, 2, 0),
              };

            bootstrapper.IncludeWixExtension(WixExtension.Util);

            bootstrapper.AddWixFragment("Wix/Bundle",
                new UtilRegistrySearch
                {
                    Root = Microsoft.Win32.RegistryHive.LocalMachine,
                    Key = @"SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full",
                    Value = "Release",
                    Variable = "NetVersion"

                });
            bootstrapper.PreserveTempFiles = true;
            bootstrapper.Application.LicensePath = null;
            bootstrapper.Build("SampleBootStrapper.exe");
        }

        private static string BuildMsi()
        {
            var TestDir = new Dir(@"%ProgramFiles%\My Company\My Product");

            TestDir.Files = System.IO.Directory.GetFiles
            (@"D:\Checkouts\WixSharp Tutorial\WixSharp Tutorial\SampleApp\bin\Release").Where
            (file => file.EndsWith(".dll") || file.EndsWith(".exe")).Select
            (file => new File(file)).ToArray();

            var project = new Project("MyProduct", TestDir);

            project.GUID = new Guid(<Insert Your MSI GUID>);
            //project.SourceBaseDir = "<input dir path>";
            //project.OutDir = "<output dir path>";

            return project.BuildMsi();
        }

        private static ExePackage Net471()
        {
            string currentN2t471webinstaller = 
               @"https://download.microsoft.com/download/8/E/2/8E2BDDE7-F06E-44CC-A145-56C6B9BBE5DD/
               NDP471-KB4033344-Web.exe";
            string Net471Installer = "Net471-web.exe";
            using (var client = new WebClient())
            {
                client.DownloadFile(currentN2t471webinstaller, Net471Installer);
            }
            ExePackage Net471exe = new ExePackage(Net471Installer)
            {
                Compressed = true,
                Vital = true,
                Name = "Net471-web.exe",
                DetectCondition = "NetVersion >= 460798",
                PerMachine = true,
                Permanent = true,
            };

            return Net471exe;
        }
    }
}

Points of Interest

History

  • 3/10/2018: Initial publication

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Tech Friends Inc.
United States United States
Software Developer specializing in Build/Test Automation. Adept Linux User and Cross Platform Developer.

Comments and Discussions

 
QuestionWix setup upgrade to 4.7.2 Pin
Member 1467388530-Nov-19 15:20
Member 1467388530-Nov-19 15:20 
AnswerRe: Wix setup upgrade to 4.7.2 Pin
CodingCoyote28-Jan-20 2:54
professionalCodingCoyote28-Jan-20 2:54 
QuestionFIPS Error- any suggestions ( I cant disable FIPS) Pin
Member 1060092815-May-18 8:20
Member 1060092815-May-18 8:20 
GeneralRe: FIPS Error- any suggestions ( I cant disable FIPS) Pin
CodingCoyote15-May-18 8:24
professionalCodingCoyote15-May-18 8:24 
AnswerRe: FIPS Error- any suggestions ( I cant disable FIPS) Pin
CodingCoyote15-May-18 8:40
professionalCodingCoyote15-May-18 8:40 
Suggestioncreated as an "alternative article"? Pin
John Torjo15-Mar-18 5:16
professionalJohn Torjo15-Mar-18 5:16 
GeneralRe: created as an "alternative article"? Pin
CodingCoyote15-Mar-18 6:32
professionalCodingCoyote15-Mar-18 6:32 
GeneralRe: created as an "alternative article"? Pin
John Torjo15-Mar-18 9:33
professionalJohn Torjo15-Mar-18 9:33 
Questionincluding an EXE package Pin
John Torjo15-Mar-18 5:12
professionalJohn Torjo15-Mar-18 5:12 
AnswerRe: including an EXE package Pin
CodingCoyote15-Mar-18 6:38
professionalCodingCoyote15-Mar-18 6:38 
GeneralRe: including an EXE package Pin
John Torjo15-Mar-18 9:34
professionalJohn Torjo15-Mar-18 9:34 
GeneralMy vote of 5 Pin
Mou_kol12-Mar-18 22:14
Mou_kol12-Mar-18 22:14 
GeneralRe: My vote of 5 Pin
CodingCoyote13-Mar-18 2:17
professionalCodingCoyote13-Mar-18 2:17 
GeneralRe: My vote of 5 Pin
Mou_kol13-Mar-18 4:13
Mou_kol13-Mar-18 4:13 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.