GTweak 命令执行器:6 个执行入口如何撑起一个 Windows 优化工具
【免费下载链接】GTweakPortable Tool for an Ideal Windows Setup项目地址: https://gitcode.com/GitHub_Trending/gt/GTweak
GTweak 是一款免安装的 Windows 系统优化工具,它的上百个功能按钮最终都收敛到同一个模块:命令执行器 CommandExecutor。本文拆解这套执行层的方法分工和两个易被跳过的细节,读完你就能为自己的项目设计一套同样克制的命令执行方案。
从一个按钮说起:操作背后到底发生了什么
以主界面的"Clearing the memory"卡片为例,点一下背后至少排着三类命令:清空 Temp 目录、删带保护属性的缓存文件、再重启资源管理器。如果没有统一封装,每张功能卡都得自己手写一遍同一套动作:构造 ProcessStartInfo、拉起进程、等退出、读输出流、catch 异常。卡片一多,这堆代码就开始失控。
| 各自裸写 Process 时的状况 | 收进 CommandExecutor 之后 |
|---|---|
| "启动—等待—读流"循环在十几个模块里各抄一遍 | 全项目只用 6 个语义化入口 |
| 管理员权限和 TrustedInstaller 逻辑四处散落,出问题时没处查 | 提权被压成 runas 与 TrustedInstaller 两条固定通道 |
| CMD 黑窗随机弹出,盖住自己的界面 | 窗口可见性统一受控,界面始终保持干净 |
核心机制拆解:按"要不要看结果 / 要不要提权"选方法
CommandExecutor.cs 是一个internal static类,方法不按"命令类型"组织,而按"执行方式"组织。选型时你只需要回答两个问题:要不要拿到输出?要不要提权或弹窗口?
只拿结果:GetCommandOutput
静默启动、隐藏窗口、按 UTF-8 解码输出、等进程退出,且只有退出码为 0 才把文本交出去,否则一律返回空串——等于告诉上层"查询失败就当没数据",对展示类逻辑非常友好。HardwareProvider.cs 就是用它跑 bcdedit,靠输出里有没有 "efi" 判断系统处于 UEFI 还是传统启动,典型的查询型用法。
发完即走:RunCommand 与 TrustedInstaller 通道
RunCommand把启动参数设成Verb = "runas",一次 UAC 确认后就拉起进程,不等待、不关心结果,像rd /s /q这类清理命令发出去就算完事。当目标换成连管理员都动不了的文件时,则改走RunCommandAsTrustedInstaller:它经由 TrustedInstaller.cs 启动系统内置的 TrustedInstaller 服务、伪装 SYSTEM 身份,再把子进程挂到这个服务之下。MemoryCleaner.cs 清理 Windows\Temp 和图标缓存用的就是这条通道。
💡 类里那个volatile int PID静态字段是这条链路的闭环点:服务启动后记录 TrustedInstaller 进程的 PID,下次提权时直接打开这个"父进程",子进程才能真正落在正确的身份树里。
必须等结果 / 必须看得见
InvokeRunCommand与RunCommand的区别只有一点:它会 await 到进程完全退出,确保下一步能拿到这一步的执行结果再推进。激活模块 WinLicenseHandler.cs 每一步slmgr.vbs都要等它跑完才进入下一环节,靠的就是它。InvokeRunVisibleProcess则相反,明确弹出窗口让用户盯着执行过程,专供自定义脚本场景,下文链路部分细说。
多行脚本防翻车:RunEncodedPowerShell
PowerShell 命令里一旦带上换行和成串引号,硬塞给-Command参数就像埋雷。这个方法的思路很直接:先把命令转成 Unicode 再 Base64 编码,交给-EncodedCommand,让引号和特殊字符根本不跨进程边界。DefenderTweaks.cs 里那些跨多行的 Defender 配置命令,只能这样才稳。
把六种组合放进一张表:
| 你实际想要的行为 | 对应方法 | 项目里的真实调用方 |
|---|---|---|
| 拿输出,失败当空 | GetCommandOutput | HardwareProvider.cs |
| 只执行,UAC 就够 | RunCommand | SystemRestoreService.cs |
| 只执行,要最高权限 | RunCommandAsTrustedInstaller | MemoryCleaner.cs |
| 静默且必须等它跑完 | InvokeRunCommand | WinLicenseHandler.cs |
| 弹可见窗口给用户看 | InvokeRunVisibleProcess | AddonsViewModel.cs |
| 多行 PowerShell 防转义 | RunEncodedPowerShell | DefenderTweaks.cs |
两个新手最容易忽略的细节
现象:async 方法里等进程,整个界面假死
现象:WPF 的 async 方法里等进程结束,命令一慢窗口就冻住,像点了假死。根因:.NET 4.x 的Process只有同步阻塞等待,没有原生异步重载,你只能自己补。解法:CommandExecutor.cs 用约 20 行补了WaitForExitAsync扩展——内部一个TaskCompletionSource订阅Exited事件,进程一退出任务即完成;完成回调里顺手解绑事件处理器,多次调用也不留悬挂引用。
现象:多行参数一到执行就报错
现象:用户填的脚本参数常带换行和多余空格,拼进命令行后要么直接报错要么被截断。根因:界面里粘进来的是纯文本,换行并不是 CMD 合法的分隔符。解法:CleanCommand先把文本按行拆开、逐行去空、用正则压缩连续空白,再看原始内容里出现过&&还是&,选对应分隔符拼回单行。自定义脚本链路和 ServicesTweaks.cs 里的批量服务配置,全都先过这道"压平"工序再执行。
串一条完整链路:从界面点击到系统调用
以"用户运行一个自己写的 .ps1 脚本"为例,数据一共经过 4 站:
- 后缀决定解释器:AddonsViewModel.cs 的
RunFile按文件后缀分支——.reg走reg.exe import,.ps1走 PowerShell 并绕过执行策略,其余丢给 CMD 的/k模式,窗口保持开着。 - 参数先压平:参数字符串交给
CleanCommand,多行文本在这里变成单行且分隔符正确,才允许离开 ViewModel 层。 - 进入执行器:整体包在后台任务里调用
InvokeRunVisibleProcess。ViewModel 只给出"跑什么",怎么跑完全由执行器决定。 - 提权分支或可见执行:脚本若标记了需要管理员,直接改走 TrustedInstaller 通道(先确保服务在跑,并把 PID 记入
CommandExecutor.PID);否则以可见窗口启动进程,用WaitForExitAsync等退出,全程不占 UI 线程。
三条可以直接带走的设计经验
- 把"怎么执行"收敛成有限个动词——外部命令出现在 10 个以上功能里时,固定的语义化入口比各处自由
new Process更能控制风险,因为新功能只能"选组合",没法再发明新的执行方式。 - 给事件驱动的旧 API 补异步,TCS 是最稳的路——不只是
Process,任何只提供回调/事件的第三方 API 都可以用TaskCompletionSource包出 await 能力,比轮询和阻塞线程都可靠。 - 提权要做成显式通道而不是散落判断——把权限升级集中进固定入口后,排查范围从"全库搜索 runas"缩小到"看两个文件",命令越危险,这个收益越明显。
写在最后
CommandExecutor 用不到 200 行的一个文件撑住了 GTweak 主页上所有按钮,本质是把"静默、可见、提权、等待"这些取舍显式化并写进方法名里。如果你的项目也要调系统命令,建议先打开 CommandExecutor.cs 把 6 个入口过一遍,再决定自己需要哪几个组合——大概率你会发现,两个就够了。
【免费下载链接】GTweakPortable Tool for an Ideal Windows Setup项目地址: https://gitcode.com/GitHub_Trending/gt/GTweak
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考