James Michael Hare

...hare-brained ideas from the realm of software development...
posts - 136 , comments - 1089 , trackbacks - 0

My Links

News

Welcome to my blog! I'm a Sr. Software Development Engineer in Seattle, WA. I've been doing C++/C#/Java development for over 18 years, but have definitely learned that there is always more to learn!

All thoughts and opinions expressed in my blog and my comments are my own and do not represent the thoughts of my employer.

Blogs I Read

MCC Logo MVP Logo

Follow BlkRabbitCoder on Twitter

Tag Cloud

Archives

Post Categories

C# Toolbox: A Debuggable, Self-Installing Windows Service Template (2 of 2)

Update: I have now placed the zip containing the source for the end result of part 1 and 2 of this service template here.

Two weeks ago I began the series with a discussion on how to make a new C# Windows Service template that is “debuggable” (you can find the article here).  I had then intended the next week to follow up with a discussion on how to modify that template to make the service “self-installing”.

Unfortunately, with my work schedule I wasn’t able to complete the series last week due to a product deployment schedule that involved a lot of nights, so I did a brief piece on features I would like to see in C# 5.0 and beyond instead (here).

But now my evenings have quieted down again and I’m ready to discuss part two of my C# Windows Services template which will cover how to make your service “self-installing”.

Taking the debuggable service template we built from the last discussion, we will now build on it and add the ability to let the service install itself.

The Other Problem With The Default Template

The second problem with the existing C# Windows Service template (the first being that its hard to debug) is that the code generated can’t be run under the debugger, and you have to write your own installer for it.

Unfortunately, this is a manual process that must be done each time, unless, of course, we write a helper class and incorporate it into our template.

You’ll notice we have a lot of helper code going into our template now.  Unfortunately, with generating templates there is not an easy way to extract references that aren’t part of the .NET Framework (or aren’t in the GAC).

So for now we will have our helper classes in a solution folder called Framework, although we could easily extract it into its own class library, sign it, and put it in the GAC instead if we chose to make our template more clean.

Building an Installable Service

The first thing I’m going to do is reorganize a few of my “helper” classes to put them in a folder called Framework and modify the namespaces accordingly.  This will make it easy to extract it into a class library later if we so choose:

folder2

Now that we’ve isolated a lot of our extra helper code into its own folder which (when closed) makes our solution look much cleaner.  This is the same code we’d pull into a class library (perhaps called WindowsServiceFramework) later if we so choose.

Now that we’ve cleaned up a bit, let’s think about how we can tell this service how to install itself.  Basically we need to be able to provide the details on what the service is called and how it behaves. 

We could just call a method call, but that feels clunky.  On things like this where you are specifying static configuration information (such as installer data), I prefer to use attributes.  I’d like to be able to decorate my ServiceImplementation class with an attribute that will tell it how to install itself.  In my mind this makes the most sense and is the most obvious.

This is how I envision it being used by someone using this template:

   1: [WindowsService("SystemMonitorProcessingService",
   2:     DisplayName = "System Monitor Processing Service",
   3:     Description = "The message processing service for the system monitor suite.",
   4:     StartMode = ServiceStartMode.Automatic)]
   5: public class ServiceImplementation : IWindowsService
   6: {
   7:     // ...
   8: }

 So let’s define this attribute, in the Framework folder:

   1: [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
   2: public class WindowsServiceAttribute : Attribute
   3: {
   4:     // The name of the service.
   5:     public string Name { get; set; }
   6:  
   7:     // The displayable name that shows in service manager (defaults to Name).
   8:     public string DisplayName { get; set; }
   9:  
  10:     // A textural description of the service name (defaults to Name).
  11:     public string Description { get; set; }
  12:  
  13:     // The user to run the service under (defaults to null).  A null or empty
  14:     // UserName field causes the service to run as ServiceAccount.LocalService.
  15:     public string UserName { get; set; }
  16:  
  17:     // The password to run the service under (defaults to null).  Ignored
  18:     // if the UserName is empty or null, this property is ignored.
  19:     public string Password { get; set; }
  20:  
  21:     // Specifies the event log source to set the service's EventLog to.  If this is
  22:     // empty or null (the default) no event log source is set.  If set, will auto-log
  23:     // start and stop events.
  24:     public string EventLogSource { get; set; }
  25:  
  26:     // The method to start the service when the machine reboots (defaults to Manual).
  27:     public ServiceStartMode StartMode { get; set; }
  28:  
  29:     // True if service supports pause and continue (defaults to true).
  30:     public bool CanPauseAndContinue { get; set; }
  31:  
  32:     // True if service supports shutdown event (defaults to true).
  33:     public bool CanShutdown { get; set; }
  34:  
  35:     // True if service supports stop event (defaults to true).
  36:     public bool CanStop { get; set; }
  37:  
  38:     // Marks an IWindowsService with configuration and installation attributes.
  39:     public WindowsServiceAttribute(string name)
  40:     {
  41:         // set name and default description and display name to name.
  42:         Name = name;
  43:         Description = name;
  44:         DisplayName = name;
  45:  
  46:         // default all other attributes.
  47:         CanStop = true;
  48:         CanShutdown = true;
  49:         CanPauseAndContinue = true;
  50:         StartMode = ServiceStartMode.Manual;
  51:         EventLogSource = null;
  52:         Password = null;
  53:         UserName = null;
  54:     }
  55: }

Now that we have that attribute defined, we can give our ServiceImplementation class a boiler-plate version of this attribute that the user will fill in later after creating the template.

   1: // Remember, $safeprojectname$ and $projectname$ are template parameters 
   2: // that get filled in when the code is generated.
   3: [WindowsService("$safeprojectname$",
   4:     DisplayName = "$projectname$",
   5:     Description = "The description of the $projectname$ service.",
   6:     EventLogSource = "$projectname$",
   7:     StartMode = ServiceStartMode.Automatic)]
   8: public class ServiceImplementation : IWindowsService
   9: {
  10:     // ...
  11: }

Okay, so now we have an attribute!  Unfortunately, attributes are not magic.  You have to query them and then use them.  I have a helper extension method I like to use that makes it easier to query class attributes (I have another one for assembly attributes as well), so we’ll drop that in our Framework too but feel free to use it elsewhere.

   1: // some of my favorite extension methods off of the Type class
   2: public static class TypeExtensions
   3: {
   4:     // Queries a type for a list of all attributes of a specific type.
   5:     public static IEnumerable<T> GetAttributes<T>(this Type typeWithAttributes)
   6:         where T : Attribute
   7:     {
   8:         // Try to find the configuration attribute for the default logger if it exists
   9:         object[] configAttributes = Attribute.GetCustomAttributes(typeWithAttributes,
  10:             typeof(T), false);
  11:     
  12:         // get just the first one
  13:         if (configAttributes != null && configAttributes.Length > 0)
  14:         {
  15:             foreach (T attribute in configAttributes)
  16:             {
  17:                 yield return attribute;
  18:             }
  19:         }
  20:     }
  21:  
  22:     // Queries a type for the first attribute of a specific type.
  23:     public static T GetAttribute<T>(this Type typeWithAttributes)
  24:         where T : Attribute
  25:     {
  26:         return GetAttributes<T>(typeWithAttributes).FirstOrDefault();
  27:     }    
  28: }

 Now that we have that, we can query the attribute easily off of our IWindowsService implementation.  But once we have that data what do we do with it?  First, we need to use this attribute to actually change our service behavior.

[Update: I had accidentally ommitted the update to the WindowsServiceHarness.cs, Sorry!  Thanks for the catch Sean!]

Now we must update the WindowsServiceHarness to take a configuration in the constructor and use it to configure the service:

   1: // A generic Windows Service that can handle any assembly that
   2: // implements IWindowsService (including AbstractWindowsService) 
   3: public sealed partial class WindowsServiceHarness : ServiceBase
   4: {
   5:     // Get the class implementing the windows service
   6:     public IWindowsService ServiceImplementation { get; private set; }
   7:  
   8:     // Constructor a generic windows service from the given class
   9:     public WindowsServiceHarness(IWindowsService serviceImplementation)
  10:     {
  11:         // make sure service passed in is valid
  12:         if (serviceImplementation == null)
  13:         {
  14:             throw new ArgumentNullException("serviceImplementation",
  15:                 "IWindowsService cannot be null in call to GenericWindowsService");
  16:         }
  17:  
  18:         // set instance and backward instance
  19:         ServiceImplementation = serviceImplementation;
  20:  
  21:         // configure our service
  22:         ConfigureServiceFromAttributes(serviceImplementation);
  23:     }
  24:  
  25:  
  26:     // Pass disposal off to service disposal method
  27:     protected override void Dispose(bool disposing)
  28:     {
  29:         if (disposing)
  30:         {
  31:             ServiceImplementation.Dispose();
  32:  
  33:             if(_components != null)
  34:             {
  35:                 _components.Dispose();
  36:             }
  37:         }
  38:         base.Dispose(disposing);
  39:     }
  40:  
  41:     // Override service control on continue
  42:     protected override void OnContinue()
  43:     {
  44:         // perform class specific behavior 
  45:         ServiceImplementation.OnContinue();
  46:     }
  47:  
  48:     // Called when service is paused
  49:     protected override void OnPause()
  50:     {
  51:         // perform class specific behavior 
  52:         ServiceImplementation.OnPause();
  53:     }
  54:  
  55:  
  56:     // Called when a custom command is requested
  57:     protected override void OnCustomCommand(int command)
  58:     {
  59:         // perform class specific behavior 
  60:         ServiceImplementation.OnCustomCommand(command);
  61:     }
  62:  
  63:     // Called when the Operating System is shutting down
  64:     protected override void OnShutdown()
  65:     {
  66:         // perform class specific behavior
  67:         ServiceImplementation.OnShutdown();
  68:     }
  69:  
  70:     // Called when service is requested to start
  71:     protected override void OnStart(string[] args)
  72:     {
  73:         ServiceImplementation.OnStart(args);
  74:     }
  75:  
  76:     // Called when service is requested to stop
  77:     protected override void OnStop()
  78:     {
  79:         ServiceImplementation.OnStop();
  80:     }
  81:  
  82:     // Set configuration data
  83:     private void ConfigureServiceFromAttributes(IWindowsService serviceImplementation)
  84:     {
  85:         var attribute = serviceImplementation.GetType().GetAttribute<WindowsServiceAttribute>();
  86:  
  87:         if(attribute != null)
  88:         {
  89:             EventLog.Source = string.IsNullOrEmpty(attribute.EventLogSource)
  90:                                 ? "WindowsServiceHarness"
  91:                                 : attribute.EventLogSource;
  92:  
  93:             CanStop = attribute.CanStop;
  94:             CanPauseAndContinue = attribute.CanPauseAndContinue;
  95:             CanShutdown = attribute.CanShutdown;
  96:  
  97:             // we don't handle: laptop power change event
  98:             CanHandlePowerEvent = false;
  99:  
 100:             // we don't handle: Term Services session event
 101:             CanHandleSessionChangeEvent = false;
 102:  
 103:             // always auto-event-log 
 104:             AutoLog = true;
 105:         }
 106:         else
 107:         {
 108:             throw new InvalidOperationException(
 109:                 string.Format("IWindowsService implementer {0} must have a WindowsServiceAttribute.",
 110:                               serviceImplementation.GetType().FullName));
 111:         }
 112:     }
 113: }

Once we've done that, we have to write an actual installer class that uses the values to install or uninstall the windows service.  The first thing we need to do in writing an installer is to add a reference to System.Configuration.Install.  This gives us access to the Install class which we will subclass to make our windows service installer.  Once we add this reference, right-click on the Framework folder and add a Installer Class item.  This will give you an Install class component which we can then fill in with the details of how to install our service.

Go into the code behind of the service and change the code to:

   1: // A generic windows service installer
   2: [RunInstaller(true)]
   3: public partial class WindowsServiceInstaller : Installer
   4: {
   5:     // Gets or sets the type of the windows service to install.
   6:     public WindowsServiceAttribute Configuration { get; set; }
   7:  
   8:     // Creates a blank windows service installer with configuration in ServiceImplementation
   9:     public WindowsServiceInstaller() : this(typeof(ServiceImplementation))
  10:     {
  11:     }
  12:  
  13:     // Creates a windows service installer using the type specified.
  14:     public WindowsServiceInstaller(Type windowsServiceType)
  15:     {
  16:         if (!windowsServiceType.GetInterfaces().Contains(typeof(IWindowsService)))
  17:         {
  18:             throw new ArgumentException("Type to install must implement IWindowsService.",
  19:                                         "windowsServiceType");
  20:         }
  21:  
  22:         var attribute = windowsServiceType.GetAttribute<WindowsServiceAttribute>();
  23:  
  24:         if (attribute == null)
  25:         {
  26:             throw new ArgumentException("Type to install must be marked with a WindowsServiceAttribute.",
  27:                                         "windowsServiceType");
  28:         }
  29:  
  30:         Configuration = attribute;
  31:     }
  32:  
  33:     // Performs a transacted installation at run-time of the AutoCounterInstaller and any other listed installers.
  34:     public static void RuntimeInstall<T>()
  35:         where T : IWindowsService
  36:     {
  37:         string path = "/assemblypath=" + Assembly.GetEntryAssembly().Location;
  38:  
  39:         using (var ti = new TransactedInstaller())
  40:         {
  41:             ti.Installers.Add(new WindowsServiceInstaller(typeof(T)));
  42:             ti.Context = new InstallContext(null, new[] { path });
  43:             ti.Install(new Hashtable());
  44:         }
  45:     }
  46:  
  47:     // Performs a transacted un-installation at run-time of the AutoCounterInstaller and any other listed installers.
  48:     public static void RuntimeUnInstall<T>(params Installer[] otherInstallers)
  49:         where T : IWindowsService
  50:     {
  51:         string path = "/assemblypath=" + Assembly.GetEntryAssembly().Location;
  52:  
  53:         using (var ti = new TransactedInstaller())
  54:         {
  55:             ti.Installers.Add(new WindowsServiceInstaller(typeof(T)));
  56:             ti.Context = new InstallContext(null, new[] { path });
  57:             ti.Uninstall(null);
  58:         }
  59:     }
  60:  
  61:     // Installer class, to use run InstallUtil against this .exe
  62:     public override void Install(System.Collections.IDictionary savedState)
  63:     {
  64:         ConsoleHarness.WriteToConsole(ConsoleColor.White, "Installing service {0}.", Configuration.Name);
  65:  
  66:         // install the service 
  67:         ConfigureInstallers();
  68:         base.Install(savedState);
  69:     }
  70:  
  71:     // Removes the counters, then calls the base uninstall.
  72:     public override void Uninstall(System.Collections.IDictionary savedState)
  73:     {
  74:         ConsoleHarness.WriteToConsole(ConsoleColor.White, "Un-Installing service {0}.", Configuration.Name);
  75:  
  76:         // load the assembly file name and the config
  77:         ConfigureInstallers();
  78:         base.Uninstall(savedState);
  79:     }
  80:  
  81:     // Method to configure the installers
  82:     private void ConfigureInstallers()
  83:     {
  84:         // load the assembly file name and the config
  85:         Installers.Add(ConfigureProcessInstaller());
  86:         Installers.Add(ConfigureServiceInstaller());
  87:     }
  88:  
  89:     // Helper method to configure a process installer for this windows service
  90:     private ServiceProcessInstaller ConfigureProcessInstaller()
  91:     {
  92:         var result = new ServiceProcessInstaller();
  93:  
  94:         // if a user name is not provided, will run under local service acct
  95:         if (string.IsNullOrEmpty(Configuration.UserName))
  96:         {
  97:             result.Account = ServiceAccount.LocalService;
  98:             result.Username = null;
  99:             result.Password = null;
 100:         }
 101:         else
 102:         {
 103:             // otherwise, runs under the specified user authority
 104:             result.Account = ServiceAccount.User;
 105:             result.Username = Configuration.UserName;
 106:             result.Password = Configuration.Password;
 107:         }
 108:  
 109:         return result;
 110:     }
 111:  
 112:     // Helper method to configure a service installer for this windows service
 113:     private ServiceInstaller ConfigureServiceInstaller()
 114:     {
 115:         // create and config a service installer
 116:         var result = new ServiceInstaller
 117:         {
 118:             ServiceName = Configuration.Name,
 119:             DisplayName = Configuration.DisplayName,
 120:             Description = Configuration.Description,
 121:             StartType = Configuration.StartMode,
 122:         };
 123:  
 124:         return result;
 125:     }
 126: }

Yes that’s a mass of code, but it’s really not that bad, here’s the highlights:

  • Construtor – queries the IWindowsService implementing type for the WindowsServiceAttribute and uses that as our configuration.
  • Install() – this virtual method is overridden from the Install class and is invoked by installutil for installation.
  • Uninstall()this virtual method is overridden from the Install class and is invoked by installutil for uninstallation.
  • RuntimeInstall() – this additional method is used to correctly invoke the Install() method at runtime (instead of installutil).
  • RuntimeUnInstall() – this additional method is used to correctly invoke the Uninstall() method at runtime (instead of installutil).
  • ConfigureXxx() – Windows Services need both a process and service installed, these configuration methods examine our WindowsServiceAttribute and create the correct sub-installers.

So, once we have this installer class, we can modify our Program class to check for an –install or –uninstall command line option and call the installer directly (note, you must have administrator access on a machine to install a service on it):

   1: static class Program
   2: {
   3:     // The main entry point for the windows service application.
   4:     static void Main(string[] args)
   5:     {
   6:         // if install was a command line flag, then run the installer at runtime.
   7:         if (args.Contains("-install", StringComparer.InvariantCultureIgnoreCase))
   8:         {
   9:             WindowsServiceInstaller.RuntimeInstall<ServiceImplementation>();
  10:         }
  11:  
  12:         // if uninstall was a command line flag, run uninstaller at runtime.
  13:         else if (args.Contains("-uninstall", StringComparer.InvariantCultureIgnoreCase))
  14:         {
  15:             WindowsServiceInstaller.RuntimeUnInstall<ServiceImplementation>();
  16:         }
  17:  
  18:         // otherwise, fire up the service as either console or windows service based on UserInteractive property.
  19:         else
  20:         {
  21:             var implementation = new ServiceImplementation();
  22:  
  23:             // if started from console, file explorer, etc, run as console app.
  24:             if (Environment.UserInteractive)
  25:             {
  26:                 ConsoleHarness.Run(args, implementation);
  27:             }
  28:  
  29:                 // otherwise run as a windows service
  30:             else
  31:             {
  32:                 ServiceBase.Run(new WindowsServiceHarness(implementation));
  33:             }
  34:         }
  35:     }
  36: }

The Contains() method is a nice LINQ extension method that allows you to check for the existence of an item in a collection, it is much better than the Contains() method in arrays because it allows you to specify a comparer, in this case we can use StringComparer.InvariantCultureIgnoreCase so that we do a string-insensitive compare so that variations like –UNINSTALL, –UnInstall, etc all work.

Now, we’ve got all our code, so we should make sure that everything compiles well and then change our namespace to use the $safeprojectname$ template parameter.

Helpful Hint: because template parameters do not compile, I like to use a simple namespace in my templates as I’m working on them (for instance, DebuggableInstallableService) and then use menu option Edit –> Find and Replace –> Replace in Files… to replace all instances of that temporary namespace to $safeprojectname$ before exporting the template.

 

 

So, before we export, take a final look at your project, it should look something like this when all expanded:

final2

So, if I’ve explained all my steps adequately and you’ve followed them correctly we should be good to go, let’s export into a template.  Choose the menu option File –> Export Template…

export2

 

Keep the default options, and hit [Next >]

done2

Make sure you fill in the details that you would like to see for the service name, description, and appropriate pictures (for the pretty factor).  Make sure the Automatically import the template into Visual Studio checkbox is selected (so you can use it right away).

You can also select Display an explorer window on the output files folder checkbox if you wish to copy or email the template file to friends and co-workers so they can import it (by double-clicking on it) as well.

Once you fill in everything, press [Finish] to generate and import the template.

Going for a Test Drive…

So, your template is now in Visual Studio and ready to use!  Let’s create a simple test application to verify everything works as planned!  So close whatever you are currently working on in Visual Studio and create a new project, from the project template list, select your new debuggable installable windows service template:

newProject

 

Enter an appropriate test project name, and select [OK] to generate the project.  If all goes well, you should be presented with a project that contains all our template code including the Framework folder and its classes.  Let’s add some simple console writes to the ServiceImplementation class just for kicks and grins:

   1: [WindowsService("TestInstallableService",
   2:     DisplayName = "TestInstallableService",
   3:     Description = "The description of the TestInstallableService service.",
   4:     EventLogSource = "TestInstallableService",
   5:     StartMode = ServiceStartMode.Automatic)]
   6: public class ServiceImplementation : IWindowsService
   7: {
   8:     // This method is called when the service gets a request to start.
   9:     public void OnStart(string[] args)
  10:     {
  11:         ConsoleHarness.WriteToConsole(ConsoleColor.Green, "Hi mom!");
  12:     }
  13:  
  14:     // This method is called when the service gets a request to stop.
  15:     public void OnStop()
  16:     {
  17:         ConsoleHarness.WriteToConsole(ConsoleColor.Red, "Bye mom!");
  18:     }
  19:  
  20:     // ...
  21: }

Now try to run your service from the debugger, you should see:

output

So our debuggable service aspect still works, now let’s try the installer aspects.  Let’s try using installutil to verify we can install the service using the .NET installutil tool.  Start a Visual Studio command prompt and then navigate to your project output folder and type:

     installutil –i TestInstallableService.exe

Substitute whatever you called the test project name of course for the executable above, you should see some output like:

installer

If everything was correct and you have admin authority on your box, you should see a success message.  Now, if you open your administrative services console, you should see your new service installed as a service:

services

It’s there and ready to run as a service!  But I promised you the service was self-installing as well and I wasn’t lying.  Why do we care?  Because installutil is often not in the path of your production machines and so it’s just a lot easier to have the option to have the service install itself.

So uninstall your service real quick using installutil again:

     installutil –u TestInstallableService.exe

 

 

 

 

You should see a success message, now try using the –install command line parameter from your executable directly, from the command prompt type:

     TestInstallableService –install

And once again you should see it go through the install steps and you should verify the service exists in the services menu. 

Summary

Windows Services can be great tools for long-running processes that handle asynchronous requests from queues or sockets, unfortunately the default Windows Service template does not make it easy to debug or install the services.

Hopefully, by following these code examples, I’ve shown you some hints that can make your own Windows Services more robust and create a new Windows Service template of your own so you don’t have to repeat the steps every time.

Hope you enjoyed it!

 

 Technorati Tags: , , , , , ,

 

Print | posted on Thursday, October 7, 2010 4:43 PM | Filed Under [ My Blog C# Software .NET Toolbox ]

Feedback

Gravatar

# re: C# Windows Services (2 of 2): Self-Installing Windows Service Template

First off, thanks for this great article. Neat stuff.

One question though, it seems no matter what I do the EventLogSource is not getting registered correctly. The Application log always shows it as Service1, even though I add the attribute. Any ideas?

Cheers,
-Sean
10/13/2010 9:09 AM | Sean
Gravatar

# re: C# Windows Services (2 of 2): Self-Installing Windows Service Template

Okay, got it to work by editing the service harness:

public WindowsServiceHarness(IWindowsService implementation)
: this()
{
var configuration = implementation.GetType().GetAttribute<WindowsServiceAttribute>();
AutoLog = configuration.AutoLog;
CanPauseAndContinue = configuration.CanPauseAndContinue;
CanStop = configuration.CanStop;
CanShutdown = configuration.CanShutdown;
if (!string.IsNullOrEmpty(configuration.EventLogSource))
{
if (!EventLog.SourceExists(configuration.EventLogSource))
EventLog.CreateEventSource(configuration.EventLogSource, "Application");
EventLog.Source = configuration.EventLogSource;
}
_implementer = implementation;
}
10/13/2010 11:33 AM | Sean
Gravatar

# re: C# Windows Services (2 of 2): Self-Installing Windows Service Template

@Sean:

You are absolutely right, I forgot to update the WindowsServiceHarness from the last post with using the configuration attribute. I'll update.
10/13/2010 1:44 PM | James Michael Hare
Gravatar

# re: C# Windows Services (2 of 2): Self-Installing Windows Service Template

Nice. Your implementation is much cleaner than my hack!
10/13/2010 2:00 PM | Sean Gough
Gravatar

# re: C# Windows Services (2 of 2): Self-Installing Windows Service Template

Hmmm, now where did this OnCustomCommand(int command) business come from? ;)
10/13/2010 2:48 PM | Sean Gough
Gravatar

# re: C# Windows Services (2 of 2): Self-Installing Windows Service Template

@sean:

Lol, you can ignore that, I cut & pasted that code out of my personal implementation which also overrides the base windows service OnCustomCommand method. Gives your servie the ability to handle "commands" which ae just integers that can instruct it to perform certain behaviors.
10/13/2010 2:55 PM | James Michael Hare
Gravatar

# re: C# Windows Services (2 of 2): Self-Installing Windows Service Template

Figured as much, I went ahead and implemented that myself too. Felt right.
10/13/2010 3:28 PM | Sean Gough
Gravatar

# re: C# Windows Services (2 of 2): Self-Installing Windows Service Template

Thanks for the articles, very useful stuff! I normally dread starting a new service project because they are such a hassle to test and debug, this makes it so much easier!

I orginally just ripped out a few bits and pieces that I needed for my project, but in retrospect having the whole thing as a template is a brilliant idea. Is there anywhere to download the template, or possibly I could trouble you to email it to me? The blogs code windows make it very hard to copy/paste, since I get line numbers and no crlf. Thanks again.
10/14/2010 5:49 AM | Patrik Stenberg
Gravatar

# re: C# Windows Services (2 of 2): Self-Installing Windows Service Template

Glad you guys liked it!

Patrik:

Yes, the code snippet plug-in for LiveWriter looks nice, but cut-n-paste out of it is awful. I need to find a better plug-in :-(

But meanwhile, I need to set up a code download point as well. Till then, if you use the contact link above and send me your email address (probably shouldn't post on blog or you'll get spam I'd imagine) and I can zip you up the code-base!
10/14/2010 10:44 AM | James Michael Hare
Gravatar

# re: C# Windows Services (2 of 2): Self-Installing Windows Service Template

Thanks a lot for this article! This is brilliant!!! Would you mind if I contact you also for sending me the code-base?

Kind regards
Daniel
10/15/2010 10:07 AM | Daniel Maier
Gravatar

# re: C# Windows Services (2 of 2): Self-Installing Windows Service Template

Excellent article, thanks very much. Please add me to the list of dev's requesting a zip of the solution/template. Thanks again.
10/20/2010 11:20 AM | Craig
Gravatar

# re: C# Windows Services (2 of 2): Self-Installing Windows Service Template

@Craig:

Hey, if you contact me above using the Contact link i can send it to you.
10/20/2010 3:06 PM | James Michael Hare
Gravatar

# re: C# Windows Services (2 of 2): Self-Installing Windows Service Template

Excellent post.

Thank you for sharing.
10/29/2010 10:16 AM | Henok Girma
Gravatar

# re: C# Windows Services (2 of 2): Self-Installing Windows Service Template

This is a really great solution. i've tried a lot, but this is by far one of the best approach i've seen and found. Great Job, thanks a lot!
1/9/2011 4:18 AM | Christian
Gravatar

# re: C# Windows Services (2 of 2): Self-Installing Windows Service Template

You've saved the day. Have been working a lot with Windows Services and always butting heads with debugging.

This template is a keeper.
2/4/2011 6:46 AM | Riaan
Gravatar

# re: C# Windows Services (2 of 2): Self-Installing Windows Service Template

Nice work!

Any thoughts on the best way to update the service so that it can run as a specified user/password?
7/15/2011 11:42 AM | Don
Gravatar

# re: C# Windows Services (2 of 2): Self-Installing Windows Service Template

Hi Michael

Nice work!

could you send me the source by email? thanks a lot!

Christian
10/23/2011 2:10 AM | Christian
Gravatar

# re: C# Windows Services (2 of 2): Self-Installing Windows Service Template

Great article. Really good information I am using right now.

But wouldn't it be better not to have username and password stored inside the assembly? Better make them part of the command line arguments I think.

And a small other thing: I am dropping the framework in a different assembly, but the default constructor of the Installer needs a reference to the implementation of the service, which is not in the framework assembly of course. I just deleted the default constructor and am testing with it now.
11/3/2011 7:31 AM | Rodi
Gravatar

# re: C# Windows Services (2 of 2): Self-Installing Windows Service Template

Now that I'm testing some more I come to the conclusion that it's not working with installUtil installation, because it requires the default parameterless constructor.

I have no idea how to put all this stuff in an external assembly, because you need the type of your service implementation in the installer class.

Only thing I can think of now, is to have a base implementation of the installer in the external assembly and then add a lightweight installer to the windows service implementation project itself. Will try that now.
11/3/2011 7:56 AM | Rodi
Gravatar

# re: C# Windows Services (2 of 2): Self-Installing Windows Service Template

@Rodi: I do agree on the username/password. I've been meaning to rework this a bit because that was a "feature" early on that became a hindrance later when deploying to machines using different user IDs.

You should be able to have the framework in a different assembly, but you'd need your actual implementation class to be in the assembly with the installer. Or, you could use the Activator class to attempt to load it dynamically...
11/3/2011 9:30 AM | James Michael Hare
Gravatar

# re: C# Windows Services (2 of 2): Self-Installing Windows Service Template

What I have now is an assembly for the framework where all main logic resides of setting up, installing etc.

Then in the Implementation project I reference the framework assembly and I need 3 classes to implement the service:
1. The service class itself, which is just a class that implements IWindowsService from the framework. It has the WindowsServiceAttribute too of course.
2. The Main Program class. In main I call a static ApplicationStartup class method from the framework, like ApplicationStartup.Run<ServiceImplementation>(args);
3. A lightweight installer class that I hate to have, but cannot work around. It inherits WindowsServiceInstaller class from the framework and it has a parameterless constructor (which the WindowsServioceInstaller has not anymore in my version) that just calls the base constructor with the type of the service implementation, like: : base(typeof(ServiceImplementation))

The lightweight installer class (number 3) is only needed to support the installutil.exe installation. It is not needed if you are sure you only need the console (debug) version and the command line install version.

Thanks for your effort. It helped me a lot!
11/9/2011 12:47 AM | Rodi
Gravatar

# Multiple service instances? re: C# Toolbox: A Debuggable, Self-Installing Windows Service Template (2 of 2)

Hi,
This looks a great improvement on what I've dealt with previously.
Q: How difficult would it be to adapt this to support multiple instances of a service, with self install/uninstall, e.g.
myservice -install 1 somedetail
myservice -install 2 otherdetail
--> two services, called myservice1 and myservice2. When each runs, it receives both its number and its string parameter as arguments.
Then,
myservice -uninstall 1

would remove #1 leaving #2 intact.
3/5/2013 4:45 PM | swordfishBob
Gravatar

# re: C# Toolbox: A Debuggable, Self-Installing Windows Service Template (2 of 2)

Any updates for VS 2012 ? how can Install Windows services without Setup projects in VS 2012 ?
6/10/2014 2:18 AM | pregunton
Post A Comment
Title:
Name:
Email:
Comment:
Verification:
 
 

Powered by: