本文描述的内容主要涵盖两个方面:
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发布的日志还算不错。但是没有摘要啊。。。郁闷。。。