Windows服务的C#实现

WindowsService(简称服务,下同)是目前做客户端软件后台运行功能的非常好的选择,本文基本解决了服务的创建和编写,代码控制服务的安装、卸载、启动、停止等,为服务传递参数,其他注意事项等。

1 服务的创建和编写

①在Add Project选择Windows Service创建项目,同时添加一个Windows Service类,在这里以IFUploaderService.cs为例

②在设计器中右键选择Add Installer,如图

图1

③在生成的ProjectInstaller的设计器中设置

serviceProcessInstaller控件的属性 Account:LocalSystem (这样不论是以哪个用户登录的系统,服务总会启动)

serviceInstaller控件的属性

  • DisplayName:在系统服务管理界面显示的服务名称,根据你的程序命名,如图
  • Description:在系统服务管理界面显示的服务描述,根据你的程序填写
  • ServiceName:服务的真实名称,在系统中应该是唯一的,这也是接下来用程序控制服务的关键
  • StartType:服务的启动类型,有自动、手动、和禁用

图2

④打开IFUploaderService.cs,代码中的OnStart和OnStop事件将在服务开启和结束时执行

为了实现定时执行的功能,你可以在OnStart中添加一个Timer,比如我要在每天8点执行自动上传功能,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
protected override void OnStart(string[] args)
{
// TODO: Add code here to start your service.
if (args.Length > 0)
{
//服务的工作路径转移到主程序所在目录
System.Environment.CurrentDirectory = args[0];
//记录服务开启的时间
serviceStartTime = DateTime.Now;
//开启计时器
System.Timers.Timer t = new System.Timers.Timer();
t.Interval = 1000;
t.Elapsed += new System.Timers.ElapsedEventHandler(CheckUploadStatus);
t.AutoReset = true;
t.Enabled = true;
LogHelper.WriteLog("Service start");
}
}

protected override void OnStop()
{
// TODO: Add code here to perform any tear-down necessary to stop your service.
LogHelper.WriteLog("Service stop");
}

private void CheckUploadStatus(object sender, System.Timers.ElapsedEventArgs e)
{
if (DateTime.Now.ToString("HH:mm:ss") == "08:00:00")
{
UploadBegin();
}
}

⑤Build项目,注意服务项目不能直接执行,接下来手动安装服务:

  • 复制Build生成的exe文件的完整路径
  • 打开Visual Studio Command Prompt(VS命令提示符),执行installutil Build后的exe文件路径,比如

installutil D:\EGFIS_IF\Eland.GEPS.POSIF.WinService\bin\Debug\Eland.GEPS.POSIF.WinService.exe

同理,卸载服务的命令是installutil /u Build后的exe文件路径

⑥调试时只需要在vs中附加项目生成的服务exe的进程即可

2 代码控制服务的安装、卸载、开启、关闭等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceProcess;
using System.Configuration.Install;
using System.Collections;
using Microsoft.Win32;

namespace Eland.GEPS.POSIF.UI.Common
{
public static class ServiceHelper
{
#region 安装服务
/// <summary>
/// 安装服务
/// </summary>
/// <param name="filePath">服务名</param>
/// <param name="nameService">程序文件路径(不带.exe)</param>
/// <returns></returns>
public static bool InstallService(string filePath, string nameService)
{
bool flag = true;
if (!IsServiceExisted(nameService))
{
try
{
string serviceFileName = filePath + ".exe";
InstallServiceExec(serviceFileName);
}
catch
{
flag = false;
}
}
return flag;
}
#endregion

#region 卸载服务
/// <summary>
/// 卸载服务
/// </summary>
/// <param name="filePath">服务名</param>
/// <param name="nameService">程序文件路径(不带.exe)</param>
/// <returns></returns>
public static bool UninstallService(string filePath, string nameService)
{
bool flag = true;
if (IsServiceExisted(nameService))
{
try
{
string serviceFileName = filePath + ".exe";
UnInstallServiceExec(serviceFileName);
}
catch
{
flag = false;
}
}
return flag;
}
#endregion

#region 检查服务的存在性
/// <summary>
/// 检查服务的存在性
/// </summary>
/// <param name="nameService">服务名</param>
/// <returns>存在返回 true,否则返回 false</returns>
public static bool IsServiceExisted(string nameService)
{
ServiceController[] services = ServiceController.GetServices();
foreach (ServiceController s in services)
{
if (s.ServiceName.ToLower() == nameService.ToLower())
{
return true;
}
}
return false;
}
#endregion

#region 安装Windows服务
/// <summary>
/// 安装Windows服务
/// </summary>
/// <param name="filePath">程序文件路径</param>
public static void InstallServiceExec(string filePath)
{
try
{
string[] cmdline = { };
TransactedInstaller transactedInstaller = new TransactedInstaller();
AssemblyInstaller assemblyInstaller = new AssemblyInstaller(filePath, cmdline);
transactedInstaller.Installers.Add(assemblyInstaller);
transactedInstaller.Install(new System.Collections.Hashtable());
}
catch (Exception)
{
throw;
}
}
#endregion

#region 卸载Windows服务
/// <summary>
/// 卸载Windows服务
/// </summary>
/// <param name="filePath">程序文件路径</param>
public static void UnInstallServiceExec(string filePath)
{
try
{
string[] cmdline = { };
TransactedInstaller transactedInstaller = new TransactedInstaller();
AssemblyInstaller assemblyInstaller = new AssemblyInstaller(filePath, cmdline);
transactedInstaller.Installers.Add(assemblyInstaller);
transactedInstaller.Uninstall(null);
}
catch (Exception)
{
throw;
}
}
#endregion

#region 判断window服务是否启动
/// <summary>
/// 判断window服务是否启动
/// </summary>
/// <param name="serviceName">服务名</param>
/// <returns></returns>
public static bool IsServiceStart(string serviceName)
{
ServiceController sc = new ServiceController(serviceName);
bool startStatus = false;
try
{
if (!sc.Status.Equals(ServiceControllerStatus.Stopped))
{
startStatus = true;
}
return startStatus;
}
catch (Exception)
{
throw;
}
}
#endregion

#region 修改服务的启动项
/// <summary>
/// 修改服务的启动项
/// </summary>
/// <param name="startType">2为自动,3为手动</param>
/// <param name="serviceName">服务名</param>
/// <returns></returns>
public static void ChangeServiceStartType(int startType, string serviceName)
{
try
{
RegistryKey regist = Registry.LocalMachine;
RegistryKey sysReg = regist.OpenSubKey("SYSTEM");
RegistryKey currentControlSet = sysReg.OpenSubKey("CurrentControlSet");
RegistryKey services = currentControlSet.OpenSubKey("Services");
RegistryKey servicesName = services.OpenSubKey(serviceName, true);
servicesName.SetValue("Start", startType);
}
catch (Exception)
{
throw;
}
}
#endregion

#region 启动服务
/// <summary>
/// 启动服务
/// </summary>
/// <param name="serviceName">服务名</param>
/// <param name="param">参数</param>
/// <returns></returns>
public static bool StartService(string serviceName, string[] param)
{
try
{
bool flag = true;
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(param);
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)
{
flag = false;
}
}
}
}
return flag;
}
catch (Exception)
{
throw;
}
}
#endregion

#region 停止服务
/// <summary>
/// 停止服务
/// </summary>
/// <param name="serviceName">服务名</param>
/// <returns></returns>
public static bool StopService(string serviceName)
{
try
{
bool flag = true;
if (IsServiceExisted(serviceName))
{
System.ServiceProcess.ServiceController service = new System.ServiceProcess.ServiceController(serviceName);
if (service.Status == System.ServiceProcess.ServiceControllerStatus.Running)
{
service.Stop();
for (int i = 0; i < 60; i++)
{
service.Refresh();
System.Threading.Thread.Sleep(1000);
if (service.Status == System.ServiceProcess.ServiceControllerStatus.Stopped)
{
break;
}
if (i == 59)
{
flag = false;
}
}
}
}
return flag;
}
catch (Exception)
{
throw;
}
}
#endregion
}
}

3 为服务传递参数

有时,我们在启动服务时需要设定一些参数,但这些参数如何从调用服务的程序传递给服务呢?

细心的朋友可能已经发现,在第2节中的启动服务方法,需要传递参数param,这是因为ServiceController.Start有两个重载,一个无参数,一个可以传递字符串数组作为参数,这个参数将在服务启动时被OnStart方法接收,这样就实现了为服务传递参数。

4 其他注意事项

①服务安装后被安装的exe文件就被锁定,此时再Build项目将报错,正确的方法是先卸载手动服务,再重新build

②首先明确概念:

  • 当前工作目录——进行某项操作的目的目录,会随着openfiledialog、filestream等对象所确定的目录而改变。
  • 当前执行目录——该进程从中启动的目录,即文件自身所在目录。

工作目录与执行目录可以不同,例如一个人住在北京,但他的工作地点不一定在北京,可能在天津。

服务安装后其工作目录将变为C:\Windows\system32,因此如果要使用OpenFileDialogFileStreamSystem.IO命名空间下的类和方法,并且使用相对路径,请先将工作目录设置到你想要的位置。而此时执行目录还是exe文件所在的文件夹,所以可以赋值给工作目录。

图3

③服务中使用工具箱生成的Timer控件,其事件将不会被触发,因此应该手写Timer控件及其事件,如本文第1节中的代码

④服务已经安装,但服务的执行文件发生了变化,此时可能出现卸载不掉的情况

可以以管理员方式打开CMD,执行以下命令卸载服务:sc delete 服务名