C#编写windows服务

前言

1 目标方案

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

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

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

2 确定方案

所以目标变成了小孩能上网,但是不能玩游戏,完全不能玩游戏其实也不太好办到,毕竟没指定是哪些游戏,市面上游戏那么多我根本控制不过来,扫描全盘游戏也要先知道这目录是游戏才行,或许有方案可以做到,但我不知道,总之我是先做一部分出来,

方案:默认写了一些进程名然后后台每隔5分钟杀一次进程。

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,搞了半天才知道是VMware版本低了

实现步骤

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

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

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

1.1 创建新项目

image

1.2 配置新项目

image

1.3 目录结构

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

image

1.4 添加安装程序

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

image

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

1.5 修改 serviceProcessInstaller

image

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

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

我选择修改为 LocalSystem

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

1.6 修改 serviceInstaller

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

设置自动启动就行

image

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

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

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

1.7 查看默认服务功能

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

image

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

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 函数

image

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 文件

image

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

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

image

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

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

image

复制到任意目录

image

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

如图表示创建成功

image

5 检查服务是否运行

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

image

管理里面查看服务名显示

image

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

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

打开我指定的安装目录

C:\Users\Public\Documents\StudyService

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

7 操作演示

image

结尾

写完了终于可以给小孩装上了吧,通过blacklist.txt写一些游戏的进程名就可以后台自动杀进程,再修改定时时间为5分钟 (3000000) 重新生成程序再加上脚本就可以用了。
但是没想到和小孩家长沟通的时候,她说她都碰不了电脑,也不知道电脑密码 ???这真是我没想到的,无语了,你这样搞我怎么控制呢,你都操作不了电脑还想控制小孩玩电脑游戏?这事基本上就算是失败了,所以以后有这种事的话就可以直接说不用做了,真的是既要又要,这种家长既想控制小孩不玩游戏又想不要和小孩有矛盾,竟然连电脑都碰不了,这放纵是你自己作的,什么程序都没用。

虽然搞了这些做了无用功,但是还算学到点东西,这个程序我本身应该不会用,但是以后电脑上有什么自动化的操作也可以照着步骤写

但是这种编写的方式还是太简单了,很多东西都没搞懂,如果需要更复杂的功能可能就不好写了,而且占用内存也很大,这么一个小软件就占用10M多,这种想优化也完全没头绪

posted @ 2023-04-02 04:01  快乐在角落里  阅读(636)  评论(0编辑  收藏  举报