There are a large number of sources on the internet describing how to create and/or install a Windows service in .Net, but as I was developing one for the company I work for I did not find one that comprehensively covered everything I needed to know so I thought I'd write a blog entry on this topic. (Note: If you have trouble reading any of the screen shots below, click on the screen shot for a larger image.)
Creating the service in Visual Studio 2008 is relatively simple. To do so, create a new project using the Windows Service template:

Once you have the project in your solution explorer, right click on the service (named Service1.cs by default) and select View Code. This will bring up a skeleton class for your service where you can add code to execute in the OnStart and OnStop methods for when your service starts and stops, respectively.
Once I got this far I went ahead and wrote my code and was ready to begin debugging it. However, as you may already know, you cannot simply run a service in debug mode from Visual Studio. You have to install it, get it running, and then attach to it as a process. I googled on how to install a service and found
this page on MSDN where it said to access the directory in which your project's compiled executable file is located and run InstallUtil.exe from the command line (i.e. installutil yourproject.exe). I tried this and got the message 'installutil' is not recognized as an internal or external command, operable program or batch file. Microsoft forgot to mention that installutil.exe is not part of the environment path by default, so I had to hunt it down. Doing a file search, I found it in the C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\ directory. Anticipating that I would be running it more than once, I added this to the command line path so that I could execute it from anywhere in my file system. To do this, right click on My Computer in Windows explorer, select Properties, click on the Advanced tab, click on the Environment Variables button, select the Path variable, click on the Edit button, add a semicolon to the existing Path value, followed by "C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\;" and click the OK button.

Okay, that wasn't so bad, I thought. I brought up the command prompt, changed to the directory where my exe file was being built, and Windows now knew what installutil was when I typed it in. So, following my instructions from MSDN, I typed in "installutil myService.exe". This generated some output in the command window which I largely ignored other than the last few lines which seemed to indicate the install was successful. However, when I went to look at the services running on my machine my service wasn't there. I went back to my command window and read the output more carefully, and saw "No public installers with the RunInstallerAttribute.Yes attribute could be found". What the heck?
After a little more research, I discovered I needed to add an installer to my project in order for the command line utility to work. This is easy enough to do once you know about it - simply bring up your service designer and right click somewhere on the gray background area(if you have added any components, make sure you are selecting the service itself and not a component) and then select Add Installer. This adds an installer class with two components - a service installer and a service process installer. If you click on the service installer component and look at the properties, you can see where you can name your service (using the ServiceName property) and specify whether to start it automatically or manually (in the StartType property). Next click on the service process installer component and in its properties you can specify what account to run your service under via the Account property (if you select user you will need to provide the name and password of the user the service will run under). I selected Local System as I didn't want to have to supply credentials and I knew this would have access rights to the file system by default. (For some applications, this may not be the best choice if security is an issue, but you should also be aware that the other choices of LocalService or NetworkService may not have the access needed to actually run your service by default.)

Once I had done all of this, I did another build of my project and then went back to the command line. Installutil worked. I could see the service in my list of services. I was happy - until I realized my service wasn't working right. It turned out that this was because I was using an App.config file to initialize some settings. After a little more research, I discovered I needed to copy this to the location of the executable that was being run as the service and rename it to the name of the executable but with a .config extension (i.e. myService.exe.config.) I did this, stopped and restarted the service, and all was well.
After some reflection, however, I thought "Why do I have to start the service manually after I install it? Is there a way to have it start automatically when it is installed?" It turns out there is a way, and it is actually pretty simple. To have your service start upon installation, bring up the code-behind for your project installer (right-click on it in Solution Explorer and select View Code). You should see something like this:
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration.Install;
using System.Linq;
namespace myService
{
[RunInstaller(true)]
public partial class ProjectInstaller : Installer
{
public ProjectInstaller()
{
InitializeComponent();
}
}
}
What we want to do here is have the Installer class start up our service after installation. It turns out the Installer class has an event called Committed that occurs after a successful installation. All that is needed is to add an event handler for this event and put code in it to start the service. To add the event handler, add this line of code after InitializeComponent():
this.Committed += new InstallEventHandler(ProjectInstaller_Committed);
and this new function to your class:
void ProjectInstaller_Committed(object sender, InstallEventArgs e)
{
}
(Intellisense will do most of the work for you if you hit the TAB key at the appropriate times.) Now we just need to add the code to actually start the service when this event occurs. First add this namespace to your using statements:
using System.ServiceProcess;
Next, in your event handler function, add this code:
ServiceController sc = new ServiceController("myService");
sc.Start();
(Replace "myService" with your actual service name.) When you are done, your project installer class should look something like this:
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration.Install;
using System.Linq;
using System.ServiceProcess;
namespace myService
{
[RunInstaller(true)]
public partial class ProjectInstaller : Installer
{
public ProjectInstaller()
{
InitializeComponent();
this.Committed += new InstallEventHandler(ProjectInstaller_Committed);
}
void ProjectInstaller_Committed(object sender, InstallEventArgs e)
{
ServiceController sc = new ServiceController("myService");
sc.Start();
}
}
}
When you are done, save and build your project. You should now find that upon installation, your service is started automatically. This was a plus for me because it was one less set of instructions that I needed to supply to our deployment group. Along these lines, I began thinking about what else I could do to make deployment simpler, and decided to go ahead and add a setup project to my solution. This would mean (in theory) that all that would be needed to install the service would be to double-click on an msi file rather than mucking about with the command prompt and installutil. To add a setup project for your service, right-click on the solution in Solution Explorer and select Add->New Project. From the project templates, select Setup Project (usually under Other Project Types->Setup and Deployment in VS 2008.)

Once you have added a setup project to your solution, you need to add a Custom Action to your setup project so that it knows what to install. To do this, right-click on your setup project in Solution Explorer and select View->Custom Actions. This will bring up the Custom Actions window. Right click on Custom Actions in this window and select Add Custom Action. This will bring up a dialog box. Double-click the Application Folder and click on the Add Output... button. This brings up another dialog box. It should already have the primary output for your service project selected using the Active configuration, so just click OK and then OK again.

Once you have done so, it will add the necessary Custom Action to the setup project to install your service.

Once you have done this, build your setup project. Assuming everything has been done correctly, once the build completes you will find an msi file in your ouput directory. If you installed your service manually using installutil then uninstall it the same way (with the /u switch). Double-click on the msi file in Windows explorer and a little setup wizard will guide you through the installation process of your service. If you have an app.config file, it will even rename and move this for you, rather than having to do it manually as with using installutil.
I did this and thought "great! I'm done!" but something was nagging at me. It was the fact that the setup wizard had an option to install the service for everyone or just me, and was defaulting to just me. I wanted this service to be installed for everyone when it was deployed. In the interests of further simplifying deployment instructions and eliminating another hazard, I wanted this to default to everyone. If you want to do this, it is fairly simple. Select the setup project in Solution Explorer and look at the properties (F4 brings up the properties window if you don't see it). There is a property called InstallAllUsers that by default is set to false. Set it to true, and when you run the setup project, it will default to everyone rather than just me. After further reflection I decided that I wanted to remove the option altogether, since I wanted to always install for everyone. If you want to do this, right-click on your setup project in Solution Explorer and select View->User Interface. This will bring up the user interface window, which is basically a tree-view of the setup screens the user will see. If you click on Installation Folder in this window and look at the properties, you will see a property called InstallAllUsersVisible. If you set this to false, the setup project will not show this option.

I was pretty happy at this point. Deploying the service couldn't be simpler. I tested it myself. I installed it. I double-clicked the msi file again and was able to uninstall it, but I noticed there was also an option to repair. I found that if I selected repair rather than uninstall, I would get a message saying "Error 1001. The specified service already exists". This was not a show-stopper, but it was annoying. I don't like sending out applications for deployment knowing they can generate an error. What if the config file was deleted or changed it such a way that the service didn't work right? What if the service was removed manually? Sure the user could uninstall and then reinstall the service, but it would be much nicer if they could choose repair and it would work rather than generating an error.
It turns out the reason for the error is that when selecting repair, it was trying to install the service again even if it was already installed. If the service was already on the machine, I didn't want repair to try to put it on there again and then generate an error. To fix this, you have to go back to your Custom Actions screen (right-click on your setup project in Solution Explorer, select View->Custom Actions.) Under the install folder, select the Primary Output for your service and look at its properties. There is one called Condition. Set this property's value to Not Intalled (this is case sensitive.)

Now the setup package knew not to install the service if it was already there, but I was still getting an error when I tried to run setup with the repair option. It was saying that it cannot start the service because an instance of the service is already running. I needed to go back to the code I added earlier to the project installer to automatically start the service and only have it do this if the service was stopped. To do this, I brought up the code-behind for my project installer, and modified the event handler as follows:
void ProjectInstaller_Committed(object sender, InstallEventArgs e)
{
ServiceController sc = new ServiceController("myService");
if (sc.Status == ServiceControllerStatus.Stopped)
{
sc.Start();
}
}
After doing this, I did a build of my service project and a build of my setup project (note that if you do a build on your solution, it will not build the setup project by default.) Using the repair option when running setup now worked as desired and did not generate any errors. I was done with setting up the installation of the service.
Back to the debug issue. Now that I had the service installed, I wanted to hit some breakpoints in my code. In order to do this, make sure your service is running. Then while your project is up in Visual Studio, select Debug->Attach to process. Check the Show processes from all users checkbox (otherwise you will only see a grayed-out, disabled process with vshost as part of your service name which you cannot attach to.) Find your service name in the list of processes, select it, and click the Attach button. You will now be able to hit breakpoints in your service (although you may not be able to hit breakpoints put directly in the OnStart method due to timing issues.)
Good for people to know.
ReplyDeleteAny tips on how to integrate cloneable list class inside the base class?
ReplyDeleteTo integrate the cloneable list or dictionary classes inside the base class, first create your own list or dictionary class that inherits from the cloneable list or dictionary classes. Then create your main class and have that inherit from the base cloneable class. You can then add your inherited list or dictionary as a member of this main class, and when you clone the main class it should be able to clone the dictionary or list member also.
ReplyDeleteThanks for your quick response.
ReplyDeleteIt now works :)
I sent you an email with som more questions including a code sample.
One correction to my comment. You don't have to inherit from the list or dictionary classes, you can just create your list or dictionary object directly from these classes and then add them to the main base class as members, at which point the base class clone method should work properly.
ReplyDelete(NOTE: Thanks to Kay for this comment. This was emailed to me and I decided it would be good to post here. If you need to use an inherited class of the CloneableList class rather than instantiating directly from it, this looks like a good solution.)
ReplyDeleteKay's Comment:
I have got it to work now, but I had to do some tweaking.
My code:
public class MyObjectList<T> : CloneableList<T>
{
public override object Clone()
{
MyObjectList<T> NewList = new MyObjectList<T>(); // CloneableList<T> NewList = new CloneableList<T>();
if (this.Count > 0)
{
Type ICloneType = this.GetType().GetInterface("ICloneable", true);
if ((ICloneType != null))
{
foreach (T Value in this)
{
NewList.Add((T)((ICloneable)Value).Clone());
}
}
else
{
foreach (T Value in this)
{
NewList.Add(Value);
}
}
return NewList;
}
return NewList;
}
}
I had to set "your" CloneableList class Clone method to virtual so I could override it because it returned a list of type CloneableList which caused an error since I need the returned list to be of type MyObjectList.
Your cloneable dictionary code is the same as the cloneable list code.
ReplyDeleteYes, the dictionary code does not compile as is.
ReplyDeleteI don't need that so will use the other two.
Thanks!! :-)
Curious, wouldn't it be more appropriate to copy the Field values, rather than the Property values?
ReplyDeleteOtherwise fields with no public property accessor, and Read Only fields which rely on a value being set during construction will not be cloned correctly.
In cloning an object, it is possible that some of the properties could have a value of nothing, which will throw an exception at:
ReplyDeleteItem.SetValue(newObject, IClone.Clone, Nothing)
Thus, wrapping this with a Try/Catch block fixes this little issue.
The way to fix the Dictionary is to change Me(0).GetType to
ReplyDeleteMe.First.GetType