I recently completed a project where I had to create a new windows service and installer. This was not the first windows service application we had built, but it was the first new one built using Visual Studio 2019 where all others were built using VS 2010 or older and much has changed since then, particularly with the installer.
VS 2010 had a built in project type called Visual Studio Installer Project which we had been using, but it was deprecated in VS 2012 and beyond. Support for the project type can be added to later versions of visual studio through an extension which allows us to build older projects in VS 2019. For this new project, we wanted to use the approach recommended by Microsoft which, at the time of this writing, is the WiX toolset. I will not be going over the history and details of what WiX is or how it is better or worse than the old Visual Studio Installer project, but I have added some links in the Sources section at the end of this post if you would like to dive deeper.
In this post, I will be detailing how to create a windows service with some additional project dependencies and how to create an installer for that service using the WiX toolset.
Install WIX Toolkit
The first thing you will need to do before creating your project is install these two components.
- WiX Toolset build tools – “includes everything you need to create installations on your development and build machines”
- Wix Toolset Visual Studio Extension – “this extension provides integration for the Wix Toolset into Visual Studio”
Both of these can be found on the Wix Toolset downloads page: https://wixtoolset.org/releases/
Create Windows Service Project
Create a new Windows Service (.NET Framework) project in Visual Studio 2019
This project is where you would add your application logic. Since the focus of this post is installing a windows service, I will be leaving the service with the default template code which creates a service that does nothing but start and stop.
Add Dependencies
I am also going to add a few empty class libraries to the sln and reference them in the WinService project to demonstrate having additional dll dependencies that we will be required to include in the installer.
When I build the sln we get the following in the WinService bin directory:
- WinService.exe
- dlls for each of the class libraries
- Application config
- pdb files
We will need to include everything except the pdb files in our installer.
Create Installer Project
Add a new “Setup Project for Wix v3” project to the sln.
The new project contains a single file named Product.wxs. This will be the file we will be editing in a bit to construct our installer.
Configure Installer Properties
Right click the Installer project and select Properties.
In the Installer tab set the Output name to the name you want for the msi. For my project I named it CMartWixInstaller.
Next select the Build tab and check “Supress output of wixpdb files”.
This step is not required. If you leave this unchecked the build will create an additional file with a .wixpdb extension. I did some very shallow research to try and figure out what this file could be used for and the best I could find was “it’s for debugging” but didn’t find any way I could actually debug. For my purposes, I only wanted the bin to contain the single msi, so this setting met my needs.
Add Project Reference To Installer
Right click references in the installer project and select “Add References…”
Select the “Projects” tab
Select your Windows Service Project and click “Add” then “OK”
Adding the project reference will allow us to use project reference variables when we define the installer components.
Define Installer in Product.wxs
We are now ready to define our installer in the Product.wxs file.
For my installer, I had the following requirements
- Include all files needed to run the service
- Save the files to a specific folder on the C: drive (not program files)
- Install and start the windows service
- Allow service to be uninstalled through Add/Remove Programs
- On uninstall and upgrade, do not modify any config files
- Any re-install replaces the existing version even if it is a downgrade
When we created the Installer project, the Product.wxs file was created with a default template which we will fill in. This section will go through each node and detail what each attribute is and what changes need to be made in order to fulfill all of the above requirements.
Product
- Product: Leave this as *.
- Name: The descriptive name of the product. Change this to whatever suits your project.
- Language: 1033 is English
- Version: This is the version of the installer. You will need to change this each time you release a new version of the installer. The first 3 fields are used by windows to determine behavior such as downgrades/upgrades.
- Manufacturer: Change this to your company name
- UpgradeCode: You can use the auto generated guid here, but be sure that once you set it not to change it because this is the guid windows uses to perform upgrades. If you change it, windows will think you are installing a completely new application. Also make sure not to duplicate UpgradeCodes between applications or you will have a bad time (which is why I do not supply an actual guid in my sample above to prevent copy/paste issues).
Package
The Package element is a required element. For my project I didn’t make any changes to the default element.
- InstallerVersion: The minimum version of the Windows Installer required to install this package
- Compressed: Set to ‘yes’ to have compressed files in the source
- InstallScope: Options are perMachine or perUser
Major Upgrade
The major upgrade element defines the behavior of the installer when performing an upgrade. By default, downgrades are not allowed and the element uses the attribute “DowngradeErrorMessage” to display a message to the user when they are attempting to perform a downgrade.
Since one of my requirements was to allow downgrades, I modified the element as shown.
- AllowDowngrades: Set this to yes to allow any version to replace the currently installed version
Media Template
The media template element describes how media is packaged up for the installer. By default the element has no attributes.
- EmbedCab: Set this to yes to embed all media in the msi so everything is contained in a single file. If this is not included the files are included along side the installer msi
Directory
In this section we have created a fragment that defines where our service will be installed. In the above example, the service will be installed at C:/CMartCoding/CMartWinService.
The Directory is defined in 3 parts
- TARGETDIR: This is the root destination directory and will always have the Name set to “SourceDir”
- Path: The middle directory element is the path to your install folder. By default this is the ProgramFiles directory and is denoted with Id=”ProgramFilesFolder”. In my case, I didn’t want my service installed in Program Files so I set my own custom directory to C:/CMartCoding
- INSTALLFOLDER: This is the folder where all your files will be installed to.
Dependency Files
In the next 3 sections we will define each file that needs to be included in our installer. In WiX, Components are used to define the units that need to be installed. While it is possible for a single component to contain multiple files, it is recommended to create a component for each file that needs to be installed.
In my project, I grouped components together into 3 groups (Dependency Files, Configs, & Service) using the ComponentGroup Element. It is not required to group components. I did it in my project to group files by their install/uninstall behavior.
The dependency files need to be overwritten every time an install is performed and removed when the service is uninstalled.
I set these attributes on my ComponentGroup elements
- Id: unique name of the group that can be used as a reference Id
- Directory: The directory where all the components inside the group will be installed to. I set this to INSTALLFOLDER for all my componnets.
Each Component element has at least this attribute
- Guid: Unique guid for each component which is used by the installer to identify the files when doing uninstall/upgrades. This Guid must be unique for every Component and should not change between versions.
In the Dependency Files component group I created a component for each dependency dll containing a single File element with the following attributes
- Source: Path to the file. Since we included WinService as a reference we are able to use the project reference varialb $(var.WinService.TargetDir).dll. For your project, you will replace WinService with the name of your window service project
- KeyPath: This tells windows that this is the main file for this component. Each component must have a KeyPath defined.
Config Files
For my project the config files need to remain unchanged on upgrades and remain on the machine on uninstalls so that any changes made manually do not get overwritten or lost.
All the attributes used to create the Dependancy File Components are exactly the same except the following Component attributes
- NeverOverwrite: Setting this to yes tells Windows to not overwrite the file on upgrades if it already exists in the INSTALLFOLDER
- Permanent: Setting this to yes leaves the file on the computer when the service is uninstalled
Service
The service group contains a single component that defines the Windows Service file, installs the service, and starts the service.
The Component and File elements are defined the same as the Dependency files.
The ServiceInstalle element installs the service
- Id: Unique id
- Type: Defines the service type. ownProcess is a service that runs its own process. Most services will fall under this category
- Name: This column is the string that gives the service name to install.
- DisplayName: Name displayed in the Windows Services list
- Description: Service description displayed in the Windows Services list
- Start: Start behavior of the service. Can be set to auto, demand, or disabled.
- ErrorControl: Determines what action should be taken on an error.
- Account: Name of the user account the service will run under.
The ServiceControl element starts the service
- Id: Unique Id
- Start: defines when the service is started. Can be set to install, uninstall, or both
- Stop: defines when the service is stopped. Can be set to install, uninstall, or both
- Remove: defines when the service is removed. Can be set to install, uninstall, or both
- Name: Name of the service to start/stop/remove
- Wait: Specifies whether or not to wait for the service to complete before continuing
Feature
The final element is the Feature element. This element ties all the component groups together and tells windows to install them.
- Id: Unique ID
- Title: Display name of the feature
- Level: 1 = install, 0 = don’t install
You can view the full wxs file on GitHub to see what it looks like all put together.
https://github.com/nitramssirc/cmartcoding_winservice_wix/blob/master/Installer/Product.wxs
Build Installer
Now that we have our installer components/features defined we can build the installer project. By default, the installer project is not set to build with the rest of the solution so it will not be built by the main build commands in the VS menus.
To build the installer, right click the installer project and select build. Once the build completes, you will have a shiny new msi in the project’s bin directory.
Run Installer
To run the installer, just double click it. Since we didn’t create any custom UI for our installer, a simple modal window will pop-up displaying the progress of the install.
Once the install completes you should see all the files in the install directory and the service running in the services menu.
Uninstall Service
You can uninstall the service through Add/Remove Programs
When uninstalled successfully, you will see the service removed from the service menu and all the files removed from the install directory except the config files.
Closing Thoughts
This was just a quick example of what can be done with the WiX Toolset. It can be used to do much more such as custom UIs, installing databases, setting registry entries, etc.
I recommend visiting wixtoolset.org and firegiant.com for additional documentation and examples.
Code
Full source of the sample project is up on GitHub.
https://github.com/nitramssirc/cmartcoding_winservice_wix/tree/master
Sources
- https://www.firegiant.com/
- https://developingsoftware.com/wix-toolset-install-windows-service/
- https://support.firegiant.com/hc/en-us/articles/230912267-Install-to-the-root-of-system-drive-
- https://stackoverflow.com/questions/11732290/how-do-i-make-a-wix-msi-always-remove-a-previous-version
- https://wixtoolset.org/documentation/manual/v3/xsd/wix/
- https://docs.microsoft.com/en-us/dotnet/framework/windows-services/walkthrough-creating-a-windows-service-application-in-the-component-designer
- https://stackoverflow.com/questions/15268613/difference-between-wix-and-visual-studio-installer
- https://www.pluralsight.com/courses/wix-introduction