Introduction to introduction
Before the article begins, just a quick word about me and C#. I don't work in C#, I don't like C# (because I know too little about it), I don't plan to start working with C# for extended periods of time any time soon. That said, this is an article about customizing installations of Windows services, in C#!
You all probably think I'm a nut case. I submitted an article to CodeProject last week with the exact same title and topic as this one. Only I wrote it in VB.NET. And someone asked me whether I'll be posting a C# version. And I said, I would. But like I said, I am not a C# programmer. The two things that the initial article meant to do (mentioned in Introduction below) works in this C# version too. You might think the code looks all crappy and I don't know the C# variable declaration conventions. Well, it does and I don't. But like I said, it works.
The addition of service descriptions and setting a service to interact with the desktop are the only things I tested, as these were the only aims of the original article. The modAPI file has a lot of additional code that you can play with and experiment to your hearts desire. All in C#, of course.
The article below is a copy of the original, with the exception that everything VB has been changed to C#.
Introduction
I have seen many articles, forums, and other writings on the pros and cons of writing Windows Services in .NET, particularly using VB.NET. One of the topics that feature very often in these discussions, if not the most often, is the lack of functionality in the services themselves or the installers that distribute these services.
Three of these things that this article will aim at solving includes adding a description to the service during installation, allowing the service to interact with the desktop, as well as setting Recovery options. This is shown in the figures below.



There are hacks or workarounds available but these all include using registry editor or a certain amount of manual labor. Not the ideal situation when distributing your application to your customers. We will try and do these things automatically during application installation.
Background
For a background on creating Windows services and adding installers to the services, I suggest you read the following article on MSDN: Walkthrough: Creating a Windows Service Application in the Component Designer. (The walkthrough covers VB and C#!)
You'll probably have to read it to understand the rest of this article.
Using the code
Included in the download on this page are two projects. The first is a service that will write an entry to the application log on startup and shutdown. (Bear in mind that this article is not aimed at the functionality of the service but rather what happens when the service is installed.) The second is the installer project for the service project. (Make sure you read the MSDN walkthrough mentioned in Background!)
The code that does the work during installation forms part of the service project. A module is included with the service project called modAPI. This module contains all the declarations, constants, structures and enumerations needed to change the service settings.
To make changes to the service during installation, you have to include your code that calls the functions in modAPI in the ProjectInstaller
's AfterInstall
event. See the code comments for more information.
The code that sets the service description and enables the service to interact with the desktop is shown below. This is the same code you will find in the download. The code is commented and pretty much self explanatory. You can also find all the information on the API calls in MSDN. The modAPI module in the project contains just about every constant, structure and enumeration you will need to change any of the service settings.
private void ProjectInstaller_AfterInstall(object sender,
System.Configuration.Install.InstallEventArgs e)
{
int iSCManagerHandle = 0;
int iSCManagerLockHandle = 0;
int iServiceHandle = 0;
bool bChangeServiceConfig = false;
bool bChangeServiceConfig2 = false;
modAPI.SERVICE_DESCRIPTION ServiceDescription;
modAPI.SERVICE_FAILURE_ACTIONS ServiceFailureActions;
modAPI.SC_ACTION[] ScActions = new modAPI.SC_ACTION[3];
bool bCloseService = false;
bool bUnlockSCManager = false;
bool bCloseSCManager = false;
IntPtr iScActionsPointer = new IntPtr();
try
{
iSCManagerHandle = modAPI.OpenSCManagerA(null, null,
modAPI.ServiceControlManagerType.SC_MANAGER_ALL_ACCESS);
if (iSCManagerHandle < 1)
{
throw new Exception("Unable to open the Services Manager.");
}
iSCManagerLockHandle = modAPI.LockServiceDatabase(iSCManagerHandle);
if (iSCManagerLockHandle < 1)
{
throw new Exception("Unable to lock the Services Manager.");
}
iServiceHandle = modAPI.OpenServiceA(iSCManagerHandle, "C#ServiceTest",
modAPI.ACCESS_TYPE.SERVICE_ALL_ACCESS);
if (iServiceHandle < 1)
{
throw new Exception("Unable to open the Service for modification.");
}
bChangeServiceConfig = modAPI.ChangeServiceConfigA(iServiceHandle,
modAPI.ServiceType.SERVICE_WIN32_OWN_PROCESS |
modAPI.ServiceType.SERVICE_INTERACTIVE_PROCESS,
modAPI.SERVICE_NO_CHANGE, modAPI.SERVICE_NO_CHANGE,
null, null, 0, null, null, null, null);
if (bChangeServiceConfig==false)
{
throw new Exception("Unable to change the Service settings.");
}
ServiceDescription.lpDescription =
"This is my custom description for my Windows Service Application!";
bChangeServiceConfig2 = modAPI.ChangeServiceConfig2A(iServiceHandle,
modAPI.InfoLevel.SERVICE_CONFIG_DESCRIPTION,ref ServiceDescription);
if (bChangeServiceConfig2==false)
{
throw new Exception("Unable to set the Service description.");
}
ServiceFailureActions.dwResetPeriod = 600;
ServiceFailureActions.lpRebootMsg =
"Service failed to start! Rebooting...";
ServiceFailureActions.lpCommand = "SomeCommand.exe Param1 Param2";
ServiceFailureActions.cActions = ScActions.Length;
ScActions[0].Delay = 20000;
ScActions[0].SCActionType = modAPI.SC_ACTION_TYPE.SC_ACTION_RESTART;
ScActions[1].Delay = 20000;
ScActions[1].SCActionType = modAPI.SC_ACTION_TYPE.SC_ACTION_RUN_COMMAND;
ScActions[2].Delay = 20000;
ScActions[2].SCActionType = modAPI.SC_ACTION_TYPE.SC_ACTION_REBOOT;
iScActionsPointer =
Marshal.AllocHGlobal(Marshal.SizeOf(new modAPI.SC_ACTION()) * 3);
modAPI.CopyMemory(iScActionsPointer,
ScActions, Marshal.SizeOf(new modAPI.SC_ACTION()) * 3);
ServiceFailureActions.lpsaActions = iScActionsPointer.ToInt32();
bChangeServiceConfig2 = modAPI.ChangeServiceConfig2A(iServiceHandle,
modAPI.InfoLevel.SERVICE_CONFIG_FAILURE_ACTIONS,
ref ServiceFailureActions);
if (bChangeServiceConfig2==false)
{
throw new Exception("Unable to set the Service Failure Actions.");
}
}
catch(Exception ex)
{
throw new Exception(ex.Message);
}
finally
{
Marshal.FreeHGlobal(iScActionsPointer);
if (iServiceHandle > 0)
{
bCloseService = modAPI.CloseServiceHandle(iServiceHandle);
}
if (iSCManagerLockHandle > 0)
{
bUnlockSCManager = modAPI.UnlockServiceDatabase(iSCManagerLockHandle);
}
if (iSCManagerHandle != 0)
{
bCloseSCManager = modAPI.CloseServiceHandle(iSCManagerHandle);
}
}
}
That's about it then. Like I mentioned before, the modAPI module can be used to change other service settings as well. It will take some experimenting and some playing on your part, but that's what makes this fun!
Enjoy!
History
- 17 November 2003
- Added a
finally
statement to the ProjectInstaller_AfterInstall
procedure code to make sure handles are closed.
- Also added the
LockServiceDatabase
and UnlockServiceDatabase
API functions after reading in MSDN that it is a good idea to use it.
- 27 November 2003
- Complete rewrite of modAPI as well as a lot of new code added to the
ProjectInstaller_AfterInstall
procedure to accommodate changing the Recovery options for services.
- Changes to modAPI include the following:
- All API declarations changed to
DllImport
.
- All
long
data types changed to the proper type, either String
, Integer
or IntPtr
depending on their function. IntPtr
is used wherever a pointer is expected. Integers are used wherever a 32 bit value is expected (long
in .NET is 64 bit). And String
s are used where ever String
s are expected. (This was a huge oversight on my part and I humbly beg for everyone's forgiveness!!)
- Also added the
CopyMemory
API function (Sub
rather) for use with the Recovery options.
- Changes to the
ProjectInstaller_AfterInstall
procedure.
- Added all variables and code necessary to change the Recovery options. (Works pretty well!)