Tray Notify - Part II (Windows Service)

by Raymond Hawco

Part two of a series that illustrates using WCF to communicate between a Windows service and client application.

1. Introduction

This is the second part of the Tray Notify series which illustrates how a Windows client app can communicate with a Window Service.

The sample application illustrates monitoring file change events from the Windows service and sending out file change notications to the task bar tray applications using the WCF service.

This article will walk through the creation of the Windows service and service installer.

  • Part I - Covers the basic architecture and the creation of the common class library.
  • Part II - Covers the creation of the Windows Service and service installer using C#.
  • Part III - Covers the creation of the WCF service and how it's hosted inside the Windows Service.
  • Part IV- Covers the WPF tray application and its communication with the WCF service.
  • 2. Windows Service Primer

    2.1. Introduction

    A Windows Service is a special type of application that is managed by the OS. Unlike a normal application where a user starts it by clicking a Start menu item, from explorer or by typing in the application name in a console window, a service application is managed by the Windows Service Control Manager (SCM). In other words, to start and stop services a user does this using the Services control panel applet (found under Adminstrator tools section of the control panel).

    2.2. Windows Service Properties

    Other than being managed by the SCM, Windows services have other unique properties. For one thing they can be set to run under specified accounts (as compared to a normal application which typically runs under the account of the user that started it).

    Another property is a Windows Service can be set to auto-restart in case of a catastrophic failure. A normal application that crashes would need to be restarted by the user whereas a windows service can be set to be restarted by the SCM.

    The final interesting property of Windows services is they run independent of any logged on user and run in a different desktop from the user(s). A Windows Service runs in Session 0 desktop only. This gives added security and prevents a Windows Service from directly interacting with a user's desktop (which from Vista on runs in Session 1 and so on).

    In pre-Vista, the OS ran Windows services in Session 0, and also ran the first logged on user in Session 0 as well, given a service the opportunity to choose the "Interact with the desktop" option. Back in the day (aka pre-Vista) users could choose this option and have a Windows Service display dialogs, forms, etc. directly on the user's desktop.

    Although Window Services were not intended to interact with the UI, in some cases it was convenient for a service to be able to do so. Unfortunately, allowing a service to interact with the desktop was also a security issue, so in Vista, Win7 and later, Microsoft not longer supports this option.

    2.3. Debugging Windows Services

    One difficulty with working with Windows services is the issue of how to debug them. Remember a Windows Service runs in a different desktop from the developer coding it. So running a Windows Service under the SCM and trying to step through the code in a debugger isn't going to work.

    For this reason, a bit of abstraction is used when coding up the service code. Rather than putting all the code inside the ServiceBase derived class, an 'implementation' class is used to perform all the work and the ServiceBase derived class just delegates the work to the implementation class. This facilitates debugging because to debug, the code in the implementation class is run (instead of running the ServiceBase derived code). More on this later.

    3. Build out the Windows Service (Host) Project

    The Windows Service project is the Windows Service application that hosts the WCF Service. The service gets registered with SCM (Windows Service Control Manager) to operate as a Windows Service. Windows services are useful because they can be set to auto-start on machine startup and don't require any logged on users to execute.

    Windows Services require several standard methods: OnStart, OnStop, OnShutdown and a few optional methods. When the Windows Service is registered, the SCM handles the startup/shutdown and management of the service. This is a bit different than a regular application.

    A Windows service is used because it is 'common' with any logged on user. If multiple 'my documents' folders of different users are being monitored, it makes sense to perform the monitoring from a single process independent of any user - a Windows Service fits this bill nicely.

    The Windows Service buildout will include adding a service installer so the project can be registered as a Window service and adding code that will host the WCF service.

    3.1. Remove the default Service1 class from the project.

    Open the CG.TrayNotify solution built in Part II and right click on the Service1.cs to remove the default service from the CG.TrayNotify.Host.Service project.

    3.2. Create a TrayNotifyHostService class

    This is the class that gets registered and executed by the Windows Service Control Manager (SCM). In order to function as a Windows Service, all Windows Service projects must contain at least one class derived from ServiceBase.

    Follow these steps to add the TrayNotifyHostService class:

    1. Right click on the "Solution 'CG.TrayNotifyHostService" node in the Solution Explorer
    2. Click "Add\Class..."
    3. Under "Categories:", expand the "Visual C# Items" node and select "General"
    4. Under "Templates:", select "Windows Service"
    5. Enter "TrayNotifyHostService.cs" for the class name
    6. Press "OK"

    3.3. Add an Installer to the TrayNotifyHostService class

    The installer allows the service to be registered using the InstallUtil.exe utility which will be outlined later.

    To add the installer:

    1. Double click on the TrayNotifyHostService to open it in the service designer
    2. In the service designer, right click and choose "Add Installer"
    3. A ProjectInstaller.cs file will be added to the project.

    3.4. Configure the Project Installer

    The installer contains the settings used when starting up the Windows Service. These settings contain the Startup account, startup mode, name, description and so on. After the service has been registered these settings appear under the service in the Services control panel applet.

    Double click on the ProjectInstaller.cs file which will cause the installer to open in the designer window. You'll notice that there are two icons: serviceProcessInstaller1 and serviceInstaller1. Open the properties window in the solution explorer and pin it.

    3.4.1. Rename the serviceProcessInstaller1 class

    The serviceProcessInstaller1 contains startup account settings. Rename this to TrayNotifyHostServiceAccount in the Name field of the properties window.

    3.4.2. Configure the TrayNotifyHostServiceAccount

    In the properties window, set the Account field to LocalSystem.

    3.4.3. Rename the serviceInstaller1 class

    The serviceInstaller1 contains the service display name, description and service dependencies, and startup type. Rename this class to TrayNotifyHostServiceInstaller.

    3.4.4. Configure the TrayNotifyHostServiceInstaller

    Fill out the following fields:

    Description: Hosts the Tray Notify WCF Service
    DisplayName: CG Tray Notify Host
    StartType: Automatic

    Note: The ServicesDependsOn string array isn't used in this sample. However, this is useful if the service depends on other services to run. For example, if a service depended on SQL Server or Message Queuing, then MSSQLSERVER and MSMQ would be entered in this list. This would tell the SCM to ensure that the MSSQLSERVER and MSMQ services were started before this service.

    About the Author

    Arjay Hawco is an application developer/architect and Microsoft MVP who works with the latest WxF .Net technologies. He is also cofounder of Iridyn, Inc, a software consulting firm.

    3.5. Modify the Program.cs to use the new service class

    Since the Service1 service was delete the main method in the program.cs file must be changed.

    Open the program.cs file. By default, the code should look like:

    3.5.1. Alter the Main method

    /// The main entry point for the application.
    static void Main( )
      ServiceBase [ ] ServicesToRun;
      ServicesToRun = new ServiceBase [ ] 
    		new Service1() 
      ServiceBase.Run( ServicesToRun );

    Change the code to:

    /// The main entry point for the application.
    static void Main( string [] args )
      TrayNotifyHostService service = new TrayNotifyHostService( );
      // To run in debug mode, add "/Debug" to the Start Options,
      // "Command Line Arguments" text box located in the Debug
      // tab of the project properties.
      if ( IsDebugMode( args ) )
        WcfServiceHost.RunServiceAsConsoleApp( "Tray Notify Host", service.EventLog );
        // Exit without running in Service mode
      // SERVICE MODE (when service is started by the SCM)
        ServiceBase [ ] servicesToRun = new ServiceBase [ ] { service };
        ServiceBase.Run( servicesToRun );
      catch ( Exception e )
        service.EventLog.WriteEntry( e.Message
          , System.Diagnostics.EventLogEntryType.Error );

    3.5.2. Add a IsDebugMode method

    This method allows the service to be debugged while running in the IDE.

    /// Checks the command line args for a /debug entry
    private static bool IsDebugMode( string [ ] args )
      if ( args == null || args.Length == 0 ) return false;
      if ( args [ 0 ].ToLower( ) == "/debug" ) return true;
      return false;

    3.5.3. Set the /debug string in the project properties debug tab

    In section 5.5.1, the main method was altered to pass in a string[] args param which allows command line params to be passed to the service. Technically these params can be passed when the service is started under the SCM (by setting Start Parameters field in the properties tab of the service); however the params are not intended to be used this way. As a result, the "/debug" switch should only be used while debugging from the Visual Studio IDE. This switch allows the program to be debugged as a console application.

    3.6. Build the solution

    Build the solution and resolve any errors.

    3.7. Register the Service

    Registering the service causes the service to be recognized by the Service Control Manager (SCM).

    To register:

    1. Open a Visual Studio 2008 Command prompt (on Vista and Win7, run as administrator)
    2. Navigate to CG.TrayNotify.Host.Service\bin\debug folder
    3. Type InstallUtil CG.TrayNotify.Host.Service.Exe
    4. In the command window, the following should appear
    5.  a. The Commit phase completed successful

       b. The transacted install has completed.

    6. Verify the service was registered properly by opening the Services control panel applet.
    7. You should see an entry called CG Tray Notify Host.

    4. Summary

    This article built out the Windows Service project, added an installer class and registered it to run as a Windows Service. Part III will cover the creation of the WCF Service and how to host it into the Windows service project that was just created.

    5. About the Author

    Arjay Hawco is an application developer/architect and Microsoft MVP who works with the latest WxF .Net technologies. He is also cofounder of Iridyn, Inc, a software consulting firm.

    6. References

    Windows Services (Platform SDK) - good background read
    Window Service Applications (.net)

    This article was originally published on Thursday May 20th 2010
    Mobile Site | Full Site