Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Debuggable Windows Service Template Project with C#

5.00/5 (10 votes)
21 Jul 2019CPOL6 min read 19.7K  
Easily debuggable Windows service project template written in C#

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.

Image 1

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.

Image 2

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.

C#
#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.

C#
class SampleBackgroundService
{
    //Get a logger for this class from log4net LogManager
    private static ILog logger = LogManager.GetLogger(typeof(SampleBackgroundService));

    //Starts the thread
    public void Start() { ... }

    //Stops the thread
    public void Stop() { ... }

    //Service thread that performs background tasks and writes logs
    private void serviceThread()
    {
       while (!stopRequested)
       {
         //Write different types of logs...
       }
    }
}

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.

C#
public partial class SampleWindowsService : ServiceBase
{
   SampleBackgroundService sampleBackgroundService;

   public SampleWindowsService()
   {
      InitializeComponent();
   }

   //Called when the Windows service is started
   protected override void OnStart(string[] args)
   {
      sampleBackgroundService = new SampleBackgroundService();
      sampleBackgroundService.Start();
   }

   //Called when the Windows service is stopped
   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.

C#
static void Main()
{
    ILog logger = LogManager.GetLogger(typeof(Program));

#if DEBUG //Run as a regular console application in Debug mode
    //Manually create an instance of SampleBackgroundService class and call its start method

    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.

Image 3

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:

C#
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()
    {
        //Installer that installs the process (in this case 'DebuggableWindowsService.exe')
        //There can be only one ServiceProcessInstaller
        m_ServiceProcessInstaller = new ServiceProcessInstaller();
        m_ServiceProcessInstaller.Account = ServiceAccount.LocalSystem;

       //Installer that registers actual Windows Service implementations in the application
       //There may be one or more ServiceInstaller
        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.

BAT
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.

BAT
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.

Image 4

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:

C#
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)