Skip to main content

.NET Windows Services

Introduction

Windows Services are long-running applications that operate in their own Windows sessions, without a user interface, and can be configured to start automatically when the system boots. They're ideal for scenarios where you need continuous background operations that don't require user interaction, such as monitoring systems, scheduled tasks, or processing queues.

In this guide, we'll explore how to create, deploy, and manage Windows Services using .NET. We'll walk through the process step-by-step, from understanding the fundamentals to building practical, real-world services.

What Are Windows Services?

Windows Services (formerly known as NT services) have several key characteristics:

  • They run independently of user logins
  • They can start automatically when Windows boots
  • They have no user interface
  • They can run with specific security credentials
  • They can be started, stopped, paused, and configured through the Windows Service Control Manager

Common examples of Windows Services include web servers, database engines, antivirus software, and various system utilities.

Prerequisites

To follow along with this tutorial, you'll need:

  • Visual Studio (2019 or later recommended)
  • .NET Framework or .NET Core/.NET 5+ installed
  • Basic knowledge of C# and .NET

Creating a Basic Windows Service in .NET Framework

Let's start by creating a simple Windows Service using .NET Framework.

Step 1: Create a New Windows Service Project

  1. Open Visual Studio
  2. Go to File → New → Project
  3. Search for "Windows Service" and select "Windows Service (.NET Framework)"
  4. Name your project (e.g., "MyFirstWindowsService") and click "Create"

Visual Studio will generate a project with a service class derived from ServiceBase.

Step 2: Implement the Service Logic

Let's modify the default service to create a simple logging service that writes to a file every minute:

csharp
using System;
using System.IO;
using System.ServiceProcess;
using System.Timers;

namespace MyFirstWindowsService
{
public partial class MyService : ServiceBase
{
private Timer timer;
private string logPath = @"C:\Logs\MyWindowsService.log";

public MyService()
{
InitializeComponent();
this.ServiceName = "MyLoggingService";

// Ensure the logs directory exists
Directory.CreateDirectory(Path.GetDirectoryName(logPath));
}

protected override void OnStart(string[] args)
{
Log("Service started.");

// Set up a timer with a 60-second interval
timer = new Timer();
timer.Interval = 60000; // 60 seconds
timer.Elapsed += Timer_Elapsed;
timer.Start();
}

private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
Log($"Service running at: {DateTime.Now}");
}

protected override void OnStop()
{
if (timer != null)
{
timer.Stop();
timer.Dispose();
}

Log("Service stopped.");
}

private void Log(string message)
{
try
{
using (StreamWriter writer = File.AppendText(logPath))
{
writer.WriteLine($"{DateTime.Now} - {message}");
}
}
catch (Exception ex)
{
// In a production service, you would want proper error handling here
}
}
}
}

Step 3: Create the Service Installer

To install the Windows Service, we need to create an installer:

  1. In the Design view of your service class, right-click and select "Add Installer"
  2. Visual Studio will create a ProjectInstaller.cs file

Open ProjectInstaller.cs and modify it to configure your service:

csharp
using System.ComponentModel;
using System.Configuration.Install;
using System.ServiceProcess;

namespace MyFirstWindowsService
{
[RunInstaller(true)]
public partial class ProjectInstaller : System.Configuration.Install.Installer
{
public ProjectInstaller()
{
InitializeComponent();

// Configure the service process installer
var processInstaller = new ServiceProcessInstaller();
processInstaller.Account = ServiceAccount.LocalSystem;

// Configure the service installer
var serviceInstaller = new ServiceInstaller();
serviceInstaller.ServiceName = "MyLoggingService";
serviceInstaller.DisplayName = "My Logging Service";
serviceInstaller.Description = "A simple service that logs timestamps to a file";
serviceInstaller.StartType = ServiceStartMode.Automatic;

// Add the installers to the collection
Installers.Add(processInstaller);
Installers.Add(serviceInstaller);
}
}
}

Step 4: Create a Service Entry Point

Now, add a Program.cs file to serve as the entry point:

csharp
using System.ServiceProcess;

namespace MyFirstWindowsService
{
internal static class Program
{
static void Main()
{
ServiceBase[] ServicesToRun = new ServiceBase[]
{
new MyService()
};
ServiceBase.Run(ServicesToRun);
}
}
}

Step 5: Building and Installing the Service

  1. Build the project (Build → Build Solution)
  2. Open a Command Prompt as Administrator
  3. Navigate to the directory containing your compiled service
  4. Install the service using the InstallUtil.exe tool:
InstallUtil.exe MyFirstWindowsService.exe
  1. Start the service from the Service Control Manager or using this command:
net start MyLoggingService

Windows Services in .NET Core / .NET 5+

Microsoft has evolved the approach for Windows Services in newer .NET versions. Instead of using the traditional ServiceBase class, the recommended approach is to use the Microsoft.Extensions.Hosting API with the Microsoft.Extensions.Hosting.WindowsServices package.

Creating a Windows Service in .NET 6

Let's create a similar logging service using the modern approach:

Step 1: Create a New Project

  1. Create a new Console Application (.NET 6)
  2. Add the required NuGet packages:
dotnet add package Microsoft.Extensions.Hosting
dotnet add package Microsoft.Extensions.Hosting.WindowsServices
dotnet add package Microsoft.Extensions.DependencyInjection

Step 2: Create the Worker Service

Create a class for your background service:

csharp
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace ModernWindowsService
{
public class LoggingService : BackgroundService
{
private readonly ILogger<LoggingService> _logger;
private readonly string _logPath = @"C:\Logs\ModernService.log";

public LoggingService(ILogger<LoggingService> logger)
{
_logger = logger;
Directory.CreateDirectory(Path.GetDirectoryName(_logPath));
}

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Service started at: {time}", DateTimeOffset.Now);
WriteToFile("Service started.");

while (!stoppingToken.IsCancellationRequested)
{
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
WriteToFile($"Service running at: {DateTime.Now}");

// Wait for 1 minute
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
}

WriteToFile("Service stopped.");
}

private void WriteToFile(string message)
{
try
{
using (StreamWriter writer = File.AppendText(_logPath))
{
writer.WriteLine($"{DateTime.Now} - {message}");
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error writing to log file");
}
}
}
}

Step 3: Configure the Host

Update your Program.cs file to configure the service:

csharp
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ModernWindowsService;

IHost host = Host.CreateDefaultBuilder(args)
.UseWindowsService(options =>
{
options.ServiceName = "ModernLoggingService";
})
.ConfigureServices(services =>
{
services.AddHostedService<LoggingService>();
})
.Build();

await host.RunAsync();

Step 4: Publishing and Installing the Service

  1. Publish your application:
dotnet publish -c Release -o c:\publish\ModernWindowsService
  1. Create and start the Windows Service using SC command:
sc create ModernLoggingService binPath= "c:\publish\ModernWindowsService\ModernWindowsService.exe"
sc start ModernLoggingService
  1. To stop and remove the service:
sc stop ModernLoggingService
sc delete ModernLoggingService

Real-World Example: File Monitoring Service

Let's create a more practical Windows Service that monitors a directory for new files and processes them.

Using .NET 6 Worker Service

csharp
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.IO;

namespace FileMonitorService
{
public class FileMonitorWorker : BackgroundService
{
private readonly ILogger<FileMonitorWorker> _logger;
private readonly string _directoryToMonitor;
private readonly string _processedDirectory;
private FileSystemWatcher _watcher;

public FileMonitorWorker(ILogger<FileMonitorWorker> logger)
{
_logger = logger;
_directoryToMonitor = @"C:\FileMonitor\Input";
_processedDirectory = @"C:\FileMonitor\Processed";

// Ensure directories exist
Directory.CreateDirectory(_directoryToMonitor);
Directory.CreateDirectory(_processedDirectory);
}

protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("File Monitor Service started at: {time}", DateTimeOffset.Now);

// Setup file watcher
_watcher = new FileSystemWatcher(_directoryToMonitor);
_watcher.Created += OnFileCreated;
_watcher.Filter = "*.txt";
_watcher.EnableRaisingEvents = true;

return Task.CompletedTask; // The service keeps running until stopped
}

private void OnFileCreated(object sender, FileSystemEventArgs e)
{
_logger.LogInformation("New file detected: {file}", e.Name);

try
{
// Wait briefly to ensure file is fully written
Thread.Sleep(1000);

// Read file content
string content = File.ReadAllText(e.FullPath);

// Process file (this is a simple example)
string processedContent = content.ToUpperInvariant();

// Save processed file
string processedFilePath = Path.Combine(_processedDirectory, e.Name);
File.WriteAllText(processedFilePath, processedContent);

// Optionally delete original file
File.Delete(e.FullPath);

_logger.LogInformation("File {file} processed successfully", e.Name);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing file {file}", e.Name);
}
}

public override Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("File Monitor Service stopping");

_watcher?.Dispose();

return base.StopAsync(cancellationToken);
}
}
}

Then update your Program.cs to use this service:

csharp
using FileMonitorService;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

IHost host = Host.CreateDefaultBuilder(args)
.UseWindowsService(options =>
{
options.ServiceName = "FileMonitorService";
})
.ConfigureServices(services =>
{
services.AddHostedService<FileMonitorWorker>();
})
.Build();

await host.RunAsync();

Best Practices for Windows Services

1. Proper Error Handling

Always implement comprehensive error handling in your services, as there will be no UI to display errors:

csharp
try
{
// Service code
}
catch (Exception ex)
{
// Log the error
_logger.LogError(ex, "An error occurred");

// Optionally restart the service or take other recovery actions
}

2. Proper Logging

Implement detailed logging to help diagnose issues:

csharp
// Using ILogger with structured logging
_logger.LogInformation("Processing file {FileName} of size {FileSize} KB",
fileName, fileSize / 1024);

Consider using more advanced logging frameworks like Serilog or NLog for production services.

3. Graceful Shutdown

Ensure your service can shut down gracefully:

csharp
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
// Check the cancellation token regularly

// Do your work
await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken);
}

// Clean up resources when cancellation is requested
}

4. Resource Management

Always dispose of resources properly in your services:

csharp
public override void Dispose()
{
_timer?.Dispose();
_fileWatcher?.Dispose();
// Dispose other resources

base.Dispose();
}

5. Configuration Management

Use .NET configuration system to manage service settings:

csharp
public class FileMonitorWorker : BackgroundService
{
private readonly string _directoryToMonitor;

public FileMonitorWorker(ILogger<FileMonitorWorker> logger,
IConfiguration configuration)
{
_directoryToMonitor = configuration["Monitoring:Directory"] ??
@"C:\DefaultMonitorPath";
}
}

Debugging Windows Services

Debugging Windows Services can be challenging since they run in a different session. Here are some approaches:

  1. Debug as a Console Application: Modify your program to run as a console app during development:
csharp
static void Main(string[] args)
{
#if DEBUG
// Run as console app for debugging
var service = new MyService();
service.OnStart(args);
Console.WriteLine("Service running in console mode. Press any key to stop.");
Console.ReadKey();
service.OnStop();
#else
// Run as Windows Service in production
ServiceBase[] ServicesToRun = new ServiceBase[] { new MyService() };
ServiceBase.Run(ServicesToRun);
#endif
}
  1. Use Event Logging: For .NET Framework services, write to the Windows Event Log:
csharp
protected override void OnStart(string[] args)
{
EventLog.WriteEntry("MyService", "Service is starting", EventLogEntryType.Information);
// Rest of your code
}
  1. Use Attach to Process: You can also attach the Visual Studio debugger to a running service process.

Summary

In this guide, we've explored how to create Windows Services in both traditional .NET Framework and modern .NET Core/.NET 5+. We've covered:

  • The fundamentals of Windows Services
  • Creating basic Windows Services
  • Installing and managing services
  • Building a practical file monitoring service
  • Best practices for production services
  • Debugging techniques

Windows Services are powerful tools for running background processes in Windows environments. They're ideal for scenarios where you need continuous operation without user interaction, like scheduled tasks, monitoring systems, or automated processes.

Exercises

  1. Modify the file monitoring service to process different file types (e.g., .csv, .xml) differently.
  2. Create a Windows Service that monitors system performance (CPU, memory) and logs when certain thresholds are exceeded.
  3. Implement a Windows Service that checks a remote API periodically and stores the results in a database.
  4. Add email notifications to the file monitoring service when certain file types are detected.
  5. Create a Windows Service that syncs folders between two locations on a schedule.

Additional Resources

Happy service development!



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)