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

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

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

  • 用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类已经完备的实现,时间仓促,无暇造轮,后面有闲心再说。

方案源码:

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[i] = 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

使用示例:

//安装
ServiceHelper.Install(
    "test",                                // 服务名
    "Test Sv",                             // 显示名称
    @"""C:\新建 文件夹\test.exe"" /s",      // 映像路径,可带参数,若路径有空格,需给路径(不含参数)套上双引号
    "描述描述描述",                         // 服务描述
    ServiceStartType.Auto,                 // 启动类型
    ServiceAccount.LocalService,           // 运行帐户
    new[] { "AudioSrv", "WebClient" }      // 依赖服务
    );

//卸载
ServiceHelper.Uninstall("test");

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

-文毕-

时间: 2024-10-01 04:25:36

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

深入浅出VC++串口编程之基于Win32 API

1.API描述 在WIN32 API中,串口使用文件方式进行访问,其操作的API基本上与文件操作的API一致. 打开串口 Win32 中用于打开串口的API 函数为CreateFile,其原型为: HANDLE CreateFile ( LPCTSTR lpFileName, //将要打开的串口逻辑名,如COM1 或COM2 DWORD dwAccess, //指定串口访问的类型,可以是读取.写入或两者并列 DWORD dwShareMode, //指定共享属性,由于串口不能共享,该参数必须置为

Win32 API文件读写操作

1.文件的创建和打开 HANDLE CreateFile( LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile ); lpFileName:指定用于创建或打开的对象的名称: dwDes

中国.NET开发者峰会特别活动-基于k8s的微服务和CI/CD动手实践报名

2019.11.9 的中国.NET开发者峰会将在上海举办,到目前为止,大会的主题基本确定,这两天就会和大家会面,很多社区的同学基于对社区的信任在我们议题没有确定的情况下已经购票超过了300张,而且分享的主题都来自于社区,来自于生产实践之中的经验分享,内容之中有一点非常值得分享-基于k8s的微服务实践内容很多,但是每一个分享的时间只有30分钟,难以全面阐述k8s 这样的一个大主题,因此陈计节.陈作.刘腾飞和我又特别策划了一个11.10号的workshop活动,采用一天的时间来带领大家使用.NET

RabbitMQ PHP操作类,守护进程及相关测试数据

封装类如下: <?php /* * amqp协议操作类,可以访问rabbitMQ * 需先安装php_amqp扩展 */ class RabbitMQCommand{ public $configs = array(); //交换机名称 public $exchange_name = ''; //队列名称 public $queue_name = ''; //路由名称 public $route_key = ''; /* * 持久化,默认True */ public $durable = Tru

分享基于.NET动态编译&amp;Newtonsoft.Json封装实现JSON转换器(JsonConverter)原理及JSON操作技巧

原文:分享基于.NET动态编译&Newtonsoft.Json封装实现JSON转换器(JsonConverter)原理及JSON操作技巧 看文章标题就知道,本文的主题就是关于JSON,JSON转换器(JsonConverter)具有将C#定义的类源代码直接转换成对应的JSON字符串,以及将JSON字符串转换成对应的C#定义的类源代码,而JSON操作技巧则说明如何通过JPath来快速的定位JSON的属性节点从而达到灵活读写JSON目的. 一.JSON转换器(JsonConverter)使用及原理介

C#调用Win32 api时的内存操作

一般情况下,C#与Win 32 Api的互操作都表现的很一致:值类型传递结构体,一维.二维指针传递IntPtr.在Win32 分配内存时,可以通过IntPtr以类似移动指针的方式读取内存.通过IntPtr移动时,需要考虑指针的计算.规则总体上来说显得一致,但Win32 Api庞杂,总有一些令人困惑的函数.比如GetIpForwardTable.该函数的功能是返回Ip(v4)的路由表.在win32 的结构体定义如下: DWORD GetIpForwardTable( _Out_ PMIB_IPFO

Docker学习总结(7)——云端基于Docker的微服务与持续交付实践

本文根据[2016 全球运维大会?深圳站]现场演讲嘉宾分享内容整理而成 讲师简介 易立 毕业于北京大学,获得学士学位和硕士学位:目前负责阿里云容器技术相关的产品的研发工作. 加入阿里之前,曾在IBM中国开发中心工作14年,担任资深技术专员,负责IBM企业平台云产品线PureApplication System的研发工作:还负责和参与了一系列IBM在Web 2.0,SOA中间件的研发和创新,也曾为全球客户提供SOA技术咨询和项目实施. 日程 大家好,我演讲的主题是<云端基于Docker的微服务与持

基于 Docker 的微服务架构实践

本文来自作者 未闻 在 GitChat 分享的{基于 Docker 的微服务架构实践} 前言 基于 Docker 的容器技术是在2015年的时候开始接触的,两年多的时间,作为一名 Docker 的 DevOps,也见证了 Docker 的技术体系的快速发展.本文主要是结合在公司搭建的微服务架构的实践过程,做一个简单的总结.希望给在创业初期探索如何布局服务架构体系的 DevOps,或者想初步了解企业级架构的同学们一些参考. Microservice 和 Docker 对于创业公司的技术布局,很多声

使用win32 API 实现串行通信 (一)

本文基于wince平台,使用win32 API实现串行通信 1.打开和关闭串行端口 串行端口设备使用CreateFile函数打开,所使用的名称要遵循特定的格式,即3个字符 COM后紧跟要打开的COM端口号,再加个冒号,冒号是Windows CE所必需的. 如,hser=CreateFile(TEXT(“COM1:”),GENERIC_READ|GENERIC_WRITE,0, NULL,OPEN_EXISTING,0,NULL),为以可读可写的方式打开COM1端口. 调用CloseHandle函