Click here to Skip to main content
15,868,016 members
Articles / Programming Languages / Visual Basic
Article

Install a Windows service the way YOU want to!

Rate me:
Please Sign up or sign in to vote.
4.94/5 (36 votes)
26 Nov 20033 min read 270.6K   1.1K   94   37
Shows how to change settings for a windows service during installation.

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.

WindowsServiceInstall1.jpg WindowsServiceInstall2.jpg

WindowsServiceInstall3.jpg

There are hacks or workarounds available but these all include using registry editor or a certain amount of manual labour. 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.

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 contain 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.

VB.NET
Private Sub ProjectInstaller_AfterInstall(ByVal sender As Object, _
ByVal e As System.Configuration.Install.InstallEventArgs) _
Handles MyBase.AfterInstall
  '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.

  Dim iSCManagerHandle As Integer
  Dim iSCManagerLockHandle As Integer
  Dim iServiceHandle As Integer
  Dim bChangeServiceConfig As Boolean
  Dim bChangeServiceConfig2 As Boolean
  Dim ServiceDescription As modAPI.SERVICE_DESCRIPTION
  Dim ServiceFailureActions As modAPI.SERVICE_FAILURE_ACTIONS
  Dim ScActions(2) As modAPI.SC_ACTION 'There should be one element for each
                    'action. The Services snap-in shows 3 possible actions.

  Dim bCloseService As Boolean
  Dim bUnlockSCManager As Boolean
  Dim bCloseSCManager As Boolean

  Dim iScActionsPointer As 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.OpenSCManager(vbNullString, vbNullString, _
    modAPI.ServiceControlManagerType.SC_MANAGER_ALL_ACCESS)

    'Check that it's open. If not throw an exception.
    If iSCManagerHandle < 1 Then
      Throw New Exception("Unable to open the Services Manager.")
    End If

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

    'Check that it's locked. If not throw an exception.
    If iSCManagerLockHandle < 1 Then
      Throw New Exception("Unable to lock the Services Manager.")
    End If

    '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.OpenService(iSCManagerHandle, "ServiceTest", _
    modAPI.ACCESS_TYPE.SERVICE_ALL_ACCESS)

    'Check that it's open. If not throw an exception.
    If iServiceHandle < 1 Then
      Throw New Exception("Unable to open the Service for modification.")
    End If

    '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.ChangeServiceConfig(iServiceHandle, _
    modAPI.ServiceType.SERVICE_WIN32_OWN_PROCESS + _
    modAPI.ServiceType.SERVICE_INTERACTIVE_PROCESS, _
    SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, vbNullString, vbNullString, _
    vbNullString, vbNullString, vbNullString, vbNullString, vbNullString)

    'If the call is unsuccessful, throw an exception.
    If Not bChangeServiceConfig Then
      Throw New Exception("Unable to change the Service settings.")
    End If

    '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.ChangeServiceConfig2(iServiceHandle, _
    modAPI.InfoLevel.SERVICE_CONFIG_DESCRIPTION, 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 Not bChangeServiceConfig2 Then
      Throw New Exception("Unable to set the Service description.")
    End If

    'All code below this line was added on 27 November 2003
    
    '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, 
    'and compare it to MSDN.
    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.ChangeServiceConfig2(iServiceHandle, _
    modAPI.InfoLevel.SERVICE_CONFIG_FAILURE_ACTIONS, ServiceFailureActions)

    'If the update of the failure actions is 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 Not bChangeServiceConfig2 Then
      Throw New Exception("Unable to set the Service failure actions.")
    End If
  Catch ex As Exception
    Throw ex
  Finally
    'Once you are done you should close/release the handles to the service
    'and the Service Control Manager, as well as free the memory that held
    'the array of SC_ACTION structures.

    Marshal.FreeHGlobal(iScActionsPointer)
    If iServiceHandle > 0 Then
      bCloseService = modAPI.CloseServiceHandle(iServiceHandle)
    End If

    bUnlockSCManager = modAPI.UnlockServiceDatabase(iSCManagerLockHandle)

    If iSCManagerHandle > 0 Then
      bCloseSCManager = modAPI.CloseServiceHandle(iSCManagerHandle)
    End If
  End Try

  'When installation is done go check out your handy work 
  'using Computer Management!
End Sub

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 Declares changed to DllImport.
    • All Long datatypes changed to the proper type, either String, Integer or IntPtr depending on their function. IntPtr is used where ever a pointer is expected. Integers are used where ever 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 oversite 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

 
AnswerRe: Service Failure Actions? Pin
red diabolo19-Nov-03 5:50
red diabolo19-Nov-03 5:50 
AnswerRe: Service Failure Actions? Pin
HanreG27-Nov-03 18:04
HanreG27-Nov-03 18:04 
GeneralRe: Service Failure Actions? Pin
WhitakerRA1-Dec-03 4:51
professionalWhitakerRA1-Dec-03 4:51 
GeneralRe: Service Failure Actions? Pin
mickbrooke225-Sep-06 6:37
mickbrooke225-Sep-06 6:37 
GeneralRe: Service Failure Actions? Pin
ICTech25-Jan-07 6:29
ICTech25-Jan-07 6:29 
GeneralC# version Pin
zhoulhh5-Nov-03 9:45
zhoulhh5-Nov-03 9:45 
GeneralRe: C# version Pin
HanreG5-Nov-03 18:17
HanreG5-Nov-03 18:17 
GeneralRe: C# version Pin
HanreG6-Nov-03 1:24
HanreG6-Nov-03 1:24 
GeneralRe: C# version Pin
zhoulhh6-Nov-03 2:03
zhoulhh6-Nov-03 2:03 
GeneralRe: C# version Pin
Randy_Miller8-Mar-08 10:42
Randy_Miller8-Mar-08 10:42 
GeneralUser name Pin
pinx4-Nov-03 21:42
pinx4-Nov-03 21:42 
GeneralRe: User name Pin
HanreG4-Nov-03 23:27
HanreG4-Nov-03 23:27 

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.