Creating a Wix# Installer That Includes Prerequisites Part 1

Creating an MSI Installer with Wix#

Introduction

In part 1, we are going to look at going from Console App to MSI using Wix# and getting the program installed and capable of being upgraded/uninstalled.

The parts of this series are:

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

Background

Wix# (GitHub) is a programmatic way to create Windows installers using the power and flexibility of the WiX Toolset. I found this to be the easiest way to make the installer editable and maintainable by a group of developers with very little knowledge of the WiX Toolset itself. The main benefit of Wix# over raw WiX is the approachability. You do not have to be a WiX Toolset expert to use Wix# effectively.

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 or another CLR language will have similar principals but will require some translation)
  4. You are using the WixSharp Project Templates Extension for Visual Studio

Getting Started


using System;
using WixSharp;

class Script
{
    static public void Main(string[] args)
    {
        var project = new Project("MyProduct",
                          new Dir(@"%ProgramFiles%\\My Company\\My Product",
                              new File(@"Files\\Docs\\Manual.txt"),
                              new File(@"Files\\Bin\\MyApp.exe")));

        project.GUID = new Guid("6f330b47-2577-43ad-9095-1861ba25889b");

        Compiler.BuildMsi(project);
    }
}

As you can see, the Project gets a name and then a Directory is defined and which files should be copied to it. Then a GUID is added (you need to replace this). And lastly, it builds the MSI. Building this project now adds a "wix" folder to our project and dumps the resulting WiX file into it.

<?xml version="1.0" encoding="utf-8"?>
<!--
<auto-generated>
    This code was generated by WixSharp.
    Changes to this file will be lost if the code is regenerated.
</auto-generated>
-->
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
  <Product Id="6fe30b47-2577-43ad-9095-1861ca25889c" Name="MyProduct" 
   Language="1033" Codepage="Windows-1252" Version="1.0.0.0" 
   UpgradeCode="6fe30b47-2577-43ad-9095-1861ba25889b" Manufacturer="ccarter">
    <Package InstallerVersion="200" Compressed="yes" 
    SummaryCodepage="Windows-1252" Languages="1033" />
    <Media Id="1" Cabinet="MyProduct.cab" EmbedCab="yes" />

    <Directory Id="TARGETDIR" Name="SourceDir">
      <Directory Id="ProgramFilesFolder" Name="ProgramFilesFolder">
        <Directory Id="ProgramFilesFolder.My\_Company" Name="My Company">
          <Directory Id="INSTALLDIR" Name="My Product">

            <Component Id="Component.Program.cs\_2096188551" 
            Guid="6fe30b47-2577-43ad-9095-1861e8d77bb1">
              <File Id="Program.cs\_2096188551" Source="Program.cs" />
            </Component>

          </Directory>
        </Directory>
      </Directory>
    </Directory>

    <UIRef Id="WixUI\_Minimal" />
    <UIRef Id="WixUI\_ErrorProgressText" />

    <Feature Id="Complete" Title="Complete" Absent="allow" Level="1">
      <ComponentRef Id="Component.Program.cs\_2096188551" />
    </Feature>

  </Product>
</Wix>

And we also find a "MyProduct.msi" is output to the project directory.

So besides the Wix# sample being more succinct, why else would we prefer Wix# over raw Wix. Well..

var TestDir = new Dir(@"%ProgramFiles%\\My Company\\My Product");

TestDir.Files = System.IO.Directory.GetFiles
(@"\\path\\to\\solution\\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);

which produces this:

<?xml version="1.0" encoding="utf-8"?>
<!--
<auto-generated>
    This code was generated by WixSharp.
    Changes to this file will be lost if the code is regenerated.
</auto-generated>
-->
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
  <Product Id="6fe30b47-2577-43ad-9095-1861ca25889c" Name="MyProduct" 
   Language="1033" Codepage="Windows-1252" Version="1.0.0.0" 
   UpgradeCode="6fe30b47-2577-43ad-9095-1861ba25889b" Manufacturer="ccarter">
    <Package InstallerVersion="200" Compressed="yes" 
    SummaryCodepage="Windows-1252" Languages="1033" />
    <Media Id="1" Cabinet="MyProduct.cab" EmbedCab="yes" />

    <Directory Id="TARGETDIR" Name="SourceDir">
      <Directory Id="INSTALLDIR" Name="ProgramFilesFolder">

        <Component Id="Component.Editor.dll\_374746071" 
        Guid="6fe30b47-2577-43ad-9095-18613d90149a">
          <File Id="Editor.dll\_374746071" 
          Source="..\\SampleApp\\bin\\Release\\Editor.dll" />
        </Component>

        <Component Id="Component.SampleApp.dll\_1981177689" 
        Guid="6fe30b47-2577-43ad-9095-1861c6f85d24">
          <File Id="SampleApp.dll\_1981177689" 
          Source="..\\SampleApp\\bin\\Release\\SampleApp.dll" />
        </Component>

        <Component Id="Component.SampleApp.exe\_60174109" 
        Guid="6fe30b47-2577-43ad-9095-1861a74d0c84">
          <File Id="SampleApp.exe\_60174109" 
          Source="..\\SampleApp\\bin\\Release\\SampleApp.exe" />
        </Component>

        <Component Id="Component.SampleAppEditor.exe\_729600358" 
         Guid="6fe30b47-2577-43ad-9095-18610c59b6e9">
          <File Id="SampleAppEditor.exe\_729600358" 
           Source="..\\SampleApp\\bin\\Release\\SampleAppEditor.exe" />
        </Component>

        <Component Id="Component.Sugar.dll\_2062009301" 
        Guid="6fe30b47-2577-43ad-9095-18616d4e4353">
          <File Id="Sugar.dll\_2062009301" 
          Source="..\\SampleApp\\bin\\Release\\Sugar.dll" />
        </Component>

        <Component Id="Component.SugarShaker.exe\_1177907971" 
         Guid="6fe30b47-2577-43ad-9095-1861746701f0">
          <File Id="SugarShaker.exe\_1177907971" 
          Source="..\\SampleApp\\bin\\Release\\SugarShaker.exe" />
        </Component>

        <Directory Id="INSTALLDIR.My\_Company" Name="My Company">
          <Directory Id="INSTALLDIR.My\_Company.My\_Product" Name="My Product">

            <Component Id="My\_Product.EmptyDirectory" 
             Guid="6fe30b47-2577-43ad-9095-18611cb1741a" KeyPath="yes">
              <CreateFolder />
              <RemoveFolder Id="INSTALLDIR.My\_Company.My\_Product" On="uninstall" />
            </Component>

          </Directory>

          <Component Id="INSTALLDIR.My\_Company" 
           Guid="6fe30b47-2577-43ad-9095-18618b356f3d" KeyPath="yes">
            <CreateFolder />
            <RemoveFolder Id="INSTALLDIR.My\_Company" On="uninstall" />
          </Component>

        </Directory>
      </Directory>

      <Component Id="TARGETDIR" 
      Guid="6fe30b47-2577-43ad-9095-18612df5f80e" KeyPath="yes">
        <CreateFolder />
        <RemoveFolder Id="TARGETDIR" On="uninstall" />
      </Component>

    </Directory>

    <UIRef Id="WixUI\_Minimal" />
    <UIRef Id="WixUI\_ErrorProgressText" />

    <Feature Id="Complete" Title="Complete" Absent="allow" Level="1">
      <ComponentRef Id="Component.Editor.dll\_374746071" />
      <ComponentRef Id="Component.SampleApp.dll\_1981177689" />
      <ComponentRef Id="Component.SampleApp.exe\_60174109" />
      <ComponentRef Id="Component.SampleAppEditor.exe\_729600358" />
      <ComponentRef Id="Component.Sugar.dll\_2062009301" />
      <ComponentRef Id="Component.SugarShaker.exe\_1177907971" />
      <ComponentRef Id="My\_Product.EmptyDirectory" />
      <ComponentRef Id="INSTALLDIR.My\_Company" />
      <ComponentRef Id="TARGETDIR" />
    </Feature>

  </Product>
</Wix>

Using linq, we can make complex rulesets that are more understandable to someone familiar with C# and Linq. The Wix way of doing that does not require the need for individual file entries in the Wix file. but also loses some of the power as we can evaluate the files however we like.

<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" 
 xmlns:fg="http://www.firegiant.com/schemas/v3/wxs/fgwep.xsd"> 
 <Fragment> 
  <ComponentGroup Id="ContentComponents"> 
   <Component Directory="ContentFolder"> 
    <fg:HarvestFolder Source="path\\to\\content\\\*.dll" /> 
   </Component> 
   <Component Directory="ContentFolder">
    <fg:HarvestFolder Source="path\\to\\content\\\*.exe" />
   </Component> 
  </ComponentGroup> 
 </Fragment> 
</Wix>

In Part 2 of this series, we are going to look at installing multiple Projects as separate MSI files, feature sets, and installing the pre-requisite .NET Framework.

Points of Interest

  • Exploring more complicated setups with Wix# often requires referencing Wix tutorials and examples, then looking for an equivalent in Wix#.
  • The Wix# Github repository has a large selection of sample projects that are very useful.

Dealing With Errors

If you get an error on build, like the following:

Severity Code Description Project File Line Suppression State Error The command ""Path\to\Project\WixSharp Setup\bin\Debug\WixSharp Setup.exe" "/MSBUILD:WixSharp Setup"" exited with code -532459699.

Debug "Path\to\Project\WixSharp Setup\bin\Debug\WixSharp Setup.exe" "/MSBUILD:WixSharp Setup". In my case, it ended up being that it was looking for a directory that did not exist to pull files from for the MSI.

History

Did you find this article valuable?

Support Curtis Carter by becoming a sponsor. Any amount is appreciated!