InfiniTec - Henning Krauses Blog

Don't adjust your mind - it's reality that is malfunctioning

Developing Windows Services with .NET

Developing Windows services with .NET is not always easy – sure, one can attach a debugger once the service is started (at least when developing on Windows XP / 2003). But this is not exactly an ‘F5’ experience one has with normal programs or even Websites. Another drawback of the “Attach to process” method is that the start of the service cannot be debugged. A common way to circumvent this is to write a System.Diagnostics.Debugger.Break statement within the first lines. This will bring up the Just-In-Time Debugger window that let’s you choose a debugger. Sadly, this does not work any more with Windows Vista / 2008 because services run on an isolated window station.

Luckily, there is a solution: A windows service is nothing more than a console application that is started slightly differently. One can take advantage of this by starting the service as a stand-alone program when started normally (via F5 or starting the service from the command line). But if the program detects if it is started by the Service Control manager, run it as a service. So, how can a program check whether it is started by the Service control manager? It depends on the user account the program runs under. If it’s running as “LocalSystem”, one can safely assume it’s running as a service. In any other case, the user account token has a special group membership: The System.Security.Principal.WellknownSidType.Service.

The default body of a program.cs file of blank service solution looks like this:

   1: using System.ServiceProcess;
   2:  
   3: namespace WindowsService1
   4: {
   5:     static class Program
   6:     {
   7:         /// <summary>
   8:         /// The main entry point for the application.
   9:         /// </summary>
  10:         static void Main()
  11:         {
  12:             var servicesToRun = new ServiceBase[] 
  13:                                               { 
  14:                                                   new Service1() 
  15:                                               };
  16:             ServiceBase.Run(servicesToRun);
  17:         }
  18:     }
  19: }

With a few additional lines, a comfortable F5 experience can be gained:

   1: using System;
   2: using System.Linq;
   3: using System.Security.Principal;
   4: using System.ServiceProcess;
   5:  
   6: namespace WindowsService1
   7: {
   8:     static class Program
   9:     {
  10:         /// <summary>
  11:         /// The main entry point for the application.
  12:         /// </summary>
  13:         static void Main()
  14:         {
  15:             var identity = WindowsIdentity.GetCurrent();
  16:             var principal = new WindowsPrincipal(identity);
  17:             // Check whether the current user account is the LocalSystem account
  18:             var isLocalSystemUser = 
  19:                 identity.User == new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null);
  20:  
  21:             if (isLocalSystemUser || principal.IsInRole(new SecurityIdentifier(WellKnownSidType.ServiceSid, null)))
  22:             {
  23:                 var servicesToRun = new ServiceBase[]
  24:                                         {
  25:                                             new Service1()
  26:                                         };
  27:                 ServiceBase.Run(servicesToRun);
  28:             }
  29:             else
  30:             {
  31:                 var service = new Service1();
  32:                 service.StartAsApplication();
  33:             }
  34:         }
  35:  
  36:     }
  37: }

I have added a StartAsApplication() method to the Service1 class, because the OnStart() method of the default service template is protected and therefore not accessible. And that is all there is. The service can now be started by just pressing F5.

Drawbacks

The gain of the F5 experience does not come without a cost – normally, a Windows service runs under a special account (which is hopefully not LocalSystem): NetworkService, LocalService or a dedicated service account. With this approach, the service runs with the permissions of the currently logged on user. Normally, that user account has far more security permissions than the service account would have. For example, the current user might have the permission to host WCF services on arbitrary ports – something only administrators are allowed to do. A service account would need explicit permission to host that service. Another example are private keys of certificates in the LocalComputer store – not accessible for the NetworkService account by default. But if a service needs to access those keys, it won’t fail unless during development. This should be kept in mind.!


Posted by Henning Krause on Saturday, June 13, 2009 12:26 PM, last modified on Monday, November 29, 2010 7:30 PM
Permalink | Post RSSRSS comment feed