C#编写windows服务

Posted 快乐在角落里

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C#编写windows服务相关的知识,希望对你有一定的参考价值。

自动杀进程程序,由Visual Studio + C#实现

前言

1 目标方案

目标是不让小孩玩游戏,考虑了一些方案,都感觉不行,比如开机自动关机,禁用网卡,修改 host 文件,修改 iP 和 DNS。

开机自动关机这个是最简单的,只需要一个关机脚本就行了,再加入开机自启项就行了,但是这种方式就是电脑都不给用了,家长不会进PE的话很难修复,要用的时候就很着急,虽然对普通家长来说现在也没那么需要电脑。

剩下的都是一些关于网络控制的,主要是我不会写,特别是针对游戏服务器的地址,这些都不明确,我觉得写不了。还有就是完全不让上网的禁用网关,修改DNS等不太人性化,而且这种都比较好修复,不让上网还是太过分

2 确定方案

所以目标变成了小孩能上网,但是不能玩游戏,完全不能玩游戏其实也不太好办到,毕竟没指定是哪些游戏,市面上游戏那么多我根本控制不过来,扫描全盘游戏也要先知道这目录是游戏才行,感觉这个需求就不太好做了。

考虑到先后台循环杀掉一些游戏也可以,虽然没确定这是否有效,但是先做一部分出来也行

3 实现方案

搜了半天windows监控程序怎么写,Java总有控制台不太好,我要的是后台运行,然后就看了计划任务,但是这必须手动创建,用程序创建文档有点多,静不下心来看。

最后选择了windows服务,这种方式是最好的,后台程序开机自启很方便。

但是用什么实现也不好选择,一开始我是用Java写了一个 while 死循环每隔5秒钟去杀掉记事本程序,这只是测试。写完后感觉差不多了,然后百度 Java 程序怎么封装成 windows 服务,弄了好久都不行,使用的 java windows wrapper 来封装,但是我在虚拟机上测试的时候根本无法后台运行,运行一次就终止了,还有的就是一直安装不了服务,或者1067 进程意外终止,应该是 while 循环的问题,没搞定我就放弃了这种,总之就是心累,没想到连这么个简单的程序都写不好。

接着就用C++来写,写完编译完也是服务安装不上,还是和 Java 一样的问题,这些都没搞定我就放弃了。

然后就是百度搜到用C#写很容易,于是就试着写了一下,没想到可以了。

虽然 C# 的语法我不太知道,但是面向百度编程嘛,边写边测试就行了。

4 开发环境

Microsoft Visual Studio Community 2022 (64 位) - Current 版本 17.5.3

.Net Framework 4.7.2

5 测试环境

VMware 17 Pro 17.0.1 build-21139696

VMware Workstation 17 Pro - 辰羿\'S_Blog 可以从这位大佬的网站上下载,这个虚拟机安装windows的步骤网上就很多了,如果嫌麻烦的话也可以在本地机器上安装服务,主要是我之前测试自动关机的时候用windows比较方便,随时都可以改。

Windows 11 22H2

系统下载就是搜msdn下载就行了。需要注意的是我之前安装过 VMware 16,一直装不了windows 10最新版和windows11,搞了半天才知道是版本低了

实现步骤

1 项目初始化以及部分代码

基本上是跟着 基于C#实现Windows服务 来做的。创建项目的难点在于取名字,对于自己项目,取个名字我觉得太难了,跟着教程取 TestService 不太好,当然这些都能改,但是改的地方很多,改漏了也不好,想了一会,就叫 kspService (Kill Specify Process Service) 终止指定进程服务

跟着这些教程,也只是创建了windows服务,没有具体怎么实现功能的,只说了启动和停止会执行的函数,没说运行时间隔一段时间执行代码怎么做,这块百度了很久才知道怎么做。

1.1 创建新项目

1.2 配置新项目

1.3 目录结构

为了和项目名统一,将默认的 Service1 类修改成 KspService

1.4 添加安装程序

双击KspService.cs,在 KspService 设计界面右键,添加安装程序

添加完安装程序后,目录会多出一个 ProjectInstaller.cs 文件,还会出现这个文件的设计节目,界面上有两个组件,分别是 serviceProcessInstaller 和 serviceInstaller,通过右键组件然后点击属性来修改服务配置

1.5 修改 serviceProcessInstaller

可以通过操作服务运行于其中的进程的 Account 属性来设置安全性上下文。 此属性允许将服务设置为以下四种帐户类型之一:

  • User,该帐户会导致系统在安装服务时提示输入有效的用户名和密码,并在网络上单个用户指定的帐户的上下文中运行;
  • LocalService,该帐户在用作本地计算机上的非特权用户的帐户的上下文中运行,并向任意远程服务器提供匿名凭据;
  • LocalSystem,该帐户在提供广泛本地权限的帐户的上下文中运行,并向任意远程服务器提供计算机凭据;
  • NetworkService,该帐户在用作本地计算机上的非特权用户的帐户的上下文中运行,并向任意远程服务器提供计算机凭据。

我选择修改为 LocalSystem

如何:为服务指定安全上下文 - .NET Framework | Microsoft Learn

1.6 修改 serviceInstaller

StartType 说明
Manual 该服务必须在安装后手动启动
Automatic 只要重启计算机,服务就将自行启动。
Disabled 服务无法启动。

设置自动启动就行

填写描述信息和服务显示信息,DisplayName是从管理里面看服务的名称

其他更具体的说明可以参考微软文档

如何:将安装程序添加到服务应用程序 - .NET Framework | Microsoft Learn

1.7 查看默认服务功能

在 KspService.cs 设计界面右键选择查看代码,代码如下

很明显,这里面只有启动和停止的代码实现

2 编写主要功能

主要功能是每隔一段时间就杀掉游戏进程,这需要哪些函数呢

写入日志文件,简单起见不用什么框架,直接写入文件就行了

杀进程函数,使用百度搜到的杀进程函数

读取文件获取进程名称

暂停,继续功能,默认的服务有启动停止,增加一下暂停和继续来重新读取文件获取进程名称

循环任务,使用 System.Timers.Timer 来每隔一段时间执行一次杀进程函数

2.1 定义用到的参数

/* 使用了using System.Timers; */
private static Timer timer = new Timer(300000);
/* 日志目录,当前目录下的log目录 */
private static readonly string logPath = AppDomain.CurrentDomain.BaseDirectory + "//log";
/* 需要终止进程的进程字文件 */
private static readonly string blackListFile = AppDomain.CurrentDomain.BaseDirectory + "blacklist.txt";
/* 文件里的进程名集合 */
private static HashSet<string> blackListFileSet = new HashSet<string>();
/* 默认提供的进程名集合 */
private static HashSet<string> blackListInnerSet = new HashSet<string>

    "steam",
    "Steam",
    "WeGame",
    "GTA5",
    "Warframe.x64",
    "League of Legends",
    "YuanShen",
    "r5Apex",
    "r5apex",
    "dota",
    "dota2",
    "csgo"
;

2.2 写入日志

private void WriteLog(string logContent)

    if (!Directory.Exists(logPath))
    
        Directory.CreateDirectory(logPath);
    
    using (FileStream stream = new FileStream(logPath + $"\\\\log_DateTime.Now.ToString("yyyyMMdd").log", FileMode.Append))
    
        using (StreamWriter writer = new StreamWriter(stream))
        
            writer.WriteLine($"DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") logContent");
        
    

2.3 杀进程

p.ProcessName.Equals(processName) 采用Equals是为了准确,使用Contains的话,关闭主程序后,当前所有进程快照中还存在包含关键字的进程,但是因为关闭关键字主程序后那些包含关键字的进程已经终止掉了,再终止就会抛异常。

private void KillProcess(string processName)

    foreach (Process p in Process.GetProcesses())
    
        //采用Equals是为了准确, 使用Contains的话有些进程关闭会附带关闭其他带关键字的程序
        if (p.ProcessName.Equals(processName))
        
            try
            
                p.Kill();
                p.WaitForExit();
                WriteLog($"[p.ProcessName] process killed");
            
            catch (Win32Exception e)
            
                WriteLog(e.Message.ToString());
            
            catch (InvalidOperationException e)
            
                WriteLog(e.Message.ToString());
            
        
    

2.4 读取文件获取进程名称

private void ReadBlackListFile()

    if (File.Exists(blackListFile))
    
        WriteLog($"read blackListFile");
        using (StreamReader reader = new StreamReader(blackListFile))
        
            while (reader.Peek() != -1)
            
                string prcessName = reader.ReadLine();
                blackListFileSet.Add(prcessName);
                WriteLog($"add [prcessName] to blacklist");
            
        
    
    else
    
        WriteLog($"[blackListFile] not found");
    

2.5 重写暂停继续

构造函数中设置属性 CanPauseAndContinuetrue

public KspService()

    InitializeComponent();
    this.CanPauseAndContinue = true;

然后重写 OnContinue,OnPause,OnShutdown 函数

2.6 循环执行任务

先写一个支持 Timer 执行的函数,主要功能是循环两个集合,终止里面的关键字程序

不写成一个集合是因为不希望完全由文件配置来管理,而且暂停继续需要重写读取文件的内容,到时会清空集合,但是默认的不能清空,因此分开使用两个集合

private void Timer_Elapsed(object sender, ElapsedEventArgs e)

    foreach (string processName in blackListInnerSet)
    
        KillProcess(processName);
    
    foreach (string processName in blackListFileSet)
    
        KillProcess(processName);
    

2.7 KspService.cs 全量代码

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Threading.Tasks;
using System.Timers;

namespace kspService

    public partial class KspService : ServiceBase
    
        /* 使用了using System.Timers; 还可以将间隔设置为5000(5秒)在测试环境测试 */
        private static Timer timer = new Timer(5000);
        /* 日志目录,当前目录下的log目录 */
        private static readonly string logPath = AppDomain.CurrentDomain.BaseDirectory + "//log";
        /* 需要终止进程的进程字文件 */
        private static readonly string blackListFile = AppDomain.CurrentDomain.BaseDirectory + "blacklist.txt";
        /* 文件里的进程名集合 */
        private static HashSet<string> blackListFileSet = new HashSet<string>();
        /* 默认提供的进程名集合 */
        private static HashSet<string> blackListInnerSet = new HashSet<string>
        
            "steam",
            "Steam",
            "WeGame",
            "GTA5",
            "Warframe.x64",
            "League of Legends",
            "YuanShen",
            "r5Apex",
            "r5apex",
            "dota",
            "dota2",
            "csgo"
        ;
        public KspService()
        
            InitializeComponent();
            this.CanPauseAndContinue = true;
        

        protected override void OnStart(string[] args)
        
            WriteLog("KillSpecifyProcessService OnStart! ");
            ReadBlackListFile();
            timer.Elapsed += Timer_Elapsed;
            timer.Start();
        

        protected override void OnStop()
        
            timer.Stop();
            WriteLog("KillSpecifyProcessService OnStop! ");
        

        protected override void OnContinue()
        
            ReadBlackListFile();
            timer.Start();
            WriteLog("KillSpecifyProcessService OnContinue! ");
        

        protected override void OnPause()
        
            blackListFileSet.Clear();
            timer.Stop();
            WriteLog("KillSpecifyProcessService OnPause !");
        

        protected override void OnShutdown()
        
            timer.Stop();
            WriteLog("KillSpecifyProcessService OnShutdown !");
        
        private void Timer_Elapsed(object sender, ElapsedEventArgs e)
        
            foreach (string processName in blackListInnerSet)
            
                KillProcess(processName);
            
            foreach (string processName in blackListFileSet)
            
                KillProcess(processName);
            
        

        private void ReadBlackListFile()
        
            if (File.Exists(blackListFile))
            
                WriteLog($"Read blackListFile");
                using (StreamReader reader = new StreamReader(blackListFile))
                
                    while (reader.Peek() != -1)
                    
                        string prcessName = reader.ReadLine();
                        blackListFileSet.Add(prcessName);
                        WriteLog($"Add [prcessName] to the blackListFileSet");
                    
                
            
            else
            
                WriteLog($"[blackListFile] Not Found! ");
            
        

        private void WriteLog(string logContent)
        
            if (!Directory.Exists(logPath))
            
                Directory.CreateDirectory(logPath);
            
            using (FileStream stream = new FileStream(logPath + $"\\\\log_DateTime.Now.ToString("yyyyMMdd").log", FileMode.Append))
            
                using (StreamWriter writer = new StreamWriter(stream))
                
                    writer.WriteLine($"DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") logContent");
                
            
        
        private void KillProcess(string processName)
        
            foreach (Process p in Process.GetProcesses())
            
                /* 采用Equals是为了准确 */
                /* 使用Contains的话,关闭主程序后,当前所有进程快照中还存在包含关键字的进程 */
                /* 但是因为关闭关键字主程序后那些包含关键字的进程已经终止掉了,再终止就会抛异常 */
                if (p.ProcessName.Equals(processName))
                
                    try
                    
                        p.Kill();
                        p.WaitForExit();
                        WriteLog($"[p.ProcessName] Process Killed");
                    
                    catch (Win32Exception e)
                    
                        WriteLog(e.Message.ToString());
                    
                    catch (InvalidOperationException e)
                    
                        WriteLog(e.Message.ToString());
                    
                
            
        
    

执行脚本

1 创建并启动服务

主要是将程序复制到公共目录,然后通过 InstallUtil.exe 创建服务

@echo off

chcp 65001

if not exist "C:\\Users\\Public\\Documents\\StudyService\\backup" (

    md C:\\Users\\Public\\Documents\\StudyService\\backup
)

if exist "C:\\Users\\Public\\Documents\\StudyService\\kspService.exe" (

    copy /y C:\\Users\\Public\\Documents\\StudyService\\kspService.exe C:\\Users\\Public\\Documents\\StudyService\\backup

    del C:\\Users\\Public\\Documents\\StudyService\\kspService.exe

    copy %~dp0\\kspService.exe C:\\Users\\Public\\Documents\\StudyService
)else (

    copy %~dp0\\kspService.exe C:\\Users\\Public\\Documents\\StudyService
)

if not exist "C:\\Users\\Public\\Documents\\StudyService\\blacklist.txt" (

    copy %~dp0\\blacklist.txt C:\\Users\\Public\\Documents\\StudyService\\blacklist.txt
)

C:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319\\InstallUtil.exe -i C:\\Users\\Public\\Documents\\StudyService\\kspService.exe

echo 创建服务成功!

sc start kspService

echo 启动服务成功!

pause

2 更新服务 暂停与继续

@echo off

sc pause kspService

sc continue kspService

3 启动服务

@echo off

sc start kspService

4 删除服务

@echo off

sc stop kspService

C:\\Windows\\Microsoft.NET\\Framework\\v4.0.30319\\InstallUtil.exe -u C:\\Users\\Public\\Documents\\StudyService\\kspService.exe

if exist "C:\\Users\\Public\\Documents\\StudyService\\kspService.exe" (

    copy /y C:\\Users\\Public\\Documents\\StudyService\\kspService.exe C:\\Users\\Public\\Documents\\StudyService\\backup

    del C:\\Users\\Public\\Documents\\StudyService\\kspService.exe
)

5 停止服务

@echo off

sc stop kspService

6 重启服务

@echo off

sc stop kspService

sc start kspService

测试

1 Visual Studio 右键项目生成 .exe 文件

2 我的exe文件在在该路径下

D:\\admin\\dev\\source\\repos\\kspService\\kspService\\bin\\Debug

3 将生成的程序和脚本复制到虚拟机任意目录下

复制到虚拟机需要先安装 VMware Tools 并重启windows虚拟机系统

复制到任意目录

4 右键 创建服务.bat 使用管理员身份运行

如图表示创建成功

5 检查服务是否运行

打开任务管理器选择详细信息发现服务已经运行了,还可以查看计算机管理服务选项,查看服务是否启动

管理里面查看服务名显示

6 在安装目录下修改 blacklist.txt

修改blacklist.txt,是为了测试暂停继续是否有效,而且也是为了以后增加需要关闭的进程不用重新生成程序

打开我指定的安装目录

C:\\Users\\Public\\Documents\\StudyService

编辑 blacklist.txt,增加记事本程序名称,在windows10下是 notepad 在windows11下是 Notepad ,区别就是win11的记事本程序N是大写的

7 操作演示

c#开发和学习(c#编写windows服务)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】

        大家有没有想过一些程序,属于那种开机即启动的。比如说web服务器程序,mysql程序等等。但是呢,这些程序本身又没有任何的console对话框,所以这个时候就要把他们编写成windows服务程序。window服务本身也是一个exe文件,中间的一部分函数功能需要做override处理,正是这些override的函数保证了这个service可以接受外界command的输入。

        那么就说c#如何编写服务程序。事实上,不仅仅是c#,vb、f#、c、c++都可以编写服务程序。只是c#编写起来比较简单一点。本次实验的环境是win11+vs2017。

1、创建windows服务工程,

         首先,在创建工程的时候选择windows 服务即可。因为我们选择的是基础的.net framework,所以这里也做了相应的Windows Service选项。

2、确认Program.cs文件

using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.Threading.Tasks;

namespace WindowsService1

    static class Program
    
        /// <summary>
        /// 应用程序的主入口点。
        /// </summary>
        static void Main()
        
            ServiceBase[] ServicesToRun;
            ServicesToRun = new ServiceBase[]
            
                new Service1()
            ;
            ServiceBase.Run(ServicesToRun);
        
    

        这部分是服务的入口点,相当于exe的主函数main。其主要作用是把Service1加入到整个ServicesToRun中。内容比较简单。这部分代码不需要做任何修改。

3、第一个服务Service1.cs文件

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.Threading.Tasks;

namespace WindowsService1

    public partial class Service1 : ServiceBase
    
        public Service1()
        
            InitializeComponent();
        

        protected override void OnStart(string[] args)
        
            using (System.IO.StreamWriter sw = new System.IO.StreamWriter("D:\\\\info.txt", true))
            
                sw.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "Start.");
            
        

        protected override void OnStop()
        
            using (System.IO.StreamWriter sw = new System.IO.StreamWriter("D:\\\\info.txt", true))
            
                sw.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ") + "Stop.");
            
        
    

        整个类继承自ServiceBase。结构上就是一个八股文,除了在构造函数中千篇一律调用InitializeComponent之外,剩下来的OnStart和OnStop就是用户自己需要添加内容的地方。OnStart对应net start命令,OnStop对应net stop命令。

        这里为了功能说明,只是在OnStart和OnStop做了一些字符保存的动作。不出意外,在Service启动和推出的时候,会在D:多一个info.txt文件出来。

4、编译

        和正常的项目编译一样,最终会生成一个可执行文件,即WindowsService1.exe。

5、注册服务

sc create WindowsService1 binpath="C:/Users/feixiaoxing/Desktop/WindowsService1/WindowsService1/bin/Debug/WindowsService1.exe" type=own start=auto displayname=WindowsService1

6、启动服务

net start WindowsService1

7、退出服务

net stop WindowsService1

8、确认d盘是否有文件生成

        不出意外的话,应该在d盘有info.txt生成,文件内容如下所示,

2022-09-25 16:20:55 Start.
2022-09-25 16:21:10 Stop.

9、解除服务注册

sc delete “WindowsService1”

以上是关于C#编写windows服务的主要内容,如果未能解决你的问题,请参考以下文章

Visual Studio 2010 性能分析能否与用 C# 编写的 Windows 服务一起工作

C#编写windows服务

c# topshelf 编写windows service

C# 编写WCF简单的服务端与客户端

c#写windows服务

如何从 Windows 服务 C# 取消关机