Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 428|回复: 0

【C#】分享基于Win32 API的服务操作类(解决ManagedInstallerClass.InstallHelper不能带参数安装的问题)

[复制链接]
  • TA的每日心情
    奋斗
    2024-4-6 11:05
  • 签到天数: 748 天

    [LV.9]以坛为家II

    2034

    主题

    2092

    帖子

    70万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    705612
    发表于 2021-5-27 12:25:00 | 显示全部楼层 |阅读模式

    注:这里的服务是指Windows 服务

    ------------------201508250915更新------------------

    刚刚得知TransactedInstaller类是支持带参数安装服务的,在此感谢猿友KOFIP的指教和代码,详情请见回复。

    ------------------201506182056原文------------------

    市面上常见的安装一个服务的方法大概有这么几种:

    • 用Process类调用sc.exe、Installutil.exe等外部工具进行安装。极不推荐,须知创建一个进程开销不小,并且依赖外部工具有损程序健壮性
    • 使用TransactedInstaller和AssemblyInstaller安装类进行安装。不推荐,既然都用托管方法,何不用更好的方法呢
    • 用ManagedInstallerClass.InstallHelper进行安装。这个我认为是托管方法中首选的方法,事实上它就是对上述两个安装类的封装。另外,Installutil.exe也是用的这个方法

    此前我一直用的就是InstallHelper法,但最近需要安装一个服务时却遇到问题,就是承载该服务的程序文件(exe),同时又是个带用户界面的桌面程序,它通过传入main方法的参数决定是以服务运行,还是以桌面程序运行(这里不讨论为什么不把服务和桌面程序分成两个exe。另外有关如何让一个exe即是服务又是桌面程序的问题,请参看园子里其它猿友的文章,或者有闲心我也会写一篇),这就需要安装该服务时,给映像文件路径带上参数,但InstallHelper不支持带参数,勉强带上参数的话,不是报文件不存在,就是报路径不能有非法字符。看了InstallHelper的源码,发现它会把路径和参数整个套进一对双引号,这样在传递给更底层的安装方法时,底层方法会将该字串视为一个路径,自然不是一个合法的路径。但要我去用sc.exe,那是一万个不愿意,伦家可是一个有追求的码屌好不好。所以好好研究了一下InstallHelper的实现,大概套路是这样:

    为exe所属程序集创建AssemblyInstaller,AssemblyInstaller会负责创建程序集中所有标记有RunInstallerAttribute特性的类的实例,并将它们加入一个installer集合,最后由TransactedInstaller埃个执行这些installer,实际上就是各个installer实例执行各自的Install方法。对于服务(ServiceBase类)来说,用VS添加安装程序后,便会自动生成一个叫ProjectInstaller的类,这个类就标有RunInstallerAttribute特性。ProjectInstaller类本身会携带两个installer实例,分别属于ServiceProcessInstaller和ServiceInstaller类,ProjectInstaller得到执行的时候会把这俩实例也加入上述installer集合,所以最终执行的是这俩实例的Install方法。其中ServiceProcessInstaller的Install并不真正执行啥玩意儿,它只是携带一些信息(比如运行服务的帐户),供ServiceInstaller的Install取用,真正执行安装服务这个事的是ServiceInstaller的Install方法。

    ——然而上面这段话并没有什么卵用,不懂的童鞋还得自己看源码推敲才能弄懂。记住一点就好,服务的安装是System.ServiceProcess.ServiceInstaller的Install方法起作用,上面一堆XXXInstaller都是浮云。而ServiceInstaller.Install内部正是调用CreateService这个系统API来执行服务的安装。所以归根结底,溯本追源,CreateService才是正宗,当然它内部是啥玩意儿就不知道了。题外,一个exe中多个服务时,ServiceProcessInstaller须只有一个,而ServiceInstaller则是每个服务对应一个,看名字就知道,前者与服务承载进程有关,大家都在一个exe里,当然要共用进程设置,后者则是存储每个服务的设置,自然要一一对应。

    回到正题,弄清InstallHelper最终是调用CreateService后,直接看后者支不支持带参数安装就行了,答案显然是支持的(该API文档在此),遂写了个基于API的操作类,问题解决。

    该操作类目前仅提供Install和Uninstall俩方法,用以替代InstallHelper,至于对服务的其它操作(启动/停止等),System.ServiceProcess.ServiceController类已经有完备的实现,时间仓促,无暇造轮,后面有闲心再说。

    方案源码:

    代码不少,如果只是实现Install会很少,这主要是搞Uninstall带来的,因为卸载前要考虑先停止,停止就要考虑先停止从属服务,所以环环相扣,API一个接一个封装,就成这样了。

    注:只支持安装自有进程服务,不支持共享进程服务。即只支持一个exe里只承载一个服务的情况,不支持多服务共享一个exe的情况。这是由CreateService的dwServiceType参数指定的,Install已写死为SERVICE_WIN32_OWN_PROCESS常量,即自有进程类服务。

    using System;
    using System.ComponentModel;
    using System.Runtime.ConstrainedExecution;
    using System.Runtime.InteropServices;
    using System.Text;
    using System.Threading;
    
    namespace AhDung
    {
        #region 异常定义
    
        /// <summary>
        /// 服务不存在异常
        /// </summary>
        public class ServiceNotExistException : ApplicationException
        {
            public ServiceNotExistException() : base("服务不存在!") { }
    
            public ServiceNotExistException(string message) : base(message) { }
        }
    
        #endregion
    
        #region 枚举定义
    
        /// <summary>
        /// 服务启动类型
        /// </summary>
        public enum ServiceStartType
        {
            Boot,
            System,
            Auto,
            Manual,
            Disabled
        }
    
        /// <summary>
        /// 服务运行帐户
        /// </summary>
        public enum ServiceAccount
        {
            LocalSystem,
            LocalService,
            NetworkService,
        }
    
        #endregion
    
        /// <summary>
        /// Windows 服务辅助类
        /// </summary>
        public static class ServiceHelper
        {
            #region 公开方法
    
            /// <summary>
            /// 安装服务
            /// </summary>
            /// <param name="serviceName">服务名</param>
            /// <param name="displayName">友好名称</param>
            /// <param name="binaryFilePath">映像文件路径,可带参数</param>
            /// <param name="description">服务描述</param>
            /// <param name="startType">启动类型</param>
            /// <param name="account">启动账户</param>
            /// <param name="dependencies">依赖服务</param>
            public static void Install(string serviceName, string displayName, string binaryFilePath, string description, ServiceStartType startType, ServiceAccount account = ServiceAccount.LocalSystem, string[] dependencies = null)
            {
                IntPtr scm = OpenSCManager();
    
                IntPtr service = IntPtr.Zero;
                try
                {
                    service = Win32Class.CreateService(scm, serviceName, displayName, Win32Class.SERVICE_ALL_ACCESS, Win32Class.SERVICE_WIN32_OWN_PROCESS, startType, Win32Class.SERVICE_ERROR_NORMAL, binaryFilePath, null, IntPtr.Zero, ProcessDependencies(dependencies), GetServiceAccountName(account), null);
    
                    if (service == IntPtr.Zero)
                    {
                        if (Marshal.GetLastWin32Error() == 0x431)//ERROR_SERVICE_EXISTS
                        { throw new ApplicationException("服务已存在!"); }
    
                        throw new ApplicationException("服务安装失败!");
                    }
    
                    //设置服务描述
                    Win32Class.SERVICE_DESCRIPTION sd = new Win32Class.SERVICE_DESCRIPTION();
                    try
                    {
                        sd.description = Marshal.StringToHGlobalUni(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)
            {
                IntPtr scmHandle = IntPtr.Zero;
                IntPtr 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
                    }
                }
            }
    
            #endregion
    
            #region 辅助方法
    
            /// <summary>
            /// 转换帐户枚举为有效参数
            /// </summary>
            private static string GetServiceAccountName(ServiceAccount account)
            {
                if (account == ServiceAccount.LocalService)
                {
                    return @"NT AUTHORITY\LocalService";
                }
                if (account == ServiceAccount.NetworkService)
                {
                    return @"NT AUTHORITY\NetworkService";
                }
                return null;
            }
    
            /// <summary>
            /// 处理依赖服务参数
            /// </summary>
            private static string ProcessDependencies(string[] dependencies)
            {
                if (dependencies == null || dependencies.Length == 0)
                {
                    return null;
                }
    
                StringBuilder sb = new StringBuilder();
                foreach (string s in dependencies)
                {
                    sb.Append(s).Append('\0');
                }
                sb.Append('\0');
    
                return sb.ToString();
            }
    
            #endregion
    
            #region API 封装方法
    
            /// <summary>
            /// 打开服务管理器
            /// </summary>
            private static IntPtr OpenSCManager()
            {
                IntPtr 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="scmHandle">服务管理器句柄。供调用者释放</param>
            private static IntPtr OpenService(string serviceName, out IntPtr scmHandle)
            {
                scmHandle = OpenSCManager();
    
                IntPtr service = Win32Class.OpenService(scmHandle, serviceName, Win32Class.SERVICE_ALL_ACCESS);
    
                if (service == IntPtr.Zero)
                {
                    int errCode = Marshal.GetLastWin32Error();
    
                    Win32Class.CloseServiceHandle(scmHandle); //关闭SCM
    
                    if (errCode == 0x424) //ERROR_SERVICE_DOES_NOT_EXIST
                    {
                        throw new ServiceNotExistException();
                    }
    
                    throw new Win32Exception();
                }
    
                return service;
            }
    
            /// <summary>
            /// 停止服务
            /// </summary>
            private static void StopService(IntPtr service)
            {
                ServiceState currState = GetServiceStatus(service);
    
                if (currState == ServiceState.Stopped)
                {
                    return;
                }
    
                if (currState != ServiceState.StopPending)
                {
                    //递归停止从属服务
                    string[] childSvs = EnumDependentServices(service, EnumServiceState.Active);
                    if (childSvs.Length != 0)
                    {
                        IntPtr scm = OpenSCManager();
                        try
                        {
                            foreach (string childSv in childSvs)
                            {
                                StopService(Win32Class.OpenService(scm, childSv, Win32Class.SERVICE_STOP));
                            }
                        }
                        finally
                        {
                            Win32Class.CloseServiceHandle(scm);
                        }
                    }
    
                    Win32Class.SERVICE_STATUS status = new Win32Class.SERVICE_STATUS();
                    Win32Class.ControlService(service, Win32Class.SERVICE_CONTROL_STOP, ref status); //发送停止指令
                }
    
                if (!WaitForStatus(service, ServiceState.Stopped, new TimeSpan(0, 0, 30)))
                {
                    throw new ApplicationException("停止服务失败!");
                }
            }
    
            /// <summary>
            /// 遍历从属服务
            /// </summary>
            /// <param name="serviceHandle"></param>
            /// <param name="state">选择性遍历(活动、非活动、全部)</param>
            private static string[] EnumDependentServices(IntPtr serviceHandle, EnumServiceState state)
            {
                int bytesNeeded = 0; //存放从属服务的空间大小,由API返回
                int 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();
                }
    
                //在非托管区域创建指针
                IntPtr structsStart = Marshal.AllocHGlobal(new IntPtr(bytesNeeded));
                try
                {
                    //往上述指针处塞存放从属服务的结构组,每个从属服务是一个结构
                    if (!Win32Class.EnumDependentServices(serviceHandle, state, structsStart, bytesNeeded, ref bytesNeeded, ref numEnumerated))
                    {
                        throw new Win32Exception();
                    }
    
                    string[] dependentServices = new string[numEnumerated];
                    int sizeOfStruct = Marshal.SizeOf(typeof(Win32Class.ENUM_SERVICE_STATUS)); //每个结构的大小
                    long structsStartAsInt64 = structsStart.ToInt64();
                    for (int i = 0; i < numEnumerated; i++)
                    {
                        Win32Class.ENUM_SERVICE_STATUS structure = new Win32Class.ENUM_SERVICE_STATUS();
                        IntPtr ptr = new IntPtr(structsStartAsInt64 + i * sizeOfStruct); //根据起始指针、结构次序和结构大小推算各结构起始指针
                        Marshal.PtrToStructure(ptr, structure); //根据指针拿到结构
                        dependentServices = structure.serviceName; //从结构中拿到服务名
                    }
    
                    return dependentServices;
                }
                finally
                {
                    Marshal.FreeHGlobal(structsStart);
                }
            }
    
            /// <summary>
            /// 获取服务状态
            /// </summary>
            private static ServiceState GetServiceStatus(IntPtr service)
            {
                Win32Class.SERVICE_STATUS status = new Win32Class.SERVICE_STATUS();
    
                if (!Win32Class.QueryServiceStatus(service, ref status))
                {
                    throw new ApplicationException("获取服务状态出错!");
                }
    
                return status.currentState;
            }
    
            /// <summary>
            /// 等候服务至目标状态
            /// </summary>
            private static bool WaitForStatus(IntPtr serviceHandle, ServiceState desiredStatus, TimeSpan timeout)
            {
                DateTime startTime = DateTime.Now;
    
                while (GetServiceStatus(serviceHandle) != desiredStatus)
                {
                    if (DateTime.Now - startTime > timeout) { return false; }
    
                    Thread.Sleep(200);
                }
    
                return true;
            }
    
            #endregion
    
            #region 嵌套类
    
            /// <summary>
            /// Win32 API相关
            /// </summary>
            private 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 ServiceState 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, ServiceStartType 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
    
            /// <summary>
            /// 服务状态枚举。用于遍历从属服务API
            /// </summary>
            private enum EnumServiceState
            {
                Active = 1,
                //InActive = 2,
                //All = 3
            }
    
            /// <summary>
            /// 服务状态
            /// </summary>
            private enum ServiceState
            {
                Stopped = 1,
                //StartPending = 2,
                StopPending = 3,
                //Running = 4,
                //ContinuePending = 5,
                //PausePending = 6,
                //Paused = 7
            }
        }
    }
    ServiceHelper.cs

    使用示例:

    由于是直接用的API安装,等于已经绕过了托管方法的一堆逻辑,所以不再需要在VS中为服务添加安装程序(即VS自动生成的ProjectInstaller类和它携带的ServiceProcessInstaller和ServiceInstaller都可以省了),添加了也没用。直接如下,传入各个信息即可:

    //安装
    ServiceHelper.Install(
        "test",                                // 服务名
        "Test Sv",                             // 显示名称
        @"""C:\新建 文件夹\test.exe"" /s",      // 映像路径,可带参数,若路径有空格,需给路径(不含参数)套上双引号
        "描述描述描述",                         // 服务描述
        ServiceStartType.Auto,                 // 启动类型
        ServiceAccount.LocalService,           // 运行帐户,可选,默认是LocalSystem,即至尊帐户
        new[] { "AudioSrv", "WebClient" }      // 依赖服务,要填服务名称,没有则为null或空数组,可选
        );
    
    //卸载
    ServiceHelper.Uninstall("test");

    最后通过一张图说明一下服务的各个概念:

    -文毕-

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-5-18 08:17 , Processed in 0.067505 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表