.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
- Open Visual Studio
- Go to File → New → Project
- Search for "Windows Service" and select "Windows Service (.NET Framework)"
- 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:
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:
- In the Design view of your service class, right-click and select "Add Installer"
- Visual Studio will create a
ProjectInstaller.cs
file
Open ProjectInstaller.cs
and modify it to configure your service:
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:
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
- Build the project (Build → Build Solution)
- Open a Command Prompt as Administrator
- Navigate to the directory containing your compiled service
- Install the service using the InstallUtil.exe tool:
InstallUtil.exe MyFirstWindowsService.exe
- 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
- Create a new Console Application (.NET 6)
- 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:
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:
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
- Publish your application:
dotnet publish -c Release -o c:\publish\ModernWindowsService
- Create and start the Windows Service using SC command:
sc create ModernLoggingService binPath= "c:\publish\ModernWindowsService\ModernWindowsService.exe"
sc start ModernLoggingService
- 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
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:
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:
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:
// 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:
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:
public override void Dispose()
{
_timer?.Dispose();
_fileWatcher?.Dispose();
// Dispose other resources
base.Dispose();
}
5. Configuration Management
Use .NET configuration system to manage service settings:
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:
- Debug as a Console Application: Modify your program to run as a console app during development:
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
}
- Use Event Logging: For .NET Framework services, write to the Windows Event Log:
protected override void OnStart(string[] args)
{
EventLog.WriteEntry("MyService", "Service is starting", EventLogEntryType.Information);
// Rest of your code
}
- 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
- Modify the file monitoring service to process different file types (e.g., .csv, .xml) differently.
- Create a Windows Service that monitors system performance (CPU, memory) and logs when certain thresholds are exceeded.
- Implement a Windows Service that checks a remote API periodically and stores the results in a database.
- Add email notifications to the file monitoring service when certain file types are detected.
- Create a Windows Service that syncs folders between two locations on a schedule.
Additional Resources
- Microsoft Docs: Windows Service Applications
- Microsoft Docs: Worker Services in .NET
- GitHub: .NET Worker Service Examples
- Top-level Statements in .NET for Worker Services
Happy service development!
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)