在我们日常工作中,不免需要编写windows后台服务程序。在这过程中,你是否有考虑过这几个问题呢?

  • 服务调试很麻烦?
  • 双击不能启动服务?
  • 如何安装服务?
  • 如何使用管理员权限启动?

针对以上问题,今天我们就来聊聊它们!

调试服务

在调试服务前,我们肯定需要先新建一个服务的解决方案。打开 Visual Studio ,“新建项目” -> “Windows 服务” 点击确定。如下图所示:

新建windows服务工程后,我们就可以在service1.cs文件里面添加我们的逻辑代码(跟普通写程序一样,这里就不做赘述)。 我们可以尝试随便写一些逻辑代码,并准备开始调试。 调试的方式有几种:

  1. 通过附加进程和断点调试

    因为服务一般情况下是不能通过双击启动的,必须得先把服务安装好后才能调试
    这个时候,OnStart/OnStop方法在执行的时候,我们没有办法去调试我们的代码,整个调试过程会变得很困难。 这个时候,我们可能会考虑采用第二种方案(即下面的这种方案)。

  2. 通过Debugger.Launch()

    在这种方案下,我们只需要将 Debugger.Launch() 这段代码插入到期望中断的代码位置,服务在启动的时候,便会把启动调试器的窗口弹出来,我们选择一个调试器就可以进去调试了。 如下图所示:


    随后的调试方法跟普通调试一样。不过这种方式也存在比较明显的缺点:每次调试一次都会弹出 打开调试器窗口 ,费时费力,体检较差。
    接下来看看第三种方法。

  3. 通过直接启动方式调试

    在种模式下,我们首先需要了解一点,程序即时控制台,服务也不例外。在使用这种模式之前,我们首先需要将我们之前添加的服务程序设置成控制台程序。 具体步骤:选择我们需要设置的项目,右键查看属性,将输出类型改成“控制台应用程序”

    到这里,还没有结束,因为我们之前添加项目是以服务的方式添加,所以我们得在 Program.cs 文件里面修改主函数的输入参数。

     主函数:static void Main(string[] args)
    
    

    到这里,我们的基本设置就完成了,接下来就需要它的这种模式,开启控制台启动逻辑了。 我们首先需要判断当前是否是用户交互模式,即如果是鼠标双击启动,则为用户交互模式;如果是服务启动,则不是用户交互模式。

     判断当前是否是用户交互模式: Environment.UserInteractive
    

    通过它,我们就可以针对两种不同的模式分别写我们的代码。下面是个完整的例子:

         static void Main(string[] args)
         {
             if (Environment.UserInteractive)
             {
                 //以控制台模式启动                
                 //服务好控制台共用的代码,初始化服务
                 MainService mainService = new MainService(); 
                 Console.WriteLine("正在启动服务");
                 //这行代码,是服务OnStart的执行代码,自行修改。
                 mainService.Start();                        
                 Console.WriteLine("服务已启动,输入 exit 退出服务");
                 while (Console.ReadLine() != "exit")
                 {
                     //do something
                 }
                 //这行代码,是服务的OnStop的执行代码
                 mainService.Stop();                         
             }
             else
             {
                 //以服务模式启动
                 ServiceBase[] ServicesToRun;
                 ServicesToRun = new ServiceBase[]
                 {
                     new MainService()
                 };
                 ServiceBase.Run(ServicesToRun);
             }
         }
    

    使用这种方法,我们可以通过控制台的输出来监控我们的程序运行是否正常,用来做测试也非常方便,可以随便打断点,跟普通的控制台程序的使用一摸一样。

安装服务

当我们开发完服务程序后,会面临这样一样问题:我应该怎么安装我的服务呢?如果给用户更好的体验?接下来,我们针对这一问题,简单的聊一聊! 服务安装的方式有多种,常见的方法有这么两种

  1. 使用cmd命令进行安装。

    这种模式下,我们需要调起cmd.exe,输入命令行来完成我们的安装。 例如:sc create “{服务名}” binpath =”{服务启动的程序绝对路径}”。后面也可以跟随参数,标记是否是自动启动等。也可以通过sc delete “{服务名}” 这种方式来删除服务。 具体细节,这里就不做赘述,感兴趣可以自行查阅,我们主要讨论第二种方法。

  2. 使用C#代码来完成安装。

    使用cmd命名的模式安装服务,难免会影响体验,也不好控制,虽然我们也可以在我们的代码里面去调用cmd来完成服务的安装,相对会显得很麻烦。所以,我们可以选择接下来讨论这种方式来安装我们的服务。 双击打开我们刚才创建的服务,以上例子是 MainService.cs,我们会看到一个设计面板。

    鼠标右键,选择添加安装程序,如下图所示:

    创建安装程序后,这个时候会在项目中自动生成一个 ProjectInstaller.cs 文件,这个文件里面就包含了我们的安装信息。 在这个文件里面,我们可以看到两个安装组件:serviceInstallerserviceProcessInstall
    在serviceProcessInstall安装组件中,我们主要关注它的访问权限设置,其他的默认就好。

    我们通常将访问权限设置为LocalSystem ,权限最大(注意:设置为这个权限,安装的时候需要以管理员权限运行)。 具体区别可以参见:https://docs.microsoft.com/zh-cn/windows/desktop/Services/localsystem-account。

    serviceProcessInstall设置完成后,我们接下来看下serviceInstaller,选中它,查看属性面板:

    我们会发现他有更多的设置项,主要有:ServiceName (服务名)DisplayName (服务显示名)StartType (启动类型), DelayedAutoStart (是否延时启动),ServicesDependedOn (服务运行所依赖的其它服务列表),Description (服务描述)。

    通常我们主要前面三个就可以了,根据自己需要设置好就行了。
    特别注意:服务安装程序设置好了后,它并不会自己进行安装,它需要由其它的程序来调用它,才能真正完成安装。

    为了测试,我们可以新建一个测试的demo,用来安装这个服务(比如说在程序启动的时候,调用安装服务逻辑)。这里贴出关键代码。


        /// <summary>
        /// 安装服务
        /// </summary>
        /// <param name="serviceFilePath">服务程序的绝对路径</param>
        public static void Install(string serviceFilePath)
        {
            using (AssemblyInstaller installer = new AssemblyInstaller())
            {
                installer.UseNewContext = true;
                installer.Path = serviceFilePath;
                IDictionary savedState = new Hashtable();
                installer.Install(savedState);
                installer.Commit(savedState);
            }
        }

        /// <summary>
        /// 卸载服务
        /// </summary>
        /// <param name="serviceFilePath">服务程序的绝对路径</param>
        public static void Uninstall(string serviceFilePath)
        {
            using (AssemblyInstaller installer = new AssemblyInstaller())
            {
                installer.UseNewContext = true;
                installer.Path = serviceFilePath;
                installer.Uninstall(null);
            }
        }

只需要在我们的程序中调用以上代码,就可以完成对服务的安装。

提升权限

前面我们有提及到,要安装服务,需要有足够的权限才能成功。不用担心,我们有办法来给我们的程序做提权。 我们找到刚才新建的demo程序,在项目的属性中,找到“安全性”选项卡,点击“启用ClickOnce安全设置”

这个时候项目中会自动生成一个 app.manifest 的配置文件 ,我们打开它:

我们可以看到红线部分的配置项,只需要将 level=”asInvoker” 修改成 requireAdministrator ,再保存。

设置完成后,再回到“安全性”选项卡,将“启用ClickOnce安全设置”取消选中。最后再编译后,我们看下我们的应用程序图标会发生变化,应用程序图标上多了个“盾牌”小图标,能看到这个,那么恭喜你成功了。

最后,可以运行程序愉快的玩耍了。


本文会经常更新,请阅读原文: https://huchengv5.github.io//post/Windows%E5%90%8E%E5%8F%B0%E6%9C%8D%E5%8A%A1%E8%B0%83%E8%AF%95%E4%B8%8E%E5%AE%89%E8%A3%85.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

知识共享许可协议 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名hucheng(包含链接: https://huchengv5.github.io/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系