Windows Service -- Part 3: Controlling Windows Service

Summary
Through out this series of tutorials we'll learn how to create a Windows service project in C#, how to install it, how to debug an installed Windows service and how control a windows service from an application. This is a three series tutorials on Windows service. In the first part of this 'Windows Service' tutorial series, we learned about how to create a Windows service project. In the second part of this 'Windows Service' tutorial series, we learned about how to install the service and how to debug it. In this last part we'll learn how to control a service from another client application. Here 'controlling a service' means starting, stopping, sending commands to a service.



The job of the service will be to monitor the directory of the service's exe for any file/folder creation during the service running. The service will keep a list of paths of items (file/folder) that will be created in the service's exe directory.



Creating The Client Application
  • Right click on the solution node --> Add --> New Project. 'Add New Project' wizard will appear on you.
  • Select 'Visual C#-->Windows' from the 'Project Types' options (left side on the wizard). Select 'Windows Form Application' template from the 'Templates' options (right side on the wizard). Give a name to our project - let us name it 'Service Manager'. And click on 'OK' button. A new project will be added in our solution named 'Service Manager'.
  • A default form (Form1) will be there in the project. Let us rename it to 'MainForm'. Change the MainForm's 'Text' property value to 'CCFSWatcher Service Manager' -- this is our application's title.
  • Double click the MainForm.cs node in the solution explorer and it will open in designer mode.
  • Drag and drop the 'Service Controller' component from the 'Toolbox' panel (find it in Components group in Toolbox) onto the MainForm.Attached File  SrvMngrProj.PNG   61.46K   10 downloads
  • Let us change this component's Name' property to '_serviceController' from the 'Properties' panel. Also set the 'ServiceName' property to CCFSWatcher (can you recall that this is the name of our service?). Actually you will see a list of service currently installed on your system from the combobox for the 'ServiceName' property. Select the our's one -- CCFSWatcher. Attached File  srvmngrprop.PNG   61.24K   12 downloads
  • Drag and drop the 'Timer' component from the 'Toolbox' panel (find it in Components group in Toolbox) onto the MainForm. Add an event handler - OnSyncWithService - for the Timer's 'Click' event. Also set it's 'Enable' property to 'True'.Attached File  timer.PNG   63.21K  &
    nbsp;15 downloads
  • Drag five buttons (Button), 2 labels (Label) and 1 number-box (NumericUpDown). Change the five buttons 'Name' property to '_btnStart', '_btnStop', '_btnPause', '_btnResume' '_btnSendCommand' and their 'Text' property to 'Start', 'Stop', 'Pause', 'Resume', 'Send Command' respectively. Also add five event handlers for each of those 5 buttons 'Click' event hanlders -- 'onstartClicked', 'onstopClicked', 'onpauseClicked', 'onresumeClicked', 'OnSendCommandClicked' respectively. Change the threelabels 'Name' property to '_lblStatus', '_lblCommand', '_lblError' and their 'Text' property to 'Status', 'Command' 'Error' respectively. Change the number-box's 'Name' property to '_nudCommand'. Change the 'Minimum' & 'Maximum' properties value for '_nudCommand' to 128 & 256 respectively. Try to position all those controls on MainForm as the image --Attached File  mainform.PNG   15.86K   12 downloads. Note that we are using absolute position for simplicity for all the controls.
Now its time to implement logic. At first let us add login in the Timer component's 'Tick' event handler.  We'll add the logic related to when to enable the buttons and showing current status of our service in _lblStatus. Note that we are using Timer component so that even if user control the service from Windows 'Service Control Manager', our application will be synced. Here is the code for method SyncWithService...
private void OnSyncWithService(object sender, EventArgs e)
{
try {
_lblStatus
.BackColor = _serviceController.Status == ServiceControllerStatus.Running ?
Color.Lime : Color.Red;
_btnStart
.Enabled = _serviceController.Status == ServiceControllerStatus.Stopped;
_btnStop
.Enabled = _serviceController.Status != ServiceControllerStatus.Stopped;
_btnPause
.Enabled = _serviceController.Status == ServiceControllerStatus.Running;
_btnResume
.Enabled = _serviceController.Status == ServiceControllerStatus.Paused;
_btnSendCommand
.Enabled = _serviceController.Status == ServiceControllerStatus.Running;
_lblStatus
.Text = _serviceController.Status.ToString();
}
catch (Exception exc) {
_lblError
.Text = exc.Message;
}
}

We added 5 buttons to control the service for starting, stopping, pausing, resuming and sending custom commands to our service. Following is the logic for all those 5 events. Actually we are just calling the respective method in our _serviceController object. Here is the code for those 5 event handlers...
private void onstartClicked(object sender, EventArgs e)
{
try{
_serviceController
.Start();
_serviceController
.WaitForStatus(ServiceControllerStatus.Running);
}
catch (Exception exc){
_lblError
.Text = exc.Message;
}
}
private void onstopClicked(object sender, EventArgs e)
{
try{
_serviceController
.Stop();
_serviceController
.WaitForStatus(ServiceControllerStatus.Stopped);
}
catch (Exception exc) {
_lblError
.Text = exc.Message;
}
}
private void onpauseClicked(object sender, EventArgs e){
try {
_serviceController
.Pause();
_serviceController
.WaitForStatus(ServiceControllerStatus.Paused);
}
catch (Exception exc){
_lblError
.Text = exc.Message;
}
}
private void onresumeClicked(object sender, EventArgs e)
{
try {
_serviceController
.Continue();
_serviceController
.WaitForStatus(ServiceControllerStatus.Running);
}
catch (Exception exc) {
_lblError
.Text = exc.Message;
}
}
private void OnSendCommandClicked(object sender, EventArgs e)
{
try {
_serviceController
.ExecuteCommand(Convert.ToInt32(_nudCommand.Value));
}
catch (Exception exc){
_lblError
.Text = exc.Message;
}
}

Let us talk a bit about sending custom command to a service: We can send a command from our client to the service. The command is just an integer in the range 128-258. In service side, we need to override 'void OnCustomCommand(int command)' method and we just need to handle specific command. We can use 'Service Controller's 'ExecuteCommand' method in client side to send a integer command to our service. As example, we'll just handle 128 command in our service and ignore others. In service side, if we get 128 command, we'll write all the items we got from our 'FileSystemWatcher' componnet in service side to a file named 'data.txt' in the same directory our service's assembly is located.
Code for 'OnCustomCommand' method in service side...

protected override void OnCustomCommand(int command)
{
System.Diagnostics.EventLog.WriteEntry("CCFSWatcher", DateTime.Now.ToLongTimeString() + " Custom command recieved " + command.ToString());
switch (command)
{
case 128:
try {
String content = String.Empty;
foreach (String item in _createdItems)
{
content
+= (item + Environment.NewLine);
}
File.WriteAllText(Path.GetDirectoryName(
Assembly.GetEntryAssembly().Location) + "data.txt", content);
}
catch (Exception exc) {
System.Diagnostics.EventLog.WriteEntry("CCFSWatcher", DateTime.Now.ToLongTimeString() + " Custom Command error for command 128");
}
break;
case 129:
//hanlde command 128
break;
case 256:
//hanlde command 128
break;
// Handle others as your demands
default:
break;
}
}

Now if we want to install our 'CCFSWatcher Service Manager' application along with the installer we created for service, follow the steps below...
  • Right click on 'CodeCallServiceInstaller' project -- > Add --> Project Output. 'Add Project Output Group' wizard will appear on you.
  • Select 'Service Manager' from the 'Project:' combobox. Click on 'OK' button.
  • Right click on 'CodeCallServiceInstaller' project -- > View --> File System. File system editor will open for you.
  • Select 'Applicaiton Folder' node. Right click on 'Primary output from ServiceManager(Active)' --> 'Create Shortcut to Primary output from ServiceManager(Active)'. An item name 'Shortcut to Primary output from ServiceManager(Active)' will be created for you. Rename it to 'Shortcut to CCFSWatcher Service Manager'
  • Right click 'Shortcut to CCFSWatcher Service Manager' --> Cut.
  • Go to 'Users Desktop' node and Right click --> Paste.
  • Rebuild the project 'CodeCallServiceInstaller' and right click on it --> Install. After installing you will see a shortcut to our beloved 'Service Manager' application

No comments:

Post a Comment