Creating a Windows Service in .NET

Mark Strawmyer Presents: .NET Nuts & Bolts


Welcome to the next installment of the .NET Nuts & Bolts column. In this column we'll explore how to create an application known as a Windows Service. We will cover what Windows Services are and how to create, install, and debug them. It will involve using classes in the System.ServiceProcess.ServiceBase namespace.

What is a Windows Service?

Windows Service applications are long-running applications that are ideal for use in server environments. The applications do not have a user interface or produce any visual output. Any user messages are typically written to the Windows Event Log. Services can be automatically started when the computer is booted. They do not require a logged in user in order to execute and can run under the context of any user including the system. Windows Services are controlled through the Service Control Manager where they can be stopped, paused, and started as needed.

Windows Services, formerly NT Services, were introduced as a part of the Windows NT operating system. They are not available on Windows 9x and Windows Me. You need to use one of the operating systems in the NT generation such as Windows NT, Windows 2000 Professional, or Windows 2000 Server to run a Windows Service. Examples of Windows Services are server products such as Microsoft Exchange, SQL Server, and other applications such as Windows Time that sets the computer clock.

Create a Windows Service

The service we will create does nothing really useful other than serve as a demonstration. When the service is started we will log an entry in a database indicating that it has started. The service will create a database entry on a specified interval during the time in which it is running. The service will create a final database entry when stopping. The service will also automatically log an entry in the Windows Application Log when it is successfully started or stopped.

Visual Studio .NET makes it relatively simple to create a Windows Service. The instructions for starting our demo service are outlined below.

  1. Start a new project
  2. Select Windows Service from the list of available project templates
  3. The designer will open in design mode
  4. Drag a Timer object from the Components tab in the Toolbox onto the design surface (Warning: make sure you use the Timer from the Components tab and not the one from the Windows Forms tab)
  5. Through the Timer properties set the Enabled property to False and the Interval property to 30000 milliseconds
  6. Switch to the code behind view (F7) to add functionality to the service

Makeup of a Windows Service

In the code behind class you will notice that your Windows Service extends the System.ServiceProcess.Service class. All Windows Services built in .NET must extend this class. It requires your service to override the following methods which Visual Studio will include by default.

  • Dispose - clean up any managed and unmanaged resources
  • OnStart - control the service startup
  • OnStop - control the service stoppage

Sample Database Table Script

The following T-SQL script can be used to create the database table used in the example. I am using SQL Server as my database of choice. You can easily modify this example to work with Access or any other database of your choice.

CREATE TABLE [dbo].[MyServiceLog] (
   [in_LogId] [int] IDENTITY (1, 1) NOT NULL,
   [vc_Status] [nvarchar] (40) 
           COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
   [dt_Created] [datetime] NOT NULL
) ON [PRIMARY]

Sample Windows Service

Below is all of the source code for a Windows Service called MyService. The majority of this source code was generated automatically by Visual Studio.

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.ServiceProcess;

namespace CodeGuru.MyWindowsService
{
  public class MyService : System.ServiceProcess.ServiceBase
  {
   private System.Timers.Timer timer1;
   /// <remarks> 
   /// Required designer variable.
   /// </remarks>
   private System.ComponentModel.Container components = null;

   public MyService()
   {
       // This call is required by the Windows.Forms 
       // Component Designer.
     InitializeComponent();
   }

   // The main entry point for the process
   static void Main()
   {
     System.ServiceProcess.ServiceBase[] ServicesToRun;
   
     ServicesToRun = new System.ServiceProcess.ServiceBase[] 
{ new MyService() };

     System.ServiceProcess.ServiceBase.Run(ServicesToRun);
   }

   /// <summary> 
   /// Required method for Designer support - do not modify 
   /// the contents of this method with the code editor.
   /// </summary>
   private void InitializeComponent()
   {
     this.timer1 = new System.Timers.Timer();
     ((System.ComponentModel.ISupportInitialize)
(this.timer1)).BeginInit();
     // 
     // timer1
     // 
     this.timer1.Interval = 30000;
     this.timer1.Elapsed += 
   new System.Timers.ElapsedEventHandler(this.timer1_Elapsed);
     // 
     // MyService
     // 
     this.ServiceName = "My Sample Service";
     ((System.ComponentModel.ISupportInitialize)
(this.timer1)).EndInit();

   }

   /// <summary>
   /// Clean up any resources being used.
   /// </summary>
   protected override void Dispose( bool disposing )
   {
     if( disposing )
     {
      if (components != null) 
      {
         components.Dispose();
      }
     }
     base.Dispose( disposing );
   }

   /// <summary>
   /// Set things in motion so your service can do its work.
   /// </summary>
   protected override void OnStart(string[] args)
   {
     this.timer1.Enabled = true;
     this.LogMessage("Service Started");
   }
 
   /// <summary>
   /// Stop this service.
   /// </summary>
   protected override void OnStop()
   {
     this.timer1.Enabled = false;
     this.LogMessage("Service Stopped");
   }

   /*
    * Respond to the Elapsed event of the timer control
    */
   private void timer1_Elapsed(object sender, 
System.Timers.ElapsedEventArgs e)
   {
     this.LogMessage("Service Running");
   }

   /*
    * Log specified message to database
    */
   private void LogMessage(string Message)
   {
     SqlConnection connection = null;
     SqlCommand command = null;
     try
     {
      connection = new SqlConnection( 
"Server=localhost;Database=SampleDatabase;Integrated 
Security=false;User Id=sa;Password=;");
command = new SqlCommand(
"INSERT INTO MyServiceLog (vc_Status, dt_Created) 
VALUES ('" + Message + "',getdate())", connection);
      connection.Open();
      int numrows = command.ExecuteNonQuery();
     }
     catch( Exception ex )
     {
      System.Diagnostics.Debug.WriteLine(ex.Message);
     }
     finally
     {
      command.Dispose();
      connection.Dispose();
     }
   }
  }
}

Install the Windows Service

Windows Services are different than normal Windows based applications. It is not possible to run a Windows Service by simply running an EXE. The Windows Service should be installed by using the InstallUtil.exe provided with the .NET Framework or through a deployment project such as a Microsoft Installer (MSI) file.

Add an Installer

Having created the Windows Service will not be enough for the InstallUtil program to be able to install the service. You must also add an Installer to your Windows Service so that the InstallUtil, or any other installation program, knows what configuration settings to apply to your service.

  1. Switch to the design view for the service
  2. Right click and select Add Installer
  3. Switch to the design view of the ProjectInstaller that is added
  4. Set the properties of the serviceInstaller1 component
    1. ServiceName = My Sample Service
    2. StartType = Automatic
  5. Set the properties of the serviceProcessInstaller1 component
    1. Account = LocalSystem
  6. Build the Solution

The following code contained in the ProjectInstaller.cs source file was automatically generated by Visual Studio after having completed the steps above.

using System;
using System.Collections;
using System.ComponentModel;
using System.Configuration.Install;

namespace CodeGuru.MyWindowsService
{
  /// <summary>
  /// Summary description for ProjectInstaller.
  /// </summary>
  [RunInstaller(true)]
  public class ProjectInstaller : 
System.Configuration.Install.Installer
  {
   private System.ServiceProcess.ServiceProcessInstaller 
serviceProcessInstaller1;
   private System.ServiceProcess.ServiceInstaller serviceInstaller1;
   /// <summary>
   /// Required designer variable.
   /// </summary>
   private System.ComponentModel.Container components = null;

   public ProjectInstaller()
   {
     // This call is required by the Designer.
     InitializeComponent();

     // TODO: Add any initialization after the InitComponent call
   }

   #region Component Designer generated code
   /// <summary>
   /// Required method for Designer support - do not modify
   /// the contents of this method with the code editor.
   /// </summary>
   private void InitializeComponent()
   {
     this.serviceProcessInstaller1 = new 
System.ServiceProcess.ServiceProcessInstaller();
     this.serviceInstaller1 = new 
System.ServiceProcess.ServiceInstaller();
     // 
     // serviceProcessInstaller1
     // 
     this.serviceProcessInstaller1.Account = 
System.ServiceProcess.ServiceAccount.LocalSystem;
     this.serviceProcessInstaller1.Password = null;
     this.serviceProcessInstaller1.Username = null;
     // 
     // serviceInstaller1
     // 
     this.serviceInstaller1.ServiceName = "My Sample Service";
     this.serviceInstaller1.StartType = 
System.ServiceProcess.ServiceStartMode.Automatic;
     // 
     // ProjectInstaller
     // 
     this.Installers.AddRange(new 
System.Configuration.Install.Installer[] 
{this.serviceProcessInstaller1, this.serviceInstaller1});
}
   #endregion
  }
}

Use InstallUtil to Install the Windows Service

Now that the service has been built you need to install it for use. The following instructions will guide you in installing your new service.

  1. Open a Visual Studio .NET Command Prompt
  2. Change to the bin\Debug directory of your project location (bin\Release if you compiled in release mode)
  3. Issue the command InstallUtil.exe MyWindowsService.exe to register the service and have it create the appropriate registry entries
  4. Open the Computer Management console by right clicking on My Computer on the desktop and selecting Manage
  5. In the Services section underneath Services and Applications you should now see your Windows Service included in the list of services
  6. Start your service by right clicking on it and selecting Start

Each time you need to change your Windows Service it will require you to uninstall and reinstall the service. Prior to uninstalling the service it is a good idea to make sure you have the Services management console closed. If you do not you may have difficulty uninstalling and reinstalling the Windows Service. To uninstall the service simply reissue the same InstallUtil command used to register the service and add the /u command switch.

Debug the Windows Service

As with all other aspects, debugging a Windows Service is different than a normal application. More steps are required to debug a Windows Service. The service cannot be debugged by simply executing it in the development environment as you can with a normal application. The service must first be installed and started, which we covered in the previous section. Once it is started you attach Visual Studio to the running process in order to step through and debug the code. Remember, each change you make to the Windows Service will require you to uninstall and reinstall the service.

Attach to a Running Windows Service

Here are the directions for attaching to a Windows Service in order to debug the application. These instructions assume that you have already installed the Windows Service and it is currently running.

  1. Load the project into Visual Studio
  2. Click on the Debug menu
  3. Click on the Processes menu item
  4. Make sure the Show system processes is selected
  5. Locate your process in the Available Processes list based on the name of your executable and click on it
  6. Click the Attach button
  7. Click OK
  8. Click Close
  9. Set a break point in the timer1_Elapsed method and wait for it to execute

Summary

You should now have a rough idea of what windows services are, how to create, install, and debug them. There is additional functionality with Windows Services that you can explore. This functionality includes the capability to pause (OnPause) and resume (OnContinue). The ability to pause and resume are not enabled by default and are setup through the Windows Service properties.

Future Columns

The next column is yet to be determined. If you have something in particular that you would like to see explained here you could reach me at mstrawmyer@crowechizek.com

About the Author

Mark Strawmyer, MCSD, MCSE (NT4/W2K), MCDBA is a Senior Architect of .NET applications for large and mid-size organizations. Mark is a technology leader with Crowe Chizek in Indianapolis, Indiana. He specializes in architecture, design and development of Microsoft-based solutions. You can reach Mark at mstrawmyer@crowechizek.com.

# # #



About the Author

Mark Strawmyer

Mark Strawmyer is a Senior Architect of .NET applications for large and mid-size organizations. He specializes in architecture, design and development of Microsoft-based solutions. Mark was honored to be named a Microsoft MVP for application development with C# for the fifth year in a row. You can reach Mark at mark.strawmyer@crowehorwath.com.

Comments

  • Facing this error in VS2008

    Posted by Kbilal79 on 03/16/2010 10:27am

    Error	1	'WindowsService1.Service1.Dispose(bool)': no suitable method found to override	F:\DATA\Working\WindowsService1\WindowsService1\Service1.Designer.cs	11	33	WindowsService1

    Reply
  • How to add Service Description?

    Posted by BruceDimon on 08/12/2005 07:34am

    I'm writing my first Windows Service and cannot see how to add the Description that shows in the Description column of the Service Manager applet. Do you know how to add this? Thanks.

    Reply
  • Debugging service made easy

    Posted by gbadauy on 03/19/2004 10:51am

    To debug, you can create a console version anb make your life a lot easier.
    To do it, change the Main() to include the folloeing code:
    if (args.Length > 0 && args[0].ToLower().Equals("/debug"))
    {
    // run as console
    try
    {
    MyService service = new MyService();
    service.RunConsole(args);
    }
    catch (Exception e)
    { ... }
    }
    else
    {
    ServicesToRun = new System.ServiceProcess.ServiceBase[] { new MyService() };
    System.ServiceProcess.ServiceBase.Run(ServicesToRun);





    The RunConsole method can be like this... protected void RunConsole(string[] args)
    {
    this.OnStart(args);
    Console.WriteLine("Press ENTER to stop.");
    Console.ReadLine();
    this.OnStop();
    Console.WriteLine("Service stopped.");
    Console.ReadLine();
    }

    • Thanks for the post, Gustavo! Here's a C++ example

      Posted by bad_source_bios on 01/07/2005 03:48pm

      I like what you did and found it inspiring! Here's a C++ example. I just got it working, but hopefully someone finds this interesting......
      
      //To install the service, type: "HiResSignalControl.exe -Install"
      int _tmain(int argc, _TCHAR* argv[])
      {
        if (argc >= 2)
        {
          if (argv[1][0] == _T('/'))
          {
            argv[1][0] = _T('-');
          }
      
          if (_tcsicmp(argv[1], _T("-Install")) == 0)
          {
            //Install this Windows Service using InstallUtil.exe
            String* myargs[] = System::Environment::GetCommandLineArgs();
            String* args[] = new String*[myargs->Length - 1];
            args[0] = (myargs[0]);
            Array::Copy(myargs, 2, args, 1, args->Length - 1);
            AppDomain* dom = AppDomain::CreateDomain(S"execDom");
            Type* type = __typeof(System::Object);
            String* path = type->get_Assembly()->get_Location();
            StringBuilder* sb = new StringBuilder(path->Substring(0, path->LastIndexOf(S"\\")));
            sb->Append(S"\\InstallUtil.exe");
            dom->ExecuteAssembly(sb->ToString(), 0, args);
          } else {
            String* myargs[] = System::Environment::GetCommandLineArgs();
            String* args[] = new String*[myargs->Length - 1];
            args[0] = (myargs[0]);
            Array::Copy(myargs, 2, args, 1, args->Length - 1);
      
            bool bMatch = String::Compare(  myargs[1]->ToUpper(), "/DEBUG" ) > -1;
            if ( bMatch ) 
            {
              HiResSignalControlWinService *hiResSignalCtrlSvc
                = new HiResSignalControlWinService(); 
              hiResSignalCtrlSvc->RunConsole(myargs);
      
            } // end if (myargs[1]->ToUpper()->IsInterned("DEBUG")) 
      
          } // end if (_tcsicmp(argv[1], _T("-Install")) == 0)
        }
        else 
        {
          ServiceBase::Run(new HiResSignalControlWinService());    
      
        }
        return 0;
      } // end int _tmain(int argc, _TCHAR* argv[])
      
      void HiResSignalControlWinService::RunConsole( String* args[])
      {
        this->OnStart(args);
        Console::WriteLine("Service is running ... hit ENTER to stop");
        Console::ReadLine();
        this->OnStop();
        Console::WriteLine("Service stopped ... hit ENTER to exit");
        Console::ReadLine();
      
      } // end void HiResSignalControlWinService::RunConsole()

      Reply
    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • As mobile devices have pushed their way into the enterprise, they have brought cloud apps along with them. This app explosion means account passwords are multiplying, which exposes corporate data and leads to help desk calls from frustrated users. This paper will discover how IT can improve user productivity, gain visibility and control over SaaS and mobile apps, and stop password sprawl. Download this white paper to learn: How you can leverage your existing AD to manage app access. Key capabilities to …

  • Packaged application development teams frequently operate with limited testing environments due to time and labor constraints. By virtualizing the entire application stack, packaged application development teams can deliver business results faster, at higher quality, and with lower risk.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds