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 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.
Private Sub ProjectInstaller_AfterInstall(ByVal sender As Object, _
ByVal e As System.Configuration.Install.InstallEventArgs) _
Handles MyBase.AfterInstall
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
Dim bCloseService As Boolean
Dim bUnlockSCManager As Boolean
Dim bCloseSCManager As Boolean
Dim iScActionsPointer As New IntPtr
Try
iSCManagerHandle = modAPI.OpenSCManager(vbNullString, vbNullString, _
modAPI.ServiceControlManagerType.SC_MANAGER_ALL_ACCESS)
If iSCManagerHandle < 1 Then
Throw New Exception("Unable to open the Services Manager.")
End If
iSCManagerLockHandle = modAPI.LockServiceDatabase(iSCManagerHandle)
If iSCManagerLockHandle < 1 Then
Throw New Exception("Unable to lock the Services Manager.")
End If
iServiceHandle = modAPI.OpenService(iSCManagerHandle, "ServiceTest", _
modAPI.ACCESS_TYPE.SERVICE_ALL_ACCESS)
If iServiceHandle < 1 Then
Throw New Exception("Unable to open the Service for modification.")
End If
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 Not bChangeServiceConfig Then
Throw New Exception("Unable to change the Service settings.")
End If
ServiceDescription.lpDescription = _
"This is my custom description for my Windows Service Application!"
bChangeServiceConfig2 = modAPI.ChangeServiceConfig2(iServiceHandle, _
modAPI.InfoLevel.SERVICE_CONFIG_DESCRIPTION, ServiceDescription)
If Not bChangeServiceConfig2 Then
Throw New Exception("Unable to set the Service description.")
End If
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.ChangeServiceConfig2(iServiceHandle, _
modAPI.InfoLevel.SERVICE_CONFIG_FAILURE_ACTIONS, ServiceFailureActions)
If Not bChangeServiceConfig2 Then
Throw New Exception("Unable to set the Service failure actions.")
End If
Catch ex As Exception
Throw ex
Finally
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
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!)