using System;
using System.Reflection;
using System.ServiceProcess;
using System.Runtime.InteropServices;
/*
Copyright (c) 2005, 2006 David Hoyt
A huge thank-you to C.V Anish for his article: http://www.codeproject.com/system/windows_nt_service.asp
It paved the way for this class. Although I used his article to better understand the internals of Win32
services, almost everything here is original except where noted.
*/
namespace HoytSoft.Service {
public enum ServiceState : byte {
Running,
Stopped,
Paused,
ShuttingDown,
Interrogating
}
///<summary>A base class for installing, starting, pausing, etc. a Windows service.</summary>
public abstract class ServiceBase {
#region Entry Point
///<summary>Executes your service. If multiple services are defined in the assembly, it will run them all in separate threads.</summary>
/// <param name="Args">The arguments passed in from the command line.</param>
public static void Main(string[] Args) {
//Reads in all the classes in the assembly and finds one that derives
//from this class. If it finds one, it checks the attributes to see
//if we should run it. If we should, we create an instance of it and
//start it on its way...
Assembly a = Assembly.GetExecutingAssembly();
if (a == null) throw new ServiceException("No currently executing assembly.");
Type[] types = a.GetTypes();
System.Collections.ArrayList alDispatchTables = new System.Collections.ArrayList();
foreach(Type t in types) {
if (t.IsClass && t.BaseType != null && t.BaseType.Equals(typeof(ServiceBase))) {
//Gets all the custom attributes of type ServiceAttribute in the class.
object[] attributes = t.GetCustomAttributes(typeof(Attributes.ServiceAttribute), true);
foreach(Attributes.ServiceAttribute info in attributes) {
if (info.Run) {
ServiceBase s = (ServiceBase)Activator.CreateInstance(t);
//Make sure we have a name set for this guy...
if (s.Name == null || s.Name.Trim() == "")
throw new ServiceRuntimeException("A service was created without a name.");
if (Args.Length > 0 && (Args[0].ToLower() == "u" || Args[0].ToLower() == "uninstall")) {
//Nothing to uninstall if it's not installed...
if (!s.baseIsInstalled(info.Name)) break;
if (!s.baseUninstall(info.Name))
throw new ServiceUninstallException("Unable to remove service \"" + info.DisplayName + "\"");
if (!s.Uninstall())
throw new ServiceUninstallException("Service \"" + info.DisplayName + "\" was unable to uninstall itself correctly.");
} else {
//Always check to see if the service is installed and if it isn't,
//then go ahead and install it...
if (!s.baseIsInstalled(info.Name)) {
string[] envArgs = Environment.GetCommandLineArgs();
if (envArgs.Length > 0 && !s.baseInstall(envArgs[0], info.Name, info.DisplayName, info.Description, info.ServiceType, info.ServiceAccessType, info.ServiceStartType, info.ServiceErrorControl))
throw new ServiceInstallException("Unable to install service \"" + info.DisplayName + "\"");
if (!s.Install())
throw new ServiceInstallException("Service was not able to install itself correctly.");
}
if (!s.debug) {
ServicesAPI.SERVICE_TABLE_ENTRY entry = new HoytSoft.Service.ServicesAPI.SERVICE_TABLE_ENTRY();
entry.lpServiceName = info.Name;
entry.lpServiceProc = new HoytSoft.Service.ServicesAPI.ServiceMainProc(s.baseServiceMain);
alDispatchTables.Add(entry);
} else {
s.args = Args;
}
}
}
break; //We can break b/c we only allow ONE instance of this attribute per object...
}
}
}
if (alDispatchTables.Count > 0) {
//Add a null entry to tell the API it's the last entry in the table...
ServicesAPI.SERVICE_TABLE_ENTRY entry = new HoytSoft.Service.ServicesAPI.SERVICE_TABLE_ENTRY();
entry.lpServiceName = null;
entry.lpServiceProc = null;
alDispatchTables.Add(entry);
ServicesAPI.SERVICE_TABLE_ENTRY[] table = (ServicesAPI.SERVICE_TABLE_ENTRY[])alDispatchTables.ToArray(typeof(ServicesAPI.SERVICE_TABLE_ENTRY));
if (ServicesAPI.StartServiceCtrlDispatcher(table) == 0) {
//There was an error. What was it?
switch(Marshal.GetLastWin32Error()) {
case ServicesAPI.ERROR_FAILED_SERVICE_CONTROLLER_CONNECT:
throw new ServiceStartupException("A service is being run as a console application. Try setting the Service attribute's \"Debug\" property to true if you're testing an application.");
case ServicesAPI.ERROR_INVALID_DATA:
throw new ServiceStartupException("The specified dispatch table contains entries that are not in the proper format.");
case ServicesAPI.ERROR_SERVICE_ALREADY_RUNNING:
throw new ServiceStartupException("A service is already running.");
default:
throw new ServiceStartupException("An unknown error occurred while starting up the service(s).");
}
}
}
alDispatchTables.Clear();
}
#endregion
#region Private Variables
private string name;
private string displayName;
private string description;
private bool run;
private bool debug;
private string[] args;
private ServiceState servState;
private Attributes.ServiceType servType;
private Attributes.ServiceAccessType servAccessType;
private Attributes.ServiceStartType servStartType;
private Attributes.ServiceErrorControl servErrorControl;
private Attributes.ServiceControls servControls;
private ServicesAPI.SERVICE_STATUS servStatus;
private IntPtr servStatusHandle;
private System.Diagnostics.EventLog log;
#endregion
#region Constructors
public ServiceBase() {
object[] attributes = this.GetType().GetCustomAttributes(typeof(Attributes.ServiceAttribute), true);
foreach(Attributes.ServiceAttribute info in attributes) {
this.name = info.Name;
this.displayName = info.DisplayName;
this.description = info.Description;
this.run = info.Run;
this.servType = info.ServiceType;
this.servAccessType = info.ServiceAccessType;
this.servStartType = info.ServiceStartType;
this.servErrorControl = info.ServiceErrorControl;
this.servControls = info.ServiceControls;
this.debug = info.Debug;
}
this.servStatus = new HoytSoft.Service.ServicesAPI.SERVICE_STATUS();
this.servState = ServiceState.Stopped;
this.args = null;
if (this.debug) {
System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ThreadStart(baseDebugStart));
t.Start();
}
}
#endregion
#region Properties
///<summary>The name of the service used in the service database.</summary>
public string Name { get { return this.name; } }
///<summary>The name of the service that will be displayed in the services snap-in.</summary>
public string DisplayName { get { return this.displayName; } }
///<summary>The description of the service that will be displayed in the service snap-in.</summary>
public string Description { get { return this.description; } }
///<summary>Indicates if you want the service to run or not on program startup.</summary>
public bool Run { get { return this.run; } }
///<summary>Indicates the type of service you want to run.</summary>
public Attributes.ServiceType ServiceType { get { return this.servType; } }
///<summary>Access to the service. Before granting the requested access, the system checks the access token of the calling process.</summary>
public Attributes.ServiceAccessType ServiceAccessType { get { return this.servAccessType; } }
///<summary>Service start options.</summary>
public Attributes.ServiceStartType ServiceStartType { get { return this.servStartType; } }
///<summary>Severity of the error, and action taken, if this service fails to start.</summary>
public Attributes.ServiceErrorControl ServiceErrorControl { get { return this.servErrorControl; } }
///<summary>The controls or actions the service responds to.</summary>
public Attributes.ServiceControls ServiceControls { get { return this.servControls; } }
///<summary>The current state of the service.</summary>
public ServiceState ServiceState { get { return this.servState; } }
///<summary>Treats the service as a console application instead of a normal service.</summary>
public bool Debug { get { return this.debug; } }
#endregion
#region Override Methods
protected virtual bool Install() { return true; }
protected virtual bool Uninstall() { return true; }
protected virtual bool Initialize(string[] Arguments) { return true; }
protected virtual void Start() { }
protected virtual void Pause() { }
protected virtual void Stop() { }
protected virtual void Continue() { }
protected virtual void Shutdown() { }
protected virtual void Interrogate() { }
#endregion
#region API Methods
[DllImport("advapi32.dll")]
public static extern IntPtr CreateService(IntPtr scHandle,
//[MarshalAs(UnmanagedType.LPTStr)]
string lpSvcName,
//[MarshalAs(UnmanagedType.LPTStr)]
string lpDisplayName,
Attributes.ServiceAccessType dwDesiredAccess,
Attributes.ServiceType dwServiceType,
Attributes.ServiceStartType dwStartType,
Attributes.ServiceErrorControl dwErrorControl,
//[MarshalAs(UnmanagedType.LPTStr)]
string lpPathName,
//[MarshalAs(UnmanagedType.LPTStr)]
string lpLoadOrderGroup,
int lpdwTagId,
//[MarshalAs(UnmanagedType.LPTStr)]
string lpDependencies,
//[MarshalAs(UnmanagedType.LPTStr)]
string lpServiceStartName,
//[MarshalAs(UnmanagedType.LPTStr)]
string lpPassword);
[DllImport("advapi32.dll")]
public static extern IntPtr OpenService(IntPtr hSCManager,
string lpServiceName,
Attributes.ServiceAccessType dwDesiredAccess);
[DllImport("advapi32.dll")]
public static extern IntPtr OpenService(IntPtr hSCManager,
string lpServiceName,
int dwDesiredAccess);
[DllImport("advapi32.dll")]
public static extern int DeleteService(IntPtr svHandle);
#endregion
#region Helper Methods
public void Log(string Message) {
Log(Message, System.Diagnostics.EventLogEntryType.Information);
}
public void Log(string Message, System.Diagnostics.EventLogEntryType EntryType) {
if (this.log == null) {
this.log = new System.Diagnostics.EventLog("Services");
this.log.Source = this.displayName;
}
this.log.WriteEntry(Message, EntryType);
}
#endregion
private void baseDebugStart() {
if (this.Initialize(this.args)) {
this.servState = ServiceState.Running;
this.Start();
this.Stop();
}
}
private void baseServiceMain(int argc, string[] argv) {
if (this.ServiceType != Attributes.ServiceType.FileSystemDriver && this.ServiceType != Attributes.ServiceType.KernelDriver)
this.servStatus.dwServiceType = ServicesAPI.SERVICE_WIN32;
else
this.servStatus.dwServiceType = (int)this.ServiceType;
this.servStatus.dwCurrentState = ServicesAPI.SERVICE_START_PENDING;
this.servStatus.dwControlsAccepted = (int)this.ServiceControls;
this.servStatus.dwWin32ExitCode = 0;
this.servStatus.dwServiceSpecificExitCode = 0;
this.servStatus.dwCheckPoint = 0;
this.servStatus.dwWaitHint = 0;
this.servStatusHandle = ServicesAPI.RegisterServiceCtrlHandler(this.Name, new ServicesAPI.ServiceCtrlHandlerProc(this.baseServiceControlHandler));
if (servStatusHandle == IntPtr.Zero) return;
this.servStatus.dwCurrentState = ServicesAPI.SERVICE_RUNNING;
this.servStatus.dwCheckPoint = 0;
this.servStatus.dwWaitHint = 0;
if (ServicesAPI.SetServiceStatus(this.servStatusHandle, ref this.servStatus) == 0) {
throw new ServiceRuntimeException("\"" + this.displayName + "\" threw an error. Error Number: " + Marshal.GetLastWin32Error());
}
//Call the initialize method...
if (!this.Initialize(argv)) {
this.servStatus.dwCurrentState = ServicesAPI.SERVICE_STOPPED;
this.servStatus.dwCheckPoint = 0;
this.servStatus.dwWaitHint = 0;
this.servStatus.dwWin32ExitCode = 1;
this.servStatus.dwServiceSpecificExitCode = 1;
ServicesAPI.SetServiceStatus(this.servStatusHandle, ref this.servStatus);
return;
}
//Initialization complete - report running status.
this.servStatus.dwCurrentState = ServicesAPI.SERVICE_RUNNING;
this.servStatus.dwCheckPoint = 0;
this.servStatus.dwWaitHint = 0;
if (ServicesAPI.SetServiceStatus(this.servStatusHandle, ref this.servStatus) == 0) {
throw new ServiceRuntimeException("\"" + this.displayName + "\" threw an error. Error Number: " + Marshal.GetLastWin32Error());
}
this.servState = ServiceState.Running;
this.Start();
}
//This is called whenever a service control event happens such as pausing, stopping, etc...
private void baseServiceControlHandler(int Opcode) {
switch(Opcode) {
case ServicesAPI.SERVICE_CONTROL_PAUSE:
this.servState = ServiceState.Paused;
this.servStatus.dwCurrentState = ServicesAPI.SERVICE_PAUSED;
this.Pause();
ServicesAPI.SetServiceStatus(this.servStatusHandle, ref this.servStatus);
break;
case ServicesAPI.SERVICE_CONTROL_CONTINUE:
this.servState = ServiceState.Running;
this.servStatus.dwCurrentState = ServicesAPI.SERVICE_RUNNING;
this.Continue();
ServicesAPI.SetServiceStatus(this.servStatusHandle, ref this.servStatus);
break;
case ServicesAPI.SERVICE_CONTROL_STOP:
this.servState = ServiceState.Stopped;
this.servStatus.dwWin32ExitCode = 0;
this.servStatus.dwCurrentState = ServicesAPI.SERVICE_STOPPED;
this.servStatus.dwCheckPoint = 0;
this.servStatus.dwWaitHint = 0;
this.Stop();
ServicesAPI.SetServiceStatus(this.servStatusHandle, ref this.servStatus);
break;
case ServicesAPI.SERVICE_CONTROL_SHUTDOWN:
this.servState = ServiceState.ShuttingDown;
this.servStatus.dwCurrentState = ServicesAPI.SERVICE_STOPPED;
this.Shutdown();
ServicesAPI.SetServiceStatus(this.servStatusHandle, ref this.servStatus);
break;
case ServicesAPI.SERVICE_CONTROL_INTERROGATE:
this.servState = ServiceState.Interrogating;
this.servStatus.dwCurrentState = ServicesAPI.SERVICE_INTERROGATE;
this.Interrogate();
ServicesAPI.SetServiceStatus(this.servStatusHandle, ref this.servStatus);
break;
}
}
private bool baseIsInstalled(string Name) {
//Iterates through all the known services and looks for one
//matching the "Name" parameter...
ServiceController[] sc = ServiceController.GetServices();
foreach(ServiceController s in sc) {
if (s.ServiceName == Name) return true;
}
sc = ServiceController.GetDevices();
foreach(ServiceController s in sc) {
if (s.ServiceName == Name) return true;
}
return false;
}
//Got some help from: http://www.c-sharpcorner.com/Code/2003/Sept/InstallingWinServiceProgrammatically.asp
private bool baseUninstall(string Name) {
if (Name.Length > 256) throw new ServiceInstallException("The maximum length for a service name is 256 characters.");
try {
IntPtr sc_hndl = ServicesAPI.OpenSCManagerA(null, null, ServicesAPI.GENERIC_WRITE);
if(sc_hndl != IntPtr.Zero) {
IntPtr svc_hndl = OpenService(sc_hndl, Name, ServicesAPI.DELETE);
if(svc_hndl != IntPtr.Zero) {
int i = DeleteService(svc_hndl);
if (i != 0) {
ServicesAPI.CloseServiceHandle(sc_hndl);
return true;
} else {
ServicesAPI.CloseServiceHandle(sc_hndl);
return false;
}
} else return false;
} else return false;
} catch {
return false;
}
}
//Got some help from: http://www.c-sharpcorner.com/Code/2003/Sept/InstallingWinServiceProgrammatically.asp
private bool baseInstall(string ServicePath, string Name, string DisplayName, string Description, Attributes.ServiceType ServType, Attributes.ServiceAccessType ServAccessType, Attributes.ServiceStartType ServStartType, Attributes.ServiceErrorControl ServErrorControl) {
if (Name.Length > 256) throw new ServiceInstallException("The maximum length for a service name is 256 characters.");
if (Name.IndexOf(@"\") >= 0 || Name.IndexOf(@"/") >= 0) throw new ServiceInstallException(@"Service names cannot contain \ or / characters.");
if (DisplayName.Length > 256) throw new ServiceInstallException("The maximum length for a display name is 256 characters.");
//The spec says that if a service's path has a space in it, then we must quote it...
//if (ServicePath.IndexOf(" ") >= 0)
// ServicePath = "\"" + ServicePath + "\"";
//ServicePath = ServicePath.Replace(@"\", @"\\");
try {
IntPtr sc_handle = ServicesAPI.OpenSCManagerA(null, null, ServicesAPI.ServiceControlManagerType.SC_MANAGER_CREATE_SERVICE);
if (sc_handle == IntPtr.Zero) return false;
IntPtr sv_handle = CreateService(sc_handle, Name, DisplayName, ServAccessType, ServType, ServStartType, ServErrorControl, ServicePath, null, 0, null, null, null);
//IntPtr sv_handle = ServicesAPI.CreateService(sc_handle, Name, DisplayName, 0xF0000 | 0x0001 | 0x0002 | 0x0004 | 0x0008 | 0x0010 | 0x0020 | 0x0040 | 0x0080 | 0x0100, 0x00000010, 0x00000002, 0x00000001, ServicePath, null, 0, null, null, null);
if (sv_handle == IntPtr.Zero) {
ServicesAPI.CloseServiceHandle(sc_handle);
return false;
}
ServicesAPI.CloseServiceHandle(sv_handle);
ServicesAPI.CloseServiceHandle(sc_handle);
//Sets a service's description by adding a registry entry for it.
if (Description != null && Description != "") {
try {
using (Microsoft.Win32.RegistryKey serviceKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"System\CurrentControlSet\Services\" + Name, true)) {
serviceKey.SetValue("Description", Description);
}
} catch {
return false;
}
}
return true;
} catch {
return false;
}
}
}
#region Helper Classes
#region Exceptions
public class ServiceRuntimeException : ServiceException {
public ServiceRuntimeException(string Message) : base(Message) { }
public ServiceRuntimeException(string Message, Exception InnerException) : base(Message, InnerException) { }
}
public class ServiceStartupException : ServiceException {
public ServiceStartupException(string Message) : base(Message) { }
public ServiceStartupException(string Message, Exception InnerException) : base(Message, InnerException) { }
}
public class ServiceUninstallException : ServiceException {
public ServiceUninstallException(string Message) : base(Message) { }
public ServiceUninstallException(string Message, Exception InnerException) : base(Message, InnerException) { }
}
public class ServiceInstallException : ServiceException {
public ServiceInstallException(string Message) : base(Message) { }
public ServiceInstallException(string Message, Exception InnerException) : base(Message, InnerException) { }
}
public class ServiceException : Exception {
public ServiceException(string Message) : base(Message) { }
public ServiceException(string Message, Exception InnerException) : base(Message, InnerException) { }
}
#endregion
#endregion
}