indows services are applications that run in the background without the need for a user interface. If you?re using Windows, you should not be new to Windows services. The Themes service running in Windows XP provides you with the various user interface features that you have come to expect from Windows. And the Wireless Zero Configuration service provides automatic configuration for your wireless adapters.
Windows services can be started automatically when the computer system boots up and they will run in the background without user intervention. The nature of Windows services makes it particularly suitable for situations in which you want to perform some required operation without interfering with the execution of other applications.
This article will show you how to write your own Windows services using Visual Studio 2005. In addition, you?ll also learn how to minimize your application to the System Tray so that it can always remain running.
Programmatically Control Windows Services
Before you see how you can create your own Windows Services, let’s take a look at how you can programmatically control the Windows services currently running on your system.
First, to list all the Windows services currently installed on your system, use the GetServices() method from the ServiceController class, available from the System.ServiceProcess.dll:
'---retrieve all the services installed on the system--- Dim services() As System.ServiceProcess.ServiceController services = ServiceProcess.ServiceController.GetServices() For i As Integer = 0 To services.Length - 1 Console.WriteLine(services(i).DisplayName) Next
The DisplayName property returns the name of the Windows service. The above code snippet prints out the list of Windows services I have on my computer (partial list shown below):
AlerterApplication Layer Gateway ServiceApplication ManagementASP.NET State ServiceATK Keyboard ServiceWindows AudioBackground Intelligent Transfer ServiceBrSplServiceComputer BrowserBluetooth Support ServiceIndexing Service...
To obtain the status of each Windows service, use the Status property from the ServiceController class:
'---retrieve all the services installed on the system and their ' status--- Dim services() As System.ServiceProcess.ServiceController services = ServiceProcess.ServiceController.GetServices(".") For i As Integer = 0 To services.Length - 1 Console.WriteLine(services(i).DisplayName & " - " & _ services(i).Status.ToString) Next
The following partial list shows the states of the various Windows services running on my computer:
Alerter - StoppedApplication Layer Gateway Service - RunningApplication Management - StoppedASP.NET State Service - StoppedATK Keyboard Service - RunningWindows Audio - RunningBackground Intelligent Transfer Service - StoppedBrSplService - RunningComputer Browser - RunningBluetooth Support Service - RunningIndexing Service ? Stopped...
To change the status of a Windows service, you can use the following methods from the ServiceController class:
- Pause(): This pauses a running Windows service.
- Continue(): This continues a Windows service that is currently paused.
- Start(): This starts a Windows service.
- Stop(): This stops a currently running (or paused) Windows service.
The following code snippet shows how you can control the Themes service running on your local computer (as specified by the ?.?; you can also use the local computer name):
'---control the Themes service running on the local computer--- Dim controller As New _ System.ServiceProcess.ServiceController("Themes", ".") '---if the service is running and it can be paused and ' continued--- If controller.Status = _ ServiceProcess.ServiceControllerStatus.Running And _ controller.CanPauseAndContinue Then '---pause the service--- controller.Pause() '---continue the service--- controller.Continue() ElseIf controller.CanStop Then '---stop the service--- controller.Stop() End If
Here, you first check to see if the Themes service is currently running and whether it can be paused (via the CanPauseAndContinue property from the ServiceController class). If it is currently running and can be paused, you then pause the service and then immediately resume the service. If not, you simply stop the service.
It is important that you check the status of a Windows service before you change its state. A Windows service can be in one of the following states:
Consider the following example:
'---start the service--- controller.Start() controller.Refresh() '---will print StartPending--- Console.WriteLine(controller.Status.ToString) System.Threading.Thread.Sleep(1000) controller.Refresh() '---will print Running--- Console.WriteLine(controller.Status.ToString) System.Threading.Thread.Sleep(1000)
Here, you first start a Windows service and then print out its status two times, each within a one second interval. You will realize that immediately after starting the service, the status printed will be “StartPending“, and one second later, the status becomes Running. It’s important that you call the Refresh() method from the ServiceController class so that you can always obtain the latest status of the Windows service.
The following example demonstrates the importance of knowing the status of a Windows service:
'---start the service--- controller.Start() '---stop the service--- controller.Stop()
The above will result in a runtime error because the Windows service is still starting up after executing the Start() method and any attempt to stop the service will trigger an InvalidOperationException exception.
To prevent this exception from happening, the above code snippet should be written this way:
'---start the service--- controller.Start() '---get the updated status--- controller.Refresh() If controller.Status = _ ServiceProcess.ServiceControllerStatus.Running Or _ controller.Status = _ ServiceProcess.ServiceControllerStatus.Paused Then '---stop the service--- controller.Stop() End If
Building Your Own Windows Service
The Windows service that you’ll be developing will monitor for file activities on your hard disk?in particular, it will monitor Word documents. I often forget the filenames of Word documents that I have created, which I need to retrieve from time to time. This Windows service will allow you to monitor a specific path on your hard disk (or the entire drive) for Word documents.
Using Visual Studio 2005, create a new Windows Service project and name it as WordFilesMonitor. You should see the design view of the windows service: Service1.vb.
In the Properties window for Service1:
- Set the CanPauseAndContinue property to True (see Figure 1) so that users can pause and continue the Windows service.
- Set the ServiceName property to WordFilesMonitor.
- Click on the Add Installer link located at the bottom of the Properties window to add two installers to aid in the installation of the Windows service.
The ServiceProcessInstaller and the ServiceInstaller classes (see Figure 2) help to configure the installation and un-installation of the Windows Service.
You need to configure the two installers as follows:
- For the ServiceProcessInstaller1 control, set the Account property to LocalSystem (see Figure 3). This property uses an account that acts as a non-privileged user on the local computer, and presents anonymous credentials to any remote server. If you use the value User, the system will prompt for a valid user name and password when the service is installed.
- For the ServiceInstaller1 control, set the StartType property with value Manual so that the service not to start until explicitly started by the user.
- For the ServiceInstaller1 control, set the ServiceName property to WordFilesMonitor.
Remember to ensure that the ServiceName property in the Service1 application matches that of the ServiceName property in the ServiceInstaller1 control. Otherwise, the Windows service cannot be installed correctly.
|Figure 3. Setting the Properties: The ServiceProcessInstaller1 and the ServiceInstaller1 controls.|
Double-click on the design surface of Service1 to switch to its code-behind. Import the following namespace:
Declare the following constant and member variables:
Public Class Service1 '---name of the log file---Const FILE_NAME = "c:WordDocumentsHistory.txt" '---used for writing to log file--- Private SR As StreamWriter '---Create a new FileSystemWatcher--- Private WordFilesWatcher As New FileSystemWatcher()
The FileSystemWatcher class provides the capability to listen to the file system change notifications and raises events when a directory, or file in a directory, changes.
There are seven events in the ServiceBase class that you can service (the Windows Service inherits from the System.ServiceProcess.ServiceBase class):
- OnStart: This specifies actions to take when a service starts.
- OnStop: This specifies actions to take when a service stops.
- OnContinue: This specifies actions to take when a service resumes normal functioning after being paused.
- OnCustomCommand: This specifies actions to take when a command with the specified parameter value occurs.
- OnPause: This specifies actions to take when a service pauses.
- OnPowerEvent: This applies to laptop computers when they go into suspended mode, which is not the same as a system shutdown.
- OnShutdown: This specifies what should happen immediately prior to the system shutting down.
|Author’s Note: For this article, I will not be discussing the last two events?OnPowerEvent and OnShutDown.|
Code the OnStart event handler as shown in Listing 1.
First, create a log file to log the activities of the file system. If the log file does not exist, create one; otherwise, open the existing one for writing. You then create a FileSystemWatcher object and then set the required parameters for monitoring files activities. The Path property sets the directory to monitor (e.g. C:); the IncludeSubdirectories property indicates whether the subdirectories of a specified path should be monitored as well; the NotifyFilter property sets the kinds of file activities to monitor (e.g. last access, last write, change in filename and change in directory name), and lastly the Filter property sets the type of files to monitor (through filename extension, e.g. *.doc). All these property values will be derived from the single input parameter of the OnStart() event handler. This allows maximum flexibility for the user to specify the degree of customization required.
Next, specify the events handlers for the different events, such as when files are modified, created, deleted, and renamed. Finally, set the EnableRaisingEvents property to true to enable the FileSystemWatcher object to start monitoring files.
Code the OnFileChanged event handler as follows:
Public Sub OnFileChanged( _ ByVal source As Object, _ ByVal e As FileSystemEventArgs) Dim str As String = Now.ToString & ": File " & _ e.ChangeType.ToString & " : " & e.FullPath '---log it to file--- SR.WriteLine(str) SR.Flush() End Sub
Code the OnFileRenamed event handler as follows:
Public Sub OnFileRenamed( _ ByVal source As Object, _ ByVal e As RenamedEventArgs) Dim str As String = Now.ToString & ": File : " & _ e.OldFullPath & " renamed to " & e.FullPath '---log it to file--- SR.WriteLine(str) SR.Flush() End Sub
Code the OnPause event handler as follows:
'---when the Windows Service is paused--- Protected Overrides Sub OnPause() WordFilesWatcher.EnableRaisingEvents = False SR.WriteLine(Now.ToString & ": WordFilesMonitor Paused.") SR.Flush() End Sub
Here, you will temporarily disable the FileSystemWatcher object (by setting its EnableRaisingEvents property to False) so that it will not raise any events when changes in the files activities in the hard disk are detected.
Code the OnContinue event handler as follows:
'---when the Windows Service is continued--- Protected Overrides Sub OnContinue() WordFilesWatcher.EnableRaisingEvents = True SR.WriteLine(Now.ToString & ": WordFilesMonitor Continued.") SR.Flush() End Sub
Here, you are doing the reverse of what you did in the OnPause event handler?you simply turn on the FileSystemWatcher object to enable it to monitor for file changes.
Finally, code the OnStop event handler as follows:
'---when the Windows Service is stopped--- Protected Overrides Sub OnStop() SR.WriteLine(Now.ToString & ": WordFilesMonitor Stopped.") SR.Flush() SR.Close() End Sub
This event handler simply closes the text file used for logging.
Installing the Windows Service
It is now time to install the Windows service. Unfortunately, Windows services cannot be executed simply by pressing the F5 function key; you need to do some work.
First, you need to build the project. Right-click on the project name in Solution Explorer and then select Build.
To install the Windows Service, use the InstallUtil utility provided by the .NET Framework. Locate the bin directory containing the executable of your Windows service and type the following:
For example, if your Windows service project is located in C:, you should see something like this:
|Author’s Note: If you cannot access the InstallUtil utility, it is likely your path is not set correctly. The easiest way would be to use the Visual Studio 2005 Command Prompt. This is located at Start | Programs | Microsoft Visual Studio 2005 | Visual Studio Tools. The Visual Studio 2005 Command Prompt has all the relevant path settings to access the tools that come with the .NET Framework.|
You can now check if the Windows Service is installed in your system by launching the Services utility (Start | Programs | Administrator Tools | Services) (see Figure 4):
|Figure 4. Installation: Verifying if the Windows service is installed successfully.|
Recall that in the OnStart event handler, it requires additional information to be passed in (the single input parameter string array), hence getting it started directly from the Services utility is a little tricky. Instead, you will write a Windows application to start it.
Uninstalling the Windows Service
Just as it is important to know how to install a Windows service, you also need to know how to uninstall a Windows service. This is because once the Windows service has started running, you cannot simply modify your Windows service project and overwrite the executable by recompiling the source. You need to stop the service and uninstall it first. To uninstall the Windows service, use the /u option as you would install the service, like this:
C:WordFilesMonitorinDebug>installutil WordFilesMonitor.exe /u
You can now modify your source codes and do a recompilation. Finally, install the service again.
There are cases in which, after doing the un-installation and recompilation, the new Windows Service may refuse to install. If this should happen, ensure that your Services utility is closed and restarted. When in doubt, the installation message that is displayed by the InstallUtil utility provides some hints as to the cause of the error.
Building a Client to Control the Windows Service
The user interface of this application is similar to the Service Manager found in SQL Server 2000. It allows you to start/continue, pause, and stop your Windows service. In addition, I will also show you how you can minimize the Windows application so that it appears in the System Tray.
Add a new Windows application project to the current solution and name it as WordFiles. Add the following controls to the default Form1:
|Figure 5. Populating: The Form1 with the various controls.|
Populate Form1 with the following controls (see also Figure 5):
Add the menu items inside the ContextMenuStrip control as shown in Figure 5 (you need to first select ContextMenuStrip1 so that you can create the menu items).
Set the properties for Form1 as follows:
For NotifyIcon1, set its ContextMenuStrip property to ContextMenuStrip1 and assign it an icon (see Figure 6). Also set its Text property to “Word Files Monitoring App”.
|Author’s Note: I suggest you download the source code for this article so that you need not go through all the steps in building the UI.|
You can now switch to the code-behind of Form1 to start coding.
|Figure 6. Assign an Icon: Configuring NotifyIcon1.|
First, import the following namespace:
Next, define the StartService() subroutine to start (or continue) the Windows service (see Listing 2).
Observe that the arguments to pass to the Windows service is a string array (args).
Next, define the PauseService() subroutine to pause the Windows service:
Private Sub PauseService() ServiceController1.Refresh() If ServiceController1.CanPauseAndContinue Then ServiceController1.Pause() ServiceController1.Refresh() ToolStripStatusLabel1.Text = _ ServiceController1.Status.ToString Else ToolStripStatusLabel1.Text = "Service cannot be paused." End If End Sub
Define the StopService() subroutine to stop the Windows service:
Private Sub StopService() ServiceController1.Refresh() If ServiceController1.Status = _ ServiceProcess.ServiceControllerStatus.Stopped Then ToolStripStatusLabel1.Text = "Service is not running." Exit Sub End If If ServiceController1.Status = _ ServiceProcess.ServiceControllerStatus.StopPending Then ToolStripStatusLabel1.Text = "Service is stopping..." Exit Sub End If Try ServiceController1.Stop() ServiceController1.Refresh() ToolStripStatusLabel1.Text = _ ServiceController1.Status.ToString Catch ex As Exception MsgBox(ex.ToString) End Try End Sub
Define the RefreshService() subroutine to refresh the status of the Windows service:
Private Sub RefreshService() ToolStripStatusLabel1.Text = "Refreshing service..." With ServiceController1 .MachineName = txtServer.Text .Refresh() End With ToolStripStatusLabel1.Text = _ ServiceController1.Status.ToString End Sub
You can now wire up all the event handlers for the various buttons with the subroutines just defined (Listing 3).
When the form is loaded for the first time, set the ServiceController1 control to the Windows service that you are controlling:
Private Sub Form1_Load( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles MyBase.Load '---display the local computer name--- txtServer.Text = My.Computer.Name With ServiceController1 .ServiceName = "WordFilesMonitor" .MachineName = txtServer.Text End With RefreshService() End Sub
Now, when the form is minimized, you want it to appear in the System Tray as an icon. In addition, you do not want it to appear in the task bar. To do this, define the MinimizeWindows() subroutine as follows:
Private Sub MinimizeWindow() With Me '---minimize the window--- .WindowState = FormWindowState.Minimized '---do not show application in task bar--- .ShowInTaskbar = False End With End Sub
The RestoreWindow() subroutine does the exact opposite?restore the form and show it in the task bar:
Private Sub RestoreWindow() With Me '---restore the window--- .WindowState = FormWindowState.Normal '---show application in task bar--- .ShowInTaskbar = True End With End Sub
When the form is minimized, the user can double-click on the icon to restore the window (see Figure 7).
This is accomplished by the servicing the MouseDoubleClick event handler of the NotifyIcon control:
'---Double-click on the icon in System Tray--- Private Sub NotifyIcon1_MouseDoubleClick( _ ByVal sender As System.Object, _ ByVal e As System.Windows.Forms.MouseEventArgs) _ Handles NotifyIcon1.MouseDoubleClick RestoreWindow() End Sub
Finally, if the user clicks on the close button of the form (see Figure 8), it will minimize the application to the System Tray instead of ending the application.
This is accomplished by the FormClosing event handler of Form1:
Private Sub Form1_FormClosing( _ ByVal sender As Object, _ ByVal e As System.Windows.Forms.FormClosingEventArgs) _ Handles Me.FormClosing MinimizeWindow() e.Cancel = True End Sub
Note that you need to set the e.Cancel property to True in order to prevent the application from ending.
That’s it! You are now ready to test the application.
Testing the Application
Before you test the application, verify that you have successfully installed the Windows service you created earlier. Then press F5 in Visual Studio 2005. Try starting, pausing, and stopping the service to verify that the Windows service works.
With the Windows service running, you can perform the following steps to verify that the Windows service works as intended:
- Create a new Word document in C:.
- Name it as Exam Questions.doc.
- Rename it as Test Questions.doc.
Now go to C: and open up the WordDocumentsHistory.txt file. You should see something like this:
2/25/2007 10:30:49 PM: WordFilesMonitor Started2/25/2007 10:30:57 PM: File Created : C:New Microsoft Word Document.doc2/25/2007 10:30:57 PM: File Changed : C:WINDOWSSHELLNEWWINWORD8.DOC2/25/2007 10:30:57 PM: File Changed : C:New Microsoft Word Document.doc2/25/2007 10:31:07 PM: File : C:New Microsoft Word Document.doc renamed to C:Exam Questions.doc2/25/2007 10:31:07 PM: File Changed : C:Exam Questions.doc2/25/2007 10:31:24 PM: File : C:Exam Questions.doc renamed to C:Test Questions.doc2/25/2007 10:31:24 PM: File Changed : C:Test Questions.doc2/25/2007 10:33:48 PM: File Deleted : C:Test Questions.doc2/25/2007 10:33:48 PM: File Created : C:RECYCLERS-1-5-21-790525478-1425521274-839522115-1003Dc2398.doc2/25/2007 10:33:48 PM: File Changed : C:RECYCLERS-1-5-21-790525478-1425521274-839522115-1003Dc2398.doc
|Figure 9. The Task Bar: Showing the running application.|
This WordDocumentsHistory.txt log file keeps track of all the files activities (with .doc extension).
Also observe that when the application is running, it has an entry in the task bar, as shown in Figure 9). When it is minimized, the entry in the task bar remains.
But when you click the “x” button, the entry in the task bar is gone. To bring the application back up, you can either double-click on the notify icon in the System Tray, or right-click on it and select the Options? menu item.
With some creativity, you can now write some cool, new utilities using Visual Studio 2005. If you have some cool ideas on how to use Windows services, email me. I would like to hear them.