C#有需求如下,一个客户端winform应用实现监听TCP端口,叫Demo.exe,重新启动操作系统时,在用户没有登陆账户之前,这个winform程序是无法运行的,要解决这个问题,通过新建一个window service服务(名称是DemoService.exe)去执行开机启动时自动打开Demo.exe。
将Service1重命名为你服务名称,这里我们命名为ServiceTest。
之后我们可以看到上图,自动为我们创建了ProjectInstaller.cs以及2个安装的组件。
右键serviceInsraller1,选择属性,将ServiceName的值改为ServiceTest。
右键serviceProcessInsraller1,选择属性,将Account的值改为LocalSystem。
右键ServiceTest,选择查看代码。
添加如下代码:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Linq; using System.ServiceProcess; using System.Text; using System.IO; namespace DemoService { public partial class DemoService : ServiceBase { //全局变量 string path = AppDomain.CurrentDomain.BaseDirectory; public DemoService() { InitializeComponent(); } protected override void OnStart(string[] args) { LogWrite("Start.");//日志 //启动目标应用程序 winform StartProgram(path + @"Demo.exe"); } protected override void OnStop() { LogWrite("Stop.");//日志 } /// <summary> /// 启动所有要启动的程序 ProgramPath:完整路径 /// </summary> private void StartProgram(string ProgramPath) { try { string fileName = System.IO.Path.GetFileNameWithoutExtension(ProgramPath); if (!IsExistProcess(fileName)) { ProcessStartInfo startInfo = new ProcessStartInfo(ProgramPath); startInfo.WindowStyle = ProcessWindowStyle.Normal; Process.Start(startInfo); LogWrite("Winform: " + fileName + " started."); } } catch (Exception err) { LogWrite(err.Message); } } /// <summary> /// 检查该进程是否已启动 /// </summary> /// <param name="processName"></param> /// <returns></returns> private bool IsExistProcess(string processName) { Process[] MyProcesses = Process.GetProcesses(); foreach (Process MyProcess in MyProcesses) { if (MyProcess.ProcessName.CompareTo(processName) == 0) { return true; } } return false; } /// <summary> /// 写日志 /// </summary> public void LogWrite(string str) { using (System.IO.StreamWriter sw = new System.IO.StreamWriter(path + @"DemoLog.txt", true)) { sw.WriteLine(DateTime.Now.ToString("[yyyy-MM-dd HH:mm:ss] ") + str); } } } }
在项目中添加2个文件如下(必须是ANSI或者UTF-8无BOM格式):
1 2 3 | cd /d %~dp0SET regpath=%cd% %SystemRoot%\Microsoft.NET\Framework\v4.0.30319\installutil.exe %regpath%\DemoService.exe Net Start DemoService ::echo "开机自动运行" sc config DemoService start= AUTO ::echo "允许服务与桌面交互" sc config DemoService type= interact type= own pause |
1 | cd /d %~dp0SET regpath=%cd% %SystemRoot%\Microsoft.NET\Framework\v4.0.30319\installutil.exe /u %regpath%\DemoService.exe pause |
请以管理员身份运行上述bat脚本,否则会提示权限不足问题。另外,以管理员身份打开bat脚本时,路径会自动变成c:/windows/system32 ,所以需要前面的两句:cd /d %~dp0 和 SET regpath=%cd%
【补充】:关于服务设置可与桌面交互选项
当我们从服务中启动winform应用程序时,会出现winform的界面无法显示的问题。因为服务是用系统账户system运行的,那么从服务里启动winform时,winform的运行账户也是system,而system账户是没有GUI用户界面的,所以我们会发现进程里有demo.EXE这个程序,但是没有界面显示。
飘易尝试了3种方式:
方式1:bat脚本里设置允许服务和桌面交互
sc config DemoService type= interact type= own
失败。
方式2:为 serviceProcessInstaller1 添加事件 serviceProcessInstaller1_Committed
//为当前Windows服务设置可与桌面交互选项,否则winform程序没有显示界面 private void serviceProcessInstaller1_Committed(object sender, InstallEventArgs e) { try { ConnectionOptions myConOptions = new ConnectionOptions(); myConOptions.Impersonation = ImpersonationLevel.Impersonate; ManagementScope mgmtScope = new System.Management.ManagementScope(@"root\CIMV2", myConOptions); mgmtScope.Connect(); ManagementObject wmiService = new ManagementObject("Win32_Service.Name='" + serviceInstaller1.ServiceName + "'"); ManagementBaseObject InParam = wmiService.GetMethodParameters("Change"); InParam["DesktopInteract"] = true; ManagementBaseObject OutParam = wmiService.InvokeMethod("Change", InParam, null); } catch (Exception ) { } }
也是失败。
方式3:手动打开“设置允许服务和桌面交互”
同样失败。
总结下:以服务的形式启动winform应用程序,并设置“允许服务与桌面交互”,在win vista以前的版本中也许行得通,但是在window vista之后的系统里,已经不行了。原因为:在vista之后的windows系统中,所有的system服务都跑在用户会话0上,而你登陆的用户永远是在会话1以及之后,所以系统服务的UI不是弹不出来,是你在当前桌面看不到。想穿透这个屏障需要使用跨会话通信技术。传统点的可以使用系统api,新鲜点的可以在会话间使用wcf通讯,也有极端点办法比如利用IIS。
可以参考:解决vista和win7在windows服务中交互桌面权限问题:穿透Session 0 隔离
我们可以转化下思路,后台服务里不放GUI相关的,也不要在服务里直接启动GUI程序,然后做个GUI应用程序去控制这个服务的启动、停止等。同时这个GUI程序设置开机启动,这样在用户登录后,这个GUI就可以启动了。
既然如此,飘易为什么还要用本文开头提到的这种方式呢?因为我的winform界面其实不是必须的,只是显示监听端口的日志而已,所以,服务里启动的winform没有界面也无所谓。但是,我调试winform的时候就方便多了,我可以不通过服务启动它,直接手工启动该winform,就可以看到调试日志了。而如果直接把监听功能也放在服务里,我每次都要先卸载该服务,重新编译,然后再重新安装服务,比较麻烦。
【参考】:
1、C#创建Windows Service(Windows 服务)基础教程:http://www.cnblogs.com/sorex/archive/2012/05/16/2502001.html/
2、C# 实现windows 系统服务(全,含代码)http://www.cnblogs.com/bq-blog/articles/windowsservice.html
3、C# 编写Windows Service(windows服务程序) http://www.cnblogs.com/bluestorm/p/3510398.html
4、C#实现WinForm随WINDOWS服务一起启动 http://www.cnblogs.com/xiaofengfeng/archive/2011/09/28/2194130.html