本文描述的內容主要涵蓋兩個方面:
1. 如何用C#創建一個簡單的服務程序
2. 如何讓服務在編譯的時候自動安裝、卸載、啓動、停止
3. 可能會遇到的問題
本文操作環境:Win7 + Visual Studio 2008
請注意:Visual Studio 2008需要以管理員權限啓動,因爲我們的命令都需要這個權限。
1. 對於前一個問題,可以參考以下幾篇文章:
MSDN:Visual Studio 2010 如何:創建 Windows 服務
用Visual C#創建Windows服務程序
用C#創建Windows服務(Windows Services)
如何用.NET創建Windows服務
這幾篇文章主要描述了服務程序的一些概念和step by step的操作。對於不瞭解Service的同學來說應該,應該會有一個比較初步的瞭解。 但是,用Visual C#創建Windows服務程序 這篇文章的例子比較久遠,我下載編譯了該例子,並安裝、啓動了編譯出來的服務,不知爲 何會同時有兩個服務在任務管理器中出現。所以,這篇文章的代碼沒有進行仔細的研究,而是自己建立了一個工程,一步一步的做下來。
2. 如何讓服務在編譯的時候自動安裝、卸載、啓動、停止?
首先,需要了解一下如何手動的進行服務的安裝和卸載,它用到VS自帶的一個工具:InstallUtil.exe。服務的啓動和停止用到了net命令或者使用sc命令。
操作:用管理員權限打開Visual Studio 2008 Command Prompt(VS自帶的命令行)->進入到編譯生成可執行文件的目錄
安裝:輸入命令 InstallUtil 服務名稱
卸載:輸入命令 InstallUtil /u 服務名稱
啓動:輸入命令 net start 服務名稱 或者 sc start 服務名稱
停止:輸入命令 net stop 服務名稱 或者 sc stop 服務名稱
我們的服務程序編譯OK後,每次都需要重複如上的操作,相當繁瑣,也大大降低了我們開發的效率,如果我們能讓這些操作完全自動化,在編譯完成後就能直接使用,這是一件相當美妙的事情。
最開始,我參照了.Net下的Windows服務程序開發指南.這篇文章,文章提供了一個思路,將如下幾行命令放在:工程屬性->Build Event->Post-build event command line裏面,這樣編譯的時候就能夠進行一些上述操作。
1: net stop ServiceName
2: "%SystemRoot%/Microsoft.NET/Framework/v2.0.50727/InstallUtil.exe" /u $(ProjectDir)bin/$(ConfigurationName)/$(TargetFileName)
3: "%SystemRoot%/Microsoft.NET/Framework/v2.0.50727/InstallUtil.exe" $(ProjectDir)bin/$(ConfigurationName)/$(TargetFileName)
4: net start ServiceName
但是,該方法還是不夠完美,原因有二:
一是在第一次編譯後該服務後,由於本地機器從來沒有安裝過ServiceName這個服務,此時就會報錯, 服務名無效。請鍵入 NET HELPMSG 2185 以獲得更多的幫助。因此,第一次使用時,要手動安裝。
二是在第二次將要編譯的時候,如果該服務沒有停掉(即進程並沒有結束),那麼此時也會報錯,因爲沒有權限修改這個可執行文件,它正在被另外一個進程佔用。
於是,我稍微改進了一下,將1,2句放到Per-build event command line裏,3,4句放到Post-build event command line,雖然或多或少的有一些改進,但不是不如意。
最根本的原因其實在於自己寫的CMD實在是太弱了,而且命令行本身對於返回值的支持貌似不怎麼好。因此,最好是有一種能夠支持複雜邏輯的腳本,並能夠很好的處理返回值。 顯然,Windows Powershell是最好的選擇。 但是,要使用Win7自帶的PowerShell來自動化處理我們的編譯,還需要解決兩個問題。
1. PowerShell本身是否支持InstallUtil, sc等等命令,如果不支持,是否有對應的替代方法?
2. 能否將PowerShell腳本嵌入到Per-build or Post-build event command line中去,如果不能,是否有對應的替代方法?
第一個問題,我的答案是不能,至少我沒調查出來,但是我們可以設置相關的環境變量到PowerShell裏面去,讓它能夠支持這些命令。參考文章:Visual Studio 2008 PowerShell
第二個問題,很遺憾,我還是沒有調查出來,但是我們可以在Per-build or Post-build event command line調用PowerShell命令執行其腳本文件。參考PowerShell的Help文檔。
OK,先看腳本文件的代碼:
1: function Get-Batchfile ($file) {
2: $cmd = "`"$file`" & set"
3: cmd /c $cmd | Foreach-Object {
4: $p, $v = $_.split('=')
5: Set-Item -path env:$p -value $v
6: }
7: }
8:
9: function VsVars32()
10: {
11: $vs90comntools = (Get-ChildItem env:VS90COMNTOOLS).Value
12: $batchFile = [System.IO.Path]::Combine($vs90comntools, "vsvars32.bat")
13: Get-Batchfile $BatchFile
14:
15: [System.Console]::Title = "Visual Studio 2008 Windows PowerShell"
16: }
17:
18: function BuildEvent([Boolean]$isPreBuild, $ServiceName, $ServiceFullPath)
19: {
20: #如果是編譯前($isPreBuild==True),
21: # 則先獲取所有服務,再遍歷所有服務,如果找到該服務,且該服務正處於運行狀態,則先Stop該服務,然後再UnInstall該服務
22: #如果是編譯後($isPreBuild==False),
23: # 則直接先安裝服務,再Start該服務
24: if ($isPreBuild)
25: {
26: $Rts = Get-Service
27: foreach ($Item in $Rts)
28: {
29: if ($Item.Name -eq $ServiceName -and $Item.Status -eq "Running")
30: {
31: Stop-Service $ServiceName
32: echo "Stop $ServiceName Success!"
33: InstallUtil /u $ServiceFullPath
34: echo "UnInstall $ServiceName Success!"
35: }
36: }
37: }
38: else
39: {
40: InstallUtil $ServiceFullPath
41: echo "Install $ServiceName Success!"
42: Start-Service $ServiceName
43: echo "Start $ServiceName Success!"
44: }
45: }
46:
47: ############################################################################
48: ############################################################################
49:
50: #程序從這裏入口
51: "Visual Studio 2008 Windows PowerShell"
52: ""
53: #將VS的相關環境變量設置到Windows PowerShell中去
54: #Set Visual Studio 2008 Command Prompt's environment variable into Windows PowerShell
55: VsVars32
56:
57: #解析從命令行傳過來的參數:
58: # 參數1:是否是Pre-Build,需要傳入true/false的字符串
59: # 參數2:服務名稱
60: # 參數3:服務對應的可執行文件全路徑(絕對路徑)
61: if ($args.Count -eq 3)
62: {
63: if ($args[0] -ne "true" -and $args[0] -ne "false")
64: {
65: echo "Argument error!!! Please check it."
66: exit
67: }
68:
69: echo "Pre-Build or Post-Build event command line[True:Pre-Build, False:Post-Build]:" $args[0]
70: echo "Service Name:" $args[1]
71: echo "Service Full Path:" $args[2]
72:
73: [Boolean]$isPreBuild = $true
74: if ($args[0] -eq "false")
75: {
76: [Boolean]$isPreBuild = $false
77: }
78:
79: #調用處理函數
80: BuildEvent $isPreBuild $args[1] $args[2]
81: }
然後,我們需要讓VS能夠調用該腳本,並傳入相應的參數即可:
在Per-build event command line中,添加: powershell -File $(ProjectDir)Pre-BuildAndPost-BuildService.ps1 true $(TargetName) $(TargetPath)
在Post-build event command line中,添加: powershell -File $(ProjectDir)Pre-BuildAndPost-BuildService.ps1 false $(TargetName) $(TargetPath)
3. 可能會遇到的問題
3.1 如何調試:
①打log
②直接Attach到進程
需要注意的是,Windows 服務管理器將所有嘗試啓動服務的時間限制在 30 秒內,如果想要調試OnStart方法,我的做法比較山寨,是在OnStart的時候加入一個Thread.Sleep(20000),然後迅速啓動服務,迅速Attach到服務進程。MS推薦使用臨時服務加載真正要處理的服務,請參考:如何:調試 Windows 服務應用程序。
3.2 在計算機->管理->服務中,我能夠看到對應的服務,但爲什麼狀態爲disabled(禁用),無法啓動,此時,如果啓動該服務,則會出現錯誤對話框:
The specified service has been marked for deletion(指定服務已標記爲刪除)。那麼,如何刪掉該服務?其實,此時你什麼都不用做,關掉這個服務的窗口,關掉其控制命令行,再重新打開服務管理窗口,發現一切正常。實在不行,只有用殺手鐗----重啓電腦。
3.3 如何刪除一個服務:①sc delete ServiceName ②在註冊表HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services下找到對應的服務,刪除即可。
貌似用Windows Live Writter發佈的日誌還算不錯。但是沒有摘要啊。。。鬱悶。。。