轻量级,即是服务部署器,又是服务本体,免命令行,免bat.直接部署和调试代码的东西,也就长这样了.

看这一篇文章应该可以把你构建和调试windows服务这些事儿玩儿转.

一.创建服务

1.可以打开VS,新建一个空的项目或者解决方案,在解决方案中添加服务

2.也可以直接创建服务项目

创建好服务以后,不需要像其他文章说的那样创建什么服务安装器,因为不需要使用命令行手动安装服务,所以那个东西不需要.我们会在代码中实现.

创建一个正式服务用的代码逻辑类

不建议直接在服务中 也就是 WindowsService1.cs(类似名字)的OnStart()函数内直接书写过多的代码,不方便维护,尤其是你的服务代码本身是从已经写好的项目中摘过来的,只要在OnStart函数中初始化和调用目标代码即可.这样比较好维护.

比如你的逻辑文件跟我的一样叫LogServer.cs,name整个服务文件(WindowsService1.cs,我命名为LogServerService.cs)的内容应该是这样的:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.ServiceProcess;
using System.Text;namespace LogServerService
{partial class LogServerService : ServiceBase{LogServer server;public LogServerService(){InitializeComponent();            }protected override void OnStart(string[] args){server = new LogServer();server.Start();}protected override void OnStop(){server.Stop();}}
}

这样,服务就是服务,逻辑就是逻辑.后续如果你项目变动可以把服务里面的代码很方便的摘出来.

二.构建部署器

部署器用来安装,卸载,启动,停止等对服务的操作.

命名为ServiceConfigurator

using System;
using System.Collections;
using System.Configuration.Install;
using System.Reflection;
using System.ServiceProcess;
/// <summary>/// 服务配置器/// </summary>
public class ServiceConfigurator
{#region 检查服务是否存在/// <summary>/// 检查服务是否存在/// </summary>/// <param name="serviceName"></param>/// <returns></returns>public static bool IsServiceExisted(string serviceName){ServiceController[] services = ServiceController.GetServices();foreach (ServiceController s in services){if (s.ServiceName == serviceName){return true;}}return false;}#endregion#region 启动和停止服务/// <summary>/// 启动服务/// </summary>/// <param name="serviceName">服务名称</param>/// <param name="checkStartResultTimes">服务启动命令执行后,执行多少次检查服务启动状态</param>/// <param name="perTimeCheckStartResultDelay">服务启动命令执行后,每隔多久检测一次服务是否在运行的状态</param>public static void StartService(string serviceName, int checkStartResultTimes = 100, int perTimeCheckStartResultDelay = 100){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();for (int i = 0; i < checkStartResultTimes; i++){service.Refresh();System.Threading.Thread.Sleep(perTimeCheckStartResultDelay);if (service.Status == System.ServiceProcess.ServiceControllerStatus.Running){Console.ForegroundColor = ConsoleColor.Green;Console.WriteLine("----服务启动成功----");Console.ResetColor();break;}if (i == checkStartResultTimes - 1){throw new Exception("启动服务发生错误,服务名:" + serviceName);}}}}}/// <summary>/// 停止服务/// </summary>/// <param name="serviceName">服务名称</param>public static void StopService(string serviceName){if (IsServiceExisted(serviceName)){System.ServiceProcess.ServiceController service = new System.ServiceProcess.ServiceController(serviceName);if (service.Status != System.ServiceProcess.ServiceControllerStatus.Stopped &&service.Status != System.ServiceProcess.ServiceControllerStatus.StopPending){service.Stop();for (int i = 0; i < 60; i++){service.Refresh();System.Threading.Thread.Sleep(1000);if (service.Status == System.ServiceProcess.ServiceControllerStatus.Stopped){Console.WriteLine("服务已停止");break;}if (i == 59){throw new Exception("停止服务发生错误,服务名:" + serviceName);}}}}}#endregion#region 获取服务状态/// <summary>/// 获取服务状态/// </summary>/// <param name="serviceName">服务名称</param>/// <returns></returns>public static ServiceControllerStatus GetServiceStatus(string serviceName){System.ServiceProcess.ServiceController service = new System.ServiceProcess.ServiceController(serviceName);return service.Status;}#endregion#region 安装和卸载服务/// <summary>/// 配置服务(安装或者卸载)/// </summary>/// <param name="serviceName">服务名称</param>/// <param name="serviceSrcDir">要把服务的应用程序安装到哪个文件夹下面,以后启动服务的时候从那里直接启动</param>/// <param name="displayName">服务在服务控制台中显示的名字,可以直接使用服务名称也可以指定其他的名称如中文名</param>/// <param name="desc">服务详情的描述</param>public static void InstallService(string serviceName, string serviceSrcDir, string displayName, string desc){TransactedInstaller installer = BuidInstaller(serviceName, displayName, desc);#region 复制文件到目标位置然后再进行安装string debuggingFilePath = Assembly.GetEntryAssembly().Location;string debuggingFileName = System.IO.Path.GetFileName(debuggingFilePath);string debuggingFileDir = System.IO.Path.GetDirectoryName(debuggingFilePath);if (serviceSrcDir.EndsWith("\\")){serviceSrcDir = System.IO.Path.GetDirectoryName(serviceSrcDir);}#region 没有目标文件夹自动创建if (System.IO.Directory.Exists(serviceSrcDir) == false){System.IO.Directory.CreateDirectory(serviceSrcDir);}#endregionstring installService2FilePath = string.Format("{0}\\{1}", serviceSrcDir, debuggingFileName);System.IO.File.Copy(debuggingFileName, installService2FilePath, true);#endregioninstaller.Context.Parameters["assemblypath"] = installService2FilePath;//ti.Context.Parameters["assemblypath"] = Assembly.GetEntryAssembly().Location;installer.Install(new Hashtable());Console.WriteLine("安装命令启动完成");}/// <summary>/// 卸载服务/// </summary>/// <param name="serviceName">服务名称</param>public static void UnInstallService(string serviceName){TransactedInstaller installer = BuidInstaller(serviceName);installer.Uninstall(null);Console.WriteLine("卸载命令启动完成");}#endregion#region 安装和卸载服务时,使用的安装器对象构建/// <summary>/// 构建安装器,在卸载时,直接指定第一参数即可/// </summary>/// <param name="serviceName">服务名称</param>/// <param name="displayName">服务在服务管理器中的显示名称</param>/// <param name="desc">服务相关描述</param>static TransactedInstaller BuidInstaller(string serviceName, string displayName = null, string desc = null){TransactedInstaller ti = new TransactedInstaller();ti.Installers.Add(new ServiceProcessInstaller{Account = ServiceAccount.LocalSystem});ti.Installers.Add(new ServiceInstaller{DisplayName = (displayName == null ? serviceName : displayName),ServiceName = serviceName,Description = desc == null ? "安装服务时未指定服务描述" : desc,//ServicesDependedOn = new string[] { "Netlogon" },//此服务依赖的其他服务StartType = ServiceStartMode.Automatic//运行方式});ti.Context = new InstallContext();return ti;}#endregion
}

服务和部署器都创建完成,构建一个又能自动化配置服务,又能作为服务被windows启用的Program.cs文件

三.构建部署器操控器&服务本体功能于一体的Program.Main

using System;
using System.Diagnostics;
using System.ServiceProcess;namespace LogServerService
{static class Program{#region 全局变量,根据自己的使用情况直接修改该包围块中的内容即可/// <summary>/// 服务的名称./// </summary>public static string ServiceName = "LogServerService";/// <summary>/// 服务显示名称/// </summary>public static string ServiceDisplayName = "LogServerService";/// <summary>/// 服务描述/// </summary>public static string ServiceDesc = "潮咖医疗付药机看门狗程序,用以检测付药机程序的状态及自动启动付药机程序";/// <summary>/// 服务的exe要安装到哪个文件夹下,生成了服务以后直接把文件复制到目标位置,然后安装的时候使用这个位置./// </summary>public static string ServiceInstallPath = "D:\\付药机看门狗服务";/// <summary>/// 用于运行console模式的时候,使用的服务相关处理情况日志目录,为保证日志文件不会过大,通常只在服务启动错误的时候使用./// </summary>public static string ConsoleTestLogFilePath = "c:\\windows服务部署器日志.log";#endregion/// <summary>/// 应用程序的主入口点。/// </summary>static void Main(string[] args){#region 不参数启动exe文件的时候就是启动服务的进程if (args == null || args.Length == 0){try{ServiceBase[] serviceToRun = new ServiceBase[] { new LogServerService() };ServiceBase.Run(serviceToRun);}catch (Exception ex){System.IO.File.AppendAllText(ConsoleTestLogFilePath, "\r\n服务启动失败,启动时间:" + DateTime.Now.ToString() + "\r\n错误信息:" + ex.Message);}}#endregion#region 带参数启动exe的时候就是运行安装部署器,在项目的属性->调试->启动选项->命令行参数 内 添加任意参数即可.可以根据具体的情况确认是否严格校验参数或指定不同的参数对应的功能.else{//开始标记的位置,可供goto使用StartLocation:Console.ForegroundColor = ConsoleColor.DarkGreen;Console.WriteLine("********************************************************");Console.ResetColor();Console.WriteLine("当前时间:{0}", DateTime.Now);Console.WriteLine("1:删除服务(如存在)+安装服务+启动服务");Console.WriteLine("2:安装服务");Console.WriteLine("3:卸载服务");Console.WriteLine("4:服务状态检查");Console.WriteLine("5:启动服务");Console.WriteLine("6:停止服务");Console.WriteLine("7:删除服务(使用sc delete 服务名)");Console.WriteLine("8:调试服务逻辑代码");Console.ForegroundColor = ConsoleColor.DarkGreen;Console.WriteLine("********************************************************");Console.ResetColor();ConsoleKey key = Console.ReadKey().Key;#region 按键1自动部署服务 如果存在服务,卸载,然后重新安装,安装后启动.if (key == ConsoleKey.NumPad1 || key == ConsoleKey.D1){if (ServiceConfigurator.IsServiceExisted(ServiceName)){ServiceConfigurator.UnInstallService(ServiceName);}if (!ServiceConfigurator.IsServiceExisted(ServiceName)){ServiceConfigurator.InstallService(ServiceName, ServiceInstallPath, ServiceDisplayName, ServiceDesc);}ServiceConfigurator.StartService(ServiceName);goto StartLocation;}#endregion#region 按键2的安装服务else if (key == ConsoleKey.NumPad2 || key == ConsoleKey.D2){if (!ServiceConfigurator.IsServiceExisted(ServiceName)){ServiceConfigurator.InstallService(ServiceName, ServiceInstallPath, ServiceDisplayName, ServiceDesc);}else{Console.WriteLine("\n服务已存在......");}goto StartLocation;}#endregion#region 按键3的卸载服务else if (key == ConsoleKey.NumPad3 || key == ConsoleKey.D3){if (ServiceConfigurator.IsServiceExisted(ServiceName)){ServiceConfigurator.UnInstallService(ServiceName);}else{Console.WriteLine("\n服务不存在......");}goto StartLocation;}#endregion#region 按键4的查看服务状态else if (key == ConsoleKey.NumPad4 || key == ConsoleKey.D4){if (!ServiceConfigurator.IsServiceExisted(ServiceName)){Console.WriteLine("\n服务不存在......");}else{Console.WriteLine("\n服务状态:" + ServiceConfigurator.GetServiceStatus(ServiceName).ToString());}goto StartLocation;}#endregion#region 按键5的启动服务else if (key == ConsoleKey.NumPad5 || key == ConsoleKey.D5){ServiceConfigurator.StartService(ServiceName);Console.WriteLine("执行启动后的服务状态:" + ServiceConfigurator.GetServiceStatus(ServiceName).ToString());goto StartLocation;}#endregion#region 按键6的停止服务else if (key == ConsoleKey.NumPad6 || key == ConsoleKey.D6){ServiceConfigurator.StopService(ServiceName);Console.WriteLine("执行停止后的服务状态:" + ServiceConfigurator.GetServiceStatus(ServiceName).ToString());goto StartLocation;}#endregion#region 按键7的删除服务使用scelse if (key == ConsoleKey.NumPad7 || key == ConsoleKey.D7){Console.WriteLine("正在使用sc命令删除服务......");Process p = new Process();p.StartInfo.FileName = @"C:\WINDOWS\system32\cmd.exe ";p.StartInfo.UseShellExecute = false;p.StartInfo.RedirectStandardInput = true;p.StartInfo.RedirectStandardOutput = true;p.StartInfo.RedirectStandardError = true;p.StartInfo.CreateNoWindow = true;p.Start();p.StandardInput.WriteLine(string.Format("net stop {0}", ServiceName));p.StandardInput.WriteLine(string.Format("sc delete {0}", ServiceName));p.StandardInput.WriteLine("exit");p.WaitForExit();p.Close();p.Dispose();//Process pr = new Process();//pr.StartInfo.FileName = "sc";//pr.StartInfo.Arguments = string.Format(" delete {0}", ServiceName);//pr.Start();if (ServiceConfigurator.IsServiceExisted(ServiceName) == false){Console.WriteLine("执行sc删除服务命令完成");}else{Console.WriteLine("执行sc删除服务命令失败");}goto StartLocation;}#endregion#region 按键8的调试服务逻辑代码else if (key == ConsoleKey.NumPad8 || key == ConsoleKey.D8){LogServer server = new LogServer();server.Start();goto StartLocation;}#endregion#region 其他无效的按键输入else{Console.ForegroundColor = ConsoleColor.Red;Console.WriteLine("无效的输入");Console.ResetColor();goto StartLocation;}#endregion}#endregion}}
}

主要就是这些,剩下的就是编写你的LogServer.cs 也就是正式逻辑运行的代码了.

四.构建可以用来调试的逻辑代码

我的项目是用来自动检测计算机上的某个程序的运行状态,检测到他不在线的话,就启动该程序,方案可以有很多,可以用管道(匿名和命名管道都可以),也可以用读写公共文件的方式,读写数据库的方式,读写公共内存的方式,TCP的方式,WS的方式,HTTP的方式等等,总之是让两个程序可以互相通讯,检测到被检测的程序没有心跳了,关掉现有的卡死的程序(如果存在),重新启动被检测的程序.

为了保证服务可以被调试,构建的逻辑代码中,应该检测是否被调试器所附加到了进程,如果没有被附加到进程,正常的逻辑已经开始跑了,你再去附加到进程的话,是没有办法保证代码能被调试的.所以,要在逻辑被启动了以后,检测是否被调试器附加,检测代码是在一个持续运行的循环中:

void bw_DoWork(object sender, DoWorkEventArgs e){Console.ForegroundColor = ConsoleColor.Blue;string startedMsg =string.Format("\r\n进入到服务bw_DoWork当前时间:{0}\r\n调试模式?:{1}\r\n配置文件路径:{2}\r\n日志文件路径:{3}\r\n",DateTime.Now,setting.Debugging, this.settingFilePath, this.setting.LogFilePath);Console.WriteLine(startedMsg);Console.ResetColor();System.IO.File.AppendAllText(setting.LogFilePath, startedMsg);while (bw.CancellationPending == false){//2021年12月10日10:41:12  调试的时候 只有当调试器介入了的时候,才会正常运行逻辑代码.非调试正常部署服务的时候这个要去掉.if (setting.Debugging && System.Diagnostics.Debugger.IsAttached == false){System.Threading.Thread.Sleep(17);continue;}#region 正常代码Console.ForegroundColor = ConsoleColor.Green;Console.WriteLine("此消息来自服务执行逻辑{0}", DateTime.Now);Console.ResetColor();System.Threading.Thread.Sleep(5000);System.IO.File.AppendAllText(setting.LogFilePath, "进入到服务正常代码" + DateTime.Now.ToString() + "\r\n");continue;#endregion}//throw new NotImplementedException();}

这里我使用到了Setting中的Debugging控制是否在调试,如果不是在调试的时候,服务正常启动,还仍然等待调试器介入,那这个服务的逻辑代码将永不会被执行.

bw是一个backgroundworker,bw_dowork是他的工作运行函数.

详细的逻辑可以研究一下我的代码,从Program.Main函数开始一步一步调试就知道了.

五.每次更新代码后的调试或重新安装服务

服务一旦被安装以后,代码做了修改,需要重新的安装或者停止再启动,为了方便,我们启动调试以后,直接让程序进入到部署器,这里在项目的调试模式中加了参数,只要有参数,main函数检测到有参数运行就是调试.如果没有参数,就是正常的服务被windows加载和启动.设置如下:

右键项目,属性

目前我的代码中 只要有参数就可以 随便指定 你可以根据你的项目做修改.

启动调试以后,出来的窗口如下:

由于构建服务到安装服务都是不可跟踪调试的,所以单独加了一个调试代码的功能,就是在Program的main中,检测到按键8就new一个LogServer的对象然后执行Start()模拟服务被启动的时候的代码,这样每次改了代码进行调试的时候直接按一下8就会走服务的正式代码.这也是把服务文件WindowsService1.cs和主要服务代码LogServer单独摘出来的原因.

六.服务安装并运行起来以后的调试

windows服务项目在开发的时候不像winform或者控制台应用程序那样可以直接调试,需要附加到进程调试(也可以使用上面提到的按8键进入主体代码更方便调试).

附加到进程的方法是,启动了上面的黑框框也就是vs调试器运行起来,你更新了服务之类的完事儿了以后,不需要关闭当前框框,也可以是在vs空闲的时候 点击菜单栏的 调试->附加到进程->勾选"显示所有用户的进程",找到服务名称.exe 我这个是LogServerService.exe ,然后点附加

这时候就会走到你要调试的代码中了.也就是LogServer的do_work函数内的while内的正常代码部分了.也就是说,如果你设定在调试服务,那么服务就会等着你的vs把他附加到调试器

七.补充

由于服务实际上是要我们安装到目标机器,而不是就在vs的debug目录下就当服务程序的实际位置了.所以在安装服务的时候.为大家考虑到了服务的实际执行路径,根据你自己的设置配置一下program中的参数,安装服务的时候会自动把服务的exe程序复制到指定的目录,然后安装服务的时候使用指定目录的文件进行服务安装.就相当于把服务单独摘出来了,复制到目标机 在cmd或者创建快捷方式的方式和 启动那个exe(比如我的是LogServerService.exe)带着参数,就可以进行部署了就想开头的图片那样,安装后会自动启动.

比如cmd这样操作:

LogServerService.exe -xxxx 启动就是部署器,不带参数启动,就是个服务,也可以直接启动就相当于一个控制台应用程序(运行正常服务逻辑内容)

或者新建一个该文件的快捷方式 然后修改属性为:

运行这个快捷方式也可以直接启动部署器部分了

如果exe有依赖一些dll,还需要你自己做一些修改,不然服务启动不了.

八.最后上代码

是希望各位能知其然知其所以然,对基础的东西有一个比较深入和全面的了解的话,再怎么复杂的东西也不是问题,因为世界上任何复杂的东西都是由简单的东西构造起来的.

该项目中的文件如果直接运行,就给你安装了LogServerService的服务,如果相应的想改名之类的,直接改类名称让vs自动修改引用,然后改一些Program.cs中的static的变量就行了.

逻辑的部分直接修改LogServer.cs中的bw_dowork中标记为#region正式代码 #endregion中的相关部分就可以了.

#region 正常代码Console.ForegroundColor = ConsoleColor.Green;Console.WriteLine("此消息来自服务执行逻辑{0}", DateTime.Now);Console.ResetColor();System.Threading.Thread.Sleep(5000);System.IO.File.AppendAllText(setting.LogFilePath, "进入到服务正常代码" + DateTime.Now.ToString() + "\r\n");continue;#endregion

LogServerService.rar-桌面系统文档类资源-CSDN下载如何使用VS+C#创建,优雅自动化的安装(不使用命令行),调试,更新Windows服务更多下载资源、学习资料请访问CSDN下载频道.https://download.csdn.net/download/Afterwards_/60198450

如何使用VS+C#创建,优雅自动化的安装(不使用命令行),调试,更新Windows服务相关推荐

  1. window环境下创建Flask项目需要安装常见模块命令

    安装Flask环境 pip install flask==0.10.1 使用命令行操作 pip install flask-script 创建表单 pip install flask-wtf 操作数据 ...

  2. win服务器创建文件夹命令行,怎样在windows的cmd命令行下创建删除文件和文件夹...

    在window下我们往往通过'右键=>新建'命令来创建文件和文件夹,但有时会遇到 以点开头的文件,比如.log,这种文件用鼠标新建是新建不了的,这时我们可以在DOS下用命令行来创建.所以在这里我 ...

  3. 如何自动化安装字体(命令行批量)

    之前只知道AddFontResourceEx安装字体,但昨天才发现这货只是在重启系统前有效,并没有真正安装到系统中,详见msdn. so,怎么才能批量安装字体呢?全选字体右键这种手工操作,虽然也比较方 ...

  4. Dotnet Core 优雅的命令行实现

    介绍一个命令行的实现库,可以优雅而简单的实现命令行应用.   前言 控制台应用 Console,在我们开发中用处很多.小到一个简单的功能测试,或一组不需要复杂 UI 的工具类应用,大到后端的服务,都会 ...

  5. widnows命令行常用命令使用 和 windows创建文件,写内容到文件

    widnows命令行常用命令使用 和 windows创建文件,写内容到文件 1 查看windows命令行常用命令的使用文档 1.1 查看windows中命令行命令 1.2 windows查看某个指定命 ...

  6. python windows服务_Python创建Windows服务

    首先让我们开始安装Python for Windows扩展: c:test>pip install pywin32 完成后,让我们编写该基类,您的Windows服务将是该基类的子类. ''' S ...

  7. 演练:在组件设计器中创建 Windows 服务应用程序

    http://msdn.microsoft.com/zh-cn/library/zt39148a(v=vs.80).aspx 演练:在组件设计器中创建 Windows 服务应用程序 .NET Fram ...

  8. 使用.NET Core创建Windows服务 - 使用.NET Core工作器方式

    原文:Creating Windows Services In .NET Core – Part 3 – The ".NET Core Worker" Way 作者:Dotnet ...

  9. 如何优雅的利用Windows服务来部署ASP.NET Core程序

    上一篇文章中我给大家讲述了五种部署ASP.NET Core网站的方法,其中有一种方式是通过Windows服务来进行部署,这样既可以做到开启自启动,又不会因为iis的反向代理而损失部分性能.但是美中不足 ...

最新文章

  1. 微软牛津计划-语音转文本-文本转语音代码和实现
  2. 关系型数据库表结构的两个设计技巧
  3. 贪心算法-03哈夫曼编码问题
  4. php mysql 平均分_平均评级计算mysql php
  5. 一秒带你穿越!AI 修复百年前北京影像,路边摊、剃头匠太真实了
  6. unity开发文档_Unity以赞助人身份加入Blender开发基金
  7. lotus Domino调用webservice
  8. 系统JNI调用和使用
  9. 网易校招linux面试题,网易校招真题——下厨房
  10. 合并石子(三种方法)
  11. 沐阳Git笔记02Git工作区与缓存区
  12. 妙用switch 计算几天是 一年的第几天
  13. (P4-P8)多线程,线程池
  14. 从马云和任正非看创业的试错过程
  15. 用IOS手机看epub小说,哪些阅读器APP更好用?
  16. 【通信原理课程设计】基于MATLAB/Simulink的2ASK数字带通传输系统建模与仿真
  17. 数据预处理之数据离散化
  18. 三维重建公开数据集整理(MVS篇)
  19. Office激活后还是弹激活窗口解决办法
  20. Macom 在 IMS 2022

热门文章

  1. H60-RS232自动售货机主板规格书
  2. 【ROS-Navigation】—— Astar路径规划算法解析
  3. 真实的90后创业者是怎样的状态?
  4. “超越融合 异筑信创”,AntDB数据库携手超云等生态伙伴共建信创大生态
  5. 超级签名-原理/机制/技术细节-完全解析
  6. SharePoint 2010 IT Professional--巧用Calendar List
  7. System.Diagnostics.Stopwatch
  8. 依照测试用例分类(按功能)的结果生成对应的universe文件
  9. ACM-ICPC 2018 南京赛区网络预赛 I. Skr (马拉车+字符串hash/回文自动机)
  10. Elasticsearch:什么是相关性