A place for my programming projects and the occasional blog about technology related matters.
Run Windows Service as a console program
Author einar
Visual Studio and the .NET framework make it really easy to create Windows Services. All you have to do is create a new project, select ‘Windows Service’ as your project type and you’re all set. However, debugging Windows Services in Visual Studio can be a big pain. The recommended way is to use InstallUtil to install them, and then restart the service and attach the debugger everytime you want to debug it. I wanted Windows Live! Bot to be available as a Windows Service, but I also wanted to be able to debug it without the hassle, so here’s what I came up with:
using System;
using System.ServiceProcess;
public partial class DemoService : ServiceBase
{
static void Main(string[] args)
{
DemoService service = new DemoService();
if (Environment.UserInteractive)
{
service.OnStart(args);
Console.WriteLine("Press any key to stop program");
Console.Read();
service.OnStop();
}
else
{
ServiceBase.Run(service);
}
}
public DemoService()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
// TODO: Add code here to start your service.
}
protected override void OnStop()
{
// TODO: Add code here to perform any tear-down
//necessary to stop your service.
}
}
This will allow you to use your program as either a normal console program or a windows service, with no special builds, #DEBUG directives, command line parameters or anything like that. What it does is in the Main method it checks the ‘Environment.UserInteractive’ property. This will be true when it is run from Visual Studio, or when you just click on the .exe file, but false if it’s being run as a service. When it’s run from Visual Studio or as a standalone program it will keep running until you press a key, then it will call your OnStop method and then terminate.
Two things to watch out for:
- You’ll have to right click on your project in Visual Studio, choose Properties and select the Output type as ‘Console application’ for this to work.
- If your Main method is not in your service class, you’ll have to add public methods to your class that can start and stop it, for instance add a public void StartConsole(string[] args) that just calls your OnStart, since OnStart and OnStop are protected methods and as such not accessible from other classes.
UPDATE: 16.08.2007
Reader Anderson Imes (who has a rather nice debugging solution of his own) pointed out some things to fix. One thing is that a Windows Service can run many services in the same process, the ServiceBase.Run method can take an array of ServiceBase objects. I still think that the code above is the best way to do it if you have just a single service, just keep the Main method in the service class. But if you’ve got multiple service objects then that doesn’t make sense anymore, we want to put our Main method somewhere else. And then we have the problem of not being able to call OnStart in the service objects, since it’s a protected method. We could define a public StartConsole method in all of them that calls the OnStart method like I described above, but that will get tiresome if you have many objects. Well, there is another way to do it with Reflection which I´ve written here below. The new code is basically the same as the first version, except that it has an array of ServiceBase objects, called servicesToRun, and loops through it, calling each object’s OnStart method through some neat Reflection tricks. When the user presses a button it will stop all services by calling their Stop method, which in turn calls the OnStop method that all Services must define.
using System.ServiceProcess;
using System;
using System.Reflection;
static class Program
{
static void Main(string[] args)
{
ServiceBase[] servicesToRun = new ServiceBase[] { new Service1()
, new Service2()
, new Service3()};
if (Environment.UserInteractive)
{
Type type = typeof(ServiceBase);
BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic;
MethodInfo method = type.GetMethod("OnStart", flags);
foreach (ServiceBase service in servicesToRun)
{
method.Invoke(service, new object[] { args });
}
Console.WriteLine("Press any key to exit");
Console.Read();
foreach (ServiceBase service in servicesToRun)
{
service.Stop();
}
}
else
{
ServiceBase.Run(servicesToRun);
}
}
}
While using this trick has worked perfectly well for my services, which have all been pretty simple, it might not work for everyone. I’m sure there are a few things different between Windows Services and console programs that I don’t know about, and might make the console version of the program behave strangely in some cases. Imes also pointed out that there was no way to test pause-and-resume in the console version. The best way I could think of to mimic service events like these would be to read in keypresses in the main thread and do the appropriate action depending on the key pressed. For instance, instead of the “Press any key to exit” message, we might have “Press p to pause, r to resume, s to stop” and then the result of Console.Read() could be used to determine which action to take and which methods to call on the Service objects. However, I don’t have the time and interest to do it right now, so implementing it is left as an exercise for the reader
.
Reader's Comments
Comment
Options
-
August 15, 2007 -
C#, code, tips & tricks -
19 comments
-
Comments RSS -
Del.ico.us
-
Digg!
Categories
- ASP.NET (1)
- C# (4)
- code (10)
- extensions (2)
- haloscan (1)
- javascript (2)
- meta (2)
- mozilla (2)
- msn (1)
- plugins (2)
- python (3)
- tips & tricks (3)
- utilities (1)
- Visual Studio (1)
- wordpress (5)
- zenphoto (2)
Nice! I like the UserInteractive check - that’s a very good optimization. I personally don’t like the tool to require the code to be written a certain way to get it to work. If you are familiar with reflection, you might use reflection to execute the OnStart and OnStop methods, even though they are non-public methods.
Also, you’ve already instantiated a DemoService object - wouldn’t it be better to do ServiceBase.Run(service)?
Things missing:
What about testing Pause & Resume?
What about controlling multiple services running the same process (notice that there is an overload for ServiceBase.Run that accepts an array of ServiceBase objects)?
Nice work! I’m looking forward to seeing what else you do with it.
http://theimes.com
Good catch on the already instantiated DemoService object. I meant to use the service object but forgot to change the VS generated code.
I’ve added an example to the post showing how to use multiple services in the same process. Pause-and-resume will have to wait for another day
Ask and ye shall receive.
http://theimes.com/archive/2007/08/22/net-windows-service-runner.aspx
Does this work in C# 2.0? When I debug I’m still getting:
Cannot start service from the command line or a debugger. A Windows Service must be installed (using installutil.exe) and then started with the ServerExplorer, Window Services Administrative tool or the NET START command.
I’ve verfied and checked that the output type is set to “Console Application”.
Thanks in advance…!
Hmmm, that’s strange, since I used VS 2005 and C# 2.0 when writing this post. Does this message come right away, or can you set a breakpoint at the first line and see if the UserInteractive check is returning something strange?
Looks nice, but I get a memory error during service.Stop();
I don’t think it’s safe to call protected members directly, since you’re not supposed to know the internals of the ServiceBase class…
I’ve changed the code to call OnStop (instead of Stop()) the same way OnStart() is called, then it works for me…
I am getting the same error as Le where the console window opens then the message box about installing the service is required.
The output type is console application — any idea of what is going on?
Thanks in advance
Steve
In order to install the service, you must add a service installer to the assembly which contains the program. For example:
[RunInstaller(true)]
public class MyServiceInstaller : Installer {
public MyServiceInstaller() {
ServiceProcessInstaller serviceProcessInstaller = new ServiceProcessInstaller();
serviceProcessInstaller.Account = System.ServiceProcess.ServiceAccount.LocalSystem;
serviceProcessInstaller.Password = null;
serviceProcessInstaller.Username = null;
ServiceInstaller serviceInstaller = new ServiceInstaller();
serviceInstaller.Description = “My Service Installer”;
serviceInstaller.ServiceName = “”;
this.Installers.AddRange(new Installer[] {serviceProcessInstaller, serviceInstaller});
}
}
The example written above is for a single service. Alternatively, you can go in Visual Studio, open the service in designer mode, right-click in an emty area in the designer and select “Add installer”. Visual Studio will create the installer for you.
Best Regards,
Mihai
Sorry, I wrongly formatted my previous post:
serviceInstaller.ServiceName is not empty, it must be set to the same value as the ServiceName property of the service that we are installing.
Mihai
Nice!.. I have one difficulty….
I have written one service and executing another program from the service…
But now my problem is that I want to execute the program with local user
privilege and not as a “system”… Is it possible to archive this…
Thanks in advance..
Sandy
You could make the service run as that user, this can be configured in the service properties. However, if you want the service to run as “system” and then start another process as another user, then you’ll have to do some more complicated stuff, something like: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfSystemSecurityPrincipalWindowsIdentityClassImpersonateTopic.asp
Thanks einar for your valuable reply.
I used 2 service objects in my process using the following code:
ServiceBase[] ServicesToRun = new ServiceBase[] { new Service1(), new Service2() };
ServiceBase.Run(ServicesToRun);
I added trace statements to the Service1 and Service2 constructors and also to the OnStart and OnStop methods for bothe services. What is strange is the although both constructors are called. Only the first OnStart() method (that of Service1 is called and not Service2.OnStart. Any idea why?
I don’t know. If you call the ServiceBase.Run method you’re running them as services, not as console programs, and I would think that the ServiceBase should call OnStart in all of them. Maybe there is something in the documentation for ServiceBase?
in order to provide standard behavior for closing a console app (ctrl+c) the following code would be a bit cooler:
[code]
static void Main(string[] args)
{
DemoService service = new DemoService ();
if (Environment.UserInteractive)
{
service.OnStart(args);
Console.CancelKeyPress += new ConsoleCancelEventHandler(
delegate(object sender, ConsoleCancelEventArgs e)
{
service.OnStop();
});
/*
Console.WriteLine(”Press any key to stop program”);
Console.Read();
service.OnStop();
*/
}
else
{
ServiceBase.Run(service);
}
}
[/code]
Cool
Gene, I have the same problem. Two services, both are instantiated, but only the first has its OnStart() called. Did you ever resolve this issue?
Thanks,
Jerry
Gene/Jerry, I’ve also seen this problem. I’m actively researching it.
http://stupiddumbguy.blogspot.com
Just to follow up, here’s how I managed to get multiple services up and running in a single process:
http://stupiddumbguy.blogspot.com/2008/04/running-multiple-net-services-within.html