In this article, you'll create a .NET Worker Service, configure it to run as a Windows Service, package it using OpenMSI, and generate a Windows Installer (MSI) that automatically registers and starts the service during installation.
Our Windows Service Example
Prerequisites
Before starting, ensure you have:
- Visual Studio 2022 or later
- .NET 8 SDK or later
- Windows 10/11
First things first, let's create a .NET Worker Service application that will generate the executable associated with the Windows Service we want to deploy.
On Visual Studio startup window click on 'Create a new project' then search for 'worker' template and select the C# option:
Let's be creative and name our application as 'WindowsServiceExample':
Click on 'Next' then click on 'Create' in the next dialog.
With our project created we now want to make it Windows specific so we can change the service name displayed in Windows Service console programatically. Open the NuGet package manager clicking with a right click over the project and click on 'Packages' in the context menu, then search and add the 'Microsoft.Extensions.Hosting.WindowsServices' NuGet package in 'Browse' tab.
Change the initial Program.cs file so it looks like this:
namespace WindowsServiceExample
{
public class Program
{
public static void Main(string[] args)
{
var builder = Host
.CreateDefaultBuilder(args)
.UseWindowsService(options =>
{
options.ServiceName = "WindowsServiceExample";
})
.ConfigureServices(services => {
services.AddHostedService<Worker>();
});
var host = builder.Build();
host.Run();
}
}
}
The UseWindowsService extension integrates the worker application with the Windows Service Control Manager (SCM). The ServiceName value is the internal service name used by Windows when registering and managing the service.
The next step is configuring a publish profile so we can easily publish our application files. Right click on 'WindowsServiceExample' project and click on 'Publish', select the 'Folder' option then click 'Next' and 'Finish' in the next dialog.
Now configure the publish profile settings clicking on 'Show all settings', in 'Profile settings' dialog set the application to be 'Self-contained' so the application includes the .NET runtime and does not require .NET to be installed on the target machine. Also, configure the application to target 'win-x64' runtime (Windows 64 bits). Click on 'Save' and publish the application.
The initial logic present in Worker.cs is good enough for this example, so we are done with our application, it is time to work on our installer.
Creating the Installer using OpenMSI
We will use OpenMSI to create our installer (don't worry, OpenMSI is free for non commercial and Open Source projects).
OpenMSI is a .NET tool, that means you can install it running:
dotnet tool install openmsi --global
Once the installation finishes, it is time to initialize the installer files using OpenMSI, to do that in the same directory of WindowsServiceExample.csproj file run:
openmsi init
This command will prompt some information about the package and application:
Once finished, the installer package files are created, the only one we need to edit is msi-package.yaml to configure the 'package' and 'services' sections so it looks like this:
# yaml-language-server: $schema=https://openmsi.dev/schemas/openmsi-package-v1.schema.json
package:
productName: WindowsServiceExample
manufacturer: John Doe
description: A simple Windows Service example
version: 1.0.0
productCode: fdb96b96-8b1c-4021-b9f4-3cd19cc8e67e
upgradeCode: d9e86ff3-9a68-4b86-b351-860c65a4290b
productIconSource: msi-package\assets\demoappmulti.ico
dialogs:
- source: msi-package
assets:
- name: OpenMSI_BmpTopBanner
source: msi-package\assets\top-banner-370x44.bmp
- name: OpenMSI_BmpBackground
source: msi-package\assets\background-370x270.bmp
- name: OpenMSI_IcoInfo
source: msi-package\assets\icon-info.ico
- name: OpenMSI_IcoWarning
source: msi-package\assets\icon-warn.ico
- name: OpenMSI_BmpFolderNew
source: msi-package\assets\folder-new.bmp
- name: OpenMSI_BmpFolderUp
source: msi-package\assets\folder-up.bmp
files:
- source: bin\release\net10.0\publish\win-x64
installFolder: true
services:
- name: WindowsServiceExample
displayName: Windows Service Example
description: A simple Windows Service example
start: auto
account: LocalSystem
executable: WindowsServiceExample.exe
Here a summary describing the services section in more detail:
| Property | Description |
|---|---|
| name | Internal service identifier |
| displayName | Displayed in Services.msc |
| description | Service description shown in Windows |
| start | Startup type (auto, manual, disabled) |
| account | Service execution account |
| executable | Executable registered as the service |
For simplicity this example runs under LocalSystem. Production services should use the least privileged account possible, such as LocalService, NetworkService, or a dedicated service account.
Now we just have to run:
openmsi build
At this point you can open the Windows Service Console (services.msc) and verify the installed service:
Or you can execute:
Get-Service WindowsServiceExample
Common Questions
- Does the service start automatically?
- Does it stop during uninstall?
- Is it removed during uninstall?
OpenMSI automatically generates the Windows Installer service entries required to install, start, stop, and remove the service during the installation/removal lifecycle.
Troubleshooting
The service does not appear in Services.msc
Verify that the executable path specified in executable matches the published file name.
The service fails to start
Check Windows Event Viewer for startup errors.
MSI installs successfully but the service is not created
Ensure the services section is present in msi-package.yaml and has a valid name property.
References
Windows Service Example GitHub Repository
OpenMSI: Deploy on Windows with a modern, simple, and intuitive tool
Worker Services - .NET | Microsoft Learn









Top comments (2)
tr.ee/dev-to
Scam reported.