├── .gitattributes
├── ProcessGuard
├── cola.ico
├── App.config
├── Properties
│ ├── Settings.settings
│ ├── Settings.Designer.cs
│ ├── AssemblyInfo.cs
│ ├── app.manifest
│ ├── Resources.Designer.cs
│ └── Resources.resx
├── packages.config
├── Utils.cs
├── App.xaml
├── App.xaml.cs
├── Resources
│ ├── StringResources.zh-CN.xaml
│ └── StringResources.xaml
├── ProcessGuard.csproj
├── MainWindowViewModel.cs
├── MainWindow.xaml.cs
└── MainWindow.xaml
├── ProcessGuardService
├── ProcessGuard
│ └── Resources
│ │ └── ProcessGuardService.exe
├── App.config
├── ServiceInstaller.cs
├── Program.cs
├── MainService.Designer.cs
├── Properties
│ └── AssemblyInfo.cs
├── ServiceInstaller.Designer.cs
├── ProcessGuardService.csproj
├── ServiceInstaller.resx
└── MainService.cs
├── ProcessGuard.Common
├── packages.config
├── Models
│ ├── GlobalConfig.cs
│ ├── ConfigItemWithProcessId.cs
│ └── ConfigItem.cs
├── Constants.cs
├── Utility
│ ├── StringUtil.cs
│ ├── Extensions.cs
│ ├── ConfigHelper.cs
│ └── ApplicationLoader.cs
├── Core
│ └── ViewModelBase.cs
├── Properties
│ └── AssemblyInfo.cs
└── ProcessGuard.Common.csproj
├── ProcessGuard.CommonTests
├── packages.config
├── Utility
│ └── ApplicationLoaderTests.cs
├── Properties
│ └── AssemblyInfo.cs
└── ProcessGuard.CommonTests.csproj
├── LICENSE
├── README-zh.md
├── README.md
├── ProcessGuard.sln
└── .gitignore
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/ProcessGuard/cola.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KamenRiderKuuga/ProcessGuard/HEAD/ProcessGuard/cola.ico
--------------------------------------------------------------------------------
/ProcessGuardService/ProcessGuard/Resources/ProcessGuardService.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KamenRiderKuuga/ProcessGuard/HEAD/ProcessGuardService/ProcessGuard/Resources/ProcessGuardService.exe
--------------------------------------------------------------------------------
/ProcessGuard/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/ProcessGuardService/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/ProcessGuard/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/ProcessGuard.Common/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/ProcessGuard.CommonTests/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/ProcessGuardService/ServiceInstaller.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 |
3 | namespace ProcessGuardService
4 | {
5 | [RunInstaller(true)]
6 | public partial class ServiceInstaller : System.Configuration.Install.Installer
7 | {
8 | public ServiceInstaller()
9 | {
10 | InitializeComponent();
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/ProcessGuard/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/ProcessGuard.Common/Models/GlobalConfig.cs:
--------------------------------------------------------------------------------
1 | using ProcessGuard.Common.Core;
2 |
3 | namespace ProcessGuard.Common.Models
4 | {
5 | ///
6 | /// Global configurations of application
7 | ///
8 | public class GlobalConfig : ViewModelBase
9 | {
10 | private string _language;
11 |
12 | ///
13 | /// language setting of application
14 | ///
15 | public string Language
16 | {
17 | get { return _language; }
18 | set { this.Set(ref this._language, value); }
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/ProcessGuardService/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.ServiceProcess;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace ProcessGuardService
9 | {
10 | static class Program
11 | {
12 | ///
13 | /// 应用程序的主入口点。
14 | ///
15 | static void Main()
16 | {
17 | ServiceBase[] ServicesToRun;
18 | ServicesToRun = new ServiceBase[]
19 | {
20 | new MainService()
21 | };
22 | ServiceBase.Run(ServicesToRun);
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/ProcessGuard.Common/Constants.cs:
--------------------------------------------------------------------------------
1 | namespace ProcessGuard.Common
2 | {
3 | public class Constants
4 | {
5 | ///
6 | /// 配置文件名称
7 | ///
8 | public const string CONFIG_FILE_NAME = "GuardianConfig.json";
9 |
10 | ///
11 | /// The global configurations file name
12 | ///
13 | public const string GLOBAL_CONFIG_FILE_NAME = "GlobalConfig.json";
14 |
15 | ///
16 | /// 服务进程名
17 | ///
18 | public const string PROCESS_GUARD_SERVICE = "ProcessGuardService";
19 |
20 | ///
21 | /// 服务文件名
22 | ///
23 | public const string FILE_GUARD_SERVICE = "ProcessGuardService.exe";
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/ProcessGuard.Common/Utility/StringUtil.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Security.Cryptography;
4 |
5 | namespace ProcessGuard.Common.Utility
6 | {
7 | public class StringUtil
8 | {
9 | ///
10 | /// 计算文件的MD5值
11 | ///
12 | /// 文件路径
13 | ///
14 | public static string CalculateMD5(string filename)
15 | {
16 | using (var md5 = MD5.Create())
17 | {
18 | using (var stream = File.OpenRead(filename))
19 | {
20 | var hash = md5.ComputeHash(stream);
21 | return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
22 | }
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/ProcessGuard/Utils.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace ProcessGuard
8 | {
9 | public class Utils
10 | {
11 | ///
12 | /// 弹出文件选择框,并返回选中的文件名
13 | ///
14 | ///
15 | public static string GetFileNameByFileDialog()
16 | {
17 | string filename = "";
18 |
19 | Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
20 | dlg.Filter = "可执行文件 (*.exe)|*.exe;";
21 |
22 | bool? result = dlg.ShowDialog();
23 |
24 | if (result == true)
25 | {
26 | filename = dlg.FileName;
27 | }
28 |
29 | return filename;
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/ProcessGuard.Common/Core/ViewModelBase.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.ComponentModel;
3 | using System.Runtime.CompilerServices;
4 |
5 | namespace ProcessGuard.Common.Core
6 | {
7 | public class ViewModelBase : INotifyPropertyChanged
8 | {
9 | public event PropertyChangedEventHandler PropertyChanged;
10 |
11 | protected bool Set(ref T field, T newValue = default(T), [CallerMemberName] string propertyName = null)
12 | {
13 | if (EqualityComparer.Default.Equals(field, newValue))
14 | {
15 | return false;
16 | }
17 |
18 | field = newValue;
19 |
20 | this.OnPropertyChanged(propertyName);
21 |
22 | return true;
23 | }
24 |
25 | protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
26 | {
27 | this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/ProcessGuard.Common/Models/ConfigItemWithProcessId.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace ProcessGuard.Common.Models
4 | {
5 | public class ConfigItemWithProcessId : ConfigItem
6 | {
7 | ///
8 | /// The processId of started process
9 | ///
10 | public int ProcessId { get; set; }
11 |
12 | ///
13 | /// The config change type
14 | ///
15 | public ChangeType ChangeType { get; set; }
16 | }
17 |
18 | ///
19 | /// The config change type
20 | ///
21 | [Flags]
22 | public enum ChangeType
23 | {
24 | None = 0,
25 |
26 | ///
27 | /// The process should be started
28 | ///
29 | Start = 1,
30 |
31 | ///
32 | /// The process should be stopped
33 | ///
34 | Stop = 2,
35 |
36 | ///
37 | /// The process should be removed from the guard list
38 | ///
39 | Remove = 4,
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/ProcessGuard/App.xaml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/ProcessGuard.CommonTests/Utility/ApplicationLoaderTests.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.VisualStudio.TestTools.UnitTesting;
2 |
3 | namespace ProcessGuard.Common.Utility.Tests
4 | {
5 | [TestClass()]
6 | public class ApplicationLoaderTests
7 | {
8 | [TestMethod()]
9 | public void RunCmdAndGetOutputTest()
10 | {
11 | string cmd = @"%SystemRoot%\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe ProcessGuardService.exe
12 | Net Start ProcessGuardService
13 | sc config ProcessGuardService = auto";
14 | ApplicationLoader.RunCmdAndGetOutput(cmd, out var output,out var error);
15 | Assert.AreNotEqual(output, "");
16 | Assert.AreNotEqual(error, "");
17 | }
18 |
19 | [TestMethod()]
20 | public void StartAppTest()
21 | {
22 | string filePath = @"E:\Program Files\Everything-1.4.1.1005.x64\Everything.exe";
23 | ApplicationLoader.StartProcessInSession0(filePath,System.IO.Path.GetDirectoryName(filePath), out var _);
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/ProcessGuardService/MainService.Designer.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace ProcessGuardService
3 | {
4 | partial class MainService
5 | {
6 | ///
7 | /// 必需的设计器变量。
8 | ///
9 | private System.ComponentModel.IContainer components = null;
10 |
11 | ///
12 | /// 清理所有正在使用的资源。
13 | ///
14 | /// 如果应释放托管资源,为 true;否则为 false。
15 | protected override void Dispose(bool disposing)
16 | {
17 | if (disposing && (components != null))
18 | {
19 | components.Dispose();
20 | }
21 | base.Dispose(disposing);
22 | }
23 |
24 | #region 组件设计器生成的代码
25 |
26 | ///
27 | /// 设计器支持所需的方法 - 不要修改
28 | /// 使用代码编辑器修改此方法的内容。
29 | ///
30 | private void InitializeComponent()
31 | {
32 | components = new System.ComponentModel.Container();
33 | this.ServiceName = "MainService";
34 | }
35 |
36 | #endregion
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 HANABI
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/ProcessGuard.Common/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // 有关程序集的一般信息由以下
6 | // 控制。更改这些特性值可修改
7 | // 与程序集关联的信息。
8 | [assembly: AssemblyTitle("ProcessGuard.Common")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("ProcessGuard.Common")]
13 | [assembly: AssemblyCopyright("Copyright © 2021")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // 将 ComVisible 设置为 false 会使此程序集中的类型
18 | //对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
19 | //请将此类型的 ComVisible 特性设置为 true。
20 | [assembly: ComVisible(false)]
21 |
22 | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
23 | [assembly: Guid("88ba32a1-946e-425c-a2d7-8a4345ad90ef")]
24 |
25 | // 程序集的版本信息由下列四个值组成:
26 | //
27 | // 主版本
28 | // 次版本
29 | // 生成号
30 | // 修订号
31 | //
32 | //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值
33 | //通过使用 "*",如下所示:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/ProcessGuardService/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // 有关程序集的一般信息由以下
6 | // 控制。更改这些特性值可修改
7 | // 与程序集关联的信息。
8 | [assembly: AssemblyTitle("ProcessGuardService")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("ProcessGuardService")]
13 | [assembly: AssemblyCopyright("Copyright © 2021")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // 将 ComVisible 设置为 false 会使此程序集中的类型
18 | //对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
19 | //请将此类型的 ComVisible 特性设置为 true。
20 | [assembly: ComVisible(false)]
21 |
22 | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
23 | [assembly: Guid("cee80e8a-5787-4103-a639-a0d236fd1190")]
24 |
25 | // 程序集的版本信息由下列四个值组成:
26 | //
27 | // 主版本
28 | // 次版本
29 | // 生成号
30 | // 修订号
31 | //
32 | //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值
33 | //通过使用 "*",如下所示:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/ProcessGuard.CommonTests/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // 有关程序集的一般信息由以下
6 | // 控制。更改这些特性值可修改
7 | // 与程序集关联的信息。
8 | [assembly: AssemblyTitle("ProcessGuard.CommonTests")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("ProcessGuard.CommonTests")]
13 | [assembly: AssemblyCopyright("Copyright © 2021")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | //将 ComVisible 设置为 false 将使此程序集中的类型
18 | //对 COM 组件不可见。 如果需要从 COM 访问此程序集中的类型,
19 | //请将此类型的 ComVisible 特性设置为 true。
20 | [assembly: ComVisible(false)]
21 |
22 | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
23 | [assembly: Guid("6061213a-b8d9-489c-b5b7-c8dd3de87c44")]
24 |
25 | // 程序集的版本信息由下列四个值组成:
26 | //
27 | // 主版本
28 | // 次版本
29 | // 生成号
30 | // 修订号
31 | //
32 | //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值
33 | //通过使用 "*",如下所示:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/README-zh.md:
--------------------------------------------------------------------------------
1 | # ProcessGuard
2 |
3 | ## [English](README.md)
4 |
5 | 关于实现的具体依据请查看[通过Windows系统服务守护进程的运行](https://lambda.cyou/posts/Tips-5/)
6 |
7 | 得益于能从Windows系统服务中启动任意进程的能力,围绕这个能力,此程序可以用来:
8 |
9 | 1. 从Windows系统服务启动带交互界面的程序,并在其被关闭后再次将其启动
10 | 2. 将一些程序配置为开机自启
11 | 3. 对于控制台类型的应用,包括但不限于`java`,`dotnet`,`node`等类型的程序,可以通过无窗应用的启动方式,将其像系统服务一样部署在Windows系统上
12 |
13 | ## ⚙配置界面
14 |
15 | > 从[Release](https://github.com/KamenRiderKuuga/ProcessGuard/releases)页面可以直接下载程序,启动程序后看到的界面只是一个配置界面,可以在这里配置要守护的进程,启动服务之后可以随时开启或关闭此配置界面
16 |
17 | 
18 |
19 | 注:只有在界面点击启动服务,守护服务正常运行后,配置才能生效
20 |
21 |
22 |
23 | ## 📕配置说明
24 |
25 | **进程名称:** 用于标识当前配置项的名称,仅用于界面显示
26 |
27 | **完整路径:** 可执行文件的完整路径
28 |
29 | **启动参数:** 也就是平时启动应用时携带的参数,如不需要携带参数可忽略此项
30 |
31 | **仅启动一次:** 在守护服务运行期间只启动一次,用于只需要配置开机启动的场景
32 |
33 | **最小化:** 对于有交互界面的程序,配置此项可以让其启动时最小化到任务栏,而不是和平时一样弹出界面
34 |
35 | **无窗应用:** 用于控制台类型的应用,对于这些没有交互界面的应用,勾选此项可以让其启动时完全不显示控制台,而作为系统服务启动
36 |
37 |
38 |
39 | ## 配置示例
40 |
41 | ### 带交互界面的程序
42 |
43 | 
44 |
45 |
46 |
47 | ### Spring Boot项目
48 |
49 | 
50 |
--------------------------------------------------------------------------------
/ProcessGuard/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 |
12 | namespace ProcessGuard.Properties
13 | {
14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
17 | {
18 |
19 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
20 |
21 | public static Settings Default
22 | {
23 | get
24 | {
25 | return defaultInstance;
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/ProcessGuard/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using ProcessGuard.Common;
2 | using System;
3 | using System.Diagnostics;
4 | using System.Runtime.InteropServices;
5 | using System.Threading;
6 | using System.Windows;
7 |
8 | namespace ProcessGuard
9 | {
10 | ///
11 | /// App.xaml 的交互逻辑
12 | ///
13 | public partial class App : Application
14 | {
15 | private static Mutex _mutex = null;
16 |
17 | [DllImport("user32.dll")]
18 | static extern bool SetForegroundWindow(IntPtr hWnd);
19 |
20 | [DllImport("user32.dll")]
21 | static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
22 |
23 | protected override void OnStartup(StartupEventArgs e)
24 | {
25 | _mutex = new Mutex(true, Constants.PROCESS_GUARD_SERVICE, out var createdNew);
26 |
27 | if (!createdNew)
28 | {
29 | using (var currentProcess = Process.GetCurrentProcess())
30 | {
31 | foreach (var process in Process.GetProcessesByName(currentProcess.ProcessName))
32 | {
33 | if (process.Id != currentProcess.Id)
34 | {
35 | ShowWindow(process.MainWindowHandle, 9);
36 | SetForegroundWindow(process.MainWindowHandle);
37 | }
38 | process.Dispose();
39 | }
40 | }
41 | //app is already running! Exiting the application
42 | Shutdown();
43 | }
44 |
45 | base.OnStartup(e);
46 | }
47 |
48 | protected override void OnExit(ExitEventArgs e)
49 | {
50 | _mutex.Dispose();
51 | base.OnExit(e);
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/ProcessGuard/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Resources;
3 | using System.Runtime.CompilerServices;
4 | using System.Runtime.InteropServices;
5 | using System.Windows;
6 |
7 | // 有关程序集的一般信息由以下
8 | // 控制。更改这些特性值可修改
9 | // 与程序集关联的信息。
10 | [assembly: AssemblyTitle("ProcessGuard")]
11 | [assembly: AssemblyDescription("")]
12 | [assembly: AssemblyConfiguration("")]
13 | [assembly: AssemblyCompany("")]
14 | [assembly: AssemblyProduct("ProcessGuard")]
15 | [assembly: AssemblyCopyright("Copyright © 2021")]
16 | [assembly: AssemblyTrademark("")]
17 | [assembly: AssemblyCulture("")]
18 |
19 | // 将 ComVisible 设置为 false 会使此程序集中的类型
20 | //对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
21 | //请将此类型的 ComVisible 特性设置为 true。
22 | [assembly: ComVisible(false)]
23 |
24 | //若要开始生成可本地化的应用程序,请设置
25 | //.csproj 文件中的 CultureYouAreCodingWith
26 | //例如,如果您在源文件中使用的是美国英语,
27 | //使用的是美国英语,请将 设置为 en-US。 然后取消
28 | //对以下 NeutralResourceLanguage 特性的注释。 更新
29 | //以下行中的“en-US”以匹配项目文件中的 UICulture 设置。
30 |
31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
32 |
33 |
34 | [assembly: ThemeInfo(
35 | ResourceDictionaryLocation.None, //主题特定资源词典所处位置
36 | //(未在页面中找到资源时使用,
37 | //或应用程序资源字典中找到时使用)
38 | ResourceDictionaryLocation.SourceAssembly //常规资源词典所处位置
39 | //(未在页面中找到资源时使用,
40 | //、应用程序或任何主题专用资源字典中找到时使用)
41 | )]
42 |
43 |
44 | // 程序集的版本信息由下列四个值组成:
45 | //
46 | // 主版本
47 | // 次版本
48 | // 生成号
49 | // 修订号
50 | //
51 | //可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值
52 | //通过使用 "*",如下所示:
53 | // [assembly: AssemblyVersion("1.0.*")]
54 | [assembly: AssemblyVersion("1.0.0.0")]
55 | [assembly: AssemblyFileVersion("1.0.0.0")]
56 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ProcessGuard
2 |
3 | ## [中文文档](README-zh.md)
4 |
5 | About how it works:
6 |
7 | [Subverting Vista UAC in Both 32 and 64 bit Architectures By Pero Matić](https://www.codeproject.com/Articles/35773/Subverting-Vista-UAC-in-Both-32-and-64-bit-Archite)
8 |
9 | [Application Compatibility - Session 0 Isolation By Craig Marcho](https://techcommunity.microsoft.com/t5/ask-the-performance-team/application-compatibility-session-0-isolation/ba-p/372361)
10 |
11 | With the ability to start a process from the Windows service, we can:
12 |
13 | 1. Start a program with an interactive interface from a Windows service and restart it after it has been closed
14 | 2. Set some programs to start automatically at boot
15 | 3. For console applications, including but not limited to `java`, `dotnet`, `node`, etc., they can be deployed on Windows systems as no window like Windows services
16 |
17 | ## ⚙Configuration Interface
18 |
19 | > You can download the program directly from the [Release](https://github.com/KamenRiderKuuga/ProcessGuard/releases) page. The interface you see is just a configuration interface for configuring the processes to be guarded here. After starting the service, you can close the configuration interface
20 |
21 | 
22 |
23 | Note: The configuration can take effect only after the service is started
24 |
25 |
26 |
27 | ## 📕Configuration Items
28 |
29 | **Process Name:** The name used to identify the current configuration item, only used for interface display
30 |
31 | **Full Path:** Full path to executable
32 |
33 | **Parameters:** The parameters be carried when starting the application, ignore this if you do not need any parameters
34 |
35 | **Start Once:** Only started once during the service running
36 |
37 | **Minimize:** For programs with an interactive interface, it can make it minimized to the taskbar when it starts, instead of popping up the interface as usual
38 |
39 | **NoWindow:** For console applications, enabling this item can make it start like a windows service, without displaying the console at all
40 |
41 |
42 |
43 | ## Configuration Example
44 |
45 | ### An Interactive Program
46 |
47 | 
48 |
49 |
50 |
51 | ### A Spring Boot Program
52 |
53 | 
54 |
--------------------------------------------------------------------------------
/ProcessGuard.Common/Utility/Extensions.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using System;
3 | using System.Diagnostics;
4 | using System.Runtime.InteropServices;
5 | using System.Text;
6 |
7 | namespace ProcessGuard.Common.Utility
8 | {
9 | public static class Extensions
10 | {
11 |
12 | [DllImport("Kernel32.dll")]
13 | private static extern bool QueryFullProcessImageName([In] IntPtr hProcess, [In] uint dwFlags, [Out] StringBuilder lpExeName, [In, Out] ref uint lpdwSize);
14 |
15 | ///
16 | /// 获取进程的完整可执行文件名称
17 | ///
18 | /// 进程实体
19 | /// 缓冲区大小,用于存放文件名称
20 | ///
21 | public static string GetMainModuleFileName(this Process process, int buffer = 1024)
22 | {
23 | var fileNameBuilder = new StringBuilder(buffer);
24 | uint bufferLength = (uint)fileNameBuilder.Capacity + 1;
25 | return QueryFullProcessImageName(process.Handle, 0, fileNameBuilder, ref bufferLength) ?
26 | fileNameBuilder.ToString() :
27 | null;
28 | }
29 |
30 | ///
31 | /// 对象序列化
32 | ///
33 | /// 源对象
34 | /// json串
35 | public static string Serialize(this object input)
36 | {
37 | try
38 | {
39 | return input == null ? string.Empty : JsonConvert.SerializeObject(input);
40 | }
41 | catch (Exception ex)
42 | {
43 | throw new Exception("Json序列化出现错误!", ex);
44 | }
45 | }
46 |
47 | ///
48 | /// 反序列化
49 | ///
50 | /// 指定实体类型
51 | /// json串
52 | /// 实体对象
53 | public static T DeserializeObject(this string input)
54 | {
55 | try
56 | {
57 | return input == string.Empty ? default(T) : JsonConvert.DeserializeObject(input);
58 | }
59 | catch (Exception ex)
60 | {
61 | throw new Exception("Json反序列化出出现错误!", ex);
62 | }
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/ProcessGuardService/ServiceInstaller.Designer.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace ProcessGuardService
3 | {
4 | partial class ServiceInstaller
5 | {
6 | ///
7 | /// 必需的设计器变量。
8 | ///
9 | private System.ComponentModel.IContainer components = null;
10 |
11 | ///
12 | /// 清理所有正在使用的资源。
13 | ///
14 | /// 如果应释放托管资源,为 true;否则为 false。
15 | protected override void Dispose(bool disposing)
16 | {
17 | if (disposing && (components != null))
18 | {
19 | components.Dispose();
20 | }
21 | base.Dispose(disposing);
22 | }
23 |
24 | #region 组件设计器生成的代码
25 |
26 | ///
27 | /// 设计器支持所需的方法 - 不要修改
28 | /// 使用代码编辑器修改此方法的内容。
29 | ///
30 | private void InitializeComponent()
31 | {
32 | this.ProcessGuardServiceProcessInstaller = new System.ServiceProcess.ServiceProcessInstaller();
33 | this.ProcessGuardServiceInstaller = new System.ServiceProcess.ServiceInstaller();
34 | //
35 | // ProcessGuardServiceProcessInstaller
36 | //
37 | this.ProcessGuardServiceProcessInstaller.Account = System.ServiceProcess.ServiceAccount.LocalSystem;
38 | this.ProcessGuardServiceProcessInstaller.Password = null;
39 | this.ProcessGuardServiceProcessInstaller.Username = null;
40 | //
41 | // ProcessGuardServiceInstaller
42 | //
43 | this.ProcessGuardServiceInstaller.DisplayName = "进程守护服务";
44 | this.ProcessGuardServiceInstaller.ServiceName = "ProcessGuardService";
45 | this.ProcessGuardServiceInstaller.StartType = System.ServiceProcess.ServiceStartMode.Automatic;
46 | //
47 | // ServiceInstaller
48 | //
49 | this.Installers.AddRange(new System.Configuration.Install.Installer[] {
50 | this.ProcessGuardServiceInstaller,
51 | this.ProcessGuardServiceProcessInstaller});
52 |
53 | }
54 |
55 | #endregion
56 |
57 | private System.ServiceProcess.ServiceProcessInstaller ProcessGuardServiceProcessInstaller;
58 | private System.ServiceProcess.ServiceInstaller ProcessGuardServiceInstaller;
59 | }
60 | }
--------------------------------------------------------------------------------
/ProcessGuard.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.31005.135
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProcessGuard", "ProcessGuard\ProcessGuard.csproj", "{FFBA2E0C-5E8F-46A6-9549-83E03EF1B77D}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProcessGuard.Common", "ProcessGuard.Common\ProcessGuard.Common.csproj", "{88BA32A1-946E-425C-A2D7-8A4345AD90EF}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProcessGuardService", "ProcessGuardService\ProcessGuardService.csproj", "{CEE80E8A-5787-4103-A639-A0D236FD1190}"
11 | EndProject
12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProcessGuard.CommonTests", "ProcessGuard.CommonTests\ProcessGuard.CommonTests.csproj", "{6061213A-B8D9-489C-B5B7-C8DD3DE87C44}"
13 | EndProject
14 | Global
15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
16 | Debug|Any CPU = Debug|Any CPU
17 | Release|Any CPU = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
20 | {FFBA2E0C-5E8F-46A6-9549-83E03EF1B77D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {FFBA2E0C-5E8F-46A6-9549-83E03EF1B77D}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {FFBA2E0C-5E8F-46A6-9549-83E03EF1B77D}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {FFBA2E0C-5E8F-46A6-9549-83E03EF1B77D}.Release|Any CPU.Build.0 = Release|Any CPU
24 | {88BA32A1-946E-425C-A2D7-8A4345AD90EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25 | {88BA32A1-946E-425C-A2D7-8A4345AD90EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
26 | {88BA32A1-946E-425C-A2D7-8A4345AD90EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
27 | {88BA32A1-946E-425C-A2D7-8A4345AD90EF}.Release|Any CPU.Build.0 = Release|Any CPU
28 | {CEE80E8A-5787-4103-A639-A0D236FD1190}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29 | {CEE80E8A-5787-4103-A639-A0D236FD1190}.Debug|Any CPU.Build.0 = Debug|Any CPU
30 | {CEE80E8A-5787-4103-A639-A0D236FD1190}.Release|Any CPU.ActiveCfg = Release|Any CPU
31 | {CEE80E8A-5787-4103-A639-A0D236FD1190}.Release|Any CPU.Build.0 = Release|Any CPU
32 | {6061213A-B8D9-489C-B5B7-C8DD3DE87C44}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {6061213A-B8D9-489C-B5B7-C8DD3DE87C44}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {6061213A-B8D9-489C-B5B7-C8DD3DE87C44}.Release|Any CPU.ActiveCfg = Release|Any CPU
35 | {6061213A-B8D9-489C-B5B7-C8DD3DE87C44}.Release|Any CPU.Build.0 = Release|Any CPU
36 | EndGlobalSection
37 | GlobalSection(SolutionProperties) = preSolution
38 | HideSolutionNode = FALSE
39 | EndGlobalSection
40 | GlobalSection(ExtensibilityGlobals) = postSolution
41 | SolutionGuid = {D0990B59-C358-4648-A72F-AAF0A36E8F36}
42 | EndGlobalSection
43 | EndGlobal
44 |
--------------------------------------------------------------------------------
/ProcessGuard/Resources/StringResources.zh-CN.xaml:
--------------------------------------------------------------------------------
1 |
4 | 进程守护服务配置
5 | 全局配置
6 | 可执行文件完整路径
7 | 填写或者选取完整路径
8 | 使用文件资源管理器进行选取
9 | 进程名称
10 | 仅启动一次
11 | 最小化
12 | 无窗应用
13 | 启动参数
14 | 可为空
15 | 确认添加
16 | 确认修改
17 | 取消
18 | 语言
19 | 好了
20 | 完整路径
21 | 停止守护并关闭进程
22 | 启动进程并开始守护
23 | 启动守护服务
24 | 停止守护服务
25 | 卸载守护服务
26 | 添加一条配置项
27 | 删除选中的配置项
28 | 注意
29 | 确认删除选中的配置项吗?
30 | 确认卸载服务吗?
31 | 即将继续
32 | 已暂停
33 | 即将暂停
34 | 运行中
35 | 正在启动
36 | 已停止
37 | 正在停止
38 | 未安装
39 | 服务状态({0})
40 | 是的
41 | 参数是必填的!
42 | 文件路径无效!
43 |
44 |
--------------------------------------------------------------------------------
/ProcessGuard.Common/Models/ConfigItem.cs:
--------------------------------------------------------------------------------
1 | using ProcessGuard.Common.Core;
2 |
3 | namespace ProcessGuard.Common.Models
4 | {
5 | ///
6 | /// 要守护的进程配置项实体
7 | ///
8 | public class ConfigItem : ViewModelBase
9 | {
10 | ///
11 | /// To identify a unique configuration row
12 | ///
13 | public string Id { get; set; }
14 |
15 | private string _exeFullPath;
16 |
17 | ///
18 | /// EXE可执行文件的完整路径
19 | ///
20 | public string EXEFullPath
21 | {
22 | get { return _exeFullPath; }
23 | set { this.Set(ref this._exeFullPath, value); }
24 | }
25 |
26 | private string _startupParams;
27 |
28 | ///
29 | /// 启动参数
30 | ///
31 | public string StartupParams
32 | {
33 | get { return _startupParams; }
34 | set { this.Set(ref this._startupParams, value); }
35 | }
36 |
37 | private string _processName;
38 |
39 | ///
40 | /// 要守护的进程名称
41 | ///
42 | public string ProcessName
43 | {
44 | get { return _processName; }
45 | set
46 | {
47 | this. Set(ref this._processName, value);
48 | if (true)
49 | {
50 |
51 | }
52 | }
53 | }
54 |
55 | private bool _onlyOpenOnce;
56 |
57 | ///
58 | /// 仅启动一次(用于开机自启)
59 | ///
60 | public bool OnlyOpenOnce
61 | {
62 | get { return _onlyOpenOnce; }
63 | set { this.Set(ref this._onlyOpenOnce, value); }
64 | }
65 |
66 | private bool _minimize;
67 |
68 | ///
69 | /// 是否以最小化方式启动
70 | ///
71 | public bool Minimize
72 | {
73 | get { return _minimize; }
74 | set { this.Set(ref this._minimize, value); }
75 | }
76 |
77 | private bool _noWindow;
78 |
79 | ///
80 | /// 启动时是否不显示Window
81 | ///
82 | public bool NoWindow
83 | {
84 | get { return _noWindow; }
85 | set { this.Set(ref this._noWindow, value); }
86 | }
87 |
88 | private bool _started;
89 |
90 | ///
91 | /// Indicates the state of the start/stop button in the config row,
92 | /// if the value is true and the Service is running, the process will be guarded
93 | ///
94 | public bool Started
95 | {
96 | get { return _started; }
97 | set { this.Set(ref this._started, value); }
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/ProcessGuard/Properties/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
47 |
54 |
55 |
69 |
--------------------------------------------------------------------------------
/ProcessGuard/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // 此代码由工具生成。
4 | // 运行时版本:4.0.30319.42000
5 | //
6 | // 对此文件的更改可能会导致不正确的行为,并且如果
7 | // 重新生成代码,这些更改将会丢失。
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace ProcessGuard.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// 一个强类型的资源类,用于查找本地化的字符串等。
17 | ///
18 | // 此类是由 StronglyTypedResourceBuilder
19 | // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
20 | // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
21 | // (以 /str 作为命令选项),或重新生成 VS 项目。
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// 返回此类使用的缓存的 ResourceManager 实例。
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ProcessGuard.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// 重写当前线程的 CurrentUICulture 属性,对
51 | /// 使用此强类型资源类的所有资源查找执行重写。
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 |
63 | ///
64 | /// 查找 System.Byte[] 类型的本地化资源。
65 | ///
66 | internal static byte[] ProcessGuardService {
67 | get {
68 | object obj = ResourceManager.GetObject("ProcessGuardService", resourceCulture);
69 | return ((byte[])(obj));
70 | }
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/ProcessGuard.Common/Utility/ConfigHelper.cs:
--------------------------------------------------------------------------------
1 | using ProcessGuard.Common.Models;
2 | using System.Collections.ObjectModel;
3 | using System.IO;
4 | using System;
5 |
6 | namespace ProcessGuard.Common.Utility
7 | {
8 | ///
9 | /// 用于操作配置文件的类
10 | ///
11 | public class ConfigHelper
12 | {
13 | ///
14 | /// 初始化配置配置,如果没有文件,则创建,如果已经有文件,则从里面初始化配置内容
15 | ///
16 | public static ObservableCollection LoadConfigFile()
17 | {
18 | var configFilePath = GetAppDataFilePath(Constants.CONFIG_FILE_NAME);
19 |
20 | if (!File.Exists(configFilePath))
21 | {
22 | File.WriteAllText(configFilePath, "");
23 | }
24 |
25 | var json = File.ReadAllText(configFilePath);
26 | var result = json.DeserializeObject>();
27 |
28 | return result ?? new ObservableCollection();
29 | }
30 |
31 | ///
32 | /// Initialize the global config from the configuration file
33 | ///
34 | public static GlobalConfig LoadGlobalConfigFile()
35 | {
36 | var configFilePath = GetAppDataFilePath(Constants.GLOBAL_CONFIG_FILE_NAME);
37 |
38 | if (!File.Exists(configFilePath))
39 | {
40 | File.WriteAllText(configFilePath, "");
41 | }
42 |
43 | var json = File.ReadAllText(configFilePath);
44 | var result = json.DeserializeObject();
45 |
46 | return result ?? new GlobalConfig();
47 | }
48 |
49 | ///
50 | /// 保存配置项
51 | ///
52 | /// 配置项实体集合
53 | public static void SaveConfigs(ObservableCollection configItems)
54 | {
55 | var configFilePath = GetAppDataFilePath(Constants.CONFIG_FILE_NAME);
56 | File.WriteAllText(configFilePath, configItems.Serialize());
57 | }
58 |
59 | ///
60 | /// Save global configurations
61 | ///
62 | /// The global config instance
63 | public static void SaveGlobalConfigs(GlobalConfig config)
64 | {
65 | var configFilePath = GetAppDataFilePath(Constants.GLOBAL_CONFIG_FILE_NAME);
66 | File.WriteAllText(configFilePath, config.Serialize());
67 | }
68 |
69 | ///
70 | /// 获取在AppData目录的文件位置
71 | ///
72 | /// 文件名
73 | ///
74 | public static string GetAppDataFilePath(string fileName)
75 | {
76 | var folderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), Constants.PROCESS_GUARD_SERVICE);
77 |
78 | if (!Directory.Exists(folderPath))
79 | {
80 | Directory.CreateDirectory(folderPath);
81 | }
82 |
83 | return Path.Combine(folderPath, fileName);
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/ProcessGuard/Resources/StringResources.xaml:
--------------------------------------------------------------------------------
1 |
4 | Service Configuration
5 | Global Configuration
6 | Executeable File
7 | Input or select the full path
8 | Select using file explorer
9 | Process Name
10 | Start Once
11 | Minimize
12 | No Window
13 | Parameters
14 | Can be left blank
15 | Confirm Add
16 | Confirm
17 | Cancel
18 | Language
19 | OK
20 | Full Path
21 | Stop the guard and close the process
22 | Open the process and start the guard
23 | Start the guard service
24 | Stop the guard service
25 | Uninstall the guard service
26 | Add a configuration item
27 | Delete the selected configuration item
28 | Warning
29 | Do you want to delete the selected item?
30 | Do you want to uninstall the service?
31 | Continue Pending
32 | Paused
33 | Pause Pending
34 | Continue Pending
35 | Start Pending
36 | Stopped
37 | Stop Pending
38 | Not Installed
39 | Service Status({0})
40 | Yes
41 | Value is required!
42 | Invalid file path!
43 |
44 |
--------------------------------------------------------------------------------
/ProcessGuard.Common/ProcessGuard.Common.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Debug
7 | AnyCPU
8 | {88BA32A1-946E-425C-A2D7-8A4345AD90EF}
9 | Library
10 | Properties
11 | ProcessGuard.Common
12 | ProcessGuard.Common
13 | v4.6.1
14 | 512
15 | true
16 |
17 |
18 |
19 |
20 | true
21 | full
22 | false
23 | bin\Debug\
24 | DEBUG;TRACE
25 | prompt
26 | 4
27 |
28 |
29 | pdbonly
30 | true
31 | bin\Release\
32 | TRACE
33 | prompt
34 | 4
35 |
36 |
37 |
38 | ..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | 这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/ProcessGuardService/ProcessGuardService.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {CEE80E8A-5787-4103-A639-A0D236FD1190}
8 | WinExe
9 | ProcessGuardService
10 | ProcessGuardService
11 | v4.6.1
12 | 512
13 | true
14 | true
15 |
16 |
17 | AnyCPU
18 | true
19 | full
20 | false
21 | bin\Debug\
22 | DEBUG;TRACE
23 | prompt
24 | 4
25 |
26 |
27 | AnyCPU
28 | pdbonly
29 | true
30 | bin\Release\
31 | TRACE
32 | prompt
33 | 4
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | Component
51 |
52 |
53 | MainService.cs
54 |
55 |
56 |
57 |
58 | Component
59 |
60 |
61 | ServiceInstaller.cs
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | ServiceInstaller.cs
70 |
71 |
72 |
73 |
74 | {88ba32a1-946e-425c-a2d7-8a4345ad90ef}
75 | ProcessGuard.Common
76 |
77 |
78 |
79 |
80 | set ILMergeConsolePath=$(SolutionDir)packages\ILRepack.2.0.18\tools\ILRepack.exe&
81 | %25ILMergeConsolePath%25 /out:$(SolutionDir)\ProcessGuard\Resources\ProcessGuardService.exe ProcessGuardService.exe Newtonsoft.Json.dll ProcessGuard.Common.dll&
82 | del $(SolutionDir)\ProcessGuard\Resources\*.pdb&
83 | del $(SolutionDir)\ProcessGuard\Resources\*.config
84 |
85 |
--------------------------------------------------------------------------------
/ProcessGuard/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 |
122 | ..\Resources\ProcessGuardService.exe;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
123 |
124 |
--------------------------------------------------------------------------------
/ProcessGuard.CommonTests/ProcessGuard.CommonTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {6061213A-B8D9-489C-B5B7-C8DD3DE87C44}
8 | Library
9 | Properties
10 | ProcessGuard.CommonTests
11 | ProcessGuard.CommonTests
12 | v4.6.1
13 | 512
14 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
15 | 10.0
16 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
17 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages
18 | False
19 | UnitTest
20 |
21 |
22 |
23 |
24 |
25 | true
26 | full
27 | false
28 | bin\Debug\
29 | DEBUG;TRACE
30 | prompt
31 | 4
32 |
33 |
34 | pdbonly
35 | true
36 | bin\Release\
37 | TRACE
38 | prompt
39 | 4
40 |
41 |
42 |
43 | ..\packages\MSTest.TestFramework.2.1.1\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll
44 |
45 |
46 | ..\packages\MSTest.TestFramework.2.1.1\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | {88BA32A1-946E-425C-A2D7-8A4345AD90EF}
68 | ProcessGuard.Common
69 |
70 |
71 |
72 |
73 |
74 |
75 | False
76 |
77 |
78 | False
79 |
80 |
81 | False
82 |
83 |
84 | False
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 | 这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。
94 |
95 |
96 |
97 |
98 |
99 |
106 |
--------------------------------------------------------------------------------
/ProcessGuardService/ServiceInstaller.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 | 27, 19
122 |
123 |
124 | 289, 19
125 |
126 |
127 | False
128 |
129 |
--------------------------------------------------------------------------------
/ProcessGuard/ProcessGuard.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {FFBA2E0C-5E8F-46A6-9549-83E03EF1B77D}
8 | WinExe
9 | ProcessGuard
10 | ProcessGuard
11 | v4.6.1
12 | 512
13 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
14 | 4
15 | true
16 | true
17 |
18 |
19 | AnyCPU
20 | true
21 | full
22 | false
23 | bin\Debug\
24 | DEBUG;TRACE
25 | prompt
26 | 4
27 |
28 |
29 | AnyCPU
30 | pdbonly
31 | true
32 | bin\Release\
33 | TRACE
34 | prompt
35 | 4
36 |
37 |
38 | false
39 |
40 |
41 | LocalIntranet
42 |
43 |
44 | false
45 |
46 |
47 | Properties\app.manifest
48 |
49 |
50 | cola.ico
51 |
52 |
53 |
54 | ..\packages\ControlzEx.4.4.0\lib\net45\ControlzEx.dll
55 |
56 |
57 | ..\packages\MahApps.Metro.2.4.3\lib\net46\MahApps.Metro.dll
58 |
59 |
60 | ..\packages\MahApps.Metro.IconPacks.Modern.4.8.0\lib\net46\MahApps.Metro.IconPacks.Core.dll
61 |
62 |
63 | ..\packages\MahApps.Metro.IconPacks.Modern.4.8.0\lib\net46\MahApps.Metro.IconPacks.Modern.dll
64 |
65 |
66 | ..\packages\Microsoft.Xaml.Behaviors.Wpf.1.1.19\lib\net45\Microsoft.Xaml.Behaviors.dll
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | 4.0
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | MSBuild:Compile
91 | Designer
92 |
93 |
94 |
95 | MSBuild:Compile
96 | Designer
97 |
98 |
99 | App.xaml
100 | Code
101 |
102 |
103 | MainWindow.xaml
104 | Code
105 |
106 |
107 | Designer
108 | MSBuild:Compile
109 |
110 |
111 | Designer
112 | MSBuild:Compile
113 |
114 |
115 |
116 |
117 |
118 | Code
119 |
120 |
121 | True
122 | True
123 | Resources.resx
124 |
125 |
126 | True
127 | Settings.settings
128 | True
129 |
130 |
131 | ResXFileCodeGenerator
132 | Resources.Designer.cs
133 | Designer
134 |
135 |
136 |
137 |
138 | SettingsSingleFileGenerator
139 | Settings.Designer.cs
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 | {88ba32a1-946e-425c-a2d7-8a4345ad90ef}
148 | ProcessGuard.Common
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 | rd /s /q "output"&
158 | mkdir output&
159 | set ILMergeConsolePath=$(SolutionDir)packages\ILRepack.2.0.18\tools\ILRepack.exe&
160 | %25ILMergeConsolePath%25 /out:output\ProcessGuard.exe ^
161 | ProcessGuard.exe ^
162 | Microsoft.Xaml.Behaviors.dll ^
163 | ControlzEx.dll ^
164 | MahApps.Metro.dll ^
165 | Newtonsoft.Json.dll ^
166 | ProcessGuard.Common.dll ^
167 | MahApps.Metro.IconPacks.Core.dll ^
168 | MahApps.Metro.IconPacks.Modern.dll&
169 | del output\*.pdb&
170 | del output\*.config
171 |
172 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Ll]og/
33 | [Ll]ogs/
34 |
35 | # Visual Studio 2015/2017 cache/options directory
36 | .vs/
37 | # Uncomment if you have tasks that create the project's static files in wwwroot
38 | #wwwroot/
39 |
40 | # Visual Studio 2017 auto generated files
41 | Generated\ Files/
42 |
43 | # MSTest test Results
44 | [Tt]est[Rr]esult*/
45 | [Bb]uild[Ll]og.*
46 |
47 | # NUnit
48 | *.VisualState.xml
49 | TestResult.xml
50 | nunit-*.xml
51 |
52 | # Build Results of an ATL Project
53 | [Dd]ebugPS/
54 | [Rr]eleasePS/
55 | dlldata.c
56 |
57 | # Benchmark Results
58 | BenchmarkDotNet.Artifacts/
59 |
60 | # .NET Core
61 | project.lock.json
62 | project.fragment.lock.json
63 | artifacts/
64 |
65 | # ASP.NET Scaffolding
66 | ScaffoldingReadMe.txt
67 |
68 | # StyleCop
69 | StyleCopReport.xml
70 |
71 | # Files built by Visual Studio
72 | *_i.c
73 | *_p.c
74 | *_h.h
75 | *.ilk
76 | *.meta
77 | *.obj
78 | *.iobj
79 | *.pch
80 | *.pdb
81 | *.ipdb
82 | *.pgc
83 | *.pgd
84 | *.rsp
85 | *.sbr
86 | *.tlb
87 | *.tli
88 | *.tlh
89 | *.tmp
90 | *.tmp_proj
91 | *_wpftmp.csproj
92 | *.log
93 | *.vspscc
94 | *.vssscc
95 | .builds
96 | *.pidb
97 | *.svclog
98 | *.scc
99 |
100 | # Chutzpah Test files
101 | _Chutzpah*
102 |
103 | # Visual C++ cache files
104 | ipch/
105 | *.aps
106 | *.ncb
107 | *.opendb
108 | *.opensdf
109 | *.sdf
110 | *.cachefile
111 | *.VC.db
112 | *.VC.VC.opendb
113 |
114 | # Visual Studio profiler
115 | *.psess
116 | *.vsp
117 | *.vspx
118 | *.sap
119 |
120 | # Visual Studio Trace Files
121 | *.e2e
122 |
123 | # TFS 2012 Local Workspace
124 | $tf/
125 |
126 | # Guidance Automation Toolkit
127 | *.gpState
128 |
129 | # ReSharper is a .NET coding add-in
130 | _ReSharper*/
131 | *.[Rr]e[Ss]harper
132 | *.DotSettings.user
133 |
134 | # TeamCity is a build add-in
135 | _TeamCity*
136 |
137 | # DotCover is a Code Coverage Tool
138 | *.dotCover
139 |
140 | # AxoCover is a Code Coverage Tool
141 | .axoCover/*
142 | !.axoCover/settings.json
143 |
144 | # Coverlet is a free, cross platform Code Coverage Tool
145 | coverage*.json
146 | coverage*.xml
147 | coverage*.info
148 |
149 | # Visual Studio code coverage results
150 | *.coverage
151 | *.coveragexml
152 |
153 | # NCrunch
154 | _NCrunch_*
155 | .*crunch*.local.xml
156 | nCrunchTemp_*
157 |
158 | # MightyMoose
159 | *.mm.*
160 | AutoTest.Net/
161 |
162 | # Web workbench (sass)
163 | .sass-cache/
164 |
165 | # Installshield output folder
166 | [Ee]xpress/
167 |
168 | # DocProject is a documentation generator add-in
169 | DocProject/buildhelp/
170 | DocProject/Help/*.HxT
171 | DocProject/Help/*.HxC
172 | DocProject/Help/*.hhc
173 | DocProject/Help/*.hhk
174 | DocProject/Help/*.hhp
175 | DocProject/Help/Html2
176 | DocProject/Help/html
177 |
178 | # Click-Once directory
179 | publish/
180 |
181 | # Publish Web Output
182 | *.[Pp]ublish.xml
183 | *.azurePubxml
184 | # Note: Comment the next line if you want to checkin your web deploy settings,
185 | # but database connection strings (with potential passwords) will be unencrypted
186 | *.pubxml
187 | *.publishproj
188 |
189 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
190 | # checkin your Azure Web App publish settings, but sensitive information contained
191 | # in these scripts will be unencrypted
192 | PublishScripts/
193 |
194 | # NuGet Packages
195 | *.nupkg
196 | # NuGet Symbol Packages
197 | *.snupkg
198 | # The packages folder can be ignored because of Package Restore
199 | **/[Pp]ackages/*
200 | # except build/, which is used as an MSBuild target.
201 | !**/[Pp]ackages/build/
202 | # Uncomment if necessary however generally it will be regenerated when needed
203 | #!**/[Pp]ackages/repositories.config
204 | # NuGet v3's project.json files produces more ignorable files
205 | *.nuget.props
206 | *.nuget.targets
207 |
208 | # Microsoft Azure Build Output
209 | csx/
210 | *.build.csdef
211 |
212 | # Microsoft Azure Emulator
213 | ecf/
214 | rcf/
215 |
216 | # Windows Store app package directories and files
217 | AppPackages/
218 | BundleArtifacts/
219 | Package.StoreAssociation.xml
220 | _pkginfo.txt
221 | *.appx
222 | *.appxbundle
223 | *.appxupload
224 |
225 | # Visual Studio cache files
226 | # files ending in .cache can be ignored
227 | *.[Cc]ache
228 | # but keep track of directories ending in .cache
229 | !?*.[Cc]ache/
230 |
231 | # Others
232 | ClientBin/
233 | ~$*
234 | *~
235 | *.dbmdl
236 | *.dbproj.schemaview
237 | *.jfm
238 | *.pfx
239 | *.publishsettings
240 | orleans.codegen.cs
241 |
242 | # Including strong name files can present a security risk
243 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
244 | #*.snk
245 |
246 | # Since there are multiple workflows, uncomment next line to ignore bower_components
247 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
248 | #bower_components/
249 |
250 | # RIA/Silverlight projects
251 | Generated_Code/
252 |
253 | # Backup & report files from converting an old project file
254 | # to a newer Visual Studio version. Backup files are not needed,
255 | # because we have git ;-)
256 | _UpgradeReport_Files/
257 | Backup*/
258 | UpgradeLog*.XML
259 | UpgradeLog*.htm
260 | ServiceFabricBackup/
261 | *.rptproj.bak
262 |
263 | # SQL Server files
264 | *.mdf
265 | *.ldf
266 | *.ndf
267 |
268 | # Business Intelligence projects
269 | *.rdl.data
270 | *.bim.layout
271 | *.bim_*.settings
272 | *.rptproj.rsuser
273 | *- [Bb]ackup.rdl
274 | *- [Bb]ackup ([0-9]).rdl
275 | *- [Bb]ackup ([0-9][0-9]).rdl
276 |
277 | # Microsoft Fakes
278 | FakesAssemblies/
279 |
280 | # GhostDoc plugin setting file
281 | *.GhostDoc.xml
282 |
283 | # Node.js Tools for Visual Studio
284 | .ntvs_analysis.dat
285 | node_modules/
286 |
287 | # Visual Studio 6 build log
288 | *.plg
289 |
290 | # Visual Studio 6 workspace options file
291 | *.opt
292 |
293 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
294 | *.vbw
295 |
296 | # Visual Studio LightSwitch build output
297 | **/*.HTMLClient/GeneratedArtifacts
298 | **/*.DesktopClient/GeneratedArtifacts
299 | **/*.DesktopClient/ModelManifest.xml
300 | **/*.Server/GeneratedArtifacts
301 | **/*.Server/ModelManifest.xml
302 | _Pvt_Extensions
303 |
304 | # Paket dependency manager
305 | .paket/paket.exe
306 | paket-files/
307 |
308 | # FAKE - F# Make
309 | .fake/
310 |
311 | # CodeRush personal settings
312 | .cr/personal
313 |
314 | # Python Tools for Visual Studio (PTVS)
315 | __pycache__/
316 | *.pyc
317 |
318 | # Cake - Uncomment if you are using it
319 | # tools/**
320 | # !tools/packages.config
321 |
322 | # Tabs Studio
323 | *.tss
324 |
325 | # Telerik's JustMock configuration file
326 | *.jmconfig
327 |
328 | # BizTalk build output
329 | *.btp.cs
330 | *.btm.cs
331 | *.odx.cs
332 | *.xsd.cs
333 |
334 | # OpenCover UI analysis results
335 | OpenCover/
336 |
337 | # Azure Stream Analytics local run output
338 | ASALocalRun/
339 |
340 | # MSBuild Binary and Structured Log
341 | *.binlog
342 |
343 | # NVidia Nsight GPU debugger configuration file
344 | *.nvuser
345 |
346 | # MFractors (Xamarin productivity tool) working folder
347 | .mfractor/
348 |
349 | # Local History for Visual Studio
350 | .localhistory/
351 |
352 | # BeatPulse healthcheck temp database
353 | healthchecksdb
354 |
355 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
356 | MigrationBackup/
357 |
358 | # Ionide (cross platform F# VS Code tools) working folder
359 | .ionide/
360 |
361 | # Fody - auto-generated XML schema
362 | FodyWeavers.xsd
363 | ProcessGuard/Resources/ProcessGuardService.exe
364 | ProcessGuard/Resources/ProcessGuardService.exe
365 |
--------------------------------------------------------------------------------
/ProcessGuard/MainWindowViewModel.cs:
--------------------------------------------------------------------------------
1 | using ProcessGuard.Common.Core;
2 | using ProcessGuard.Common.Models;
3 | using System.Collections.ObjectModel;
4 | using System.ComponentModel;
5 | using System.IO;
6 | using System.Windows;
7 |
8 | namespace ProcessGuard
9 | {
10 | public class MainWindowViewModel : ViewModelBase, IDataErrorInfo
11 | {
12 | private ObservableCollection _configItems;
13 | ///
14 | /// 配置项集合
15 | ///
16 | public ObservableCollection ConfigItems
17 | {
18 | get { return _configItems; }
19 | set { this.Set(ref this._configItems, value); }
20 | }
21 |
22 | private readonly ReadOnlyObservableCollection _languages = new ReadOnlyObservableCollection(new ObservableCollection
23 | {
24 | "English",
25 | "简体中文",
26 | });
27 |
28 | ///
29 | /// Languages in ComboBox for selection
30 | ///
31 | public ReadOnlyObservableCollection Languages
32 | {
33 | get { return _languages; }
34 | }
35 |
36 | private GlobalConfig _globalConfig;
37 |
38 | ///
39 | /// Global configurations of application
40 | ///
41 | public GlobalConfig GlobalConfig
42 | {
43 | get { return _globalConfig; }
44 | set { this.Set(ref this._globalConfig, value); }
45 | }
46 |
47 | ///
48 | /// Id of selected item
49 | ///
50 | public string SelectedId { get; set; }
51 |
52 | private string _selectedFile;
53 |
54 | ///
55 | /// 选中的文件
56 | ///
57 | public string SelectedFile
58 | {
59 | get { return _selectedFile; }
60 | set { this.Set(ref this._selectedFile, value); }
61 | }
62 |
63 | private string _startupParams;
64 |
65 | ///
66 | /// 启动参数
67 | ///
68 | public string StartupParams
69 | {
70 | get { return _startupParams; }
71 | set { this.Set(ref this._startupParams, value); }
72 | }
73 |
74 | private string _statusColor;
75 |
76 | ///
77 | /// 服务运行状态对应的状态颜色
78 | ///
79 | public string StatusColor
80 | {
81 | get { return _statusColor; }
82 | set { this.Set(ref this._statusColor, value); }
83 | }
84 |
85 | private string _runStatus;
86 |
87 | ///
88 | /// 服务运行状态
89 | ///
90 | public string RunStatus
91 | {
92 | get { return _runStatus; }
93 | set { this.Set(ref this._runStatus, value); }
94 | }
95 |
96 | private string _selectedProcessName;
97 |
98 | ///
99 | /// 当前的进程名
100 | ///
101 | public string SeletedProcessName
102 | {
103 | get { return _selectedProcessName; }
104 | set { this.Set(ref this._selectedProcessName, value); }
105 | }
106 |
107 | private bool _isOnlyOpenOnce;
108 |
109 | ///
110 | /// 当前是否勾选只启动一次
111 | ///
112 | public bool IsOnlyOpenOnce
113 | {
114 | get { return _isOnlyOpenOnce; }
115 | set { this.Set(ref this._isOnlyOpenOnce, value); }
116 | }
117 |
118 | private bool _isMinimize;
119 |
120 | ///
121 | /// 当前是否勾选以最小化方式启动
122 | ///
123 | public bool IsMinimize
124 | {
125 | get { return _isMinimize; }
126 | set { this.Set(ref this._isMinimize, value); }
127 | }
128 |
129 | private bool _noWindow;
130 |
131 | ///
132 | /// 启动时是否不显示Window
133 | ///
134 | public bool NoWindow
135 | {
136 | get { return _noWindow; }
137 | set { this.Set(ref this._noWindow, value); }
138 | }
139 |
140 | ///
141 | /// Indicates the state of the start/stop button in the config row,
142 | /// if the value is true and the Service is running, the process will be guarded
143 | ///
144 | public bool Started { get; set; }
145 |
146 | private bool _isNew;
147 |
148 | ///
149 | /// Identifies whether the current config is new
150 | ///
151 | public bool IsNew
152 | {
153 | get { return _isNew; }
154 | set { this.Set(ref this._isNew, value); }
155 | }
156 |
157 | private bool _canStart;
158 |
159 | ///
160 | /// 是否可以点击开始按钮
161 | ///
162 | public bool CanStart
163 | {
164 | get { return _canStart; }
165 | set { this.Set(ref this._canStart, value); }
166 | }
167 |
168 | private bool _canStop;
169 |
170 | ///
171 | /// 是否可以点击停止按钮
172 | ///
173 | public bool CanStop
174 | {
175 | get { return _canStop; }
176 | set { this.Set(ref this._canStop, value); }
177 | }
178 |
179 | private bool _canUnistall;
180 |
181 | ///
182 | /// 是否可以点击卸载按钮
183 | ///
184 | public bool CanUnistall
185 | {
186 | get { return _canUnistall; }
187 | set { this.Set(ref this._canUnistall, value); }
188 | }
189 |
190 | private bool? _isRun;
191 |
192 | ///
193 | /// 设置服务运行状态
194 | ///
195 | public bool? IsRun
196 | {
197 | get { return _isRun; }
198 | set
199 | {
200 | if (_isRun == value)
201 | {
202 | return;
203 | }
204 |
205 | _isRun = value;
206 |
207 | switch (value)
208 | {
209 | case true:
210 | CanStart = false;
211 | CanStop = true;
212 | CanUnistall = true;
213 | break;
214 |
215 | case false:
216 | CanStart = true;
217 | CanStop = false;
218 | CanUnistall = true;
219 | break;
220 |
221 | default:
222 | CanStart = true;
223 | CanStop = false;
224 | CanUnistall = false;
225 | break;
226 | }
227 | }
228 | }
229 |
230 |
231 | [Description("Test-Property")]
232 | public string Error => string.Empty;
233 |
234 | public int MyProperty { get; set; }
235 |
236 | public string this[string columnName]
237 | {
238 | get
239 | {
240 | switch (columnName)
241 | {
242 | case nameof(SelectedFile):
243 | if (string.IsNullOrWhiteSpace(SelectedFile))
244 | {
245 | return Application.Current.FindResource("Required").ToString();
246 | }
247 |
248 | if (!File.Exists(SelectedFile) || !SelectedFile.EndsWith(".exe"))
249 | {
250 | return Application.Current.FindResource("InvalidFilePath").ToString();
251 | }
252 |
253 | break;
254 |
255 | case nameof(SeletedProcessName):
256 | if (string.IsNullOrWhiteSpace(SeletedProcessName))
257 | {
258 | return Application.Current.FindResource("Required").ToString();
259 | }
260 | break;
261 |
262 | default:
263 | break;
264 | }
265 |
266 | return null;
267 | }
268 | }
269 | }
270 | }
271 |
--------------------------------------------------------------------------------
/ProcessGuardService/MainService.cs:
--------------------------------------------------------------------------------
1 | using ProcessGuard.Common;
2 | using ProcessGuard.Common.Models;
3 | using ProcessGuard.Common.Utility;
4 | using System;
5 | using System.Collections.Concurrent;
6 | using System.Diagnostics;
7 | using System.IO;
8 | using System.IO.Pipes;
9 | using System.Linq;
10 | using System.ServiceProcess;
11 | using System.Threading;
12 | using System.Threading.Tasks;
13 |
14 | namespace ProcessGuardService
15 | {
16 | public partial class MainService : ServiceBase
17 | {
18 | private Task _guardianTask = null;
19 | private NamedPipeServerStream _namedPipeServer = null;
20 | private readonly ConcurrentDictionary _startedProcesses = new ConcurrentDictionary();
21 | private readonly ConcurrentDictionary _changedProcesses = new ConcurrentDictionary();
22 |
23 | public MainService()
24 | {
25 | InitializeComponent();
26 | }
27 |
28 | private bool _running;
29 |
30 | protected override void OnStart(string[] args)
31 | {
32 | _running = true;
33 | _guardianTask = Task.Factory.StartNew(StartGuardian, TaskCreationOptions.LongRunning);
34 | _ = Task.Factory.StartNew(StartNamedPipeServer, TaskCreationOptions.LongRunning);
35 | }
36 |
37 | private void StartGuardian()
38 | {
39 | LoadConfig();
40 |
41 | while (_running)
42 | {
43 | Thread.Sleep(3000);
44 |
45 | foreach (var keyValuePair in _startedProcesses)
46 | {
47 | var config = keyValuePair.Value;
48 |
49 | if (config.ProcessId > 0)
50 | {
51 | try
52 | {
53 | using (var process = Process.GetProcessById(config.ProcessId))
54 | {
55 | // Just for dispose
56 | }
57 | }
58 | catch (Exception)
59 | {
60 | // The process has not started, should be restarted
61 | config.ProcessId = 0;
62 | }
63 | }
64 |
65 | if (config.ProcessId <= 0)
66 | {
67 | var startFilePath = config.EXEFullPath;
68 |
69 | if (File.Exists(startFilePath))
70 | {
71 | ApplicationLoader.StartProcessInSession0(startFilePath, Path.GetDirectoryName(startFilePath), out var processInfo, config.Minimize,
72 | string.IsNullOrEmpty(config.StartupParams) ? null : $" {config.StartupParams}", config.NoWindow);
73 |
74 | if (processInfo.dwProcessId > 0)
75 | {
76 | config.ProcessId = (int)processInfo.dwProcessId;
77 | if (config.OnlyOpenOnce)
78 | {
79 | _changedProcesses.TryAdd(keyValuePair.Key, new ConfigItemWithProcessId
80 | {
81 | Id = config.Id,
82 | ChangeType = ChangeType.Remove,
83 | });
84 | }
85 | }
86 | }
87 | }
88 | }
89 |
90 | var changeInfos = _changedProcesses.ToList();
91 |
92 | foreach (var changeInfo in changeInfos)
93 | {
94 | if (changeInfo.Value.ChangeType.HasFlag(ChangeType.Stop))
95 | {
96 | try
97 | {
98 | _startedProcesses.TryGetValue(changeInfo.Key, out var startedProcess);
99 | if (startedProcess != null && startedProcess.ProcessId > 0)
100 | {
101 | using (var process = Process.GetProcessById(startedProcess.ProcessId))
102 | {
103 | process.Kill();
104 | }
105 | }
106 | }
107 | catch (Exception)
108 | {
109 | // do nothing
110 | }
111 | }
112 |
113 | if (changeInfo.Value.ChangeType.HasFlag(ChangeType.Start))
114 | {
115 | _startedProcesses[changeInfo.Key] = changeInfo.Value;
116 | }
117 |
118 | if (changeInfo.Value.ChangeType.HasFlag(ChangeType.Remove))
119 | {
120 | _startedProcesses.TryRemove(changeInfo.Key, out _);
121 | }
122 |
123 | _changedProcesses.TryRemove(changeInfo.Key, out _);
124 | }
125 | }
126 | }
127 |
128 | ///
129 | /// Load the config file content to the dictionary
130 | ///
131 | private void LoadConfig()
132 | {
133 | var configList = ConfigHelper.LoadConfigFile();
134 |
135 | foreach (var item in configList)
136 | {
137 | if (item.Started)
138 | {
139 | _startedProcesses[item.Id] = new ConfigItemWithProcessId
140 | {
141 | EXEFullPath = item.EXEFullPath,
142 | Id = item.Id,
143 | Minimize = item.Minimize,
144 | NoWindow = item.NoWindow,
145 | OnlyOpenOnce = item.OnlyOpenOnce,
146 | ProcessName = item.ProcessName,
147 | Started = item.Started,
148 | StartupParams = item.StartupParams,
149 | };
150 | }
151 | }
152 | }
153 |
154 | ///
155 | /// Start the NamedPipeServer, listen to the changes of the config
156 | ///
157 | private void StartNamedPipeServer()
158 | {
159 | _namedPipeServer = new NamedPipeServerStream(Constants.PROCESS_GUARD_SERVICE, PipeDirection.In);
160 | _namedPipeServer.WaitForConnection();
161 | StreamReader reader = new StreamReader(_namedPipeServer);
162 |
163 | while (_running)
164 | {
165 | try
166 | {
167 | var line = reader.ReadLine();
168 | if (line == null)
169 | {
170 | throw new IOException("The client disconnected");
171 | }
172 |
173 | var config = line.DeserializeObject();
174 | if (config.Started)
175 | {
176 | config.ChangeType = ChangeType.Start;
177 |
178 | if (_startedProcesses.ContainsKey(config.Id))
179 | {
180 | config.ChangeType |= ChangeType.Stop;
181 | }
182 | }
183 | else
184 | {
185 | config.ChangeType = ChangeType.Stop | ChangeType.Remove;
186 | }
187 |
188 | _changedProcesses[config.Id] = config;
189 | }
190 | catch (IOException)
191 | {
192 | _namedPipeServer.Dispose();
193 | reader.Dispose();
194 | _namedPipeServer = new NamedPipeServerStream(Constants.PROCESS_GUARD_SERVICE, PipeDirection.In);
195 | _namedPipeServer.WaitForConnection();
196 | reader = new StreamReader(_namedPipeServer);
197 | }
198 | }
199 | }
200 |
201 | protected override void OnStop()
202 | {
203 | _running = false;
204 | _namedPipeServer.Dispose();
205 | while (!_guardianTask.IsCompleted)
206 | {
207 | Thread.Sleep(1);
208 | }
209 | }
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/ProcessGuard.Common/Utility/ApplicationLoader.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Runtime.InteropServices;
4 |
5 | namespace ProcessGuard.Common.Utility
6 | {
7 | public class ApplicationLoader
8 | {
9 | #region Structures
10 |
11 | [StructLayout(LayoutKind.Sequential)]
12 | private struct STARTUPINFO
13 | {
14 | public int cb;
15 | public string lpReserved;
16 | public string lpDesktop;
17 | public string lpTitle;
18 | public uint dwX;
19 | public uint dwY;
20 | public uint dwXSize;
21 | public uint dwYSize;
22 | public uint dwXCountChars;
23 | public uint dwYCountChars;
24 | public uint dwFillAttribute;
25 | public uint dwFlags;
26 | public short wShowWindow;
27 | public short cbReserved2;
28 | public IntPtr lpReserved2;
29 | public IntPtr hStdInput;
30 | public IntPtr hStdOutput;
31 | public IntPtr hStdError;
32 | }
33 |
34 | [StructLayout(LayoutKind.Sequential)]
35 | public struct PROCESS_INFORMATION
36 | {
37 | public IntPtr hProcess;
38 | public IntPtr hThread;
39 | public uint dwProcessId;
40 | public uint dwThreadId;
41 | }
42 |
43 | [StructLayout(LayoutKind.Sequential)]
44 | private struct WTS_SESSION_INFO
45 | {
46 | public readonly uint SessionID;
47 |
48 | [MarshalAs(UnmanagedType.LPStr)]
49 | public readonly string pWinStationName;
50 |
51 | public readonly WTS_CONNECTSTATE_CLASS State;
52 | }
53 |
54 | #endregion
55 |
56 | #region Enumerations
57 |
58 | private enum TOKEN_TYPE : int
59 | {
60 | TokenPrimary = 1,
61 | TokenImpersonation = 2
62 | }
63 |
64 | private enum SECURITY_IMPERSONATION_LEVEL : int
65 | {
66 | SecurityAnonymous = 0,
67 | SecurityIdentification = 1,
68 | SecurityImpersonation = 2,
69 | SecurityDelegation = 3,
70 | }
71 |
72 | private enum SW : int
73 | {
74 | SW_HIDE = 0,
75 | SW_SHOWNORMAL = 1,
76 | SW_NORMAL = 1,
77 | SW_SHOWMINIMIZED = 2,
78 | SW_SHOWMAXIMIZED = 3,
79 | SW_MAXIMIZE = 3,
80 | SW_SHOWNOACTIVATE = 4,
81 | SW_SHOW = 5,
82 | SW_MINIMIZE = 6,
83 | SW_SHOWMINNOACTIVE = 7,
84 | SW_SHOWNA = 8,
85 | SW_RESTORE = 9,
86 | SW_SHOWDEFAULT = 10,
87 | SW_FORCEMINIMIZE = 11,
88 | }
89 |
90 | private enum WTS_CONNECTSTATE_CLASS
91 | {
92 | WTSActive,
93 | WTSConnected,
94 | WTSConnectQuery,
95 | WTSShadow,
96 | WTSDisconnected,
97 | WTSIdle,
98 | WTSListen,
99 | WTSReset,
100 | WTSDown,
101 | WTSInit
102 | }
103 |
104 | #endregion
105 |
106 | #region Constants
107 |
108 | private const uint MAXIMUM_ALLOWED = 0x2000000;
109 | private const int CREATE_NEW_CONSOLE = 0x00000010;
110 | private const int CREATE_NO_WINDOW = 0x08000000;
111 | private const int CREATE_UNICODE_ENVIRONMENT = 0x00000400;
112 | private const int NORMAL_PRIORITY_CLASS = 0x20;
113 | private const int STARTF_USESHOWWINDOW = 0x00000001;
114 |
115 | #endregion
116 |
117 | #region Win32 API Imports
118 |
119 | [DllImport("kernel32.dll", SetLastError = true)]
120 | private static extern bool CloseHandle(IntPtr hSnapshot);
121 |
122 | [DllImport("kernel32.dll")]
123 | private static extern uint WTSGetActiveConsoleSessionId();
124 |
125 | [DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
126 | private extern static bool CreateProcessAsUser(IntPtr hToken, string lpApplicationName, string lpCommandLine, IntPtr lpProcessAttributes,
127 | IntPtr lpThreadAttributes, bool bInheritHandle, int dwCreationFlags, IntPtr lpEnvironment,
128 | string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);
129 |
130 | [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]
131 | private extern static bool DuplicateTokenEx(IntPtr ExistingTokenHandle, uint dwDesiredAccess,
132 | IntPtr lpThreadAttributes, int TokenType,
133 | int ImpersonationLevel, ref IntPtr DuplicateTokenHandle);
134 |
135 | [DllImport("userenv.dll", SetLastError = true)]
136 | private static extern bool CreateEnvironmentBlock(ref IntPtr lpEnvironment, IntPtr hToken, bool bInherit);
137 |
138 | [DllImport("userenv.dll", SetLastError = true)]
139 | [return: MarshalAs(UnmanagedType.Bool)]
140 | private static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment);
141 |
142 | [DllImport("wtsapi32.dll", SetLastError = true)]
143 | private static extern uint WTSQueryUserToken(uint SessionId, ref IntPtr phToken);
144 |
145 | [DllImport("wtsapi32.dll", SetLastError = true)]
146 | private static extern int WTSEnumerateSessions(IntPtr hServer, int Reserved, int Version, ref IntPtr ppSessionInfo, ref int pCount);
147 |
148 | [DllImport("wtsapi32.dll", SetLastError = false)]
149 | public static extern void WTSFreeMemory(IntPtr memory);
150 |
151 | #endregion
152 |
153 | ///
154 | /// 在Seesion 0,主要是为了在Windows Service中启动带有交互式界面的程序
155 | ///
156 | /// 要启动的应用程序的完全路径
157 | /// 程序启动时的工作目录,通常传递要启动的程序所在目录即可,特殊情况包括需要在指定的文件夹打开cmd窗口等
158 | /// 创建完成的进程信息
159 | /// 是否最小化窗体
160 | /// 表示要使用的命令内容,比如需要启动一个cmd程序,因为获取其真实路径比较麻烦,此时可以直接传"cmd",要启动的应用程序路径留空即可
161 | /// 创建完成的进程信息
162 | public static bool StartProcessInSession0(string applicationFullPath, string startingDir, out PROCESS_INFORMATION procInfo, bool minimize = false, string commandLine = null, bool noWindow = false)
163 | {
164 | IntPtr hUserTokenDup = IntPtr.Zero;
165 | IntPtr hPToken = IntPtr.Zero;
166 | IntPtr pEnv = IntPtr.Zero;
167 | procInfo = new PROCESS_INFORMATION();
168 | bool result = false;
169 |
170 | try
171 | {
172 | procInfo = new PROCESS_INFORMATION();
173 |
174 | // 使用两种方法获取当前正在使用的系统用户的session id,每一个登录到系统的用户都有一个唯一的session id
175 | // 这一步是为了可以正确在当前登录的用户界面启动程序
176 | if (WTSQueryUserToken(WTSGetActiveConsoleSessionId(), ref hPToken) == 0 &&
177 | WTSQueryUserToken(GetSessionIdFromEnumerateSessions(), ref hPToken) == 0)
178 | {
179 | return false;
180 | }
181 |
182 | // 复制当前用户的访问令牌,产生一个新令牌
183 | if (!DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, IntPtr.Zero, (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, (int)TOKEN_TYPE.TokenPrimary, ref hUserTokenDup))
184 | {
185 | return false;
186 | }
187 |
188 | // lpDesktop参数是用来设置程序启动的界面,这里设置的参数是winsta0\default,代表交互式用户的默认桌面
189 | STARTUPINFO si = new STARTUPINFO();
190 | si.cb = (int)Marshal.SizeOf(si);
191 | si.lpDesktop = @"winsta0\default";
192 |
193 | if (minimize)
194 | {
195 | si.dwFlags = STARTF_USESHOWWINDOW;
196 | si.wShowWindow = (short)SW.SW_MINIMIZE;
197 | }
198 |
199 | // 指定进程的优先级和创建方法,这里代表是普通优先级,并且创建方法是带有UI的进程
200 | int dwCreationFlags = CREATE_UNICODE_ENVIRONMENT | NORMAL_PRIORITY_CLASS | (noWindow ? CREATE_NO_WINDOW : CREATE_NEW_CONSOLE);
201 |
202 | // 创建新进程的环境变量
203 | if (!CreateEnvironmentBlock(ref pEnv, hUserTokenDup, false))
204 | {
205 | return false;
206 | }
207 |
208 | // 在当前用户的session中创建一个新进程
209 | result = CreateProcessAsUser(hUserTokenDup, // 用户的访问令牌
210 | applicationFullPath, // 要执行的程序的路径
211 | commandLine, // 命令行内容
212 | IntPtr.Zero, // 设置进程的SECURITY_ATTRIBUTES,主要用来控制对象的访问权限,这里传空值
213 | IntPtr.Zero, // 设置线程的SECURITY_ATTRIBUTES,控制对象的访问权限,这里传空值
214 | false, // 开启的进程不需继承句柄
215 | dwCreationFlags, // 创建标识
216 | pEnv, // 新的环境变量
217 | startingDir, // 程序启动时的工作目录,通常传递要启动的程序所在目录即可
218 | ref si, // 启动信息
219 | out procInfo // 用于接收新创建的进程的信息
220 | );
221 |
222 | if (!result)
223 | {
224 | Debug.WriteLine(Marshal.GetLastWin32Error());
225 | }
226 | }
227 | finally
228 | {
229 | // 关闭句柄
230 | CloseHandle(hPToken);
231 | CloseHandle(hUserTokenDup);
232 | if (pEnv != IntPtr.Zero)
233 | {
234 | DestroyEnvironmentBlock(pEnv);
235 | }
236 | CloseHandle(procInfo.hThread);
237 | CloseHandle(procInfo.hProcess);
238 | }
239 |
240 | return result;
241 | }
242 |
243 | ///
244 | /// Get session id via WTSEnumerateSessions
245 | ///
246 | ///
247 | private static uint GetSessionIdFromEnumerateSessions()
248 | {
249 | var pSessionInfo = IntPtr.Zero;
250 | try
251 | {
252 | var sessionCount = 0;
253 |
254 | // Get a handle to the user access token for the current active session.
255 | if (WTSEnumerateSessions(IntPtr.Zero, 0, 1, ref pSessionInfo, ref sessionCount) != 0)
256 | {
257 | var arrayElementSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
258 | var current = pSessionInfo;
259 |
260 | for (var i = 0; i < sessionCount; i++)
261 | {
262 | var si = (WTS_SESSION_INFO)Marshal.PtrToStructure(current, typeof(WTS_SESSION_INFO));
263 | current += arrayElementSize;
264 |
265 | if (si.State == WTS_CONNECTSTATE_CLASS.WTSActive)
266 | {
267 | return si.SessionID;
268 | }
269 | }
270 | }
271 |
272 | return uint.MaxValue;
273 | }
274 | finally
275 | {
276 | WTSFreeMemory(pSessionInfo);
277 | CloseHandle(pSessionInfo);
278 | }
279 | }
280 |
281 | ///
282 | /// 执行命令行并且获取输出的内容(包括输出内容和错误内容)
283 | ///
284 | /// 命令行内容
285 | public static bool RunCmdAndGetOutput(string cmd, out string output, out string error)
286 | {
287 | using (var process = new Process())
288 | {
289 | output = "";
290 | error = "";
291 | string outputContent = "";
292 | string outputError = "";
293 | ProcessStartInfo processInfo = new ProcessStartInfo();
294 | // 隐藏可视化界面
295 | processInfo.CreateNoWindow = true;
296 | processInfo.UseShellExecute = false;
297 | // 对标准输出流和错误流进行重定向
298 | processInfo.RedirectStandardOutput = true;
299 | processInfo.RedirectStandardError = true;
300 | process.OutputDataReceived += (_, e) =>
301 | {
302 | outputContent += e.Data;
303 | };
304 | process.ErrorDataReceived += (_, e) =>
305 | {
306 | outputError += e.Data;
307 | };
308 |
309 | // 对标准输入流进行重定向
310 | processInfo.RedirectStandardInput = true;
311 | processInfo.FileName = "cmd.exe";
312 | processInfo.Arguments = cmd;
313 | processInfo.Verb = "runas"; // 提升cmd程序的权限
314 | process.StartInfo = processInfo;
315 |
316 | if (!process.Start())
317 | {
318 | return false;
319 | }
320 |
321 | process.StandardInput.WriteLine(cmd);
322 | process.StandardInput.WriteLine("exit");
323 |
324 | process.BeginOutputReadLine();
325 | process.BeginErrorReadLine();
326 |
327 | process.WaitForExit();
328 |
329 | output = outputContent;
330 | error = outputError;
331 |
332 | return true;
333 | }
334 | }
335 | }
336 | }
337 |
--------------------------------------------------------------------------------
/ProcessGuard/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using ProcessGuard.Common;
2 | using ProcessGuard.Common.Models;
3 | using ProcessGuard.Common.Utility;
4 | using MahApps.Metro.Controls;
5 | using MahApps.Metro.Controls.Dialogs;
6 | using System;
7 | using System.Collections.ObjectModel;
8 | using System.IO;
9 | using System.ServiceProcess;
10 | using System.Threading.Tasks;
11 | using System.Timers;
12 | using System.Windows;
13 | using System.Windows.Controls;
14 | using System.IO.Pipes;
15 | using System.Linq;
16 | using System.Globalization;
17 |
18 | namespace ProcessGuard
19 | {
20 | ///
21 | /// MainWindow.xaml 的交互逻辑
22 | ///
23 | public partial class MainWindow
24 | {
25 | private readonly MainWindowViewModel _mainWindowViewModel;
26 |
27 | ///
28 | /// 用于定时检查服务状态的Timer
29 | ///
30 | private readonly Timer _checkTimer;
31 |
32 | public MainWindow()
33 | {
34 | InitializeComponent();
35 | _mainWindowViewModel = new MainWindowViewModel() { ConfigItems = new ObservableCollection(), StatusColor = "Transparent" };
36 | DataContext = _mainWindowViewModel;
37 | this.MetroDialogOptions.AnimateShow = true;
38 | this.MetroDialogOptions.AnimateHide = false;
39 | _mainWindowViewModel.ConfigItems = ConfigHelper.LoadConfigFile();
40 | _mainWindowViewModel.IsRun = _mainWindowViewModel.IsRun == true ? false : true;
41 | _mainWindowViewModel.GlobalConfig = ConfigHelper.LoadGlobalConfigFile();
42 |
43 | if (string.IsNullOrEmpty(_mainWindowViewModel.GlobalConfig.Language))
44 | {
45 | if (CultureInfo.CurrentUICulture.Name == "zh-CN")
46 | {
47 | _mainWindowViewModel.GlobalConfig.Language = "简体中文";
48 | }
49 | else
50 | {
51 | _mainWindowViewModel.GlobalConfig.Language = "English";
52 | }
53 | }
54 |
55 | SetLanguageDictionary();
56 | UpdateServiceStatus();
57 |
58 | _checkTimer = new Timer();
59 | _checkTimer.Elapsed += CheckTimer_Elapsed;
60 | _checkTimer.Start();
61 | _checkTimer.Interval = 1000;
62 | }
63 |
64 | #region 窗体事件
65 |
66 | ///
67 | /// 按钮点击事件
68 | ///
69 | private async void Button_Click(object sender, RoutedEventArgs e)
70 | {
71 | var button = sender as Button;
72 |
73 | switch (button?.Name)
74 | {
75 | case nameof(btnAdd):
76 | var dialog = new CustomDialog(this.MetroDialogOptions) { Content = this.Resources["CustomAddDialog"], Title = string.Empty };
77 | _mainWindowViewModel.SelectedId = Guid.NewGuid().ToString("N");
78 | _mainWindowViewModel.Started = false;
79 | _mainWindowViewModel.IsNew = true;
80 |
81 | await this.ShowMetroDialogAsync(dialog);
82 | await dialog.WaitUntilUnloadedAsync();
83 | break;
84 |
85 | case nameof(btnMinus):
86 | ConfigItem selectedItem = null;
87 |
88 | if (configDataGrid.SelectedCells?.Count > 0)
89 | {
90 | selectedItem = configDataGrid.SelectedCells[0].Item as ConfigItem;
91 | }
92 |
93 | if (selectedItem == null)
94 | {
95 | break;
96 | }
97 |
98 | MessageDialogResult result = await ShowMessageDialogAsync(FindResource("Warning").ToString(), FindResource("ConfirmDelete").ToString());
99 |
100 | if (result == MessageDialogResult.Affirmative)
101 | {
102 | _mainWindowViewModel.ConfigItems.Remove(selectedItem);
103 | ConfigHelper.SaveConfigs(_mainWindowViewModel.ConfigItems);
104 | selectedItem.Started = false;
105 | SendCommandToService(selectedItem);
106 | }
107 |
108 | break;
109 |
110 | case "btnSelectFile":
111 | var filePath = Utils.GetFileNameByFileDialog();
112 | if (!string.IsNullOrEmpty(filePath))
113 | {
114 | FileInfo fileInfo = new FileInfo(filePath);
115 | _mainWindowViewModel.SelectedFile = fileInfo.FullName;
116 | _mainWindowViewModel.SeletedProcessName = fileInfo.Name.Replace(fileInfo.Extension, string.Empty);
117 | }
118 | break;
119 |
120 | case nameof(btnStart):
121 | StartService();
122 | break;
123 |
124 | case nameof(btnStop):
125 | StopService();
126 | break;
127 |
128 | case nameof(btnUninstall):
129 | await UninstallService();
130 | break;
131 |
132 | case nameof(btnSetting):
133 | var settingDialog = new CustomDialog(this.MetroDialogOptions) { Content = this.Resources["CustomSettingDialog"], Title = string.Empty };
134 | await this.ShowMetroDialogAsync(settingDialog, new MetroDialogSettings { });
135 | await settingDialog.WaitUntilUnloadedAsync();
136 | break;
137 |
138 | default:
139 | break;
140 | }
141 | }
142 |
143 | ///
144 | /// Click event of datagrid button
145 | ///
146 | ///
147 | ///
148 | private void DataGridButton_Click(object sender, RoutedEventArgs e)
149 | {
150 | var currentRow = this.configDataGrid.CurrentItem as ConfigItem;
151 |
152 | if (currentRow != null)
153 | {
154 | currentRow.Started = !currentRow.Started;
155 | ConfigHelper.SaveConfigs(_mainWindowViewModel.ConfigItems);
156 | SendCommandToService(currentRow);
157 | }
158 | }
159 |
160 | ///
161 | /// Double click event of datagrid cell
162 | ///
163 | ///
164 | ///
165 | private async void DataGridCell_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
166 | {
167 | var currentRow = this.configDataGrid.CurrentItem as ConfigItem;
168 |
169 | if (currentRow != null)
170 | {
171 | var dialog = new CustomDialog(this.MetroDialogOptions) { Content = this.Resources["CustomAddDialog"], Title = string.Empty };
172 |
173 | _mainWindowViewModel.SelectedId = currentRow.Id;
174 | _mainWindowViewModel.SelectedFile = currentRow.EXEFullPath;
175 | _mainWindowViewModel.StartupParams = currentRow.StartupParams;
176 | _mainWindowViewModel.SeletedProcessName = currentRow.ProcessName;
177 | _mainWindowViewModel.IsOnlyOpenOnce = currentRow.OnlyOpenOnce;
178 | _mainWindowViewModel.IsMinimize = currentRow.Minimize;
179 | _mainWindowViewModel.NoWindow = currentRow.NoWindow;
180 | _mainWindowViewModel.Started = currentRow.Started;
181 | _mainWindowViewModel.IsNew = false;
182 |
183 | await this.ShowMetroDialogAsync(dialog);
184 | await dialog.WaitUntilUnloadedAsync();
185 | }
186 | }
187 |
188 | ///
189 | /// Event after language comboBox selection changed
190 | ///
191 | ///
192 | ///
193 | private void LanguageComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
194 | {
195 | ConfigHelper.SaveGlobalConfigs(_mainWindowViewModel.GlobalConfig);
196 | SetLanguageDictionary();
197 | }
198 |
199 | ///
200 | /// 停止服务
201 | ///
202 | private void StopService()
203 | {
204 | ServiceController service = null;
205 | try
206 | {
207 | service = new ServiceController(Constants.PROCESS_GUARD_SERVICE);
208 | service.Stop();
209 | }
210 | catch (Exception)
211 | {
212 | // do nothing
213 | }
214 | finally
215 | {
216 | service.Dispose();
217 | }
218 | }
219 |
220 | ///
221 | /// 卸载服务
222 | ///
223 | /// 卸载过程中是否产生错误
224 | private async Task UninstallService()
225 | {
226 | var error = string.Empty;
227 |
228 | MessageDialogResult dialogResult = await ShowMessageDialogAsync(FindResource("Warning").ToString(), FindResource("ConfirmUninstall").ToString());
229 |
230 | if (dialogResult == MessageDialogResult.Affirmative)
231 | {
232 | CreateServiceFile();
233 | var servicePath = ConfigHelper.GetAppDataFilePath(Constants.FILE_GUARD_SERVICE);
234 | string cmd = @"%SystemRoot%\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe /U ";
235 | cmd += $"/LogFile={ConfigHelper.GetAppDataFilePath("myLog.InstallLog")} ";
236 | cmd += servicePath;
237 |
238 | await Task.Run(() =>
239 | {
240 | ApplicationLoader.RunCmdAndGetOutput(cmd, out var _, out error);
241 | try
242 | {
243 | File.Delete(servicePath);
244 | }
245 | catch (Exception)
246 | {
247 | // do nothing
248 | }
249 | });
250 | }
251 |
252 | return !string.IsNullOrEmpty(error);
253 | }
254 |
255 | ///
256 | /// 若服务已存在,启动服务,否则先安装,再启动服务
257 | ///
258 | private async void StartService()
259 | {
260 | if (GetServiceStatus(Constants.PROCESS_GUARD_SERVICE) == default(ServiceControllerStatus))
261 | {
262 | CreateServiceFile();
263 |
264 | string cmd = @"%SystemRoot%\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe ";
265 | cmd += $"/LogFile={ConfigHelper.GetAppDataFilePath("myLog.InstallLog")} ";
266 | cmd += $@"{ConfigHelper.GetAppDataFilePath(Constants.FILE_GUARD_SERVICE)}
267 | Net Start ProcessGuardService
268 | sc config ProcessGuardService = auto";
269 |
270 | await Task.Run(() =>
271 | {
272 | ApplicationLoader.RunCmdAndGetOutput(cmd, out var _, out var _);
273 | });
274 | }
275 | else
276 | {
277 | ServiceController service = null;
278 | try
279 | {
280 | service = new ServiceController(Constants.PROCESS_GUARD_SERVICE);
281 | service.Start();
282 | }
283 | catch (Exception)
284 | {
285 | // do nothing
286 | }
287 | finally
288 | {
289 | service.Dispose();
290 | }
291 | }
292 | }
293 |
294 | ///
295 | /// 在安装和卸载服务之前,需要确保服务文件已存在
296 | ///
297 | private void CreateServiceFile()
298 | {
299 | var serviceFileName = ConfigHelper.GetAppDataFilePath(Constants.FILE_GUARD_SERVICE);
300 |
301 | // 从资源文件中拷贝出服务程序
302 | if (!File.Exists(serviceFileName))
303 | {
304 | File.WriteAllBytes(serviceFileName, Properties.Resources.ProcessGuardService);
305 | }
306 | }
307 |
308 | ///
309 | /// 用于检查服务状态的计时器
310 | ///
311 | private void CheckTimer_Elapsed(object sender, ElapsedEventArgs e)
312 | {
313 | UpdateServiceStatus();
314 | }
315 |
316 | #endregion
317 |
318 | #region 窗体私有函数
319 |
320 | ///
321 | /// 获取指定Windows系统服务的状态,如果其没有安装,默认返回0
322 | ///
323 | /// 服务名
324 | ///
325 | private ServiceControllerStatus GetServiceStatus(string serviceName)
326 | {
327 | ServiceController service = null;
328 | try
329 | {
330 | service = new ServiceController(serviceName);
331 | return service.Status;
332 | }
333 | catch (Exception)
334 | {
335 | return default(ServiceControllerStatus);
336 | }
337 | finally
338 | {
339 | service.Dispose();
340 | }
341 | }
342 |
343 | ///
344 | /// 更新当前的服务状态标识
345 | ///
346 | private void UpdateServiceStatus()
347 | {
348 | string statusColor = "";
349 | string runStatus = "";
350 | bool? isRun = null;
351 |
352 | switch (GetServiceStatus(Constants.PROCESS_GUARD_SERVICE))
353 | {
354 | case ServiceControllerStatus.ContinuePending:
355 | statusColor = "Yellow";
356 | runStatus = FindResource("ContinuePending").ToString();
357 | isRun = false;
358 | break;
359 | case ServiceControllerStatus.Paused:
360 | statusColor = "Orange";
361 | runStatus = FindResource("Paused").ToString();
362 | isRun = false;
363 | break;
364 | case ServiceControllerStatus.PausePending:
365 | statusColor = "Yellow";
366 | runStatus = FindResource("PausePending").ToString();
367 | isRun = true;
368 | break;
369 | case ServiceControllerStatus.Running:
370 | statusColor = "Green";
371 | runStatus = FindResource("Running").ToString();
372 | isRun = true;
373 | break;
374 | case ServiceControllerStatus.StartPending:
375 | statusColor = "Yellow";
376 | runStatus = FindResource("StartPending").ToString();
377 | isRun = false;
378 | break;
379 | case ServiceControllerStatus.Stopped:
380 | statusColor = "Orange";
381 | runStatus = FindResource("Stopped").ToString();
382 | isRun = false;
383 | break;
384 | case ServiceControllerStatus.StopPending:
385 | statusColor = "Yellow";
386 | runStatus = FindResource("StopPending").ToString();
387 | isRun = true;
388 | break;
389 | default:
390 | statusColor = "Red";
391 | runStatus = FindResource("NotInstalled").ToString();
392 | isRun = null;
393 | break;
394 | }
395 |
396 | if (!string.IsNullOrEmpty(runStatus))
397 | {
398 | this.Dispatcher.Invoke(() =>
399 | {
400 | _mainWindowViewModel.StatusColor = statusColor;
401 | _mainWindowViewModel.RunStatus = string.Format(FindResource("ServiceStatus").ToString(), runStatus);
402 | _mainWindowViewModel.IsRun = isRun;
403 | });
404 | }
405 | }
406 |
407 | ///
408 | /// 使用自定义的标题和消息弹出确认对话框
409 | ///
410 | /// 标题
411 | /// 要提示的消息
412 | ///
413 | private async Task ShowMessageDialogAsync(string title, string message)
414 | {
415 | var mySettings = new MetroDialogSettings()
416 | {
417 | AffirmativeButtonText = FindResource("Yes").ToString(),
418 | NegativeButtonText = FindResource("Cancel").ToString(),
419 | ColorScheme = MetroDialogOptions.ColorScheme,
420 | DialogButtonFontSize = 20D,
421 | AnimateShow = false,
422 | AnimateHide = false,
423 | };
424 |
425 | MessageDialogResult result = await this.ShowMessageAsync(title, message,
426 | MessageDialogStyle.AffirmativeAndNegative, mySettings);
427 |
428 | return result;
429 | }
430 |
431 | ///
432 | /// Send command to service to control the process
433 | ///
434 | ///
435 | private void SendCommandToService(ConfigItem config)
436 | {
437 | if (_mainWindowViewModel.IsRun != true)
438 | {
439 | return;
440 | }
441 | using (var client = new NamedPipeClientStream(".", Constants.PROCESS_GUARD_SERVICE, PipeDirection.Out))
442 | {
443 | try
444 | {
445 | client.Connect(1000);
446 | using (var writer = new StreamWriter(client))
447 | {
448 | writer.WriteLine(config.Serialize());
449 | }
450 | }
451 | catch (Exception)
452 | {
453 | // do nothing
454 | }
455 | }
456 | }
457 |
458 | ///
459 | /// 关闭当前弹出的Dialog窗口
460 | ///
461 | private async void CloseCustomDialog(object sender, RoutedEventArgs e)
462 | {
463 | var button = sender as Button;
464 | var dialog = button.TryFindParent();
465 |
466 | switch (button.Name)
467 | {
468 | case "btnConfirmAdd":
469 | if (_mainWindowViewModel[nameof(_mainWindowViewModel.SelectedFile)] == null &&
470 | _mainWindowViewModel[nameof(_mainWindowViewModel.SeletedProcessName)] == null)
471 | {
472 | var config = _mainWindowViewModel.ConfigItems.Where(c => c.Id == _mainWindowViewModel.SelectedId).FirstOrDefault();
473 |
474 | if (config != null)
475 | {
476 | config.Id = _mainWindowViewModel.SelectedId;
477 | config.EXEFullPath = _mainWindowViewModel.SelectedFile;
478 | config.StartupParams = _mainWindowViewModel.StartupParams;
479 | config.ProcessName = _mainWindowViewModel.SeletedProcessName;
480 | config.OnlyOpenOnce = _mainWindowViewModel.IsOnlyOpenOnce;
481 | config.Minimize = _mainWindowViewModel.IsMinimize;
482 | config.NoWindow = _mainWindowViewModel.NoWindow;
483 | config.Started = _mainWindowViewModel.Started;
484 |
485 | if (config.Started)
486 | {
487 | SendCommandToService(config);
488 | }
489 | }
490 | else
491 | {
492 | _mainWindowViewModel.ConfigItems.Add(new ConfigItem()
493 | {
494 | Id = _mainWindowViewModel.SelectedId,
495 | EXEFullPath = _mainWindowViewModel.SelectedFile,
496 | StartupParams = _mainWindowViewModel.StartupParams,
497 | ProcessName = _mainWindowViewModel.SeletedProcessName,
498 | OnlyOpenOnce = _mainWindowViewModel.IsOnlyOpenOnce,
499 | Minimize = _mainWindowViewModel.IsMinimize,
500 | NoWindow = _mainWindowViewModel.NoWindow,
501 | Started = _mainWindowViewModel.Started,
502 | });
503 | }
504 |
505 | ConfigHelper.SaveConfigs(_mainWindowViewModel.ConfigItems);
506 | await this.HideMetroDialogAsync(dialog);
507 | }
508 | break;
509 |
510 | case "btnCancelAdd":
511 | case "btnOK":
512 | await this.HideMetroDialogAsync(dialog);
513 | break;
514 | default:
515 | break;
516 | }
517 | }
518 |
519 | private void SetLanguageDictionary()
520 | {
521 | ResourceDictionary dict = new ResourceDictionary();
522 | var uriString = string.Empty;
523 |
524 | switch (_mainWindowViewModel.GlobalConfig?.Language)
525 | {
526 | case "简体中文":
527 | uriString = "..\\Resources\\StringResources.zh-CN.xaml";
528 | break;
529 | default:
530 | uriString = "..\\Resources\\StringResources.xaml";
531 | break;
532 | }
533 |
534 | dict.Source = new Uri(uriString, UriKind.Relative);
535 |
536 | var existedDict = Application.Current.Resources.MergedDictionaries.Where(d => d.Source.OriginalString == uriString).FirstOrDefault();
537 |
538 | Application.Current.Resources.MergedDictionaries.Add(dict);
539 |
540 | if (existedDict != null)
541 | {
542 | Application.Current.Resources.MergedDictionaries.Remove(existedDict);
543 | }
544 | }
545 |
546 | #endregion
547 |
548 | }
549 | }
550 |
--------------------------------------------------------------------------------
/ProcessGuard/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
20 |
21 |
22 |
23 |
29 |
30 |
31 |
32 |
33 |
34 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
103 |
104 |
123 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
153 |
154 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
184 |
185 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
198 |
199 |
200 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
213 |
214 |
215 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
228 |
229 |
230 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
263 |
264 |
265 |
266 |
267 |
275 |
276 |
277 |
278 |
304 |
305 |
306 |
307 |
308 |
309 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
336 |
337 |
338 |
339 |
340 |
341 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
355 |
356 |
357 |
358 |
369 |
370 |
371 |
382 |
383 |
384 |
395 |
396 |
397 |
407 |
408 |
409 |
419 |
420 |
421 |
422 |
423 |
--------------------------------------------------------------------------------