Click here to Skip to main content
15,889,449 members
Articles / All Topics

Self Installable and Runnable Service or How to Make Generic Service and Console Hybrid

Rate me:
Please Sign up or sign in to vote.
0.00/5 (No votes)
24 Jan 2012Ms-PL3 min read 10K   2  
Self installable and runnable service or how to make generic service and console hybrid

Frankly, I thought that one of basic things in Windows development, such as “debugability” and “installability” of services, were changed for the last 10 years in development environments. However, I was disappointed to discover that nothing actually changed. You still cannot build easy to debug (in command line) service, which also can be installed without special additional tools.

image

Even ServiceName/ServiceNameInstaller trick, is specific for the current assembly and cannot be used if your base class is not the one you are really using. This is not the only approach. Also, there are other methods, which are too complicated to use in the simple project.

So, I decided to write quick basic service which can be used as common base for self-installable and debugable service development. Let’s start.

First of all, we need an abstract service base:

C#
public abstract class ServiceProvider : ServiceBase

Then is the identification for derived classes:

C#
public static string Name;     
public static string ProviderKind;
public ServiceProvider(string name) {     
         ProviderKind = name;      
         Name = "MagicService." + ProviderKind;      
         ServiceName = Name;      
      }

Now method to start it from the hosting application (e.g. command prompt) or run it if installed.

C#
/// <summary>Starts the provider service in interactive mode.</summary>     
public void Start(string[] args) {      
   if (Environment.UserInteractive) {      
      OnStart(args);      
   } else {      
      ServiceBase.Run(this);      
   }      
}

But how to install it? Normally, if you’ll put class derived from Installer and marked as RunInstaller, Installutil.exe can initialize it and install or uninstall the service.

C#
public class ServiceProviderInstaller : Installer {     
   private static readonly string ServiceName = ServiceProvider.Name;      
   
   public ServiceProviderInstaller() {      
      var processInstaller = new ServiceProcessInstaller {      
         Account = ServiceAccount.LocalSystem      
      };
         var serviceInstaller = new ServiceInstaller {     
         DisplayName = "Magic Server " + ServiceProvider.ProviderKind + " Provider",      
         Description = "Process the interface to the Magic service " + 
                        ServiceProvider.ProviderKind + " provider.",      
         ServiceName = ServiceName,      
         StartType = ServiceStartMode.Automatic,      
      };      
    
      this.Installers.Add(processInstaller);      
      this.Installers.Add(serviceInstaller);      
   }

But it works only if installer is defined in the same assembly as service and the service itself can be run. In our case, this is not true. Service is abstract and we allow to run service from any other assembly which is referenced to the base one. So what to do? Simple! Let’s create our own installer. We will create the private instance of installer inside the actual service itself and pass it as additional installer to basic TransactedInstaller. Also, we’ll use calling (the actual running) assembly as the service reference.

C#
/// <summary>Installs the provider service in interactive mode.</summary>     
public void Install() {      
   if (Environment.UserInteractive) {      
      var ti = new TransactedInstaller();      
      var spi = new ServiceProviderInstaller();      
      ti.Installers.Add(spi);      
      var path = "/assemblypath=" + Assembly.GetEntryAssembly().Location;      
      var ctx = new InstallContext("", new string[] { path });      
      ti.Context = ctx;      
      ti.Install(new Hashtable());      
   }      
}

The same way we’ll do uninstaller:

C#
/// <summary>Uninstalls the provider service in interactive mode.</summary>     
public void Uninstall() {      
   if (Environment.UserInteractive) {      
      var ti = new TransactedInstaller();      
      var spi = new ServiceProviderInstaller();      
      ti.Installers.Add(spi);      
      var path = "/assemblypath=" + Assembly.GetEntryAssembly().Location;      
      var ctx = new InstallContext("", new string[] { path });      
      ti.Context = ctx;      
      ti.Uninstall(null);      
   }      
}

We're almost done, the only problem is Component Designer which wants to be run when you click on any class derived from ServiceBase. I know that Visual Studio developers wanted to do our life easier, but this designer (especially one cannot initialize abstract classes) is very annoying. In order to get rid of this thing, we can override DesignerCategory of the class and tell VS that it is not SeriviceComponent anymore. To do this, all we need is one small attribute set on classes.

C#
[System.ComponentModel.DesignerCategory("")]     
public abstract class ServiceProvider : ServiceBase {      
…      
[RunInstaller(true), System.ComponentModel.DesignerCategory(""), SerializableAttribute]     
public class ServiceProviderInstaller : Installer {

Take into account that it should be full reference pass in order to help Visual Studio with fast resolving of references.

We're done, let’s put everything together and see what we have and how to use it.

C#
/// <summary>Provides service class to respond to 
/// service control manager (all responses are defaults).</summary>     
[System.ComponentModel.DesignerCategory("")]      
public abstract class ServiceProvider : ServiceBase {
   public static string Name;     
   public static string ProviderKind;
   public ServiceProvider(string name) {     
      ProviderKind = name;      
      Name = "Magic.Provider." + ProviderKind;      
      ServiceName = Name;      
   }
   /// <summary>Starts the provider service in interactive mode.</summary>      
   public void Start(string[] args) {      
      if (Environment.UserInteractive) {      
         OnStart(args);      
      } else {      
         ServiceBase.Run(this);      
      }      
   }
   /// <summary>Installs the provider service in interactive mode.</summary>     
   public void Install() {      
      if (Environment.UserInteractive) {      
         var ti = new TransactedInstaller();      
         var spi = new ServiceProviderInstaller();      
         ti.Installers.Add(spi);      
         var path = "/assemblypath=" + Assembly.GetEntryAssembly().Location;      
         var ctx = new InstallContext("", new string[] { path });      
         ti.Context = ctx;      
         ti.Install(new Hashtable());      
      }      
   }
   /// <summary>Uninstalls the provider service in interactive mode.</summary>     
   public void Uninstall() {      
      if (Environment.UserInteractive) {      
         var ti = new TransactedInstaller();      
         var spi = new ServiceProviderInstaller();      
         ti.Installers.Add(spi);      
         var path = "/assemblypath=" + Assembly.GetEntryAssembly().Location;      
         var ctx = new InstallContext("", new string[] { path });      
         ti.Context = ctx;      
         ti.Uninstall(null);      
      }      
   }      
}
[RunInstaller(true), System.ComponentModel.DesignerCategory(""), SerializableAttribute]     
public class ServiceProviderInstaller : Installer {      
   private static readonly string ServiceName = ServiceProvider.Name;      
   
   public ServiceProviderInstaller() {      
      var processInstaller = new ServiceProcessInstaller {      
         Account = ServiceAccount.LocalSystem      
      };
      var serviceInstaller = new ServiceInstaller {     
         DisplayName = "Magic Service " + 
                       ServiceProvider.ProviderKind + " Provider",      
         Description = "Process the interface to the Magic service " 
                       + ServiceProvider.ProviderKind + " provider.",      
         ServiceName = ServiceName,      
         StartType = ServiceStartMode.Automatic,      
      };      
    
      this.Installers.Add(processInstaller);      
      this.Installers.Add(serviceInstaller);      
   }
   protected override void OnCommitted(IDictionary savedState) {     
      base.OnCommitted(savedState);      
      var c = new ServiceController(ServiceName);      
      c.Start();      
   }      
}

In order to use it, just reference the hosting assembly and inherit this class:

C#
   public abstract class SampleService : ServiceProvider {
      /// <summary>Creates a new <see cref="SampleService"/> instance.
      /// </summary>     
      public SampleService()      
         : base("Sample") {      
      }      
}

And run it from command prompt:

C#
class Program {     
   static void Main(string[] args) {      
      var p = new SampleService();      
      if (args.Length > 0) {      
         if (args[0] == "/i"){      
            p.Install();      
            return;      
         }      
         if (args[0] == "/u") {      
            p.Uninstall();      
            return;      
         }      
      }      
      p.Start(null);      
      Console.Read();      
      p.Stop();      
   }      
}

We're done. The only remaining thing is how to prevent component designer appearance on derived (SampleService) class. As for now, I found no way to do this and asked collective intelligence to help me with it. Once I’ll have an answer, I will update it here.

Be good people and have a good day!

Related Posts

  1. RSA private key import from PEM format in C#
  2. Video encoder and metadata reading by using Windows Media Foundation
This article was originally posted at http://khason.net?p=2192

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
Architect Better Place
Israel Israel
Hello! My name is Tamir Khason, and I am software architect, project manager, system analyst and [of course] programmer. In addition to writing big amount of documentation, I also write code, a lot of code. I used to work as a freelance architect, project manager, trainer, and consultant here, in Israel, but recently join the company with extremely persuasive idea - to make a world better place. I have very pretty wife and 3 charming kids, but unfortunately almost no time for them.

To be updated within articles, I publishing, visit my blog or subscribe RSS feed. Also you can follow me on Twitter to be up to date about my everyday life.

Comments and Discussions

 
-- There are no messages in this forum --