最近在為一款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", "\nService Start Error:" + DateTime.Now.ToString()+"\n"+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("\n服務已存在......"); } goto StartLable; } else if (key == ConsoleKey.NumPad3 || key == ConsoleKey.D3) { if (ServiceHelper.IsServiceExisted("MyService")) { ServiceHelper.ConfigService("MyService", false); } else { Console.WriteLine("\n服務不存在......"); } goto StartLable; } else if (key == ConsoleKey.NumPad4 || key == ConsoleKey.D4) { if (!ServiceHelper.IsServiceExisted("MyService")) { Console.WriteLine("\n服務不存在......"); } else { Console.WriteLine("\n服務狀態:" + ServiceHelper.GetServiceStatus("MyService").ToString()); } goto StartLable; } else if (key == ConsoleKey.NumPad5 || key == ConsoleKey.D5) { } else { Console.WriteLine("\n請輸入一個有效鍵!"); 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()+"\n"+ex.Message); } }
還有最重要的一點:Win7+系統,由於權限問題,需要為執行程序設置以管理員方式運行,通過VS直接Debug是無法執行成功的,這也是很多網上的老舊資料忽略的一點
以下是運行效果演示
這里我首先獲取了一下服務狀態,顯示服務並不存在,然后通過自動部署(其實就是判斷服務狀態,不存在則安裝,存在則先卸載再安裝,最后啟動服務),完成后,再次獲取服務狀態,顯示Running!!!
然后我們進入服務管理器看下
沒錯,就是它了,跟我們上面設置的友好名,以及說明一致,然后我們點開這個服務的屬性
你是不是已經看到玄機了,可執行文件路徑后面帶上了參數,這就是動態選擇啟動控制台程序還是啟動服務的關鍵,同時依存關系里面有了SQLServer,接下來我們做最后一步驗證,打開D盤日志文件
至此,證明整套的服務安裝,啟動均已完成,是不是很方便,以后再也不用通過命令行去安裝,卸載了,每次都可以直接啟動這個控制台應用程序,就可以完成對服務的配置!!!
原創文章,轉載請注明出處
最后附上源碼
開發環境:Visual studio 2015 / .Net Framework 4.5.2