Table of Contents
If you're an ASP.NET developer working with client applications using either Windows Forms (aka WinForms) projects, WPF Projects, Windows Console applications, Windows Service projects and/or any .NET Core Desktop App project (and so on), you most likely know that sooner or later you'll have to find a way to deploy your work to your end-users. Such need is usually handled by creating an installer package which would arguably have the form of either a MSI file (acronym for MicroSoft Installer) or a EXE file, just like any typical software application or tool you can download from the web. You click on it, it prompts you to accept the software license and/or EULA, then (if you accept) asks you to confirm the installation path where it will unpack the files, create the desktop/start menu shortcuts, and so on. Right? Right.
Now, the question is: how can we pull it off? In other words, what's the best way to create these installer packages for the aforementioned ASP.NET projects in order to grant our end-users a decent user-friendly "installing experience", possibly using the standard Windows Installer API?
If you're using Visual Studio, there are mostly three ways to achieve such goal:
- Using the Visual Studio Windows Installer Deployment Project Template, also known as Setup Project Template: this has been the "standard" way to perform such task since Visual Studio 2002 and (officially) available up to Visual Studio 2010, which is the last VS version that came out before Mirosoft chose stop supporting it in 2012. However, these projects can still be created on Visual Studio 2012 (and above, up to 2019) thanks to the great MSI Installer Project extension port.
- Using WiX, a open-source toolset that lets developers create installers for Windows Installer, the Windows installation engine.
- Using other third-party tools, such as Advanced Installer, Actual Installer and so on (here's a useful list of installation software tools for Windows).
In this post we'll learn how to use WiX, which is arguably the best option of the pack (at least in our humble ASP.NET developers opinion) for a number of reasons: it's open source, has a strong community support and easily is the most powerful and configurable setup and deployment framework available.
Installation
To setup WiX in your Visual Studio environment you need to download and install the following packages, both available from wixtoolset.org, the official WiX project website:
- WiX v3.11.1 (latest stable at the time of writing - check here for the most recent release).
- WiX Toolset Visual Studio 2017 Extension, a VS extension providing "seamless" integration for the WiX Toolset into Visual Studio (requires the WiX Toolset v3.11.1 build tools from the above link).
If you have older version of Visual Studio, you can also get the extension for VS2015 down to VS2010 from this link. There's also a VS2019 Preview Extension that mostly works on Visual Studio 2019.
The installment process is quite straightforward, you'll just have to hit Next and OK a couple times to get it done: don't forget to reboot Visual Studio once the installation is done, to ensure that both the extension and the project templates will be loaded.
Creating a Sample Project
For the sake of simplicity, let's pretend you already have a WPF project ready to be deployed to the end-users called MyWFPProject, which you want pack into a MSI file. Right-click to the solution name in the Solution Explorer panel and choose Add... > New Project.
From the project template list, choose the Setup Project for WiX v3 project template:
As we can see, we called the setup project MyWPFProject_Setup: once done, you'll have a brand new WiX project containing a single Product.wxs file, which is the WiX configuration file. If you open it, you'll get a graps of how WiX works... it basically has its very own scripted XML file, which you can use to tell it what to do.
The default template that is generated when you create a new WiX project will generates a build warning. If you try to build it without performing any change, the Output window will most likely show the following warning:
The cabinet 'MySetup.cab' does not contain any files. If this installation contains no files, this warning can likely be safely ignored. Otherwise, please add files to the cabinet or remove it.
This error message is telling us that the WiX project does not yet reference an application, therefore there is nothing to install: once we add at least a single file to the installer, this warning will go away.
Minimal Configuration
Here's the minimal configuration to generate a working MSI file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<?xml version="1.0" encoding="UTF-8"?> <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"> <Product Id="*" Name="MyWPFProject" Language="1033" Version="1.0.0.0" Manufacturer="Ryadel" UpgradeCode="424d8179-0d5c-46bc-9984-24964850059b"> <Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" /> <MajorUpgrade DowngradeErrorMessage="A newer version of MyWPFProject is already installed." /> <MediaTemplate /> <WixVariable Id="WixUILicenseRtf" Value="LicenseAgreement.rtf" /> <Feature Id="ProductFeature" Title="MySetup" Level="1"> <ComponentGroupRef Id="ProductComponents" /> </Feature> </Product> <Fragment> <Directory Id="TARGETDIR" Name="SourceDir"> <Directory Id="ProgramFilesFolder"> <Directory Id="INSTALLFOLDER" Name="MyWPFProject" /> </Directory> </Directory> </Fragment> <Fragment> <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"> <Component Id="ProductComponent"> <File Source="$(var.MyWPFProject.TargetPath)" KeyPath="yes" /> </Component> </ComponentGroup> </Fragment> </Wix> |
As we can see, the XML structure is quite understandable: we do have a <Product> element, containing the general info about the project we want to deploy, and a couple <Fragment> elements: the first one containing the directory info (which folder to create on the end-users machine), and the latter containing the product components, i.e. the files to copy.
It's worth noting that the <Product> element contains a <Feature> element referencing the ID of a ComponentGroup specified later on: we'll talk about this in a while.
We also removed the TODO comments and replaced them with our actual content: notice how we used $(var.MyWPFProject.TargetPath) instead of a full path. That's one of the many project reference variables, which is one of the most useful features of WiX: think of them as environment variables that you can use within the XML configuration file instead of specifying static literal values. Here's the full list of the available variables: needless to say, we used the one that references our project's main executable file - the one that is built whenever we issue the Build Visual Studio command. This sample XML configuration file will package that newly-built executable within a convenient MSI file, which we can send to our end-users for a one-click install.
That's great, right? Well, not yet! Such MSI file would still have the following major flaws:
- No icon (argh!) - the MSI will have the ugly "nameless app" default icon, which is something we would really like to change.
- Forced installation path - The end-user won't have the chance to select a custom installation folder: our application will be forcefully installed on C:\Program Files\MyWPFProject\.
- No EULA - The end-user won't have the chance to read and (be forced to) accept our software's EULA before installing and using it.
- No DLLs or external files/components - In the (most likely) scenario we have some third-party DLLs not included in the .NET Framework used, they won't be shipped, which basically means that our app will utterly crash as soon as they will be called by the compiled code: the same goes for external files and/or folders, such as: icons, images, resource files, and so on.
In the following paragraphs we'll briefly deal with these 3 critical aspects, leaving all the rest to the WiX official documentation and the WiX v3 Manual - which we strongly suggest to look at.
Application Icon
Let's start with the easy task: add a icon.ico file to the Setup project containing your favourite image(s) and then link it to the <Product> element within the XML configuration file in the following way:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<Product Id="*" Name="MyWPFProject" Language="1033" Version="1.0.0.0" Manufacturer="Ryadel" UpgradeCode="424d8179-0d5c-46bc-9984-24964850059b"> <Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" /> <MajorUpgrade DowngradeErrorMessage="A newer version of MyWPFProject is already installed." /> <MediaTemplate /> <WixVariable Id="WixUILicenseRtf" Value="LicenseAgreement.rtf" /> <Feature Id="ProductFeature" Title="MySetup" Level="1"> <ComponentGroupRef Id="ProductComponents" /> </Feature> <!--Add Icon and ARPPRODUCTICON (the installer icon). ref.: http://wixtoolset.org/documentation/manual/v3/howtos/ui_and_localization/configure_arp_appearance.html --> <Icon Id="icon.ico" SourceFile="icon.ico" /> <Property Id="ARPPRODUCTICON" Value="icon.ico" /> </Product> |
As we can see, we added two elements: the <Icon> element, which contains the icon.ico file full path, and a <Property> element defining the ARPPRODUCTICON value, which specifies the foreign key to the Icon table, which is the primary icon for the Windows Installer package (for more info about the ARPPRODUCTICON property, read here).
Custom Installation Path
Giving the end-user the chance to choose a custom installation path requires to use a WiX GUI sequence, which is basically a pre-made GUI wizard containing some additional "steps" which we can configure using pre-defined variables. This part is rather obscure in the official WiX documentation and it's one of the main reasons that drove us into writing this tutorial.
Here's how we can use the WixUI_InstallDir sequence to give our end-users the chance to replace the default installation folder with their own path:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<Product Id="*" Name="MyWPFProject" Language="1033" Version="1.0.0.0" Manufacturer="Ryadel" UpgradeCode="424d8179-0d5c-46bc-9984-24964850059b"> <Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine" /> <MajorUpgrade DowngradeErrorMessage="A newer version of MyWPFProject is already installed." /> <MediaTemplate /> <WixVariable Id="WixUILicenseRtf" Value="LicenseAgreement.rtf" /> <Feature Id="ProductFeature" Title="MySetup" Level="1"> <ComponentGroupRef Id="ProductComponents" /> </Feature> <!--Add Icon and ARPPRODUCTICON (the installer icon). ref.: http://wixtoolset.org/documentation/manual/v3/howtos/ui_and_localization/configure_arp_appearance.html --> <Icon Id="icon.ico" SourceFile="icon.ico" /> <Property Id="ARPPRODUCTICON" Value="icon.ico" /> <!-- Add the WixUI_InstallDir pre-made sequence to allow some additional steps to the setup wizard --> <!-- IMPORTANT: Remember to add a reference to the WixUIExtension.dll reference! --> <!-- Right click the References project folder > Add Reference, then navigate to C:\Program Files (x86)\WiX Toolset v3.11\bin\ and select WixUIExtension.dll --> <UIRef Id="WixUI_InstallDir" /> <!-- This property will allow the end-user to choose a custom install folder --> <Property Id="WIXUI_INSTALLDIR" Value="INSTALLFOLDER" /> </Product> |
Again, all the action happens within the <Product> element. As we can see, we added the <UIRef> element (pointing to the WixUI_InstallDir GUI sequence) and another <Property> element: the former element will empower the setup wizard with a "Choose folder..." page, while the latter will setup the default install folder that will appear - and that will be used if the user doesn't want to change it.
Adding the EULA
Now that we added the WixUI_InstallDir GUI sequence, adding our very own License Agreement file is rather simple. We just have to add the following elements to the <Product> element:
1 2 |
<!-- This property will force the end-user to read and accept the EULA --> <WixVariable Id="WixUILicenseRtf" Value="LicenseAgreement.rtf" /> |
The <WixVariable> element mentions a LicenseAgreement.rtf file, which we'll have to add to our Setup project (right-click > Add new). If we do that, our very own EULA will be shown to our end-users during the installation phase, forcing them to either accept it or decline the whole setup process.
Adding external DLL files
Adding external DLL files will require slightly more work, as we'll have to mention each one of them individually within a brand new <ComponentGroup> element within the <Fragment> containing the files to put into the MSI package:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<Fragment> <!-- All external DLLs goes here --> <ComponentGroup Id="DllComponents" Directory="INSTALLFOLDER" Source="$(var.MyWPFProject.TargetDir)"> <Component Id="DDLName_01"> <File Name="DDLName_01.dll" /> </Component> <Component Id="DDLName_02"> <File Name="DDLName_02.dll" /> </Component> <Component Id="DDLName_03"> <File Name="DDLName_03.dll" /> </Component> </ComponentGroup> <ComponentGroup Id="ProductComponents" Directory="INSTALLFOLDER"> <Component Id="ProductComponent"> <File Source="$(var.MyWPFProject.TargetPath)" KeyPath="yes" /> </Component> </ComponentGroup> </Fragment> |
Needless to say, replace the sample names DLLName_01 and DLLName_01.dll (and so on) with the name & filename of your own DLLs. As we can see, since we specified the Source attribute using the $(var.MyWPFProject.TargetDir) WiX variable, we won't have to add the full path to the DLLs... as long as they will be copied to the "build" target folder.
Once done, we'll just have to add a reference to the new ComponentGroup to the <Feature> node present in the <Product> element:
1 2 3 4 |
<Feature Id="ProductFeature" Title="MySetup" Level="1"> <ComponentGroupRef Id="ProductComponents" /> <ComponentGroupRef Id="DllComponents" /> </Feature> |
Conclusion
That's it, at least for now: if you want to look we strongly suggest to take a look at Stefan Kruger's installsite.org website, a peculiar website dedicated to the installer topic which offers valuable insights, software reviews and other useful resources for Setup Developers.
Here are other useful resources taken to this StackOverflow thread by Stein Asmul, which also contains a list of other non-WiX installer resources:
- WixEdit - a great visual editor for WiX XML configuration files: if you hate learning the WiX custom syntax and manually write XML files, this tool is for you.
- Wix Quick-Start Tips, a great answer posted on the StackOverflow website.
- FireGiant, WiX's commercial branch, featuring a neat WiX expansion pack (not open-source, though).
- Windows Installer and the creation of WiX (the idea behind WiX).
- How to install and start a Windows Service using WiX, another great tutorial available on StackOverflow.