用C#創建簡單的服務程序(Service)及其編譯自動化

本文描述的內容主要涵蓋兩個方面:
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發佈的日誌還算不錯。但是沒有摘要啊。。。鬱悶。。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章