注:这里的服务是指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 AUTHORITYLocalService"; } if (account == ServiceAccount.NetworkService) { return @"NT AUTHORITYNetworkService"; } 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('