Someone asked me to create a step by step guide on how to make a windows service that is easy to debug too. So here it is. You can do this in two ways. Either start by choosing the Windows Service template, chance the Application Type under MY Project –> Application to “Console Application” then select “Sub Main” in Startup Object. Add a Module, and add a function main to that. After that you can add a project installer under Service Class. Or if you want a bit more control you can start by creating a Windows Forms Application or Console Application and then add the Service things.
Create a Windows Application.
Add a reference to System.ServiceProcess and System.Configuration.Install
Add a class called ServiceManager with the following code
Private _serviceName As String
Public Const PROCESSBASICINFORMATION As UInteger = 0
<System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential, Pack:=1)> _
Public Structure Process_Basic_Information
Public ExitStatus As IntPtr
Public PepBaseAddress As IntPtr
Public AffinityMask As IntPtr
Public BasePriority As IntPtr
Public UniqueProcessID As IntPtr
Public InheritedFromUniqueProcessId As IntPtr
End Structure
<System.Runtime.InteropServices.DllImport("ntdll.dll", EntryPoint:="NtQueryInformationProcess")> _
Public Shared Function NtQueryInformationProcess(ByVal handle As IntPtr, ByVal processinformationclass As UInteger, ByRef ProcessInformation As Process_Basic_Information, ByVal ProcessInformationLength As Integer, ByRef ReturnLength As UInteger) As Integer
End Function
Sub New(ByVal serviceName As String)
_serviceName = serviceName
End Sub
Public ReadOnly Property status() As System.ServiceProcess.ServiceControllerStatus
Get
Using serviceController As New System.ServiceProcess.ServiceController(_serviceName)
If Not serviceController Is Nothing Then
Try
Return serviceController.Status
Catch ex As Exception
Return ServiceProcess.ServiceControllerStatus.Stopped
End Try
Else
Return ServiceProcess.ServiceControllerStatus.Stopped
End If
End Using
End Get
End Property
Function IsServiceInstalled() As Boolean
Using serviceController As New System.ServiceProcess.ServiceController(_serviceName)
Try
Dim status As System.ServiceProcess.ServiceControllerStatus = serviceController.Status
Catch generatedExceptionName As InvalidOperationException
Return False
Catch ex As Exception
EventLog.WriteEntry("ServiceManager", ex.ToString(), EventLogEntryType.[Error])
Return False
End Try
Return True
End Using
End Function
Private Function GetAssemblyInstaller(ByVal commandLine As String()) As System.Configuration.Install.AssemblyInstaller
Dim installer As New System.Configuration.Install.AssemblyInstaller()
installer.Path = System.Reflection.Assembly.GetExecutingAssembly().Location
installer.CommandLine = commandLine
installer.UseNewContext = True
Return installer
End Function
Sub UninstallService()
If Not IsServiceInstalled() Then
Exit Sub
End If
Dim commandLine As String() = New String(0) {}
commandLine(0) = System.Reflection.Assembly.GetExecutingAssembly().Location
Dim mySavedState As IDictionary = New Hashtable()
mySavedState.Clear()
Dim installer As System.Configuration.Install.AssemblyInstaller = GetAssemblyInstaller(commandLine)
Try
installer.Uninstall(mySavedState)
Catch ex As Exception
EventLog.WriteEntry("ServiceManager", ex.ToString(), EventLogEntryType.[Error])
End Try
End Sub
Sub InstallService()
If IsServiceInstalled() Then
Exit Sub
End If
Try
Dim commandLine As String() = New String(0) {}
commandLine(0) = System.Reflection.Assembly.GetExecutingAssembly().Location
Dim mySavedState As IDictionary = New Hashtable()
Dim installer As System.Configuration.Install.AssemblyInstaller = GetAssemblyInstaller(commandLine)
Try
installer.Install(mySavedState)
installer.Commit(mySavedState)
Catch ex As Exception
installer.Rollback(mySavedState)
EventLog.WriteEntry("ServiceManager", ex.ToString(), EventLogEntryType.[Error])
End Try
Catch ex As Exception
EventLog.WriteEntry("ServiceManager", ex.ToString())
End Try
End Sub
Sub StartService()
If Not IsServiceInstalled() Then Exit Sub
Using serviceController As New System.ServiceProcess.ServiceController(_serviceName)
If serviceController.Status = ServiceProcess.ServiceControllerStatus.Stopped Then
Try
serviceController.Start()
WaitForStatusChange(serviceController, ServiceProcess.ServiceControllerStatus.Running)
Catch ex As InvalidOperationException
EventLog.WriteEntry("ServiceManager", ex.ToString(), EventLogEntryType.[Error])
End Try
End If
End Using
End Sub
Sub StopService()
If Not IsServiceInstalled() Then Exit Sub
Using serviceController As New System.ServiceProcess.ServiceController(_serviceName)
If serviceController.Status <> ServiceProcess.ServiceControllerStatus.Running Then
Exit Sub
End If
serviceController.Stop()
WaitForStatusChange(serviceController, ServiceProcess.ServiceControllerStatus.Stopped)
End Using
End Sub
Private Sub WaitForStatusChange(ByVal serviceController As System.ServiceProcess.ServiceController, ByVal newStatus As System.ServiceProcess.ServiceControllerStatus)
If Not IsServiceInstalled() Then Exit Sub
Dim count As Integer = 0
While serviceController.Status <> newStatus AndAlso count < 30
System.Threading.Thread.Sleep(1000)
serviceController.Refresh()
count += 1
End While
If serviceController.Status <> newStatus Then
Throw New Exception("Failed to change status of service. New status: " & newStatus)
End If
End Sub
Public Shared Function getParentProcess(ByVal Process As Process) As Process
Dim pi As New Process_Basic_Information
Dim RetLength As UInteger
NtQueryInformationProcess(Process.Handle, PROCESSBASICINFORMATION, pi, Runtime.InteropServices.Marshal.SizeOf(pi), RetLength)
Return Process.GetProcessById(pi.InheritedFromUniqueProcessId)
End Function
End Class
Add a new Class and call it something meaningful. This will be where you onStart onStop functions will be. I called it TestServiceControl
Add the following code to the Class
Inherits System.ServiceProcess.ServiceBase
Sub New()
Me.ServiceName = "TestService"
End Sub
Protected Overrides Sub OnStart(ByVal args() As String)
Try
isStopping = False
Dim ts As System.Threading.ThreadStart
ts = AddressOf mainModule.doWork
workerThread = New System.Threading.Thread(ts)
workerThread.Start()
'Me.CanStop = True
Catch ex As Exception
Log("TestService.OnStart: " & ex.ToString, EventLogEntryType.Error)
End Try
End Sub
Protected Overrides Sub OnStop()
Try
isStopping = True
' stall for 10 seconds to let work finish
For i As Integer = 0 To 10
Threading.Thread.Sleep(1000)
If Not workerThread.IsAlive Then Exit For
Next
' Kill it if not done yet
If workerThread.IsAlive Then workerThread.Abort()
Catch ex As Exception
Log("TestService.OnStop: " & ex.ToString, EventLogEntryType.Error)
End Try
End Sub
End Class
<System.ComponentModel.RunInstallerAttribute(True)> _
Public Class TestServiceControlInstaller
Inherits System.Configuration.Install.Installer
Friend WithEvents myServiceProcessInstaller As New System.ServiceProcess.ServiceProcessInstaller
Friend WithEvents myServiceInstaller As New System.ServiceProcess.ServiceInstaller
Sub New()
myServiceProcessInstaller.Account = System.ServiceProcess.ServiceAccount.LocalSystem
myServiceProcessInstaller.Password = Nothing
myServiceProcessInstaller.Username = Nothing
myServiceInstaller.Description = "Test Service"
myServiceInstaller.DisplayName = "Test Service"
myServiceInstaller.ServiceName = "TestService"
myServiceInstaller.StartType = System.ServiceProcess.ServiceStartMode.Automatic
Me.Installers.AddRange(New System.Configuration.Install.Installer() {myServiceProcessInstaller, myServiceInstaller})
End Sub
Private Sub ServiceProcessInstaller1_BeforeInstall(ByVal sender As Object, ByVal e As System.Configuration.Install.InstallEventArgs) Handles myServiceProcessInstaller.BeforeInstall
Try
' examle of how to dyamicly set what service should run as.
' you can find the code to some of these functions else where on my blog
'If _MachineMemberOfDomain() Then
'LsaUtility.SetRight(InstallerAccount, "SeServiceLogonRight")
'AddUserToLocalAdministrators(InstallerDomain & "\" & InstallerAccount)
'ServiceProcessInstaller1.Account = ServiceProcess.ServiceAccount.User
'ServiceProcessInstaller1.Username = InstallerDomain & "\" & InstallerAccount
'ServiceProcessInstaller1.Password = InstallerPassword
'Else
'ServiceProcessInstaller1.Account = ServiceProcess.ServiceAccount.LocalSystem
'End If
Catch ex As Exception
Log("ProjectInstaller: " & ex.ToString, EventLogEntryType.Error)
Throw New Exception(ex.Message, ex)
End Try
End Sub
End Class
Rename Module1 to something meaningful (I called it mainModule ), and add the following code.
Friend myManager As New ServiceManager("TestService")
Public isStopping As Boolean = False
Public workerThread As System.Threading.Thread
Public isService As Boolean = False
Sub Main()
Try
' Uncomment the following like if you need to attach as a debuger to the windows service
Dim ParentProcess As Process = ServiceManager.getParentProcess(Process.GetCurrentProcess)
isService = (ParentProcess.ProcessName.ToLower = "services")
If My.Application.CommandLineArgs.Count > 0 Then
Dim arg1 As String = My.Application.CommandLineArgs(0).ToLower
If arg1 = "-u" Or arg1 = "-uninstall" Or arg1 = "/u" Or arg1 = "/uninstall" Then
If myManager.IsServiceInstalled Then
myManager.UninstallService()
Else
Console.WriteLine("im not installed as a service.")
End If
Exit Sub
End If
End If
' Some might prefere using /i instead. You can also start the service after using myManager.StartService
If Not myManager.IsServiceInstalled Then
Try
myManager.InstallService()
Catch ex As Exception
Throw ex
End Try
End If
If isService Then
Try
System.ServiceProcess.ServiceBase.Run(New TestServiceControl)
Catch ex As Exception
'screw it, guess im running as console then.
isService = False
End Try
Else
doWork()
End If
If Debugger.IsAttached Then
Console.WriteLine("Process has ended. Press enter to exit.")
Console.ReadLine()
End If
Catch ex As Exception
Log("mainModule.Main " & ex.ToString, EventLogEntryType.Error)
End Try
End Sub
Public Sub Log(ByVal msg As String, ByVal msgType As System.Diagnostics.EventLogEntryType)
Try
If isService Then
System.Diagnostics.EventLog.WriteEntry("TestService", msg, msgType)
Else
Console.WriteLine("[" & Now.Hour & ":" & Now.Minute & "." & Now.Second & "] " & msg)
End If
Catch ex As Exception
End Try
End Sub
Public Sub doWork()
Try
' Add all you initializing code here. Dont use Main. The Service controler dont like waiting on Start Controls
' A cute trick here is closing gracefullt using stopMyself() in case that is needed. You can do that pretty much anywhere
While Not isStopping
' Do what ever you need to do as a service
If Not isService Then
isStopping = True
Else
Threading.Thread.Sleep(10000)
End If
End While
Catch ex As System.Threading.ThreadAbortException
' this is known, ignore it ( TestServiceControl.OnStop is killing me )
Catch ex As Exception
Log("mMain.doWork: MainLoop cought an unhandled exception: " & ex.ToString, EventLogEntryType.Error)
End Try
End Sub
Sub stopMyself()
Log("mMain.stopMyself: Forcing my self to stop (CloudInstallerService)", EventLogEntryType.Information)
If isService Then
Try
myManager.StopService()
Catch ex As Exception
Log("mMain.stopMyself: " & ex.ToString, EventLogEntryType.Error)
isStopping = True
End Try
Else
isStopping = True
End If
End Sub
End Module
And that’s it. You can now more rapidly work with a Windows Service.
You can download this as a Microsoft Visual Studio 2010 solution here.
Ingen kommentarer:
Send en kommentar