Windows服务(Service)安装及启动停止方案
目录
一、创作背景
二、问题解决
2.1 安装Windows service服务
2.2 主方法Main()主方法改写
2.3 安装service服务/卸载service服务
2.4 服务启停
2.5 服务调用运行
2.6 关于权限的提升
三、资源分享
3.1 引入组件
3.2 新手使用
一、创作背景
今天,我们完成了一个应用程序的开发,我们要将其设置成开机自启程序,继上篇文章介绍有两种方式。
一种,开机启动目录下创建应有程序的快捷方式;
另一种,写Windows服务;我们现在采用“服务方式自启程序”。
因为用户不具备管理员权限,为了安全考虑,我们方便监控管理。最终确认方案二。
先说说我们面临的问题,或者说是待解决的问题:
①安装服务,通过咱新开发的C#应用软件安装,不希望借助第三方工具(installutil.exe),因为面向人群非科班出身,操作越简单越好。
②开启、停止服务,不希望出现【黑色命令框】弹窗,看着就烦,用户体验不好。
③不希望出现Windows权限不够的提示框,因为用户是普通用户,不具备“管理员操作权限”,本程序有一些敏感操作,有需要最高权限,不要出现“权限不够,哪怕是否确认授权”消息提示框。
怎么样?需求是不是很变态,需要同时满足以上三点,网上很多技术教程已经不适用。变态需求即是挑战,下面我将介绍本文解决方案,彻底解决以上问题,希望可以帮助面临同样困状的你们。
二、问题解决
2.1 安装Windows service服务
点击【是否开机自启】,安装Windows service服务,并实现服务启停。
2.2 主方法Main()主方法改写
Program.cs文件Main()有两种运行方式
①客户端方式启动,最常用的方式,相当于鼠标双击可执行程序
②服务service方式启动,采用Windows service安装,默认含有参数。
因此我们可以通过参数args区分两种方式,如下图代码所示,其中客户端启动参用“单例模式”,我这里使用了全局信号量卡控,一次只能运行一个程序客户端。
static void Main(string[] args){//ProcessExec processExec = new ProcessExec();//string aa = "";//processExec.MediaServiceStart("", out aa);if (args.Length > 0){var serviceName = Const.ct_strServiceName;var host = HostFactory.New(x =>{x.EnableShutdown();x.Service<ServiceCtrl>();x.RunAsLocalSystem();x.SetDescription(Const.ct_strProjName);x.SetDisplayName(serviceName);x.SetServiceName(serviceName);});LogHelper.Info("***********主程序运行(服务方式)**********");host.Run();}else{bool createNew;//使用用互斥量(System.Threading.Mutex)只运行一个实列using (System.Threading.Mutex mutex = new System.Threading.Mutex(true, Application.ProductName, out createNew)){if (createNew){Application.EnableVisualStyles();Application.SetCompatibleTextRenderingDefault(false);LogHelper.Info("***********主程序运行(客户端方式)**********");Application.Run(new FormMain());}else{System.Threading.Thread.Sleep(1000);System.Environment.Exit(1);}}}}
2.3 安装service服务/卸载service服务
①添加新项-->Windows服务--AutoPlayerService.cs
②右键空白处,【添加安装程序】,它会自动创建ProjectInstaller.cs文件,打开该文件有两个组件serviceProcessInstaller1、serviceInstaller1
网上大部分文章,教你们在属性中填写 服务信息,这种方式存在缺陷,常常服务不能够自动注入,需要借助第三方工具写入注册表,我教你们另外一种方式,如下图所示:
③我的方式,通过使用底层入口实现。
添加以下4行代码。
[DllImport("kernel32.dll")]public static extern int WinExec(string CmdLine, int ShowCmd);[DllImport("shell32.dll")]public static extern int ShellExecute(IntPtr hWnd, string Operation, string FileName,string Parameters, string Directory, int ShowCmd);
④实现方法
安装服务
string cmd = Application.ExecutablePath;cmd += " install";WinExec(cmd, 1);MessageBox.Show("服务安装完毕!");ShellExecute(this.Handle, "open", "Services.msc", "", "", 1);
卸载方法
ServiceStop(Const.ct_strServiceName);//停止服务string cmd = Application.ExecutablePath;cmd += " uninstall";WinExec(cmd, 1);MessageBox.Show("服务卸载完毕!");
2.4 服务启停
①创建服务类ServiceCtrl.cs ,实现接口ServiceControl。
public class ServiceCtrl : ServiceControl{MainTimer m_MainTimer;public ServiceCtrl(){try{m_MainTimer = new MainTimer(21600);//6小时检查一次m_MainTimer.InitTimer();}catch (Exception err){LogHelper.Error("初始化服务发生错误:" + err.ToString());}}/// <summary>/// 服务启动时执行/// </summary>/// <param name="c"></param>/// <returns></returns>public bool Start(Topshelf.HostControl c){try{m_MainTimer.StartTimer();string l_strMsgError = "";if (SysParameter.shutdown_tag == "T"){ProcessExec exec = new ProcessExec();exec.StartShutDown(out l_strMsgError);}return true;}catch (Exception err){LogHelper.Error("启动服务发生错误:" + err.ToString());return false;}}/// <summary>/// 服务关闭时执行/// </summary>/// <param name="c"></param>/// <returns></returns>public bool Stop(Topshelf.HostControl c){try{m_MainTimer.StopTimer();return true;}catch (Exception err){LogHelper.Error("停止服务发生错误:" + err.ToString());return false;}}}
②服务启动
//启动服务public static void ServiceStart(string serviceName){using (ServiceController control = new ServiceController(serviceName)){if (control.Status == ServiceControllerStatus.Stopped){control.Start();}}}
③服务停止
//停止服务public static void ServiceStop(string serviceName){using (ServiceController control = new ServiceController(serviceName)){if (control.Status == ServiceControllerStatus.Running){control.Stop();}}}
④判断服务是否在运行状态(可选)
//判断服务状态public static bool IsServiceRuning(string serviceName){ServiceController[] services = ServiceController.GetServices();foreach (ServiceController sc in services){if (sc.ServiceName.ToLower() == serviceName.ToLower()){if (sc.Status == ServiceControllerStatus.Running){return true;}else{return false;}}}return false;}
2.5 服务调用运行
以上基本完成了服务的开发,关于服务调用我给一个案例:我这里鼠标右键点击,自动启动服务,开始执行主方法。
//启动服务(Windows服务运行)private void BTN_ServiceRun_Click(object sender, EventArgs e){m_service.Start(null);try{BTN_ServiceRun.Enabled = false;//ToDo:采用服务式,无dos黑框//string cmd = Const.ct_strServiceName;//cmd = "net start " + cmd;//FormSet.WinExec(cmd, 1);FormSet.ServiceStart(Const.ct_strServiceName);if (DataOperation.UpdateStateType("正在运行")){GC_Main.DataSource = DataOperation.SelectData();GC_Main.RefreshDataSource();}}finally{BTN_ServiceStop.Enabled = true;}}//停止服务(Windows服务停止)private void BTN_ServiceStop_Click(object sender, EventArgs e){m_service.Stop(null);try{BTN_ServiceStop.Enabled = false;FormSet.ServiceStop(Const.ct_strServiceName);if (DataOperation.UpdateStateType("已停止")){GC_Main.DataSource = DataOperation.SelectData();GC_Main.RefreshDataSource();}}finally{BTN_ServiceRun.Enabled = true;}}
2.6 关于权限的提升
有时,你或许没有管理员权限,毕竟公司电脑嘛,每次服务开启都会有弹窗提示“权限不足”,很是厌烦,而且我们还要再次人工干预,这时你需要再程序中增加用户权限配置清单。
创建app.manifest应用程序清单文件。
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1"><assemblyIdentity version="1.0.0.0" name="MyApplication.app"/><trustInfo xmlns="urn:schemas-microsoft-com:asm.v2"><security><requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3"><!-- UAC 清单选项如果想要更改 Windows 用户帐户控制级别,请使用以下节点之一替换 requestedExecutionLevel 节点。n<requestedExecutionLevel level="asInvoker" uiAccess="false" /><requestedExecutionLevel level="requireAdministrator" uiAccess="false" /><requestedExecutionLevel level="highestAvailable" uiAccess="false" />指定 requestedExecutionLevel 元素将禁用文件和注册表虚拟化。如果你的应用程序需要此虚拟化来实现向后兼容性,则删除此元素。--><requestedExecutionLevel level="requireAdministrator" uiAccess="false" /></requestedPrivileges></security></trustInfo><compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"><application><!-- 设计此应用程序与其一起工作且已针对此应用程序进行测试的Windows 版本的列表。取消评论适当的元素,Windows 将自动选择最兼容的环境。 --><!-- Windows Vista --><!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />--><!-- Windows 7 --><!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />--><!-- Windows 8 --><!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />--><!-- Windows 8.1 --><!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />--><!-- Windows 10 --><!--<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />--></application></compatibility><!-- 指示该应用程序可以感知 DPI 且 Windows 在 DPI 较高时将不会对其进行自动缩放。Windows Presentation Foundation (WPF)应用程序自动感知 DPI,无需选择加入。选择加入此设置的 Windows 窗体应用程序(目标设定为 .NET Framework 4.6 )还应在其 app.config 中将 "EnableWindowsFormsHighDpiAutoResizing" 设置设置为 "true"。--><!--<application xmlns="urn:schemas-microsoft-com:asm.v3"><windowsSettings><dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware></windowsSettings></application>--><!-- 启用 Windows 公共控件和对话框的主题(Windows XP 和更高版本) --><!--<dependency><dependentAssembly><assemblyIdentitytype="win32"name="Microsoft.Windows.Common-Controls"version="6.0.0.0"processorArchitecture="*"publicKeyToken="6595b64144ccf1df"language="*"/></dependentAssembly></dependency>--></assembly>
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />这句代码帮你以管理员身份运行,这样就不会弹窗了。
三、资源分享
3.1 引入组件
你需要引入最强大的组件库 Geyc.Utils.dll,我已经本文介绍的方法封装了,本组件包含大量实战项目的操作类、底层库、UI控件等,定期更新补充。
倘若你在项目引用过程中,发现组件中的错误,或是库不支持,或是组件适配环境性问题,请联系我修改封装底层库文件。
链接:https://pan.baidu.com/s/1sEO9aH2_re7Xwa-WDL_V7w?pwd=l6d0
提取码:l6d0
3.2 新手使用
我已将方法封装,你也只需要几行代码就可以实现本文阐述的功能。
//判断服务是否运行
IsServiceRuning(string serviceName)//安装服务
InstallService(string serviceFilePath)//卸载服务
UninstallService(string serviceFilePath)//启动服务
ServiceStart(string serviceName)//停止服务
ServiceStop(string serviceName)