diff --git a/AnyService.sln b/AnyService.sln new file mode 100644 index 0000000..289e0b7 --- /dev/null +++ b/AnyService.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnyService", "AnyService\AnyService.csproj", "{E600FAE1-2DAA-4175-831A-C917E8E72050}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E600FAE1-2DAA-4175-831A-C917E8E72050}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E600FAE1-2DAA-4175-831A-C917E8E72050}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E600FAE1-2DAA-4175-831A-C917E8E72050}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E600FAE1-2DAA-4175-831A-C917E8E72050}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/AnyService/Disposable.cs b/AnyService/Disposable.cs new file mode 100644 index 0000000..14447c0 --- /dev/null +++ b/AnyService/Disposable.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace AnyService +{ + public class Disposable : IDisposable + { + ~Disposable() + { + this.Dispose(false); + } + + protected virtual void Dispose(bool disposing) + { + if (!this.Disposed) + { + if (disposing) this.Release(); + this.Disposed = true; + } + } + + protected virtual void Release() { } + + protected bool Disposed + { + get; + private set; + } + + #region IDisposable Members + + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + #endregion + } +} diff --git a/AnyService/Program.cs b/AnyService/Program.cs new file mode 100644 index 0000000..3021d02 --- /dev/null +++ b/AnyService/Program.cs @@ -0,0 +1,156 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; + +namespace AnyService +{ + class Program + { + public enum Cmd + { + Install, Uninstall, Start, Stop, Help, Run + }; + + static int Main(string[] args) + { + if (args.Length == 0) + args = new string[] { "help" }; + + int rc = 0; + + string command = args[0].ToLowerInvariant(); + try + { + Cmd cmd = (Cmd)Enum.Parse(typeof(Cmd), command, true); + switch(cmd) + { + case Cmd.Install: rc = Program.HandleInstall(args); break; + case Cmd.Uninstall: rc = Program.HandleUninstall(args); break; + case Cmd.Start: rc = Program.HandleStart(args); break; + case Cmd.Stop: rc = Program.HandleStop(args); break; + case Cmd.Run: rc = Program.HandleRun(args); break; + case Cmd.Help: + default: rc = Program.HandleHelp(null); break; + } + + } + catch(Exception e) + { + rc = Program.HandleHelp(e.Message); + } + + return rc; + } + + static int HandleInstall(string[] args) + { + int retval = 0; + + if (args.Length < 3) + throw new ArgumentException("Not enough arguments for 'install' command."); + string serviceName = args[1]; + string externalCommandLine = args[args.Length - 1]; + + try + { + + Service s = new Service(serviceName); + if (!s.CheckExternalImagePath(externalCommandLine)) + throw new Exception("I do not understand your external program argument."); + s.Install(externalCommandLine); + + Console.WriteLine(string.Format("Successfully installed an AnyService called: {0}", serviceName)); + } + catch (Exception e) + { + Console.Error.WriteLine("Unable to install service: " + e.Message); + retval = -1; + } + return retval; + } + + static int HandleUninstall(string[] args) + { + int retval = 0; + + if (args.Length < 2) + throw new ArgumentException("Not enough arguments for 'uninstall' command."); + string serviceName = args[1]; + + try + { + Service s = new Service(serviceName); + s.Uninstall(); + + Console.WriteLine("Successfully uninstalled an AnyService called: " + serviceName); + } + catch (Exception e) + { + Console.Error.WriteLine("Unable to uninstall service: " + e.Message); + retval = -1; + } + return retval; + } + + static int HandleRun(string[] args) + { + int retval = 0; + + if (args.Length < 2) + throw new ArgumentException("Not enough arguments for 'run' command."); + string serviceName = args[1]; + try + { + + Service s = new Service(serviceName); + s.Run(); + } + catch (Exception e) + { + Console.Error.WriteLine("Unable to install service: " + e.Message); + retval = -1; + } + return retval; + } + + static int HandleStart(string[] args) + { + int retval = 0; + + return retval; + } + + static int HandleStop(string[] args) + { + int retval = 0; + + return retval; + } + + static int HandleHelp(string message) + { + int retval = 0; + + if (!string.IsNullOrEmpty(message)) + { + Console.WriteLine("Error handling command: " + message + Environment.NewLine); + retval = 1; + } + Console.WriteLine("AnyService, Copyright © 2010, Sascha Kiefer." + Environment.NewLine); + Console.WriteLine("Usage: AnyService.exe [options] [file]" + Environment.NewLine); + Console.WriteLine("Command:"); + Console.WriteLine("\tinstall\t\t- Installs an AnyService named with application [file]"); + Console.WriteLine("\tuninstall\t- Uninstalls an AnyService name "); + Console.WriteLine("\tstart\t\t- Tries to start an AnyService named "); + Console.WriteLine("\tstop\t\t- Tries to stop an AnyService named "); + Console.WriteLine("\thelp\t\t- Displays this help message."); + Console.WriteLine(); + Console.WriteLine("Examples:"); + Console.WriteLine("\tAnyService.exe install MyService c:\\Path\\To\\MyNotYetAService.exe"); + + return retval; + } + } +} diff --git a/AnyService/Properties/AssemblyInfo.cs b/AnyService/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..2ec9d13 --- /dev/null +++ b/AnyService/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AnyService")] +[assembly: AssemblyDescription("Run anything as a service")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Sascha Kiefer")] +[assembly: AssemblyProduct("AnyService")] +[assembly: AssemblyCopyright("Copyright © 2010, Sascha Kiefer")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("f3938e67-a846-4d2b-9478-5e85d9b01d10")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/AnyService/Service.cs b/AnyService/Service.cs new file mode 100644 index 0000000..79ce1d1 --- /dev/null +++ b/AnyService/Service.cs @@ -0,0 +1,122 @@ +using System; +using System.IO; +using System.ComponentModel; +using System.Diagnostics; +using System.Threading; + +using AnyService.Win32; + +namespace AnyService +{ + public class Service : Win32.WinService + { + private RegistryHelper m_registry = null; + private Process m_externalProgram = null; + + public Service(string name) + : base(name) + { + m_registry = new RegistryHelper(Microsoft.Win32.Registry.LocalMachine, @"SYSTEM\CurrentControlSet\Services\" + name); + } + + public string ExternalImagePath + { + get { return m_registry.LoadString("ExternalImagePath", "", true); } + set + { + if (value == null) value = ""; + value = value.Replace('\'', '"'); + m_registry.SaveString("ExternalImagePath", value, true); + } + } + + public bool CheckExternalImagePath(string externalImagePath) + { + string t1, t2; + return this.CheckExternalImagePath(externalImagePath, out t1, out t2); + } + + private bool CheckExternalImagePath(string externalImagePath, out string imagePath, out string arguments) + { + bool retval = false; + imagePath = arguments = null; + + int pos = externalImagePath.IndexOf(".exe", StringComparison.InvariantCultureIgnoreCase); + if (pos > 0) + { + imagePath = externalImagePath.Substring(0, pos + 4); + arguments = externalImagePath.Substring(pos + 4).Trim(); + retval = true; + } + + return retval; + } + + protected override void OnStart(string[] args) + { + string externalImagePath = this.ExternalImagePath; + // cleanup and devide into imagepath and arguments + string imagePath = null, arguments = null; + if (this.CheckExternalImagePath(this.ExternalImagePath, out imagePath, out arguments)) + { + ProcessStartInfo psi = new ProcessStartInfo(imagePath, arguments); + psi.WorkingDirectory = Path.GetDirectoryName(imagePath); + + m_externalProgram = new Process(); + m_externalProgram.Exited += new EventHandler(HandleProcessExited); + m_externalProgram.StartInfo = psi; + if(m_externalProgram.Start()) + base.OnStart(args); + } + } + + protected override void OnStop() + { + if (m_externalProgram != null) + { + m_externalProgram.Exited -= new EventHandler(HandleProcessExited); + + int count = 4; + while (count < 8) + { + try { m_externalProgram.Kill(); break; } + catch (Win32Exception) { count++; Thread.Sleep(count * 50); } + catch { break; } + } + m_externalProgram.Close(); + m_externalProgram = null; + } + + base.OnStop(); + } + + private void HandleProcessExited(object sender, EventArgs e) + { + // External Program died or stopped + // so exit as well + int exitCode = -1; + try { exitCode = m_externalProgram.ExitCode; } + catch { } + Environment.Exit(exitCode); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (m_registry != null) + { + m_registry.Dispose(); + m_registry = null; + } + } + base.Dispose(disposing); + } + + public void Install(string externalCommandLine) + { + this.Install(false); + this.ExternalImagePath = externalCommandLine; + } + } +} diff --git a/AnyService/Win32/API/ApiAdvapi32.cs b/AnyService/Win32/API/ApiAdvapi32.cs new file mode 100644 index 0000000..0445ded --- /dev/null +++ b/AnyService/Win32/API/ApiAdvapi32.cs @@ -0,0 +1,95 @@ +using System; +using System.Runtime.InteropServices; +using Microsoft.Win32; + + +namespace AnyService.Win32.API +{ + public static class ApiAdvapi32 + { + [Flags] + public enum RegChangeNotifyFilter + { + /// Notify the caller if a subkey is added or deleted. + Key = 1, + /// Notify the caller of changes to the attributes of the key, + /// such as the security descriptor information. + Attribute = 2, + /// Notify the caller of changes to a value of the key. This can + /// include adding or deleting a value, or changing an existing value. + Value = 4, + /// Notify the caller of changes to the security descriptor + /// of the key. + Security = 8, + } + + [DllImport("advapi32.dll", SetLastError = true)] + public static extern int RegOpenKeyEx(IntPtr hKey, string subKey, uint options, int samDesired, out IntPtr phkResult); + + [DllImport("advapi32.dll", SetLastError = true)] + public static extern int RegNotifyChangeKeyValue(IntPtr hKey, bool bWatchSubtree, RegChangeNotifyFilter dwNotifyFilter, IntPtr hEvent, bool fAsynchronous); + + [DllImport("advapi32.dll", SetLastError = true)] + public static extern int RegCloseKey(IntPtr hKey); + + [DllImport("advapi32.dll", SetLastError = true)] + public static extern int LockServiceDatabase(int hSCManager); + + [DllImport("advapi32.dll", SetLastError = true)] + public static extern bool UnlockServiceDatabase(int hSCManager); + + [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Ansi)] + public static extern bool ChangeServiceConfigA( + int hService, ApiEnums.ServiceType dwServiceType, int dwStartType, + int dwErrorControl, string lpBinaryPathName, string lpLoadOrderGroup, + int lpdwTagId, string lpDependencies, string lpServiceStartName, + string lpPassword, string lpDisplayName); + + + + [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Ansi)] + public static extern bool ChangeServiceConfig2A( + int hService, ApiEnums.ServiceInfoLevel dwInfoLevel, + [MarshalAs(UnmanagedType.Struct)] ref ApiStructs.ServiceDescription lpInfo); + + [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Ansi)] + public static extern bool ChangeServiceConfig2A( + int hService, ApiEnums.ServiceInfoLevel dwInfoLevel, + [MarshalAs(UnmanagedType.Struct)] ref ApiStructs.ServiceFailureActions lpInfo); + + [DllImport("advapi32.dll", SetLastError = true)] + public static extern int OpenServiceA( + int hSCManager, string lpServiceName, ApiEnums.ServiceAccessType dwDesiredAccess); + + [DllImport("advapi32.dll", SetLastError = true)] + public static extern int DeleteService(int hService); + + + [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Ansi)] + public static extern int CreateServiceA( + int hSCManager, + string lpServiceName, + string displayName, + ApiEnums.ServiceControlManagerType dwDesiredAccess, + ApiEnums.ServiceType dwServiceType, + ApiEnums.ServiceStartType dwStartType, + ApiEnums.ServiceErrorControl dwErrorControl, + string lpBinaryPathName, string lpLoadOrderGroup, + string lpdwTagId, + string lpDependencies, string lpServiceStartName, string lpPassword); + + [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Ansi)] + public static extern int OpenSCManagerA( + string lpMachineName, string lpDatabaseName, ApiEnums.ServiceControlManagerType dwDesiredAccess); + + [DllImport("advapi32.dll", SetLastError = true)] + public static extern bool CloseServiceHandle( + int hSCObject); + + [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Ansi)] + public static extern bool QueryServiceConfigA( + int hService, [MarshalAs(UnmanagedType.Struct)] ref ApiStructs.QueryServiceConfig lpServiceConfig, + int cbBufSize, + int pcbBytesNeeded); + } +} diff --git a/AnyService/Win32/API/ApiEnums.cs b/AnyService/Win32/API/ApiEnums.cs new file mode 100644 index 0000000..a2f9e60 --- /dev/null +++ b/AnyService/Win32/API/ApiEnums.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace AnyService.Win32.API +{ + public static class ApiEnums + { + public const int STANDARD_RIGHTS_REQUIRED = 0xF0000; + public const int GENERIC_READ = -2147483648; + public const int ERROR_INSUFFICIENT_BUFFER = 122; + public const int SERVICE_NO_CHANGE = -1; + + [Flags] + public enum ServiceType + { + SERVICE_KERNEL_DRIVER = 0x1, + SERVICE_FILE_SYSTEM_DRIVER = 0x2, + SERVICE_WIN32_OWN_PROCESS = 0x10, + SERVICE_WIN32_SHARE_PROCESS = 0x20, + SERVICE_INTERACTIVE_PROCESS = 0x100, + SERVICETYPE_NO_CHANGE = SERVICE_NO_CHANGE + } + + public enum ServiceStartType : int + { + SERVICE_BOOT_START = 0x0, + SERVICE_SYSTEM_START = 0x1, + SERVICE_AUTO_START = 0x2, + SERVICE_DEMAND_START = 0x3, + SERVICE_DISABLED = 0x4, + SERVICESTARTTYPE_NO_CHANGE = SERVICE_NO_CHANGE + } + + public enum ServiceErrorControl : int + { + SERVICE_ERROR_IGNORE = 0x0, + SERVICE_ERROR_NORMAL = 0x1, + SERVICE_ERROR_SEVERE = 0x2, + SERVICE_ERROR_CRITICAL = 0x3, + msidbServiceInstallErrorControlVital = 0x8000, + SERVICEERRORCONTROL_NO_CHANGE = SERVICE_NO_CHANGE + } + + public enum ServiceStateRequest : int + { + SERVICE_ACTIVE = 0x1, + SERVICE_INACTIVE = 0x2, + SERVICE_STATE_ALL = (SERVICE_ACTIVE + SERVICE_INACTIVE) + } + + public enum ServiceControlType : int + { + SERVICE_CONTROL_STOP = 0x1, + SERVICE_CONTROL_PAUSE = 0x2, + SERVICE_CONTROL_CONTINUE = 0x3, + SERVICE_CONTROL_INTERROGATE = 0x4, + SERVICE_CONTROL_SHUTDOWN = 0x5, + SERVICE_CONTROL_PARAMCHANGE = 0x6, + SERVICE_CONTROL_NETBINDADD = 0x7, + SERVICE_CONTROL_NETBINDREMOVE = 0x8, + SERVICE_CONTROL_NETBINDENABLE = 0x9, + SERVICE_CONTROL_NETBINDDISABLE = 0xA, + SERVICE_CONTROL_DEVICEEVENT = 0xB, + SERVICE_CONTROL_HARDWAREPROFILECHANGE = 0xC, + SERVICE_CONTROL_POWEREVENT = 0xD, + SERVICE_CONTROL_SESSIONCHANGE = 0xE, + } + + public enum ServiceState : int + { + SERVICE_STOPPED = 0x1, + SERVICE_START_PENDING = 0x2, + SERVICE_STOP_PENDING = 0x3, + SERVICE_RUNNING = 0x4, + SERVICE_CONTINUE_PENDING = 0x5, + SERVICE_PAUSE_PENDING = 0x6, + SERVICE_PAUSED = 0x7, + } + + public enum ServiceControlAccepted : int + { + SERVICE_ACCEPT_STOP = 0x1, + SERVICE_ACCEPT_PAUSE_CONTINUE = 0x2, + SERVICE_ACCEPT_SHUTDOWN = 0x4, + SERVICE_ACCEPT_PARAMCHANGE = 0x8, + SERVICE_ACCEPT_NETBINDCHANGE = 0x10, + SERVICE_ACCEPT_HARDWAREPROFILECHANGE = 0x20, + SERVICE_ACCEPT_POWEREVENT = 0x40, + SERVICE_ACCEPT_SESSIONCHANGE = 0x80 + } + + public enum ServiceControlManagerType : int + { + SC_MANAGER_CONNECT = 0x1, + SC_MANAGER_CREATE_SERVICE = 0x2, + SC_MANAGER_ENUMERATE_SERVICE = 0x4, + SC_MANAGER_LOCK = 0x8, + SC_MANAGER_QUERY_LOCK_STATUS = 0x10, + SC_MANAGER_MODIFY_BOOT_CONFIG = 0x20, + SC_MANAGER_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED + SC_MANAGER_CONNECT + SC_MANAGER_CREATE_SERVICE + SC_MANAGER_ENUMERATE_SERVICE + SC_MANAGER_LOCK + SC_MANAGER_QUERY_LOCK_STATUS + SC_MANAGER_MODIFY_BOOT_CONFIG + } + + public enum ServiceAccessType : int + { + SERVICE_QUERY_CONFIG = 0x1, + SERVICE_CHANGE_CONFIG = 0x2, + SERVICE_QUERY_STATUS = 0x4, + SERVICE_ENUMERATE_DEPENDENTS = 0x8, + SERVICE_START = 0x10, + SERVICE_STOP = 0x20, + SERVICE_PAUSE_CONTINUE = 0x40, + SERVICE_INTERROGATE = 0x80, + SERVICE_USER_DEFINED_CONTROL = 0x100, + SERVICE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED + SERVICE_QUERY_CONFIG + SERVICE_CHANGE_CONFIG + SERVICE_QUERY_STATUS + SERVICE_ENUMERATE_DEPENDENTS + SERVICE_START + SERVICE_STOP + SERVICE_PAUSE_CONTINUE + SERVICE_INTERROGATE + SERVICE_USER_DEFINED_CONTROL + } + + public enum ServiceActionType : int + { + SC_ACTION_NONE = 0, + SC_ACTION_RESTART = 1, + SC_ACTION_REBOOT = 2, + SC_ACTION_RUN_COMMAND = 3, + } + + public enum ServiceInfoLevel : int + { + SERVICE_CONFIG_DESCRIPTION = 1, + SERVICE_CONFIG_FAILURE_ACTIONS = 2 + } + } +} diff --git a/AnyService/Win32/API/ApiStructs.cs b/AnyService/Win32/API/ApiStructs.cs new file mode 100644 index 0000000..2fdd6b4 --- /dev/null +++ b/AnyService/Win32/API/ApiStructs.cs @@ -0,0 +1,57 @@ +using System; +using System.Runtime.InteropServices; + +namespace AnyService.Win32.API +{ + public static class ApiStructs + { + [StructLayout(LayoutKind.Sequential)] + public struct ServiceStatus + { + public int ServiceType; + public int CurrentState; + public int ControlsAccepted; + public int Win32ExitCode; + public int ServiceSpecificExitCode; + public int CheckPoint; + public int WaitHint; + } + + [StructLayout(LayoutKind.Sequential)] + public struct QueryServiceConfig + { + public int ServiceType; + public int StartType; + public int ErrorControl; + public string BinaryPathName; + public string LoadOrderGroup; + public int TagId; + public string Dependencies; + public string ServiceStartName; + public string DisplayName; + } + + [StructLayout(LayoutKind.Sequential)] + public struct ServiceAction + { + public ApiEnums.ServiceActionType ActionType; + public int Delay; + } + + [StructLayout(LayoutKind.Sequential)] + public struct ServiceDescription + { + public string Description; + } + + [StructLayout(LayoutKind.Sequential)] + public struct ServiceFailureActions + { + public int ResetPeriod; + public string RebootMsg; + public string Command; + public int ActionsCount; + public int Actions; + } + } +} diff --git a/AnyService/Win32/RegistryHelper.cs b/AnyService/Win32/RegistryHelper.cs new file mode 100644 index 0000000..4de6e64 --- /dev/null +++ b/AnyService/Win32/RegistryHelper.cs @@ -0,0 +1,150 @@ +using System; +using System.Runtime.InteropServices; +using Microsoft.Win32; + +namespace AnyService.Win32 +{ + public class RegistryHelper : Disposable + { + public RegistryHelper(RegistryKey registryKey, string registryPath) + { + this.RegistryKey = registryKey; + this.RegistryPath = registryPath; + } + + protected override void Release() + { + if (this.RegistryKey != null) + { + this.RegistryKey.Close(); + this.RegistryKey = null; + } + base.Release(); + } + + protected RegistryKey ConfigurationStore + { + get { return this.RegistryKey.CreateSubKey(this.RegistryPath); } + } + + public RegistryKey RegistryKey { get; set; } + + public string RegistryPath { get; set; } + + public string LoadString(string name, string def, bool create) + { + return this.LoadString(name, def, create, false); + } + + public string LoadString(string name, string def, bool create, bool expandable) + { + using (RegistryKey key = this.ConfigurationStore) + { + try{ return key.GetValue(name, def).ToString(); } + finally + { + if (create && !this.HasValue(name)) + this.SaveString(name, def, expandable); + } + } + } + + public string[] LoadStringArray(string name, string[] def, bool create) + { + return this.LoadStringArray(name, def, ",", create); + } + + public string[] LoadStringArray(string name, string[] def, string sep, bool create) + { + string s = this.LoadString(name, def != null ? string.Join(sep, def) : "", create); + return s.Split(new string[] { sep }, StringSplitOptions.RemoveEmptyEntries); + } + + public void SaveString(string name, string val, bool expandable) + { + using(RegistryKey key = this.ConfigurationStore) + key.SetValue(name, val, expandable ? RegistryValueKind.ExpandString : RegistryValueKind.String); + } + + public int LoadInteger(string name, int def, bool create) + { + using (RegistryKey key = this.ConfigurationStore) + { + try + { + return (int)key.GetValue(name, def); + } + finally + { + if (create && !this.HasValue(name)) + this.SaveInteger(name, def); + } + } + } + + public uint LoadDword(string name, uint def, bool create) + { + using (RegistryKey key = this.ConfigurationStore) + { + try + { + return (uint)key.GetValue(name, def); + } + finally + { + if (create && !HasValue(name)) + this.SaveDword(name, def); + } + } + } + + public void SaveDword(string name, uint val) + { + using(RegistryKey key = this.ConfigurationStore) + key.SetValue(name, val, RegistryValueKind.DWord); + } + + public void SaveInteger(string name, int val) + { + using(RegistryKey key = this.ConfigurationStore) + key.SetValue(name, val, RegistryValueKind.DWord); + } + + public Type GetValueType(string name) + { + using (RegistryKey key = this.ConfigurationStore) + { + try + { + RegistryValueKind kind = key.GetValueKind(name); + if (kind == RegistryValueKind.DWord) + return typeof(int); + else if (kind == RegistryValueKind.String) + return typeof(string); + else + return typeof(object); + } + catch (System.IO.IOException) { return null; } + } + } + + public bool HasValue(string name) + { + using(RegistryKey key = this.ConfigurationStore) + return key.GetValue(name) != null; + } + + public void DeleteValue(string Name) + { + using(RegistryKey key = this.ConfigurationStore) + key.DeleteValue(Name, false); + } + + public void DeleteKey(string name, bool recursiv) + { + using(RegistryKey key = this.ConfigurationStore) + if (recursiv) key.DeleteSubKeyTree(name); + else key.DeleteSubKey(name, false); + } + } +} diff --git a/AnyService/Win32/WinService.cs b/AnyService/Win32/WinService.cs new file mode 100644 index 0000000..827f2e7 --- /dev/null +++ b/AnyService/Win32/WinService.cs @@ -0,0 +1,145 @@ +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.InteropServices; +using System.ServiceProcess; +using System.Threading; + +using AnyService.Win32.API; + +namespace AnyService.Win32 +{ + public abstract partial class WinService : ServiceBase + { + private string m_displayName = ""; + + public WinService() + : this(null) + { + } + + public WinService(string name) + { + if (string.IsNullOrEmpty(name)) + name = this.GetType().Name; + + if (name.Length > ServiceBase.MaxNameLength) + throw new ArgumentException("Length of name must not exceed " + ServiceBase.MaxNameLength, "name"); + + this.ServiceName = name; + this.CanStop = true; + this.CanShutdown = true; + this.CanPauseAndContinue = false; + this.CanHandleSessionChangeEvent = false; + this.CanHandlePowerEvent = false; + } + + public string DisplayName + { + get { return string.IsNullOrEmpty(m_displayName) ? this.ServiceName : m_displayName; } + set { m_displayName = value; } + } + + public void Run() + { + ServiceBase.Run(this); + } + + + public void Uninstall() + { + + int handle = 0, lockHandle = 0, serviceHandle = 0; + + try + { + handle = ApiAdvapi32.OpenSCManagerA(null, null, ApiEnums.ServiceControlManagerType.SC_MANAGER_ALL_ACCESS); + if (handle <= 0) + throw new Win32Exception(Marshal.GetLastWin32Error(), "Unable to open the Services Manager."); + + lockHandle = ApiAdvapi32.LockServiceDatabase(handle); + if (lockHandle <= 0) + throw new Win32Exception(Marshal.GetLastWin32Error(), "Unable to lock the Services Manager."); + + serviceHandle = ApiAdvapi32.OpenServiceA(handle, this.ServiceName, ApiEnums.ServiceAccessType.SERVICE_ALL_ACCESS); + if (serviceHandle <= 0) + throw new Win32Exception("Service does not exist."); + + ApiAdvapi32.DeleteService(serviceHandle); + } + finally + { + if (serviceHandle > 0) + ApiAdvapi32.CloseServiceHandle(serviceHandle); + + if (lockHandle > 0) + ApiAdvapi32.UnlockServiceDatabase(lockHandle); + + if (handle > 0) + ApiAdvapi32.CloseServiceHandle(handle); + } + } + + public void Install(bool interactive) + { + int handle = 0, lockHandle = 0, serviceHandle = 0; + + try + { + handle = ApiAdvapi32.OpenSCManagerA(null, null, ApiEnums.ServiceControlManagerType.SC_MANAGER_ALL_ACCESS); + if (handle <= 0) + throw new Win32Exception(Marshal.GetLastWin32Error(), "Unable to open the Services Manager."); + + lockHandle = ApiAdvapi32.LockServiceDatabase(handle); + if (lockHandle <= 0) + throw new Win32Exception(Marshal.GetLastWin32Error(), "Unable to lock the Services Manager."); + + serviceHandle = ApiAdvapi32.OpenServiceA(handle, this.ServiceName, ApiEnums.ServiceAccessType.SERVICE_ALL_ACCESS); + if (serviceHandle > 0) // service does exist + throw new Win32Exception("Service already exists."); + + string location = Assembly.GetEntryAssembly().Location; + if (location.Contains(" ")) + location = "\"" + location + "\""; + + string serviceNameArgument = this.ServiceName; + if(serviceNameArgument.Contains(" ")) + serviceNameArgument = "\"" + serviceNameArgument + "\""; + + ApiEnums.ServiceType serviceType = ApiEnums.ServiceType.SERVICE_WIN32_OWN_PROCESS; + if (interactive) + serviceType |= ApiEnums.ServiceType.SERVICE_INTERACTIVE_PROCESS; + + + serviceHandle = ApiAdvapi32.CreateServiceA( + handle, + this.ServiceName, + this.DisplayName, + ApiEnums.ServiceControlManagerType.SC_MANAGER_ALL_ACCESS, + serviceType, + ApiEnums.ServiceStartType.SERVICE_AUTO_START, + ApiEnums.ServiceErrorControl.SERVICE_ERROR_NORMAL, + string.Format("{0} run {1}", location, serviceNameArgument), + null, + null, + null, null, null); + + if (serviceHandle == 0) + throw new Win32Exception(Marshal.GetLastWin32Error(), "Unable to install the Service."); + } + finally + { + if (serviceHandle > 0) + ApiAdvapi32.CloseServiceHandle(serviceHandle); + + if (lockHandle > 0) + ApiAdvapi32.UnlockServiceDatabase(lockHandle); + + if (handle > 0) + ApiAdvapi32.CloseServiceHandle(handle); + } + + } + } +}