ServiceHelper.cs

来源:互联网 发布:聊城行知中学 编辑:程序博客网 时间:2024/06/04 18:12
namespace EMAS.Helper{    using System;    using System.ComponentModel;    using System.Linq;    using System.Management;    using System.Runtime.ConstrainedExecution;    using System.Runtime.InteropServices;    using System.ServiceProcess;    using System.Text;    using System.Threading;    #region 异常定义    /// <summary>    /// 服务不存在异常    /// </summary>    public class ServiceNotExistException : ApplicationException    {        public ServiceNotExistException() : base("服务不存在!") { }        public ServiceNotExistException(string message) : base(message) { }    }    #endregion    #region 结构定义    /// <summary>    /// 服务配置对象    /// </summary>    public struct ServiceConfig    {        /// <summary>        /// 服务名称:启动和关闭服务时使用的名称,可以与显示名称相同        /// </summary>        public string ServiceName;        /// <summary>        /// 显示名称:服务列表中显示的名称        /// </summary>        public string DisplayName;        /// <summary>        /// 描述:服务列表中描述该服务的功能        /// </summary>        public string Description;        /// <summary>        /// 可执行文件的路径,可带参数        /// <para>若路径有空格,需给路径(不含参数)套上双引号,例如:@"""C:\新建 文件夹\test.exe"" /s"</para>        /// <para>举例说明: 要启动的服务路径为 D:\srv.exe ,启动参数为 -s ,则填写路径@"D:\srv.exe -s"</para>        /// </summary>        public string ExeFilePath;        /// <summary>        /// 启动类型:设置服务启动的方式,自动或手动等        /// </summary>        public ServiceStartMode StartType;        /// <summary>        /// 登录身份:为了避免不必要的权限问题,建议设置为LocalSystem        /// </summary>        public ServiceAccount Account;        /// <summary>        /// 登录名称        /// </summary>        public string Username;        /// <summary>        /// 登录密码        /// </summary>        public string Password;        /// <summary>        /// 依赖服务:当启动服务时同时启动依赖服务时需要设置此参数,否则设置为null        /// </summary>        public string[] ServicesDependedOn;    }    #endregion    #region 枚举定义    /// <summary>    /// 服务状态枚举。用于遍历从属服务API    /// </summary>    public enum EnumServiceState    {        Active = 1,        InActive = 2,        All = 3    }    #endregion    /// <summary>    /// Windows 服务辅助类    /// </summary>    public static class ServiceHelper    {        /// <summary>        /// 超时等待时间(秒)        /// </summary>        public static int TimeOut = 30;        /// <summary>        /// 当前工作目录        /// </summary>        public static string CurrentDirectory => Environment.CurrentDirectory;        /// <summary>        /// 当前调用可执行文件的路径        /// </summary>        public static string InvokeFileName => System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;        #region 日志方法        /// <summary>        /// 日志记录        /// </summary>        /// <param name="fileName">文件名称</param>        /// <param name="contents">日志内容</param>        public static void Log(string fileName, string contents)        {            System.IO.File.AppendAllText($"{Environment.CurrentDirectory}\\{fileName}.log", $"{contents}\r\n");        }        #endregion        #region 服务管理        /// <summary>        /// 安装服务        /// </summary>        /// <param name="config">服务配置对象,描述了服务相关的配置信息</param>        public static void Install(ServiceConfig config)        {            var scm = OpenScManager();            var service = IntPtr.Zero;            try            {                service = Win32Class.CreateService(                    scm,                    config.ServiceName,                    config.DisplayName,                    Win32Class.SERVICE_ALL_ACCESS,                    Win32Class.SERVICE_WIN32_OWN_PROCESS,                    config.StartType,                    Win32Class.SERVICE_ERROR_NORMAL,                    config.ExeFilePath,                    null,                    IntPtr.Zero,                    ProcessDependencies(config.ServicesDependedOn),                    GetServiceAccountName(config.Account),                    null                    );                if (service == IntPtr.Zero)                {                    if (Marshal.GetLastWin32Error() == 0x431) //ERROR_SERVICE_EXISTS                    {                        throw new ApplicationException("服务已存在!");                    }                    throw new ApplicationException("服务安装失败!");                }                //设置服务描述                var sd = new Win32Class.SERVICE_DESCRIPTION();                try                {                    sd.description = Marshal.StringToHGlobalUni(config.Description);                    Win32Class.ChangeServiceConfig2(service, 1, ref sd);                }                finally                {                    Marshal.FreeHGlobal(sd.description);// 释放                }            }            finally            {                if (service != IntPtr.Zero)                {                    Win32Class.CloseServiceHandle(service);                }                Win32Class.CloseServiceHandle(scm);            }        }        /// <summary>        /// 卸载服务        /// </summary>        /// <param name="serviceName">服务名称</param>        public static void Uninstall(string serviceName)        {            var scmHandle = IntPtr.Zero;            var service = IntPtr.Zero;            try            {                service = OpenService(serviceName, out scmHandle);                StopService(service); //停止服务。里面会递归停止从属服务                if (!Win32Class.DeleteService(service) && Marshal.GetLastWin32Error() != 0x430) //忽略已标记为删除的服务。ERROR_SERVICE_MARKED_FOR_DELETE                {                    throw new ApplicationException("删除服务失败!");                }            }            catch (ServiceNotExistException) { } //忽略服务不存在的情况            finally            {                if (service != IntPtr.Zero)                {                    Win32Class.CloseServiceHandle(service);                    Win32Class.CloseServiceHandle(scmHandle);//放if里面是因为如果服务打开失败,在OpenService里就已释放SCM                }            }        }        /// <summary>        /// 启动服务        /// </summary>        /// <param name="serviceName">服务名称</param>        /// <returns>成功:true,失败:false</returns>        public static bool StartService(string serviceName)        {            if (!IsExists(serviceName)) return false;            var sc = new ServiceController(serviceName);            if (sc.Status != ServiceControllerStatus.Running && sc.Status != ServiceControllerStatus.StartPending)            {                sc.Start();            }            sc.WaitForStatus(ServiceControllerStatus.Running, new TimeSpan(0, 0, TimeOut));            return sc.Status == ServiceControllerStatus.Running;        }        /// <summary>        /// 停止服务        /// </summary>        /// <param name="serviceName">服务名称</param>        /// <returns>成功:true,失败:false</returns>        public static bool StopService(string serviceName)        {            bool isDone;            var scmHandle = IntPtr.Zero;            var service = IntPtr.Zero;            try            {                service = OpenService(serviceName, out scmHandle);                StopService(service);                isDone = true;            }            catch            {                isDone = false;            }            finally            {                if (service != IntPtr.Zero)                {                    Win32Class.CloseServiceHandle(service);                    Win32Class.CloseServiceHandle(scmHandle);                }            }            return isDone;        }        /// <summary>        /// 运行服务,用于安装服务后的服务调用        /// </summary>        /// <param name="services"> 一组 ServiceBase 实例,指示要启动的服务</param>        public static void RunService(params ServiceBase[] services)        {            ServiceBase.Run(services);        }        /// <summary>        /// 检查服务是否运行        /// </summary>        /// <param name="serviceName">服务名称</param>        /// <returns>运行:true,停止:false</returns>        public static bool IsRunning(string serviceName)        {            return ServiceController.GetServices().Where(t => t.ServiceName.ToLower().Equals(serviceName.ToLower())).Any(t => t.Status == ServiceControllerStatus.Running);        }        /// <summary>        /// 检查服务是否存在        /// </summary>        /// <param name="serviceName">服务名称</param>        /// <returns>存在:true,不存在:false</returns>        public static bool IsExists(string serviceName)        {            return ServiceController.GetServices().Any(t => t.ServiceName.ToLower().Equals(serviceName.ToLower()));        }        /// <summary>        /// 设置服务允许与桌面交互        /// </summary>        /// <param name="serviceName">服务名称</param>        public static void DesktopInteract(string serviceName)        {            var co = new ConnectionOptions { Impersonation = ImpersonationLevel.Impersonate };            var ms = new ManagementScope(@"root/CIMV2", co);            ms.Connect();            using (var wmiService = new ManagementObject($"Win32_Service.Name='{serviceName}'"))            using (var inParam = wmiService.GetMethodParameters("Change"))            {                inParam["DesktopInteract"] = true;                wmiService.InvokeMethod("Change", inParam, null);            }        }        #endregion        #region 接口方法        /// <summary>        /// 打开服务管理器        /// </summary>        public static IntPtr OpenScManager()        {            var scm = Win32Class.OpenSCManager(null, null, Win32Class.SC_MANAGER_ALL_ACCESS);            if (scm == IntPtr.Zero)            {                throw new ApplicationException("打开服务管理器失败!");            }            return scm;        }        /// <summary>        /// 打开服务        /// </summary>        /// <param name="serviceName">服务名称</param>        /// <param name="scManagerHandle">服务管理器句柄</param>        public static IntPtr OpenService(string serviceName, out IntPtr scManagerHandle)        {            scManagerHandle = OpenScManager();            var serviceHandle = Win32Class.OpenService(scManagerHandle, serviceName, Win32Class.SERVICE_ALL_ACCESS);            if (serviceHandle != IntPtr.Zero) return serviceHandle;            var errCode = Marshal.GetLastWin32Error();            Win32Class.CloseServiceHandle(scManagerHandle); //关闭SCM            if (errCode == 0x424) //ERROR_SERVICE_DOES_NOT_EXIST            {                throw new ServiceNotExistException();            }            throw new Win32Exception();        }        /// <summary>        /// 停止服务        /// </summary>        /// <param name="serviceHandle">服务管理器句柄</param>        public static void StopService(IntPtr serviceHandle)        {            var currState = GetServiceStatus(serviceHandle);            if (currState == ServiceControllerStatus.Stopped)            {                return;            }            if (currState != ServiceControllerStatus.StopPending)            {                //递归停止从属服务                var childSvs = EnumDependentServices(serviceHandle, EnumServiceState.Active);                if (childSvs.Length != 0)                {                    var scm = OpenScManager();                    try                    {                        foreach (var childSv in childSvs)                        {                            StopService(Win32Class.OpenService(scm, childSv, Win32Class.SERVICE_STOP));                        }                    }                    finally                    {                        Win32Class.CloseServiceHandle(scm);                    }                }                var status = new Win32Class.SERVICE_STATUS();                Win32Class.ControlService(serviceHandle, Win32Class.SERVICE_CONTROL_STOP, ref status); //发送停止指令            }            if (!WaitForStatus(serviceHandle, ServiceControllerStatus.Stopped, new TimeSpan(0, 0, TimeOut)))            {                throw new ApplicationException("停止服务失败!");            }        }        /// <summary>        /// 等候服务至目标状态        /// </summary>        /// <param name="serviceHandle">服务管理器句柄</param>        /// <param name="desiredStatus">服务状态描述</param>        /// <param name="timeout">超时时间</param>        public static bool WaitForStatus(IntPtr serviceHandle, ServiceControllerStatus desiredStatus, TimeSpan timeout)        {            var startTime = DateTime.Now;            while (GetServiceStatus(serviceHandle) != desiredStatus)            {                if (DateTime.Now - startTime > timeout)                {                    return false;                }                Thread.Sleep(200);            }            return true;        }        /// <summary>        /// 遍历从属服务        /// </summary>        /// <param name="serviceHandle">服务管理器句柄</param>        /// <param name="state">要遍历的服务状态(活动、非活动、全部)</param>        public static string[] EnumDependentServices(IntPtr serviceHandle, EnumServiceState state)        {            var bytesNeeded = 0; //存放从属服务的空间大小,由API返回            var numEnumerated = 0; //从属服务数,由API返回            //先尝试以空结构获取,如获取成功说明从属服务为空,否则拿到上述俩值            if (Win32Class.EnumDependentServices(serviceHandle, state, IntPtr.Zero, 0, ref bytesNeeded, ref numEnumerated))            {                return new string[0];            }            if (Marshal.GetLastWin32Error() != 0xEA) //仅当错误值不是大小不够(ERROR_MORE_DATA)时才抛异常            {                throw new Win32Exception();            }            //在非托管区域创建指针            var structsStart = Marshal.AllocHGlobal(new IntPtr(bytesNeeded));            try            {                //往上述指针处塞存放从属服务的结构组,每个从属服务是一个结构                if (!Win32Class.EnumDependentServices(serviceHandle, state, structsStart, bytesNeeded, ref bytesNeeded, ref numEnumerated))                {                    throw new Win32Exception();                }                var dependentServices = new string[numEnumerated];                var sizeOfStruct = Marshal.SizeOf(typeof(Win32Class.ENUM_SERVICE_STATUS)); //每个结构的大小                var structsStartAsInt64 = structsStart.ToInt64();                for (var i = 0; i < numEnumerated; i++)                {                    var structure = new Win32Class.ENUM_SERVICE_STATUS();                    var ptr = new IntPtr(structsStartAsInt64 + i * sizeOfStruct); //根据起始指针、结构次序和结构大小推算各结构起始指针                    Marshal.PtrToStructure(ptr, structure); //根据指针拿到结构                    dependentServices[i] = structure.serviceName; //从结构中拿到服务名                }                return dependentServices;            }            finally            {                Marshal.FreeHGlobal(structsStart);            }        }        /// <summary>        /// 获取服务状态        /// </summary>        /// <param name="serviceHandle">服务管理器句柄</param>        public static ServiceControllerStatus GetServiceStatus(IntPtr serviceHandle)        {            var status = new Win32Class.SERVICE_STATUS();            if (!Win32Class.QueryServiceStatus(serviceHandle, ref status))            {                throw new ApplicationException("获取服务状态出错!");            }            return status.currentState;        }        #endregion        #region 辅助方法        /// <summary>        /// 转换帐户枚举为有效参数        /// </summary>        /// <param name="account">账户类型枚举</param>        static string GetServiceAccountName(ServiceAccount account)        {            return account == ServiceAccount.LocalService                ? @"NT AUTHORITY\LocalService"                : (account == ServiceAccount.NetworkService ? @"NT AUTHORITY\NetworkService" : null);        }        /// <summary>        /// 处理依赖服务参数        /// </summary>        /// <param name="dependencies">依赖服务参数</param>        static string ProcessDependencies(params string[] dependencies)        {            if (dependencies == null || dependencies.Length == 0)            {                return null;            }            var sb = new StringBuilder();            foreach (var s in dependencies)            {                sb.Append(s).Append('\0');            }            sb.Append('\0');            return sb.ToString();        }        #endregion        #region 嵌套类        /// <summary>        /// Win32 API相关        /// </summary>        static class Win32Class        {            #region 常量定义            /// <summary>            /// 打开服务管理器时请求的权限:全部            /// </summary>            public const int SC_MANAGER_ALL_ACCESS = 0xF003F;            /// <summary>            /// 服务类型:自有进程类服务            /// </summary>            public const int SERVICE_WIN32_OWN_PROCESS = 0x10;            /// <summary>            /// 打开服务时请求的权限:全部            /// </summary>            public const int SERVICE_ALL_ACCESS = 0xF01FF;            /// <summary>            /// 打开服务时请求的权限:停止            /// </summary>            public const int SERVICE_STOP = 0x20;            /// <summary>            /// 服务操作标记:停止            /// </summary>            public const int SERVICE_CONTROL_STOP = 0x1;            /// <summary>            /// 服务出错行为标记            /// </summary>            public const int SERVICE_ERROR_NORMAL = 0x1;            #endregion            #region API所需类和结构定义            /// <summary>            /// 服务状态结构体            /// </summary>            [StructLayout(LayoutKind.Sequential)]            public struct SERVICE_STATUS            {                public int serviceType;                public ServiceControllerStatus currentState;                public int controlsAccepted;                public int win32ExitCode;                public int serviceSpecificExitCode;                public int checkPoint;                public int waitHint;            }            /// <summary>            /// 服务描述结构体            /// </summary>            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]            public struct SERVICE_DESCRIPTION            {                public IntPtr description;            }            /// <summary>            /// 服务状态结构体。遍历API会用到            /// </summary>            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]            public class ENUM_SERVICE_STATUS            {                public string serviceName;                public string displayName;                public int serviceType;                public int currentState;                public int controlsAccepted;                public int win32ExitCode;                public int serviceSpecificExitCode;                public int checkPoint;                public int waitHint;            }            #endregion            #region API定义            [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]            public static extern bool ChangeServiceConfig2(IntPtr serviceHandle, uint infoLevel, ref SERVICE_DESCRIPTION serviceDesc);            [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]            public static extern IntPtr OpenSCManager(string machineName, string databaseName, int dwDesiredAccess);            [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]            public static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, int dwDesiredAccess);            [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]            public static extern IntPtr CreateService(IntPtr hSCManager, string lpServiceName, string lpDisplayName, int dwDesiredAccess, int dwServiceType, ServiceStartMode dwStartType, int dwErrorControl, string lpBinaryPathName, string lpLoadOrderGroup, IntPtr lpdwTagId, string lpDependencies, string lpServiceStartName, string lpPassword);            [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success), DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]            public static extern bool CloseServiceHandle(IntPtr handle);            [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]            public static extern bool QueryServiceStatus(IntPtr hService, ref SERVICE_STATUS lpServiceStatus);            [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]            public static extern bool DeleteService(IntPtr serviceHandle);            [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]            public static extern bool ControlService(IntPtr hService, int dwControl, ref SERVICE_STATUS lpServiceStatus);            [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]            public static extern bool EnumDependentServices(IntPtr serviceHandle, EnumServiceState serviceState, IntPtr bufferOfENUM_SERVICE_STATUS, int bufSize, ref int bytesNeeded, ref int numEnumerated);            #endregion        }        #endregion    }}