Skip to main content

Generating a MSI Installer for a WPF Application

This post serves as a tutorial for creating a MSI installer for your WPF Applications in Visual Studio. For this we use WiX (recommended by Microsoft).

Prerequisites

I assume you already have a Visual Studio solution containing a WPF Project. And I assume you have some experience with Visual Studio and XML. You do not need any prior knowledge of WiX.

Step 1: Install Wix

We are going to need WiX to generate our MSI.

If you don't already have Wix installed, go ahead and download it from here. You'll be redirected to the relevant GitHub releases page where you can download the .exe file to install WiX.

For example, on this page you can download WiX version 3.11 by downloading and running the 'wix311.exe' file.




When the .exe is running you should see some tiles. Select the 'Install' option. Once installed it will look something like this:











Step 2: Install Wix Visual Studio Extension

With Wix you can use the Command Line to generate your MSI, but instead we'll be creating a project in Visual Studio. To do this we first need the WiX Visual Studio Extension installed.

For Visual Studio 2019 you can download the extension from the marketplace here.

If you are using a different version of Visual Studio then you can find links to download the extensions from the WiX downloads page.

Step 3: Add a Wix Installer Project to your Solution

Now open your solution in Visual Studio and right click on the solution in the Solution Explorer. Select 'Add' -> 'New Project'




In the New Project Wizard select project type 'Setup Project for WiX v3'.


Enter a name for the project like 'MyApp.Installer' and create it.

Step 4: Add files to the Wix Project

If you perform a release build of your WPF Application, and look at the output exe it may have various '.dll' files it requires. These might come from NuGet dependencies, library projects in the same solution or '.dll' files you've manually added. We need our MSI Installer to output all of these files to the installation directory, so first the WiX project needs to know what these files are.

In the Solution Explorer, right click on the WiX project references and select 'Add Reference'.

In the window that appears, select the 'Projects' tab. This is where we add files which come from other projects in our solution.

To add the 'exe' output by the WPF project, select the project from the list which produces the WPF application and press 'Add'.

You can also add libraries output by other projects in the same way.

You can now close the references window. We still need to add those '.dll' files. To do this perform a release build of the WPF Application, so they are created in the output 'bin/Release' directory.

 In the Solution Explorer right-click on the WiX project and select 'Add' -> 'Existing Item ...'

Now select all the '.dll' files which your release build produced.

Now all the required files are part of the WiX project, but we still need to tell WiX to actually bundle them in the installer.

Step 5: Bundle files with MSI

In the WiX project open the 'Product.wxs' file. This is where all the logic for the Installer is contained. The file contents are in XML format.

Scroll down to the 'TODO's. This is where we need to add references to our files.
<Fragment>
  <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER">
    <!-- TODO: Remove the comments around this Component element and the ComponentRef below in order to add resources to this installer. -->
    <!-- <Component Id="ProductComponent"> -->
      <!-- TODO: Insert files, registry keys, and other resources here. -->
    <!-- </Component> -->
  </ComponentGroup>
</Fragment>
To add our '.exe' file add the following <Component> tag within the <ComponentGroup>
<Component Id="ProductComponent">
  <File Source="$(var.WpfProjectName.TargetPath)" />
</Component>
... replacing the 'WpfProjectName' with the actual name of your WPF project.

You can add other <Component> tags like this to add other files which are the output of other projects within the solution (that have been referenced in step 4).

We still need to add any other '.dll' files. To do this we add more <Component> tags in the following format:
<Component Id="MyDll.dll" Guid="ADD-GUID-HERE">
  <File Id="MyDll.dll" Source="MyDll.dll" KeyPath="yes" Checksum="yes"/>
</Component>
... where you replace the name of the file (with extension), and add a unique GUID. You can generate unique GUIDs through websites like this one (or through a C# script of your own).

Now if you build the Installer project (through right-clicking in Solution Explorer), there will be a '.msi' installer generated in the 'bin/Release' directory of the Installer project.



The generated MSI should work, and now is a good time to check.

But notice it also outputs a '.cab' file and the installer won't work without it. In the remaining steps we'll remove this '.cab' file and make some other improvements.

Step 6: Embed Cab file in Installer

To remove the '.cab' file we want to embed it within the '.msi' file. This is really easy to do by adding 'EmbedCab="yes"' to the <MediaTemplate> tag inside the 'Product.wxs' file.
<MediaTemplate EmbedCab="yes"/>
 Again just rebuild the WiX project to get the new MSI Installer.

Step 7: Make installer check for .NET version

If you run your MSI installer on any Windows Machine, the installation will complete successfully. This happens even if that machine doesn't have the required version of .NET Framework / .NET Core installed. It would be much better if our installer failed if the required framework was missing.

Again this is easy to do with WiX. First add a reference to the WiX project (like in step 2), under the 'Browse' tab, navigate to the WiX install location and select the 'WixNetFxExtension.dll' file.
(for me this was at path: C:\Program Files (x86)\WiX Toolset v3.11\bin\WixNetFxExtension.dll')

Then in your 'Product.wxs' file declare we are using the namespace at the top of the file by adding it to the <Wix> tag.
So the <Wix> tag should look like this:
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
     xmlns:netfx="http://schemas.microsoft.com/wix/NetFxExtension">
Within the <Package> tags add a <PropertyRef> such as the following:
<PropertyRef Id="WIX_IS_NETFRAMEWORK_462_OR_LATER_INSTALLED"/>
Make sure the Id refers to a property from this page. For example, the Id used above represents a boolean value which is True when .NET Framework version 4.6.2 or a later version is installed (it gets this information by checking the registry).

Next you need to actually use this property, and prevent the install from going ahead if it is false. This can be done using a <Condition> tag with an error message as follows:
<Condition Message=".NET Framework 4.6.2 or later not found. Install the .NET Framework and try again.">
  <![CDATA[INSTALLED OR WIX_IS_NETFRAMEWORK_462_OR_LATER_INSTALLED]]>
</Condition>

Step 8: Add Start Menu Shortcut

When you run the MSI installer, no shortcuts to your application are added. It simply installs the application to Program Files, and expects you to find it yourself!

 Users expect start menu shortcuts to appear with new software, so we better add one.

In the 'Product.wxs' file within the <Product> tag and within a <Feature> tag add the following line:
<ComponentRef Id="ApplicationShortcut"/>
This tells the installer to load the Component which will add the shortcut files. But before we define the component we need to specify a directory structure for the files.

In the <Directory> tag with Id "TARGETDIR" we'll need to add a start menu folder directory. In the code below we've just added the three lines starting with the "ProgramMenuFolder". You can copy this code changing it to use the correct project name:
<Fragment>
  <Directory Id="TARGETDIR" Name="SourceDir">
    <Directory Id="ProgramMenuFolder">
      <Directory Id="ApplicationProgramsFolder" Name="WpfProjectName"/>
    </Directory>
    <Directory Id="ProgramFilesFolder">
      <Directory Id="INSTALLFOLDER" Name="WpfProjectName" />
    </Directory>
  </Directory>
</Fragment>
And finally within the outermost <Wix> tags we'll add the component which will create the shortcut files. To do this copy the following:
<Fragment>
  <DirectoryRef Id="ApplicationProgramsFolder">
    <Component Id="ApplicationShortcut" Guid="ab43392d-d93c-4e0f-b2b0-652112a557f3">
      <Shortcut Id="ApplicationStartMenuShortcut"
                Name="ProductName"
                Description="ProductDescription"
                Target="[#WpfProjectName.exe]"
                WorkingDirectory="APPLICATIONROOTDIRECTORY"/>
      <RemoveFolder Id="CleanUpShortCut" Directory="ApplicationProgramsFolder" On="uninstall"/>
      <RegistryValue Root="HKCU" Key="Software\CompanyName\ProductName" Name="installed" Type="integer" Value="1" KeyPath="yes"/>
    </Component>
  </DirectoryRef>
</Fragment>
... where you should replace 'WpfProjectName', 'ProductName', 'ProductDescription' and 'CompanyName' with the correct values.

We're Done!

Note that these MSIs are not signed. It is quite easy to add a digital signature to your MSI and isdefinitley worthwhile if you are distributing it publically. Maybe I'll post about this in the future. If you choose not to add a signature, at least make sure your users are given other reasons to trust it!

I hope you got what you were looking for with this post. Please leave comments and questions below.




Comments

Popular posts from this blog

Terminals in Sublime Text 3

TL;DR - Need a quick answer on how to create terminals in Sublime Text 3? Scroll down and watch the video or read the instructions below it. A while ago I started a series on YouTube of 'Sublime Text Tips'. Sublime Text 3 is one the best code editors currently in existence (fact), but most people just install it an use it without realising how it can be customized and extended to meet your needs. My aim was to release a series of videos explaining some of these widely unknown capabilities. I got as far as the third video and then got distracted with other things 😅 But recently I noticed the 3rd video I made has been increasing in popularity. For at least 6 months it sat at less than 200 views, and over the course of the last 2 months it has shot up to 850 (at the time of writing). Perhaps it's coincidence, or perhaps YouTube's algorithms have changed. Either way, there seem to be people who want this information. The video explains how to set up termin

Best Packages for Sublime Text 3 (Excluding Themes)

Sublime Text 3 is pretty good out-of-the-box but reaches a whole new level when you install some of the great packages on offer. Below I'll list my favourite packages for Sublime Text. These are all packages which will enhance your productivity across all languages, so no themes or language-specific packages will be listed here.