Introduction
Windows services are very powerful applications that can be used to perform many different types of tasks in the background. They can start without requiring any user to login and they can run with a different user account other than the logged in user. But Windows service applications are hard to debug even in development environment if they are developed by following the regular service development steps.
This article suggests a different way of developing a Windows service without using any service development library (like Topshelf) for easy monitoring and debugging in development phase.
Features
The sample project has the following features:
- It runs as a console application in debug mode and as a regular Windows service in release mode.
- Displays log messages on the console using different text colors depending on the log type.
- Windows service related operations and actual background task processing operations are separated so the actual service operations can be unit tested easily.
- Service can be installed and uninstalled using InstallUtil.exe. But you do not need to install it in order to test and debug.
Steps Followed to Prepare this Application
The sample project was prepared using Visual Studio 2017.
1. Create a Windows Service Project
In Visual Studio, click File\New\Project. Select "Visual C#\Windows Desktop\Windows Service" project type.
2. Change Project Output Type From Windows Application to Console Application
Open project properties by right clicking on the project name and selecting "Properties".
Select Console Application from "Output type" selection list.
3. Install log4net package
Install log4net nuget package either from Package Manager Console or from Manage Nuget Packages menu option.
4. Configure log4net
log4net is a very powerful logging library that can write logs to many targets like text files, console, database, Windows eventlog or even send them as e-mails. Those log writers (or targets) are called "appenders". A log4net configuration must have at least one appender, but it may have many. Each appender has its own settings.
log4net configuration can be added to app.config file or it can be a separate file. I prefer separate file approach so add two config files for log4net that will be used in debug and release modes. Names does not matter, you may name them as "log4net.debug.config" and "log4net.prod.config". Debug config file has two appenders; RollingFileAppender
and ColoredConsoleAppender
. Production config file has also two appenders; RollingFileAppender
and EventLog
. But EventLog
appender is commented out, you can uncomment it if you want to write Windows event log.
The minimum log level is defined under the <root>
element as <level>
configuration element. For debug config level is DEBUG
, for production, it is INFO
. Valid levels are; DEBUG
, INFO
, WARN
, ERROR
. Please see log4net documentation for more information.
The next configuration step is to tell log4net
library which file to use in debug and release modes. To do this, open AssemblyInfo.cs file and add assembly level attributes for log4net. Add these lines to switch between two files in debug and release modes.
#if DEBUG
[assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4net.debug.config", Watch = true)]
#else
[assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4net.prod.config", Watch = true)]
#endif
5. Add SampleBackgroundService Class which Contains Actual Background Operations that Our Windows Service Will Perform
It is a very simple class with Start
, Stop
methods for starting and stopping background task processing thread and a thread method that continuously writes logs.
class SampleBackgroundService
{
private static ILog logger = LogManager.GetLogger(typeof(SampleBackgroundService));
public void Start() { ... }
public void Stop() { ... }
private void serviceThread()
{
while (!stopRequested)
{
}
}
}
6. Update the Code in the Automatically Generated Windows Service Class
I have renamed automatically added Windows Services class as SampleWindowsService
. This class is inherited from ServiceBase
and it is the class whose OnStart
and OnStop
methods are called when the Windows service is started or stopped. This class only creates an instance of SampleBackgroundService
class and calls its Start
and Stop
methods.
public partial class SampleWindowsService : ServiceBase
{
SampleBackgroundService sampleBackgroundService;
public SampleWindowsService()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
sampleBackgroundService = new SampleBackgroundService();
sampleBackgroundService.Start();
}
protected override void OnStop()
{
sampleBackgroundService.Stop();
}
}
7. Update Main Method in Program.cs File to Behave Differently in Debug and Release Modes
When a new Windows service project is created, the automatically generated Main
method contains code for creating and starting the auto generated Windows service. But a Windows service cannot be started and hosted as a regular application. So, an error message appears when you try to run the application.
To run and test our application, we do not need to create a real Windows service instance, since it does not contain any code other than creating and starting an instance of SampleBackgroundService
class. Updated code in Main
method creates and starts an instance of SampleBackgroundService
class in Debug mode and runs as a console application. But creates and runs the real Windows service in Release mode.
static void Main()
{
ILog logger = LogManager.GetLogger(typeof(Program));
#if DEBUG //Run as a regular console application in Debug mode
logger.Info("Starting services...");
SampleBackgroundService _backgroundService = new SampleBackgroundService();
_backgroundService.Start();
logger.Info("Services started. Press enter to stop...");
Console.ReadLine();
logger.Info("Stopping service...");
_backgroundService.Stop();
logger.Info("Stopped.");
#else //Create and run the real Windows service instance in Release mode
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new SampleWindowsService()
};
ServiceBase.Run(ServicesToRun);
#endif
}
8. Add Service Installer Component to be Able to Install this Service using InstallUtil.exe
To add an installer component, double click SampleWindowsService.cs on solution explorer. It will display design view for the service.
Right click on the design area and click "Add Installer" on the context menu.
This will add ProjectInstaller.cs and a designer file to the project. Remove the auto generated code ProjectInstaller.InitializeComponent()
method and auto generated variables (serviceProcessInstaller1
, serviceInstaller1
).
Add the following code into the ProjectInstaller.cs file:
public partial class ProjectInstaller : Installer
{
public const string SERVICE_NAME = "Sample Background Service";
private readonly ServiceProcessInstaller m_ServiceProcessInstaller;
private readonly ServiceInstaller m_ServiceInstaller;
public ProjectInstaller()
{
m_ServiceProcessInstaller = new ServiceProcessInstaller();
m_ServiceProcessInstaller.Account = ServiceAccount.LocalSystem;
m_ServiceInstaller = new ServiceInstaller();
m_ServiceInstaller.ServiceName = SERVICE_NAME;
m_ServiceInstaller.Description = "";
m_ServiceInstaller.StartType = ServiceStartMode.Automatic;
m_ServiceInstaller.DelayedAutoStart = true;
Installers.Add(m_ServiceProcessInstaller);
Installers.Add(m_ServiceInstaller);
InitializeComponent();
}
}
If you want to perform any task before and after the service is installed, you can override the appropriate base class methods like OnBeforeInstall
, OnBeforeUninstall
...
You do not need to install (or register service to Windows service registry) your service in order to run and debug. But if you want, you can use InstallUtil.exe to install and uninstall the service. InstallUtil.exe is under .NET Framework installation folder. For example, "C:\Windows\Microsoft.NET\Framework\v4.0.30319".
To register the service, open a command line window and run InstallUtil.exe with the full path of the executable file. You may need to run command line window "as Administrator" in order to register a service.
C:\Windows\Microsoft.NET\Framework\v4.0.30319>InstallUtil.exe
"D:\DebuggableWindowsService\src\bin\Release\DebuggableWindowsService.exe"
To uninstall a service, run the same command with /u
option.
C:\Windows\Microsoft.NET\Framework\v4.0.30319>InstallUtil.exe
/u "D:\DebuggableWindowsService\src\bin\Release\DebuggableWindowsService.exe"
9. Run the Application in Debug Mode to See Log Output and Debug
Below is a sample output displayed when the application is running in Debug mode.
Important Notes About Windows Services
Windows Service applications are different from other regular applications in few points. So, the application may behave differently in Debug (console application mode) and Release (Windows Service mode) modes.
Firstly, when you run the application in Debug mode, its working directory will be the path where the executable file resides, e.g., "D:\DebuggableWindowsService\src\bin\Release\DebuggableWindowsService.exe".
But, when you install it with InstallUtil.exe or with a setup application and run it from Windows Services management application, its working directory will be "C:\Windows\System32" or "C:\Windows\SysWOW64" depending on whether your service is 64 or 32 bit, and whether it runs on a 32 bit or 64 bit Windows.
If you want to read or write files on the installation directory instead of system directories, you can change the working directory at startup. For example:
Environment.CurrentDirectory =
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
Secondly, Windows services may run with a different user account than the logged in user. Some of the operations that run in debug mode may not run when the application runs as a real Windows service. Examples for these operations are accessing a directory or network path, opening a port...
Thirdly, Windows services have no user interface and normally cannot display user interfaces. Their access to graphics cards are blocked by Windows operating system. Unfortunately, it is not possible to use powerful GPUs from a Windows service. To learn more about this restriction, search for "Session 0 Isolation".
For displaying user interfaces from a Windows service, check this article, Can a Windows Service have a GUI? Should a Windows Service have a GUI? Thanks to Burak-Ozdiken for sharing this link.
History
- 18th July, 2019 Initial version.