WindowsService(简称服务,下同)是目前做客户端软件后台运行功能的非常好的选择,本文基本解决了服务的创建和编写,代码控制服务的安装、卸载、启动、停止等,为服务传递参数,其他注意事项等
1、服务的创建和编写:
①在Add Project选择Windows Service创建项目,同时添加一个Windows Service类,在这里以IFUploaderService.cs为例
②在设计器中右键选择Add Installer,如图
③在生成的ProjectInstaller的设计器中设置
serviceProcessInstaller控件的属性 Account:LocalSystem (这样不论是以哪个用户登录的系统,服务总会启动)
serviceInstaller控件的属性 DisplayName:在系统服务管理界面显示的服务名称,根据你的程序命名,如图
Description:在系统服务管理界面显示的服务描述,根据你的程序填写
ServiceName:服务的真实名称,在系统中应该是唯一的,这也是接下来用程序控制服务的关键
StartType:服务的启动类型,有自动、手动、和禁用
④打开IFUploaderService.cs,代码中的OnStart和OnStop事件将在服务开启和结束时执行
为了实现定时执行的功能,你可以在OnStart中添加一个Timer,比如我要在每天8点执行自动上传功能,代码如下
1 protected override void OnStart(string[] args) 2 { 3 // TODO: Add code here to start your service. 4 if (args.Length > 0) 5 { 6 //服务的工作路径转移到主程序所在目录 7 System.Environment.CurrentDirectory = args[0]; 8 //记录服务开启的时间 9 serviceStartTime = DateTime.Now; 10 //开启计时器 11 System.Timers.Timer t = new System.Timers.Timer(); 12 t.Interval = 1000; 13 t.Elapsed += new System.Timers.ElapsedEventHandler(CheckUploadStatus); 14 t.AutoReset = true; 15 t.Enabled = true; 16 LogHelper.WriteLog("Service start"); 17 } 18 } 19 20 protected override void OnStop() 21 { 22 // TODO: Add code here to perform any tear-down necessary to stop your service. 23 LogHelper.WriteLog("Service stop"); 24 } 25 26 private void CheckUploadStatus(object sender, System.Timers.ElapsedEventArgs e) 27 { 28 if (DateTime.Now.ToString("HH:mm:ss") == "08:00:00") 29 { 30 UploadBegin(); 31 } 32 }
IFUploaderService.cs
⑤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 using System; 2 using System.Collections.Generic; 3 using System.Text; 4 using System.ServiceProcess; 5 using System.Configuration.Install; 6 using System.Collections; 7 using Microsoft.Win32; 8 9 namespace Eland.GEPS.POSIF.UI.Common 10 { 11 public static class ServiceHelper 12 { 13 #region 安装服务 14 /// <summary> 15 /// 安装服务 16 /// </summary> 17 /// <param name="filePath">服务名</param> 18 /// <param name="nameService">程序文件路径(不带.exe)</param> 19 /// <returns></returns> 20 public static bool InstallService(string filePath, string nameService) 21 { 22 bool flag = true; 23 if (!IsServiceExisted(nameService)) 24 { 25 try 26 { 27 string serviceFileName = filePath + ".exe"; 28 InstallServiceExec(serviceFileName); 29 } 30 catch 31 { 32 flag = false; 33 } 34 } 35 return flag; 36 } 37 #endregion 38 39 #region 卸载服务 40 /// <summary> 41 /// 卸载服务 42 /// </summary> 43 /// <param name="filePath">服务名</param> 44 /// <param name="nameService">程序文件路径(不带.exe)</param> 45 /// <returns></returns> 46 public static bool UninstallService(string filePath, string nameService) 47 { 48 bool flag = true; 49 if (IsServiceExisted(nameService)) 50 { 51 try 52 { 53 string serviceFileName = filePath + ".exe"; 54 UnInstallServiceExec(serviceFileName); 55 } 56 catch 57 { 58 flag = false; 59 } 60 } 61 return flag; 62 } 63 #endregion 64 65 #region 检查服务的存在性 66 /// <summary> 67 /// 检查服务的存在性 68 /// </summary> 69 /// <param name="nameService">服务名</param> 70 /// <returns>存在返回 true,否则返回 false</returns> 71 public static bool IsServiceExisted(string nameService) 72 { 73 ServiceController[] services = ServiceController.GetServices(); 74 foreach (ServiceController s in services) 75 { 76 if (s.ServiceName.ToLower() == nameService.ToLower()) 77 { 78 return true; 79 } 80 } 81 return false; 82 } 83 #endregion 84 85 #region 安装Windows服务 86 /// <summary> 87 /// 安装Windows服务 88 /// </summary> 89 /// <param name="filePath">程序文件路径</param> 90 public static void InstallServiceExec(string filePath) 91 { 92 try 93 { 94 string[] cmdline = { }; 95 TransactedInstaller transactedInstaller = new TransactedInstaller(); 96 AssemblyInstaller assemblyInstaller = new AssemblyInstaller(filePath, cmdline); 97 transactedInstaller.Installers.Add(assemblyInstaller); 98 transactedInstaller.Install(new System.Collections.Hashtable()); 99 } 100 catch (Exception) 101 { 102 throw; 103 } 104 } 105 #endregion 106 107 #region 卸载Windows服务 108 /// <summary> 109 /// 卸载Windows服务 110 /// </summary> 111 /// <param name="filePath">程序文件路径</param> 112 public static void UnInstallServiceExec(string filePath) 113 { 114 try 115 { 116 string[] cmdline = { }; 117 TransactedInstaller transactedInstaller = new TransactedInstaller(); 118 AssemblyInstaller assemblyInstaller = new AssemblyInstaller(filePath, cmdline); 119 transactedInstaller.Installers.Add(assemblyInstaller); 120 transactedInstaller.Uninstall(null); 121 } 122 catch (Exception) 123 { 124 throw; 125 } 126 } 127 #endregion 128 129 #region 判断window服务是否启动 130 /// <summary> 131 /// 判断window服务是否启动 132 /// </summary> 133 /// <param name="serviceName">服务名</param> 134 /// <returns></returns> 135 public static bool IsServiceStart(string serviceName) 136 { 137 ServiceController sc = new ServiceController(serviceName); 138 bool startStatus = false; 139 try 140 { 141 if (!sc.Status.Equals(ServiceControllerStatus.Stopped)) 142 { 143 startStatus = true; 144 } 145 return startStatus; 146 } 147 catch (Exception) 148 { 149 throw; 150 } 151 } 152 #endregion 153 154 #region 修改服务的启动项 155 /// <summary> 156 /// 修改服务的启动项 157 /// </summary> 158 /// <param name="startType">2为自动,3为手动</param> 159 /// <param name="serviceName">服务名</param> 160 /// <returns></returns> 161 public static void ChangeServiceStartType(int startType, string serviceName) 162 { 163 try 164 { 165 RegistryKey regist = Registry.LocalMachine; 166 RegistryKey sysReg = regist.OpenSubKey("SYSTEM"); 167 RegistryKey currentControlSet = sysReg.OpenSubKey("CurrentControlSet"); 168 RegistryKey services = currentControlSet.OpenSubKey("Services"); 169 RegistryKey servicesName = services.OpenSubKey(serviceName, true); 170 servicesName.SetValue("Start", startType); 171 } 172 catch (Exception) 173 { 174 throw; 175 } 176 } 177 #endregion 178 179 #region 启动服务 180 /// <summary> 181 /// 启动服务 182 /// </summary> 183 /// <param name="serviceName">服务名</param> 184 /// <param name="param">参数</param> 185 /// <returns></returns> 186 public static bool StartService(string serviceName, string[] param) 187 { 188 try 189 { 190 bool flag = true; 191 if (IsServiceExisted(serviceName)) 192 { 193 System.ServiceProcess.ServiceController service = new System.ServiceProcess.ServiceController(serviceName); 194 if (service.Status != System.ServiceProcess.ServiceControllerStatus.Running && service.Status != System.ServiceProcess.ServiceControllerStatus.StartPending) 195 { 196 service.Start(param); 197 for (int i = 0; i < 60; i++) 198 { 199 service.Refresh(); 200 System.Threading.Thread.Sleep(1000); 201 if (service.Status == System.ServiceProcess.ServiceControllerStatus.Running) 202 { 203 break; 204 } 205 if (i == 59) 206 { 207 flag = false; 208 } 209 } 210 } 211 } 212 return flag; 213 } 214 catch (Exception) 215 { 216 throw; 217 } 218 } 219 #endregion 220 221 #region 停止服务 222 /// <summary> 223 /// 停止服务 224 /// </summary> 225 /// <param name="serviceName">服务名</param> 226 /// <returns></returns> 227 public static bool StopService(string serviceName) 228 { 229 try 230 { 231 bool flag = true; 232 if (IsServiceExisted(serviceName)) 233 { 234 System.ServiceProcess.ServiceController service = new System.ServiceProcess.ServiceController(serviceName); 235 if (service.Status == System.ServiceProcess.ServiceControllerStatus.Running) 236 { 237 service.Stop(); 238 for (int i = 0; i < 60; i++) 239 { 240 service.Refresh(); 241 System.Threading.Thread.Sleep(1000); 242 if (service.Status == System.ServiceProcess.ServiceControllerStatus.Stopped) 243 { 244 break; 245 } 246 if (i == 59) 247 { 248 flag = false; 249 } 250 } 251 } 252 } 253 return flag; 254 } 255 catch (Exception) 256 { 257 throw; 258 } 259 } 260 #endregion 261 } 262 }
ServiceHelper.cs
3、为服务传递参数
有时,我们在启动服务时需要设定一些参数,但这些参数如何从调用服务的程序传递给服务呢?
细心的朋友可能已经发现,在第2节中的启动服务方法,需要传递参数param,这是因为ServiceController.Start有两个重载,一个无参数,一个可以传递字符串数组作为参数,这个参数将在服务启动时被OnStart方法接收,这样就实现了为服务传递参数。
4、其他注意事项
①服务安装后被安装的exe文件就被锁定,此时再Build项目将报错,正确的方法是先卸载手动服务,再重新Build
②首先明确概念:
当前工作目录——进行某项操作的目的目录,会随着OpenFileDialog、FileStream等对象所确定的目录而改变。
当前执行目录——该进程从中启动的目录,即文件自身所在目录。
工作目录与执行目录可以不同,例如一个人住在北京,但他的工作地点不一定在北京,可能在天津。
服务安装后其工作目录将变为"C:\Windows\system32",因此如果要使用OpenFileDialog、FileStream等System.IO命名空间下类和方法,并且使用相对路径,请先将工作目录设置到你想要的位置。而此时执行目录还是exe文件所在的文件夹,所以可以赋值给工作目录。
③服务中使用工具箱生成的Timer控件,其事件将不会被触发,因此应该手写Timer控件及其事件,如本文第1节中的代码
④服务已经安装,但服务的执行文件发生了变化,此时可能出现卸载不掉的情况
可以以管理员方式打开CMD,执行以下命令卸载服务:sc delete 服务名