Click here to Skip to main content
15,867,704 members
Articles / Programming Languages / C#
Article

Install a Windows service the way YOU want to! (C# version)

Rate me:
Please Sign up or sign in to vote.
4.72/5 (25 votes)
27 Nov 20034 min read 191.6K   2.3K   77   28
Shows how to change settings for a Windows service during installation.

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.

CSharpWindowsServiceInst1.jpg

CSharpWindowsServiceInst2.jpg

CSharpWindowsServiceInst3.jpg

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.

C#
private void ProjectInstaller_AfterInstall(object sender, 
               System.Configuration.Install.InstallEventArgs e)
{
  //Our code goes in this event because it is the only one that will do
  //a proper job of letting the user know that an error has occurred,
  //if one indeed occurs. Installation will be rolled back 
  //if an error occurs.

  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];
  //There should be one element for each action. 
  //The Services snap-in shows 3 possible actions.

  bool bCloseService = false;
  bool bUnlockSCManager = false;
  bool bCloseSCManager = false;

  IntPtr iScActionsPointer = new IntPtr();

  try
  {
    //Obtain a handle to the Service Control Manager, 
    //with appropriate rights.
    //This handle is used to open the relevant service.
    iSCManagerHandle = modAPI.OpenSCManagerA(null, null, 
    modAPI.ServiceControlManagerType.SC_MANAGER_ALL_ACCESS);

    //Check that it's open. If not throw an exception.
    if (iSCManagerHandle < 1)
    {
      throw new Exception("Unable to open the Services Manager.");
    }

    //Lock the Service Control Manager database.
    iSCManagerLockHandle = modAPI.LockServiceDatabase(iSCManagerHandle);

    //Check that it's locked. If not throw an exception.
    if (iSCManagerLockHandle < 1)
    {
      throw new Exception("Unable to lock the Services Manager.");
    }

    //Obtain a handle to the relevant service, with appropriate rights.
    //This handle is sent along to change the settings. The second parameter
    //should contain the name you assign to the service.
    iServiceHandle = modAPI.OpenServiceA(iSCManagerHandle, "C#ServiceTest",
    modAPI.ACCESS_TYPE.SERVICE_ALL_ACCESS);

    //Check that it's open. If not throw an exception.
    if (iServiceHandle < 1)
    {
      throw new Exception("Unable to open the Service for modification.");
    }

    //Call ChangeServiceConfig to update the ServiceType 
    //to SERVICE_INTERACTIVE_PROCESS.
    //Very important is that you do not leave out or change the other relevant
    //ServiceType settings. The call will return False if you do.
    //Also, only services that use the LocalSystem account can be set to
    //SERVICE_INTERACTIVE_PROCESS.
    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 the call is unsuccessful, throw an exception.
    if (bChangeServiceConfig==false)
    {
      throw new Exception("Unable to change the Service settings.");
    }

    //To change the description, create an instance of the SERVICE_DESCRIPTION
    //structure and set the lpDescription member to your desired description.
    ServiceDescription.lpDescription = 
      "This is my custom description for my Windows Service Application!";

    //Call ChangeServiceConfig2 with SERVICE_CONFIG_DESCRIPTION in the second
    //parameter and the SERVICE_DESCRIPTION instance in the third parameter
    //to update the description.
    bChangeServiceConfig2 = modAPI.ChangeServiceConfig2A(iServiceHandle,
    modAPI.InfoLevel.SERVICE_CONFIG_DESCRIPTION,ref ServiceDescription);

    //If the update of the description is unsuccessful it is up to you to
    //throw an exception or not. The fact that the description did not update
    //should not impact the functionality of your service.
    if (bChangeServiceConfig2==false)
    {
      throw new Exception("Unable to set the Service description.");
    }

    //To change the Service Failure Actions, create an instance of the
    //SERVICE_FAILURE_ACTIONS structure and set the members to your
    //desired values. See MSDN for detailed descriptions.
    ServiceFailureActions.dwResetPeriod = 600;
    ServiceFailureActions.lpRebootMsg = 
             "Service failed to start! Rebooting...";
    ServiceFailureActions.lpCommand = "SomeCommand.exe Param1 Param2";
    ServiceFailureActions.cActions = ScActions.Length;

    //The lpsaActions member of SERVICE_FAILURE_ACTIONS is a pointer to an
    //array of SC_ACTION structures. This complicates matters a little,
    //and although it took me a week to figure it out, the solution
    //is quite simple.

    //First order of business is to populate our array of SC_ACTION structures
    //with appropriate values.
    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;

    //Once that's done, we need to obtain a pointer to a memory location
    //that we can assign to lpsaActions in SERVICE_FAILURE_ACTIONS.
    //We use 'Marshal.SizeOf(New modAPI.SC_ACTION) * 3' because we pass 
    //3 actions to our service. If you have less 
    //actions change the * 3 accordingly.
    iScActionsPointer = 
      Marshal.AllocHGlobal(Marshal.SizeOf(new modAPI.SC_ACTION()) * 3);

    //Once we have obtained the pointer for the memory location we need to
    //fill the memory with our structure. We use the CopyMemory API function
    //for this. Please have a look at it's declaration in modAPI.
    modAPI.CopyMemory(iScActionsPointer, 
      ScActions, Marshal.SizeOf(new modAPI.SC_ACTION()) * 3);

    //We set the lpsaActions member 
    //of SERVICE_FAILURE_ACTIONS to the integer
    //value of our pointer.
    ServiceFailureActions.lpsaActions = iScActionsPointer.ToInt32();

    //We call bChangeServiceConfig2 with the relevant parameters.
    bChangeServiceConfig2 = modAPI.ChangeServiceConfig2A(iServiceHandle,
          modAPI.InfoLevel.SERVICE_CONFIG_FAILURE_ACTIONS, 
          ref ServiceFailureActions);

    //If the update of the failure actions 
    //are unsuccessful it is up to you to
    //throw an exception or not. The fact that 
    //the failure actions did not update
    //should not impact the functionality of your service.
    if (bChangeServiceConfig2==false)
    {
      throw new Exception("Unable to set the Service Failure Actions.");
    }
  }
  catch(Exception ex)
  {
    //Throw the exception again so the installer can get to it
    throw new Exception(ex.Message);
  }
  finally
  {
    //Close the handles if they are open.
    Marshal.FreeHGlobal(iScActionsPointer);

    if (iServiceHandle > 0)
    {
      bCloseService = modAPI.CloseServiceHandle(iServiceHandle);
    }

    if (iSCManagerLockHandle > 0)
    {
      bUnlockSCManager = modAPI.UnlockServiceDatabase(iSCManagerLockHandle);
    }

    if (iSCManagerHandle != 0)
    {
      bCloseSCManager = modAPI.CloseServiceHandle(iSCManagerHandle);
    }
  }
  //When installation is done go check out your 
  //handy work using Computer Management!
}

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 Strings are used where ever Strings 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!)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United Kingdom United Kingdom
I have been a VB developer since 1999 and started on .NET 3 months before it was released (on the beta).

I love what I do and even though I always write applications that work with data in one way or another for the company I work for, I love working on things like encryption, Windows API, graphics (trying out managed DirectX) and anything else that the big corporates hardly have an interest in.

Comments and Discussions

 
QuestionMust use event handler "processInstaller.Committed" instead of "processInstaller.AfterInstall" Pin
smt5210-Mar-14 4:44
smt5210-Mar-14 4:44 
QuestionChangeServiceConfig2A returns false if it is called using 64-bit installutil.exe Pin
markoueis9-Apr-12 9:34
markoueis9-Apr-12 9:34 
Questionunder windows 2008 : Unable to set the Service Failure Actions. Any idea ? Pin
ziemiecki5-Jul-11 6:53
ziemiecki5-Jul-11 6:53 
GeneralMy vote of 5 Pin
Member 78996445-May-11 7:59
Member 78996445-May-11 7:59 
GeneralAddition vs Bitwise OR Pin
Mr 45824665-Mar-08 9:17
Mr 45824665-Mar-08 9:17 
GeneralNeed help Pin
kant7728-Oct-07 20:45
kant7728-Oct-07 20:45 
GeneralRe: Need help Pin
kant7728-Oct-07 20:57
kant7728-Oct-07 20:57 
Questionservice logon information Pin
saybar8-Jun-07 22:16
saybar8-Jun-07 22:16 
GeneralIssue! Pin
Moin Ahmed6-Nov-06 23:52
Moin Ahmed6-Nov-06 23:52 
GeneralNO Issue! Pin
Peter Wone22-Aug-07 22:17
Peter Wone22-Aug-07 22:17 
QuestionLatest Version :-? Pin
Nov.Ice24-Sep-06 19:45
Nov.Ice24-Sep-06 19:45 
GeneralAnother way to set desktop permission Pin
Steve Messer1-Sep-06 9:44
Steve Messer1-Sep-06 9:44 
GeneralObsolete API Functions Pin
Vrungar6-Jun-06 13:10
Vrungar6-Jun-06 13:10 
The following functions are obsolete according to MSDN[^]:
     EnumServicesStatus
     LockServiceDatabase
     QueryServiceLockStatus
     QueryServiceStatus
     UnlockServiceDatabase


I commented out all of the code related to the previous functions (specificly the LockServiceDatabase function) and it worked perfectly.
GeneralI think I've found a big problem Pin
ghettoblaster2-Dec-05 6:30
ghettoblaster2-Dec-05 6:30 
Questionthe zip source file is corrupted??? Pin
Budi Mulyawan23-Jun-05 18:02
Budi Mulyawan23-Jun-05 18:02 
GeneralIt is working now Pin
Indulis11-May-05 6:10
Indulis11-May-05 6:10 
GeneralI recompailed the installer and service did not start Pin
Indulis11-May-05 5:18
Indulis11-May-05 5:18 
Generalw0w! Pin
chinimimita20-Dec-04 12:42
chinimimita20-Dec-04 12:42 
QuestionHow to consume a Windows Service? Pin
PrasadGVL24-Nov-04 21:01
PrasadGVL24-Nov-04 21:01 
AnswerRe: How to consume a Windows Service? Pin
Lensouille8-Jul-05 4:58
Lensouille8-Jul-05 4:58 
GeneralGreat articles Pin
jadedgeek25-Aug-04 1:38
jadedgeek25-Aug-04 1:38 
GeneralRe: Great articles Pin
HanreG25-Aug-04 19:11
HanreG25-Aug-04 19:11 
GeneralClose Handles!! Pin
PhilWilson11-Nov-03 14:14
PhilWilson11-Nov-03 14:14 
GeneralRe: Close Handles!! Pin
NDGZR15-Nov-03 23:11
NDGZR15-Nov-03 23:11 
GeneralRe: Close Handles!! Pin
HanreG16-Nov-03 18:30
HanreG16-Nov-03 18:30 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.