Tuesday, April 7, 2009

Create, Debug, Install Windows Service in Visual Studio .Net 2005/2008

Stumble del.icio.us Reddit MyWeb! Facebook Google bookmark

I was looking for resources on creating and installing a windows service in visual studio 2005 and greater but could not find everything at one place or could not find something that was explained in detail. So I thought, for the benefit for the developers' community, I'll draft something based on what I did. So here is a discussion explaining the steps required to create and then install/uninstall your windows service in visual studio.net 2005 or greater. I'm not sure if the same steps will be valid on VS 2003 as well (I assume they should) as I do not have VS2003. This discussion also proposes a way to easily debug the service application by creating it as a console application. The service runs as a console application if launched in non-service mode. It runs in service mode if the SERVICE compilation flag is defined as explained in one of the steps below.

I will assume here that you are on a development machine here and you have service install/start/stop permissions on your local machine or the machine on which you want to install the service. Easily said, it means that you should have administrative access/rights on your machine.

To create a windows service in VS 2005/2008, follow these steps:


1. Launch your visual studio development environment (you can also launch it by start->Run-> devenv).

2. Hit File -> New->Project to launch the project template box. (see picture below)























3. This will bring up the new project dialog. Choose Windows -> Console application. Give it a name as MyServiceConsoleApp and save it in c:\src\Article directory. You can also give it any other name that you want and save it anywhere on your local machine or remote machine. But I'll assume that you gave the project the same name and saved it in the location as specified. (see picture below)



















4. This will create a console application project and will also generate a Program.cs class file as shown.

5. Right click on Program.cs file, after expanding the solution explorer, and hit delete. (see picture below)

















6. Now right click on the project file in solution explorer and add a new class as shown in picture.

























7. Give this new class a name like MyMainServiceClass.cs. Hit Ok. This will add a new class in your project. (see picture).
















8. Now replace the contents of the newly added MyMainServiceClass.cs with the following code:



// Copyright © 2009 Your truly. All rights reserved.

#if SERVICE
using System;
using System.Diagnostics;
using System.ServiceProcess;

namespace ServiceExample
{
//**************************************************************************
/// <summary>
/// Service shell for MyMainServiceClass.
/// </summary>
/// <remarks>
/// <para>$Id$</para>
/// <author>Author: Your truly</author>
/// </remarks>
//**************************************************************************
public class MyMainServiceClass : System.ServiceProcess.ServiceBase
{
/// <summary>Required designer variable.</summary>
private System.ComponentModel.Container components = null;
/// <summary>The main functional class for this service.</summary>
private MyMainServerAppClass m_mainApp;


//**********************************************************************
/// <summary>
/// Performs basic initialization including creating the instance of
/// <tt>MyMainServerAppClass</tt> which does the real work for this service.
/// </summary>
/// <seealso cref="MyMainServerAppClass"/>
public MyMainServiceClass ()
{
// This call is required by the Windows.Forms Component Designer.
InitializeComponent ();

// Declare that we can handle power events.
CanHandlePowerEvent = true;

// Create our main application instance.
m_mainApp = new MyMainServerAppClass ();
} // end ctor

//**********************************************************************
/// <summary>
/// The main entry point for the service process.
/// </summary>
static void Main ()
{
System.ServiceProcess.ServiceBase[] ServicesToRun;
// More than one user Service may run within the same process. To add
// another service to this process, change the following line to
// create a second service object. For example,
//
// ServicesToRun = New System.ServiceProcess.ServiceBase[]
// {new MyMainServiceClass(), new MySecondUserService()};
//
ServicesToRun = new System.ServiceProcess.ServiceBase[]
{
new MyMainServiceClass()
};

System.ServiceProcess.ServiceBase.Run (ServicesToRun);
} // end main

//**********************************************************************
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">
/// <tt>true</tt> to release both managed and unmanaged resources;
/// <tt>false</tt> to release only unmanaged resources.
/// </param>
protected override void Dispose (bool disposing)
{
if (disposing)
{
if (components != null)
{
components.Dispose ();
}
}
base.Dispose (disposing);
} // end Dispose

//**********************************************************************
/// <summary>
/// Starts this service.
/// </summary>
/// <param name="args">Data passed by the start command.</param>
protected override void OnStart (string[] args)
{
// DEVNOTE: We want any exceptions thrown by Start to go back to our
// caller so it will terminate properly or else the Windows SCM will
// get very confused.
m_mainApp.Start ();
}

//**********************************************************************
/// <summary>
/// Stops this service.
/// </summary>
protected override void OnStop ()
{
m_mainApp.Stop ();
}

//**********************************************************************
/// <summary>
/// Handles changes in the computer's power status for this service.
/// </summary>
/// <remarks>
/// This method does the following actions based on <tt>powerStatus</tt>.
/// <list type="bullet">
/// <item>QuerySuspend => returns <tt>true</tt> to grant permission</item>
/// <item>Suspend => calls <tt>MyMainServerAppClass.Stop</tt></item>
/// <item>Resume* => calls <tt>MyMainServerAppClass.Start</tt></item>
/// <item>else, does nothing.</item>
/// </list>
/// Except for QuerySuspend, the base class <tt>OnPowerEvent</tt> is
/// also called.
/// </remarks>
/// <param name="powerStatus">A <tt>PowerBroadcastStatus</tt> that
/// indicates a notification from the system about its power status.</param>
/// <returns>Always returns <tt>true</tt>.</returns>
protected override bool OnPowerEvent (PowerBroadcastStatus powerStatus)
{
switch (powerStatus)
{
case PowerBroadcastStatus.QuerySuspend:
// The system has requested permission to suspend the
// computer. We return true to grant our permission.
//MySystemLogger.Log ("PowerBroadcastStatus: "+powerStatus,
// MySeverity.Info, "MyMainServiceClass");
return (true);
case PowerBroadcastStatus.Suspend:
// The computer is about to enter a suspended state.
//MySystemLogger.Log ("PowerBroadcastStatus: "+powerStatus,
// MySeverity.Info, "MyMainServiceClass");
m_mainApp.Stop ();
break;
case PowerBroadcastStatus.ResumeAutomatic:
case PowerBroadcastStatus.ResumeCritical:
case PowerBroadcastStatus.ResumeSuspend:
// The system has resumed operation after being suspended.
//MySystemLogger.Log ("PowerBroadcastStatus: "+powerStatus,
// MySeverity.Info, "MyMainServiceClass");
m_mainApp.Start ();
break;
default:
break;
} // end switch

// Let the base class method know about this.
base.OnPowerEvent (powerStatus);

return (true);
} // end OnPowerEvent


//**********************************************************************
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent ()
{
components = new System.ComponentModel.Container ();
this.ServiceName = "MyVS.NetService";
}

} // end class MyMainServiceClass

} // end namespace ServiceExample
#endif // SERVICE



9. In the above code you will notice the #IF directive. This code file will be compiled/executed if the SERVICE compilation flag is defined. I have done this to help in debugging a windows service application. The usual way to debug is to attach the service application debugger to the service process (From tools->Attach to process). But that method was kind of time dependent. You had to be quick to attach to the service process and use Debugger.Break or include some Thread.Sleep methods before you can actually start debugging. Also, another disadvantage was that you had start from OnStart method. Creating the service as a console application gives us a whole new way to debug the service application. While deploying, one can always define the SERVICE flag (I'll show how to do that below), and deploy the exe as a service assembly. Let's continue now.

Having replaced the full code with the one shown above, you may go ahead and define the SERVICE flag in the project properties dialog, build tab as shown here:












































If you check the code in MyMainServiceClass.cs, you will find that the full code is under the IF directive.

























Also, note the usual OnStart, OnStop methods. One should also take care of the OnPowerEvent these days, specially when users can run services/applications on laptops and hibernate or use Sleep on vista etc.

You will soon see that we are going to have two main methods defined in two separate classes. As I said the main method contained in MyServiceClass.cs will be used as entry point when the assembly is compiled with SERVICE flag (as shown above). For all other practical purposes and while debugging, we can remove the SERVICE flag from the project properties dialog and continue as usual as a console application.

10. Now go ahead and add a new class called MyMainServerAppClass.cs following the same steps as shown in step 6 and step 7 above.

11. After adding the new class, replace the code generated in MyMainServerAppClass.cs with the following chunk of code:


// Copyright © 2009 Your truly. All rights reserved.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Globalization;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Threading;

namespace ServiceExample
{
//**************************************************************************
/// <summary>
/// Main server application for my service.
/// </summary>
/// <remarks>
/// An instance of this class will:
/// <list type="number">
/// <item>start the system and app-specific loggers</item>
/// <item>cause the application configuration data to be read</item>
/// </list>
/// <para>$Id$</para>
/// <author>Authors: Your truly</author>
/// </remarks>
//**************************************************************************
public class MyMainServerAppClass
{
/// <summary>
/// Definitions of the state codes for the server process.
/// </summary>
private enum MyServerState
{
/// <summary>The server is not running.</summary>
Stopped = 0,
/// <summary>The server is starting.</summary>
Starting,
/// <summary>The server is running.</summary>
Running,
/// <summary>The server is stopping.</summary>
Stopping,
};

/// <summary>
/// Time in milliseconds to respond to a <see cref="Start"/> or
/// <see cref="Stop"/>.
/// </summary>
const int SYSRESPONSE_INTERVAL = 10000;

/// <summary>The current process state.</summary>
private volatile MyServerState m_srvrState;

/// <summary>
/// Lock object reserved for <see cref="Start"/> and <see cref="Stop"/>.
/// </summary>
private readonly object m_startStopLock = new object();

//**********************************************************************
/// <summary>
/// Constructor.
/// </summary>
public MyMainServerAppClass()
{
this.m_srvrState = MyServerState.Stopped;

// Create event handler to log an unhandled exception before the
// server application is terminated by the CLR.
AppDomain.CurrentDomain.UnhandledException
+= new UnhandledExceptionEventHandler
(AppUnhandledExceptionEventHandler);
} // end ctor

//**********************************************************************
/// <summary>
/// Start the Server.
/// </summary>
/// <remarks>
/// Start the subsystems needed to run the server.
/// </remarks>
public void Start()
{
if (MyServerState.Stopped == this.m_srvrState)
{
this.m_srvrState = MyServerState.Starting;
// Do some logging here if you want

lock (this.m_startStopLock)
{
// DEVNOTE: We do not want any exceptions to be caught at
// this level because they are
// considered fatal and we want the service to shutdown so that
// it can be restarted.
Thread startup = new Thread(Startup);
startup.Priority = ThreadPriority.AboveNormal;
startup.Name = "My Server Startup";
startup.Start();
startup.IsBackground = true;
startup.Join(SYSRESPONSE_INTERVAL);
}
}
} // end Start

//**********************************************************************
/// <summary>
/// Stops the Server.
/// </summary>
/// <remarks>
/// Stops the server and its subsystems.
/// </remarks>
public void Stop()
{
if ((MyServerState.Starting == this.m_srvrState) ||
(MyServerState.Running == this.m_srvrState))
{
this.m_srvrState = MyServerState.Stopping;

lock (this.m_startStopLock)
{
Thread shutdown = new Thread(Shutdown);
shutdown.Priority = ThreadPriority.Highest;
shutdown.Name = "My Server Shutdown";
shutdown.IsBackground = true;
shutdown.Start();
shutdown.Join(SYSRESPONSE_INTERVAL);
}
}
// Stop the logging thread and close the log files here

} // end Stop

// *********************************************************************
/// <summary>
/// A sample routine where you'll start your server work
/// </summary>
private void DoSomeSampleWork()
{
// Do some work here. For example start listening to some event on
// a serial port or start a tcp listener or start watching your
// directories and files on your machine etc
// Or wire up event listeners
}

// *********************************************************************
/// <summary>
/// A sample routine where you'll start your server work
/// </summary>
private void StopDoingSampleWork()
{
// Stop doing your sample work here. For example stop listening to the
// event on your serial port or stop the tcp listener or stop watching your
// directories and files on your machine etc. that you started in
// DoSomeSampleWork
// or un-wire your event listeners
}

//**********************************************************************
/// <summary>
/// Starts the logging subsystem for the server, the server's system
/// logger, and the audit logger.
/// </summary>
/// <remarks>
/// <para>
/// Startup of the logging subsystem creates a static work queue that
/// is used by all loggers.
/// </para>
/// <para>
/// Important server process information is written to the system log
/// at startup.
/// </para>
/// </remarks>
private void StartLogging()
{
// starup your custom logging here in some text file etc.
return;
} // end StartLogging

//*********************************************************************
/// <summary>
/// Event handler to log the unhandled exception event and perform an
/// orderly shutdown before the application is terminated by the CLR.
/// </summary>
/// <remarks>
/// This event handler is called by the CLR on the thread that
/// encountered the unhandled exception. The code in this handler, as
/// well as any single-threaded code the handler calls, will be executed
/// without being suddenly terminated. When done executing this handler,
/// the CLR immediately and silently terminates the application and all
/// application threads. However, while executing this handler, the other
/// threads in the application are still alive and the CLR keeps scheduling
/// and running them. Plus, the CLR allows the thread executing the handler
/// to be programmatically controlled. It does not automatically abort the
/// thread if the handler goes into a tight loop or suspends its thread
/// (which of course could be abused and allow an application to get into
/// an unknown state). If code in this handler or code called by this
/// handler encounters another unhandled exception, this handler is NOT
/// called again. Instead, the CLR proceeds to terminate the application.
/// </remarks>
/// <param name="sender">Reference to object that initiated event.</param>
/// <param name="ea">Object that holds the event data.</param>
private void AppUnhandledExceptionEventHandler
(
object sender,
UnhandledExceptionEventArgs ea
)
{
Thread.CurrentThread.Priority = ThreadPriority.Highest;
Exception excp = ea.ExceptionObject as Exception;

// Note: In a Debug build, the system logger writes the log message
// to all debug listeners before placing the message in the log
// message queue for logging.
string msg = "Unhandled Exception! Server state=" + m_srvrState;
if (null != excp)
{
//MySystemLogger.Log(msg, excp,
// "MyMainServerAppClass.LogUnhandledExceptionEventHandler");
}
else
{
// Do some logging
}

// TODO: write to Windows Event Log.

// Termination has begun. Resistance is futile! Let's do an
// orderly shutdown before the CLR terminates the application.
Stop();
// TODO: Exit on AppUnhandledExceptionEventHandler is a only a test.
Environment.Exit(5); // 5 was arbitrarily chosen for this 'Test'.

} // end AppUnhandledExceptionEventHandler

//**********************************************************************
/// <summary>
/// Startup the server subsystems.
/// </summary>
/// <exception cref="Exception">Exceptions are captured in <c>Startup</c>
/// only to update state info or log additional details.
/// The <see cref="Exception"/> must be rethrown so that the OS service
/// control routines can handle it.</exception>
private void Startup()
{
// Exit if not in "Starting" state
if (MyServerState.Starting != this.m_srvrState) return;

try
{
// Startup our logging.
StartLogging();
// TODO: Do not always need to log the Thread apartment model on Start.
// Do some logging

this.m_srvrState = MyServerState.Running;
// Now start doing some work
DoSomeSampleWork();
}
catch (Exception excp)
{
// Do logging and process exception
throw;
}
Debug.Print("MyMainServerAppClass.Startup has finished.");
} // end Startup

//**********************************************************************
/// <summary>
/// Shutdown the server subsystems.
/// </summary>
private void Shutdown()
{
// Exit if not in "Stopping" state
if (MyServerState.Stopping != this.m_srvrState) return;

try
{
StopDoingSampleWork();
}
catch (Exception excp)
{
// Log and process exception
// TODO: write to Windows Event Log.
}
finally
{
this.m_srvrState = MyServerState.Stopped;
// Log statistics
}
Debug.Print("MyMainServerAppClass.Shutdown has finished.");
} // end Shutdown

#if !SERVICE
/// <summary>
/// The number of milliseconds between printing a # on the console.
/// </summary>
const int HEARTBEAT_INTERVAL = 10000;

/// <summary>
/// Reminder message.
/// </summary>
static private string s_prompt =
"\r\nPress q<enter> to exit or r<enter> to restart...";

/// <summary>
/// Accumulating beat count.
/// </summary>
static private uint s_beatCount;

/// <summary>
/// State of heartbeat.
/// </summary>
static private volatile bool s_heartBeat;

/// <summary>
/// A timer to drive the rhythmic heartbeat console output.
/// </summary>
/// <seealso cref="DoHeartBeat"/>
static private readonly Timer HEARTBEAT_TIMER = new Timer(
MyMainServerAppClass.DoHeartBeat,
null,
Timeout.Infinite,
HEARTBEAT_INTERVAL);

//**********************************************************************
/// <summary>
/// Turn on the heartbeat if <see langword="true"/>, otherwise turn off.
/// </summary>
static private bool HeartBeat
{
set
{
MyMainServerAppClass.s_heartBeat = value;
if (value) // turn on
{
MyMainServerAppClass.HEARTBEAT_TIMER.Change(0, HEARTBEAT_INTERVAL);
}
else // turn off
{
MyMainServerAppClass.HEARTBEAT_TIMER.Change(
Timeout.Infinite, Timeout.Infinite);
}
}
}

//**********************************************************************
/// <summary>
/// The timer callback to print the heartbeat symbol on the console.
/// </summary>
/// <param name="state">Ignored.</param>
static private void DoHeartBeat(object state)
{
if (MyMainServerAppClass.s_heartBeat)
{
MyMainServerAppClass.s_beatCount++;

if (0 == MyMainServerAppClass.s_beatCount % 81)
{
Console.WriteLine(MyMainServerAppClass.s_prompt);
}
else
{
Console.Write('#');
}
}
} // end HeartBeat

//**********************************************************************
/// <summary>
/// The main entry point for debugging the application.
/// </summary>
[MTAThread]
static void Main(string[] args)
{
Thread.CurrentThread.Name = "My Server (App) Console main thread";
Thread.CurrentThread.Priority = ThreadPriority.AboveNormal;

MyMainServerAppClass server = new MyMainServerAppClass();
bool flagRestart = false;
bool flagQuit = false;
do
{
while (server.m_srvrState != MyServerState.Stopped)
{
Console.WriteLine("\r\nWaiting for server to stop...");
Thread.Sleep(SYSRESPONSE_INTERVAL);
}

Console.WriteLine("\r\nSTARTING...Please wait...");
server.Start();
Thread.Sleep(SYSRESPONSE_INTERVAL);

Console.WriteLine(MyMainServerAppClass.s_prompt);

flagRestart = false;
MyMainServerAppClass.HeartBeat = true;
bool _continue = true;
string line;
while (_continue)
{
line = Console.ReadLine();
flagRestart =
line.Equals("r", StringComparison.InvariantCultureIgnoreCase);
flagQuit =
line.Equals("q", StringComparison.InvariantCultureIgnoreCase);

if (!flagQuit && !flagRestart)
{
// Do something, if you want to test via console based
// on console input
}
else
{
_continue = false;
}
}
MyMainServerAppClass.HeartBeat = false;
string endStr = flagRestart ? "Restarting..." : "STOPPING";
Console.WriteLine(endStr);

//if (null != server.m_server &&
if((MyServerState.Running == server.m_srvrState ||
MyServerState.Starting == server.m_srvrState))
{
server.Stop();
Thread.Sleep(SYSRESPONSE_INTERVAL);
}

} while (flagRestart);
Debug.Print("MyMainServerAppClass.Main has finished.");
}

#endif // !SERVICE

} // end class MyMainServerAppClass

} // end namespace ServiceExample



12. Having replaced the code in MyMainServerAppClass.cs, you will notice the IF directive again.
























This code will be executed only if the SERVICE flag is not defined. Thus helping us debug the code better as a console application when DEBUG is defined in project properties :). Note that even when you are trying to debug the service via the usual way of attaching to the service process, you will have to deploy the service application with debug build.

At this point you are actually done developing the windows service ;). You can go ahead and add your code in DoSomeSampleWork and StopDoingSampleWork methods. you can have your own logging routines at whatever place you like in MyMainServerAppClass. Keep in mind though, that you should ideally not do much in your MyMainServiceClass except creating a new instance of MyMainServerAppClass and calling start/stop methods.

For debugging purposes, the console application will keep running and if you want you can have some test routines called from Main method from within the while loop. The console application will keep blinking and printing a # every few seconds. I call it HeartBeat to indicate that it's doing something. You can process your test routines while debugging based on whatever you want to do in the sample methods above or based on the Console input too.

Having said that, the next question is how to install/uninstall this windows service. The usual manual way is to use installutil.exe. But a better way is to have the installer within the project so you can bundle it within the setup that you create. I have tried to explain creating the setup for deploying/installing/un-installing a windows service here.

13. Right click on the project node (see step 6 above) and add a new class (actually an installer class) as shown here:
















and name it MyServiceInstaller.cs.

14. Go ahead and replace the code in this cs file with the following:


// Copyright © 2009 Your truly. All rights reserved.

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

namespace ServiceExample
{
// *************************************************************************
/// <summary>
/// This is a custom project installer.
/// Applies a name to the service, sets description to the service,
/// sets user name and password.
/// </summary>
[RunInstaller(true)]
public class MyServiceInstaller : Installer
{

/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
private ServiceProcessInstaller m_processInstaller;
private ServiceInstaller m_serviceInstaller;

// *********************************************************************
/// <summary>
/// Initializes a new instance of <see cref="MyServiceInstaller"/>.
/// </summary>
public MyServiceInstaller()
{
InitializeComponent();

m_processInstaller = new ServiceProcessInstaller();
m_processInstaller.Account = ServiceAccount.LocalService;
m_processInstaller.Username = null;
m_processInstaller.Password = null;
m_serviceInstaller = new ServiceInstaller();
m_serviceInstaller.StartType = ServiceStartMode.Automatic;
m_serviceInstaller.ServiceName = "MyVS.NetService";
m_serviceInstaller.DisplayName = "MyVS.NetService";
m_serviceInstaller.Description = "MyVS.NetService";

Installers.AddRange(new Installer[] {
m_processInstaller,
m_serviceInstaller});
}

// *********************************************************************
/// <summary>
/// This method is run before the install process.
/// This method cane be overriden to set the following parameters:
/// service name ,service description, account type, user account user
/// name,a user account password. Note that when using a user account,
/// if the user name or password is not set,
/// the installing user is prompted for the credentials to use.
/// </summary>
/// <param name="savedState"></param>
protected override void OnBeforeInstall(IDictionary savedState)
{
base.OnBeforeInstall(savedState);
// If you want to customize the name/description/accounttype etc
// you can do it here.
}

// *********************************************************************
/// <summary>
/// Uninstall based on the service name
/// </summary>
/// <param name="savedState"></param>
protected override void OnBeforeUninstall(IDictionary savedState)
{
base.OnBeforeUninstall(savedState);

// Set the service name based on the input custom name as in
// OnBeforeInstall
}

// *********************************************************************
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed;
/// otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}

#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()
{
components = new System.ComponentModel.Container();
}

#endregion

} // end class MyServiceInstaller
} //end namespace ServiceExample



15. You will notice that the service name, display name and the service description is being set here. ServiceName is the name by which windows will identify this service (for example when issueing a net start/stop command from command prompt). Display name is the name that will appear in the windows service console. Description is obviously just the service description as to what it does.

If you want you can override OnBeforeInstall and OnBeforeUnInstall and get your own custom defined parameters for service name/displayname/description/account/username/password/startuptype etc and then go ahead with install or uninstall. In most of the cases, you will be fine with just declaring the servicename/display name and description. By default, the startuptype will be automatic (telling the SCM that the service should start as soon as OS starts) and account will be local system account with no user defined username/password. If you definitely need to have all of these custom parameters defined at the time of install/uninstall, I suggest using an ini file to read these values and then proceed. you will likely want to use OnBeforeInstall to accomplish this. If you face any problem doing so, let me know and I will definitely help you.

16. So far we have developed the code for both service as well as console debugging, but haave not included the reference to ServiceProcess namespace. Had we started with Windows service template, VS automatically does that. So let's do it here. Go ahead, right click on References node in solution explorer and hit "Add Reference".
















17. this will bring you to add reference dialog. Select system.ServiceProcess from the list and hit OK.




















At this point if you want you can build your service as a console app, but I suggest you walk with me. So let's go ahead and create a new project for installation and add this project to the current solution.

18. Right click on the solution node and select "Add -> New project...".


















19. This will bring up the "Add new project" dialog. so Let's add this project as MyServiceSetup and save it in the same directory where we saved the service project in step 3 above.
Make sure you select the project type as "Setup and deployment->Setup project" a shown here.

















20. This will bring you to the following solution structure with the Filesystem view in setup project.





















21. Select the MyServiceSetup node as shown above and hit "F4" function key. This will allow you to define the setup parameters like author. company name etc. (The usual setup stuff :))























22. Now right click on the MyServiceSetUp node again and select "Add->Project output...".

























23. Select the project as "MyServiceConsoleApp" as primary output. you may select one of the Active-debug/release configurations from drop down for configurations. I suggest you select debug configuration for now. If you are going to install in production, select Release configuration.
























24. You will see the primary output added to your application folder as shown here.










25. Now the next step is to select View menu from the menubar and select "View->Editor->Custom actions".





















26. This will bring you to the custom actions editor view.
















27. Now right click on the custom actions node at the top left and select "Add Custom Action..."










28. This should bring you to "Select Item in Project" dialog.


















29. Double click "Application folder" and then select "Primary output from MyServiceConsoleApp...". Hit ok.


















30. As soon as you do this, you will see that the primary output (our service exe!) is added to all the four folders listed under "Custom actions". this is because the same exe must be used in all of these cases.











31. Now that we are done with adding our setup output, lets go ahead and try building our solution. Right click on the solution node in the solution explorer and hit Properties.



















32. Navigate to configuration treenode and hit "Configuration manager..." button.
















33. From configuration manager dialog, select solution configuration as "debug" for now (change it to Release when deploying in production) and check both the checkboxes against Build column.
















Hit close. Hit OK.

34. Make sure you have the SERVICE constant defined in the MyServiceConsoleapp project's property window.






















35. Now, go ahead and right click again on solution node and hit "Rebuild Solution" (Also Ctrl+Shift+B).





















36. If you followed every step dutifully, yu should have the following output window at the bottom. (If you do not have the output window already at the bottom, go to menubar View-> Output ) (also Ctrl+W,O). You should see the success message and the msi package details:










37. It's time we check if our service installs on our machine. You must have the required permissions to install/uninstall services at this point or it will fail.

Go ahead and right click MyServicSetUp project node in solution explorer and hit Install.

























38. The installation launches the windows installer package. Just keep clicking next and finish the installation.
































































































39. Alright!If the installer completed successfully, go ahead and hit the Windows start button ->Run.
























40. Bring up the run dialog and type "SERVICES.MSC" a shown here:












41. Wait until the Windows services console is launched and see if your service is listed there. (for enthusiasts , you can start a command prompt and start your service right away from there by issuing net start ). Your service console should display the same display name/description as you noted while programming.











42. Go ahead start or stop the service.

43. Now let's come back and see if uninstall works properly. you might want to keep the service started before we go forward with un-installation just for test purposes for your service.












So, start the service and then come back to solution explorer in VS, select MyServiceSetUp node and right click -> select Uninstall. this should launch the windows installer again and uninstall the service. you can verify this by going to the service console again.











If, however, the service is still listed there and startuptype is changed to disabled, just close and reopen the services console (start->run->services.msc). Your service should be gone now. If it still shows up, that means the uninstallation was not successful.

Congratulation!!! You just created and installed/uninstalled your windows service!

If you want to launch the service in console app mode, just remove the "SERVICE" definition from the project properties dialog (step 9) and launch the project (hit F5.) The service will launch in console mode:













Happy debugging the easier way !!!

:)

10 comments:

  1. If you want to auto-start the service as soon as it is installed, you can override the OnAfterInstall or subscribe to the ServiceInstaller's Committed event. In the eventhandler, you may create an instance of ServiceController class and call the Start method passing your serviceName as an argument. So for the code given above, just paste the following code at the end of MyServiceInstaller class.
    // *********************************************************************
    /// <summary>
    /// Raises the System.Configuration.Install.Installer.AfterInstall event.
    /// </summary>
    /// <param name="savedState">An
    /// <see cref="System.Collections.IDictionary "/> that contains the
    /// state of the computer after all the installers contained in the
    /// System.Configuration.Install.Installer.Installers property have
    /// completed their installations.</param>
    protected override void OnAfterInstall(IDictionary savedState)
    {
    base.OnAfterInstall(savedState);
    ServiceController controller = new ServiceController(ServiceName);
    controller.Start();

    }

    ReplyDelete
  2. Do you have this in a vb version?

    ReplyDelete
  3. Do you mean VB.Net or VB6? If you are looking for a VB.Net version, you can simply copy the code and convert it 100% reliably at http://www.carlosag.net/Tools/CodeTranslator/. If you are looking for a VB6 version of the same code, let me know. I can write it and send it across.

    ReplyDelete
  4. Hi, I tested this to my web application with a seperate projects inside the solution so when I worked inside console mode it gives me the results successfuly which I retrive the results from DB and extract it into log file all of this works successfully inside console mode but inside the window services there is no results with the text file is your example just support printing #

    ReplyDelete
  5. Hello,
    I followed your steps and everything is working. The problem is that I do not know how to debug. Please send me the detailed steps on this.
    Thank you.

    ReplyDelete
  6. Hello,
    I am using Visual Studio 2008 on Vista 64bit. I have followed your steps exactly. I am getting the following errors:

    1. 'MyServiceConsoleApp.MyServiceInstaller.Dispose(bool)': no suitable method found to override
    2. Unable to build custom action named 'Primary output from MyServiceConsoleApp (Debug Any CPU)', InstallerClass property is only valid for assemblies.

    Could you please let me know what to do to fix this?

    ReplyDelete
  7. Very helpful article. Thanks for sharing.

    ReplyDelete
  8. PLEASE HLP.Two failed
    1. 'MyServiceConsoleApp.MyServiceInstaller.Dispose(bool)': no suitable method found to override
    2. Unable to build custom action named 'Primary output from MyServiceConsoleApp (Debug Any CPU)', InstallerClass property is only valid for assemblies.

    ReplyDelete
  9. Hi,

    This is a really helpful example. One question, what is the advantage of using a thread to start and stop the work?

    From what little I understand about this in the StartWork method one will either fire of a new thread, start a timer, start a listener or similar.

    And your code waits for the startup/shutdown thread to complete anyway... so is there any real need to put the startup and shutdown inside their own threads?

    Cheers,

    Dale

    ReplyDelete

Please leave your opinion...