最近在为一款C/S架构的科研软件开发云计算版,需要用到WCF,考虑到不需要什么界面以及稳定性,无人值守性,准备用Windows Service作为宿主,无奈Windows Service的安装太为繁复,就想如何通过C#代码完成Windows服务的安装及配置,在网上找了些资料,大多都是非常简单的代码,并没有一个完整的示例,可能一些初学者看起来不是很清晰,特别做了这个Demo!
首先建立项目,结构非常简单,一个控制台应用程序,添加一个类库(ServiceHelper.cs 安装、卸载服务辅助类),一个Window服务(WindowsService.cs)
项目的思路就是,启动控制台应用程序,自动识别是应该以服务身份启动服务还是以控制台应用身份启动服务配置
首先来看Windows服务定义
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Linq; using System.ServiceProcess; using System.Text; using System.Threading.Tasks; namespace ConsoleWithWindowsService { partial class WindowsService : ServiceBase { public WindowsService() { InitializeComponent(); } protected override void OnStart(string[] args) { System.IO.File.AppendAllText(@"D:Log.txt", " Service Start :" + DateTime.Now.ToString());
new Task(() =>
{
// TODO: 在此处添加代码 这里你自己的代码 使用Task 就可以解决原来作者那个启动服务后需要循环60秒判断任务是否开始等一系列问题
}).Start();
} protected override void OnStop() { // TODO: 在此处添加代码以执行停止服务所需的关闭操作。 System.IO.File.AppendAllText(@"D:Log.txt", " Service Stop :" + DateTime.Now.ToString()); } } }
为了简单演示,这里我没有做什么工作,只是简单的往D盘写入了一个日志,证明服务正常工作就行了
然后来看Windows服务的辅助类
using System; using System.Collections; using System.Collections.Generic; using System.Configuration.Install; using System.Linq; using System.Reflection; using System.ServiceProcess; using System.Text; using System.Threading.Tasks; namespace ConsoleWithWindowsService { public class ServiceHelper { /// <summary> /// 服务是否存在 /// </summary> /// <param name="serviceName"></param> /// <returns></returns> public static bool IsServiceExisted(string serviceName) { ServiceController[] services = ServiceController.GetServices(); foreach (ServiceController s in services) { if (s.ServiceName == serviceName) { return true; } } return false; } /// <summary> /// 启动服务 /// </summary> /// <param name="serviceName"></param> public static void StartService(string serviceName) { if (IsServiceExisted(serviceName)) { System.ServiceProcess.ServiceController service = new System.ServiceProcess.ServiceController(serviceName); if (service.Status != System.ServiceProcess.ServiceControllerStatus.Running && service.Status != System.ServiceProcess.ServiceControllerStatus.StartPending) { service.Start(); for (int i = 0; i < 60; i++) { service.Refresh(); System.Threading.Thread.Sleep(1000); if (service.Status == System.ServiceProcess.ServiceControllerStatus.Running) { break; } if (i == 59) { throw new Exception("Start Service Error:" + serviceName); } } } } } /// <summary> /// 获取服务状态 /// </summary> /// <param name="serviceName"></param> /// <returns></returns> public static ServiceControllerStatus GetServiceStatus(string serviceName) { System.ServiceProcess.ServiceController service = new System.ServiceProcess.ServiceController(serviceName); return service.Status; } /// <summary> /// 配置服务 /// </summary> /// <param name="serviceName"></param> /// <param name="install"></param> public static void ConfigService(string serviceName, bool install) { TransactedInstaller ti = new TransactedInstaller(); ti.Installers.Add(new ServiceProcessInstaller { Account = ServiceAccount.LocalSystem }); ti.Installers.Add(new ServiceInstaller { DisplayName = serviceName, ServiceName = serviceName, Description = "MicroID微检系统数据后台服务", ServicesDependedOn = new string[] { "MSSQLSERVER" },//前置服务 StartType = ServiceStartMode.Automatic//运行方式 }); ti.Context = new InstallContext(); ti.Context.Parameters["assemblypath"] = """ + Assembly.GetEntryAssembly().Location + "" /service"; if (install) { ti.Install(new Hashtable()); } else { ti.Uninstall(null); } } } }
这里有四个函数,分别是用于验证指定服务是否存在,启动服务,获取指定服务状态和最关键的服务安装及卸载(服务配置)
验证服务存在性及获取服务状态没什么好说的,启动服务因为会有一个延时,通过一个循环模拟等待服务启动的这段时间,超时即为启动失败,重点是第四个服务配置函数
public static void ConfigService(string serviceName, bool install)
这里我们需要传入你希望命名的服务名称,通过一个bool值判断是安装还是卸载服务,项目中我统一都命名为MyService,接下来看下面这段代码
ti.Installers.Add(new ServiceInstaller { DisplayName = serviceName, ServiceName = serviceName, Description = "MicroID微检系统数据后台服务", ServicesDependedOn = new string[] { "MSSQLSERVER" },//前置服务 StartType = ServiceStartMode.Automatic//运行方式 });
在这段代码中,为服务设置了友好名(DisplayName即为任务管理器、服务管理器中看到的服务名),服务名,说明,前置服务,以及运行方式
这里的前置服务是告诉Windows,启动我的时候,记得要先等待SQLServer启动,我需要用到它,如果你的服务需要访问SQLServer数据库,那可千万不要忘了这里
最后是控制台应用程序的入口
using System; using System.Collections.Generic; using System.Linq; using System.ServiceProcess; using System.Text; using System.Threading.Tasks; namespace ConsoleWithWindowsService { class Program { static void Main(string[] args) { //带参启动运行服务 if (args.Length > 0) { try { ServiceBase[] serviceToRun = new ServiceBase[] { new WindowsService() }; ServiceBase.Run(serviceToRun); } catch (Exception ex) { System.IO.File.AppendAllText(@"D:Log.txt", " Service Start Error:" + DateTime.Now.ToString()+" "+ex.Message); } } //不带参启动配置程序 else { StartLable: Console.WriteLine("请选择你要执行的操作——1:自动部署服务,2:安装服务,3:卸载服务,4:验证服务状态,5:退出"); Console.WriteLine("————————————————————"); ConsoleKey key = Console.ReadKey().Key; if (key == ConsoleKey.NumPad1 || key == ConsoleKey.D1) { if (ServiceHelper.IsServiceExisted("MyService")) { ServiceHelper.ConfigService("MyService", false); } if (!ServiceHelper.IsServiceExisted("MyService")) { ServiceHelper.ConfigService("MyService", true); } ServiceHelper.StartService("MyService"); goto StartLable; } else if (key == ConsoleKey.NumPad2 || key == ConsoleKey.D2) { if (!ServiceHelper.IsServiceExisted("MyService")) { ServiceHelper.ConfigService("MyService", true); } else { Console.WriteLine(" 服务已存在......"); } goto StartLable; } else if (key == ConsoleKey.NumPad3 || key == ConsoleKey.D3) { if (ServiceHelper.IsServiceExisted("MyService")) { ServiceHelper.ConfigService("MyService", false); } else { Console.WriteLine(" 服务不存在......"); } goto StartLable; } else if (key == ConsoleKey.NumPad4 || key == ConsoleKey.D4) { if (!ServiceHelper.IsServiceExisted("MyService")) { Console.WriteLine(" 服务不存在......"); } else { Console.WriteLine(" 服务状态:" + ServiceHelper.GetServiceStatus("MyService").ToString()); } goto StartLable; } else if (key == ConsoleKey.NumPad5 || key == ConsoleKey.D5) { } else { Console.WriteLine(" 请输入一个有效键!"); Console.WriteLine("————————————————————"); goto StartLable; } } } } }
有了这三部分,就算完成啦,当我们生成项目后,启动应用程序,这时会默认启动控制台应用程序,因为arg[] 参数为空,有人可能有疑问,那服务又如何启动呢,注意Windows服务辅助类中的这句代码
ti.Context.Parameters["assemblypath"] = """ + Assembly.GetEntryAssembly().Location + "" /service";
我们在为服务注册的时候,在后面加了"/service"这个参数,也就是说,当我们直接启动可执行文件时,这个参数为空,程序会启动控制台应用程序,而我们注册的服务会携带这个参数,你会在后面看到效果,每次服务启动时因为都带了这个参数,程序会自动执行下面的代码来直接启动服务,由此做到了两种程序的动态选择
///带参启动运行服务 if (args.Length > 0) { try { ServiceBase[] serviceToRun = new ServiceBase[] { new WindowsService() }; ServiceBase.Run(serviceToRun); } catch (Exception ex) { System.IO.File.AppendAllText(@"D:Log.txt", "Service Start Error:" + DateTime.Now.ToString()+" "+ex.Message); } }
还有最重要的一点:Win7+系统,由于权限问题,需要为执行程序设置以管理员方式运行,通过VS直接Debug是无法执行成功的,这也是很多网上的老旧资料忽略的一点
以下是运行效果演示
这里我首先获取了一下服务状态,显示服务并不存在,然后通过自动部署(其实就是判断服务状态,不存在则安装,存在则先卸载再安装,最后启动服务),完成后,再次获取服务状态,显示Running!!!
然后我们进入服务管理器看下
没错,就是它了,跟我们上面设置的友好名,以及说明一致,然后我们点开这个服务的属性
你是不是已经看到玄机了,可执行文件路径后面带上了参数,这就是动态选择启动控制台程序还是启动服务的关键,同时依存关系里面有了SQLServer,接下来我们做最后一步验证,打开D盘日志文件
至此,证明整套的服务安装,启动均已完成,是不是很方便,以后再也不用通过命令行去安装,卸载了,每次都可以直接启动这个控制台应用程序,就可以完成对服务的配置!!!
原创文章,转载请注明出处
最后附上源码
开发环境:Visual studio 2015 / .Net Framework 4.5.2