Creating an Extensible Windows Service

By Robert Chartier


The first article in this series shows how to create a Windows Service that never needs modification and that allows system administrators and developers to develop and deploy custom plug-ins (although changing any of the plug-ins means stopping and restarting the service).


The second article covers actual plug-in development.


The code in this article is written with the aid of Visual Studio .NET (VS .NET), but it is not a requirement if you’re familiar with the command line compilers that ship with the .NET Framework SDK.



Step One: Creating the Windows Service


The first step describes the process of setting up the actual Windows Service class. It will be based on a timer control, which has an interval set by the administrator via the configuration file.


Open up Visual Studio .NET (VS .NET) and create a New C# Windows Service.


Creating a New C# Windows Service in VS.NET.

Figure 1.1 – Creating a New C# Windows Service in VS.NET.


VS .NET will default to the designer for the Service. We won’t need the designer for anything except changing the Service name. Change both “(Name)” and “ServiceName” to “scheduler”. Also, switch to the “Solution Explorer”, and rename the default file from “Service1.cs” to “Scheduler.cs”.


By default VS .NET will implement a lot of code that is essential for the Service to execute normally. Notice that our Service class derives from the framework’s “System.ServiceProcess.ServiceBase” class.


We must first implement a timer control to run our plug-in at given intervals.

1. Create a timer object at class scope:



private System.Timers.Timer timer=null;


2. Set up the timer within the class constructor:


public scheduler() {

string servicepollinterval = System.Configuration.ConfigurationSettings.AppSettings
[“servicepollinterval”];

double interval=10000;
try {
interval = Convert.ToDouble(servicepollinterval);
}catch(Exception) {}

timer = new System.Timers.Timer(interval);
timer.Elapsed += new ElapsedEventHandler( this.ServiceTimer_Tick );

}


Notice that we are getting the timer’s interval value by using


System.Configuration.ConfigurationSettings.AppSettings
[“servicepollinterval”];


I will cover the configuration file in more detail in step three.


timer.Elapsed += new ElapsedEventHandler( this.ServiceTimer_Tick );


This sets up the event handler for when the timer elapses. It will be fired when the timer runs for the duration of the set interval. I’ll get into more detail on this further down.


3. Next we need to start and stop the timer based on our Service’s events.



protected override void OnStart(string[] args) {
timer.AutoReset = true;
timer.Enabled=true;
timer.Start();
}

protected override void OnStop() {
timer.AutoReset = false;
timer.Enabled = false;
}

Optionally you can define the OnPause and OnContinue members:

protected override void OnPause() {
this.timer.Stop();
}

protected override void OnContinue() {
this.timer.Start();
}


4. The timer’s Elapsed Event handler.


timer.Elapsed += new ElapsedEventHandler( this.ServiceTimer_Tick );


This line instructs the Framework to call the “ServiceTimer_Tick” method within our current class when the timer elapses — that is, when its interval runs its course. So we must implement this method in our class as such:


private void ServiceTimer_Tick(object sender, System.Timers.ElapsedEventArgs e) {
this.timer.Stop();
//IMPLEMENT YOUR TIMER TICK HERE
this.timer.Start();
}


This is a bare bones version of our timer’s elapsed event. Notice that we first “Stop()” the timer, execute the task, and then “Start()” it again. This prevents running into any problems if the code takes longer than the interval. This is optional, of course.


This is all we need for now in our Windows Service. The next step is to finish creating an installer class so we can install the Service into our system.



Step Two: Creating the Project Installer


The Project Installer is responsible for taking our Windows Service class and installing it into our system. We first need to create a class in our current project. Please name it “ProjectInstaller.cs”.


Now I want to review each relevant line marked “//**’X’**” in the entire Project Installer code.



public class ProjectInstaller {

[RunInstaller(true)] //**1**
public class ProjectInstaller : System.Configuration.Install.Installer { //**2**
private System.ComponentModel.Container components;
private System.ServiceProcess.ServiceInstaller serviceInstaller1; //**3**
private System.ServiceProcess.ServiceProcessInstaller serviceProcessInstaller1; //**4**

public ProjectInstaller() {
InitializeComponent();
}

private void InitializeComponent() {
this.serviceInstaller1 = new System.ServiceProcess.ServiceInstaller();
this.serviceProcessInstaller1 = new System.ServiceProcess.ServiceProcessInstaller();

this.serviceInstaller1.DisplayName = “Scheduler_Net”; //**5**
this.serviceInstaller1.ServiceName = “Scheduler_Net”; //**6**

this.serviceInstaller1.StartType = System.ServiceProcess.ServiceStartMode.Automatic; //**7**

this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalSystem; //**8**
this.serviceProcessInstaller1.Password = null; //**9**
this.serviceProcessInstaller1.Username = null;

//**10**
this.Installers.AddRange(new System.Configuration.Install.Installer[] {

this.serviceProcessInstaller1,
this.serviceInstaller1});
}

}
}


#1. Taken right out of the VS .NET documentation:


“Specifies whether an installer should be invoked during installation of an assembly.”
(ms-help://MS.VSCC/MS.MSDNVS/cpref/html/frlrfSystemComponentModelRunInstallerAttributeClassTopic.htm)


#2. Notice that our ProjectInstaller class must be derived from the Framework-supplied “System.Configuration.Install.Installer”

#3. The Install Utility needs a ServiceInstaller when it installs the Service application. It contains properties for such things as the Service Display Name, the Service name, Start Type, etc.


#4. A ServiceProcessInstaller is the actual class that will install the ServiceInstaller and its executable into the system, typically through the use of a tool such as “InstallUtil.exe” (more on this later).


If you would like to read more on these, please refer to the documentation (ms-help://MS.VSCC/MS.MSDNVS/cpref/html/frlrfSystemServiceProcessServiceInstallerClassTopic.htm).


#5 and #6. Demonstrates how to use the ServiceInstaller to set the desired DisplayName and ServiceName of the Service that is going to be installed.


#7. Demonstrates how to set the StartType of the Service. Your options include:


  1. Automatic
  2. Disabled
  3. Manual

#8. Demonstrates how to use the ServiceProcessInstaller to specify the desired default user account this Service will use once installed.


#9. Demonstrates how we could set the username and password if we were to choose the option, “System.ServiceProcess.ServiceAccount.User”, for the desired user account.


#10. This binds the ServiceProcessInstaller, along with our ServiceInstaller, into our Installers. Installers are inherited from our derived class. Again, if you want more information on this, please consult the documentation (ms-help://MS.VSCC/MS.MSDNVS/cpref/html/frlrfSystemConfigurationInstallInstallerClassInstallersTopic.htm).



Step Three: Creating the Configuration File



The easiest way to configure the service is though the standard “APPNAME.EXE.config” (config) file, which the Framework automatically allows you to use through the “System.Configuration.ConfigurationSettings” class. With this class, we can actually create separate sections within the same configuration file, one for the service itself and others for any plug-ins we create. This simplifies administration to one XML file.


As mentioned in step one, #2, we are using the “System.Configuration.ConfigurationSettings” class to read the Timer interval into our class. It is using the standard “appSettings” section in the config file. See the XML snippet in Figure 1.2 below.


For our purposes we want to create and use different sections for our plug-ins. We first need to create a “configSections” node with a “sectionGroup” for our plug-ins. The easiest way to describe this is to look at a slimed down version of the config file.



<?xml version=”1.0″ encoding=”utf-8″ ?>
<configuration>
<!– application specific settings –>
<configSections>
<sectionGroup name=”PlugInSettings”>
<!–notice we have an entry for each plugin’s settings node below–>

<section name=”TemplatePluginSettings” type=”System.Configuration.NameValueSectionHandler” />
</sectionGroup>
</configSections>

<appSettings>
<add key=”servicepollinterval” value=”10000″ />
</appSettings>

<PlugInSettings>
<TemplatePluginSettings>
<add key=”whatever” value=”whatever-value” />
</TemplatePluginSettings>
</PlugInSettings>
</configuration>



Figure 1.2 A snippet of the XML Configuration File


To use the different sections for each item under the PlugInSettings node, you will need to create a special collection for that section. In part 2, we will develop custom plug-ins to interface with the Windows Service.



About the Author



Robert Chartier has developed IT solutions for more than nine years with a diverse background in both software and hardware development. He is internationally recognized as an innovative thinker and leading IT architect with frequent speaking engagements showcasing his expertise. He’s been an integral part of many open forums on cutting-edge technology, including the .NET Framework and Web Services. His current position as vice president of technology for Santra Technology has allowed him to focus on innovation within the Web Services market space.


He uses expertise with many Microsoft technologies, including .NET, and a strong background in Oracle, BEA Systems, Inc.’s BEA WebLogic, IBM, Java 2 Platform Enterprise Edition (J2EE), and similar technologies to support his award-winning writing. He frequently publishes to many of the leading developer and industry support Web sites and publications. He has a bachelor’s degree in Computer Information Systems.


Robert Chartier can be reached at rob@aspfree.com.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read