相信很多人在面对Windows时,时常和我有一样的烦恼,右键菜单被一些无用的软件垃圾填满,windows自动更新难以关闭,每次重装就需要重新去查看一下注册表目录重新去设置一下,莫名其妙的软件自启项,和一些文件的格式转换,还有内存的优化,对于计算机大佬来说这很简单,但是对于一个计算机小白,这无疑是十分麻烦的,这也是我开发这个工具箱的初衷。接下来给一些刚开始接触计算机这个行业的同学讲解一些小知识。(大佬勿喷)
0x00 右键菜单
windows的资源管理器会按「当前对象是什么」去 HKEY_CLASSES_ROOT 里找菜单。同一个右键会有很多层:先看这个对象自己的键,再看它的类型,最后看 * 和 AllFilesystemObjects。所以「文件」页和「所有对象」页看到的不是同一批项。
静态命令放在 shell\<名字>。显示名读 MUIVerb,没有就读默认值;要点击后执行的程序在子键 command 的默认值里。@shell32.dll,-8506 这种值不是标题,调用 SHLoadIndirectString 才能换成当前语言的文字。
HKEY_CLASSES_ROOT\*\shell\OpenWith
MUIVerb REG_SZ
command REG_SZ "C:\Program Files\App\app.exe" "%1"
各页面实际读取的键:
| 页面 |
注册表位置 |
| 文件 |
HKEY_CLASSES_ROOT\*\shell |
| 文件夹 |
HKEY_CLASSES_ROOT\Folder\shell |
| 目录 |
HKEY_CLASSES_ROOT\Directory\shell |
| 目录背景 |
HKEY_CLASSES_ROOT\Directory\Background\shell |
| 桌面 |
HKEY_CLASSES_ROOT\DesktopBackground\shell |
| 磁盘 |
HKEY_CLASSES_ROOT\Drive\shell |
| 所有对象 |
HKEY_CLASSES_ROOT\AllFilesystemObjects\shell |
| 此电脑 |
HKEY_CLASSES_ROOT\CLSID\{20D04FE0-3AEA-1069-A2D8-08002B30309D}\shell |
| 回收站 |
HKEY_CLASSES_ROOT\CLSID\{645FF040-5081-101B-9F08-00AA002F954E}\shell |
| 库 |
LibraryFolder、LibraryFolder\Background、UserLibraryFolder |
| 快捷方式 / exe |
先查扩展名的 ProgId,再读那个 ProgId 的 shell |
| UWP |
HKEY_CLASSES_ROOT\Launcher.ImmersiveApplication\shell |
| 未知类型 |
HKEY_CLASSES_ROOT\Unknown\shell |
很多第三方程序挂在 ShellEx\ContextMenuHandlers。每一项的默认值是一个 CLSID,资源管理器加载这个 COM 对象来画菜单。拖放菜单是同一层的 DragDropHandlers。
隐藏不能一律删键,删了就找不回命令。这里按菜单的种类改:
普通 shell 项写入三个值。HideBasedOnVelocityId 用 DWORD 0x639bc8,这是资源管理器用来折叠菜单的标记;没有子菜单的项再写空字符串 LegacyDisable 和 ProgrammaticAccessOnly。子菜单只写第一个,后两个会把带子命令的项拆开。重新显示就是把这三个值删掉,command 原样留着。
private const int Velocity = 0x639bc8;
RegStore.SetDword(path, "HideBasedOnVelocityId", Velocity);
if (!entry.IsSubMenu)
{
RegStore.SetString(path, "LegacyDisable", "");
RegStore.SetString(path, "ProgrammaticAccessOnly", "");
}
ShellEx 不改值。资源管理器只枚举 ContextMenuHandlers,把整个子键搬到同级的 -ContextMenuHandlers 后它就不再加载;显示时再搬回去。拖放项对应 -DragDropHandlers。
其余入口不在 shell 下面:
| 入口 |
位置 |
隐藏做法 |
| 新建 |
HKEY_CLASSES_ROOT\.ext\ShellNew |
键改名为 -ShellNew。只处理带 NullFile、Data、FileName、Directory、Command、Handler 之一的项 |
| 发送到 |
%AppData%\Microsoft\Windows\SendTo\*.lnk |
给 lnk 加 FILE_ATTRIBUTE_HIDDEN |
| 打开方式 |
HKEY_CLASSES_ROOT\Applications\<程序.exe> |
写空值 NoOpenWith,命令在 shell\open\command |
| Win+X |
%LocalAppData%\Microsoft\Windows\WinX\GroupN\*.lnk |
同样加隐藏属性,要重新登录才生效 |
| IE |
HKCU\SOFTWARE\Microsoft\Internet Explorer\MenuExt |
搬到同级 -MenuExt |
| GUID 屏蔽 |
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Blocked,以及 HKCU 的同一路径 |
值名是 CLSID,值是空字符串。已有菜单靠这一项消失 |
改名写的是 MUIVerb。改命令写的是 shell\<名字>\command 的默认值。新增一项就是在对应对象的 shell 下建子键,默认值放名字,command 放要执行的命令行。
有的键管理员也写不进去。这时先打开 SeTakeOwnershipPrivilege、SeRestorePrivilege,把所有者改成 Administrators,再给这个 SID 完全控制,然后重试创建。
0x01 开机启动项
Windows 没有单独的「启动项数据库」。能在登录时跑起来的东西分散在注册表、两个自启文件夹、任务计划和服务里。程序按这个范围逐个读,界面分成注册表、文件夹、计划任务、服务、应用五类。
最常见的是 Run。值名是显示名,值是命令行:
HKCU\Software\Microsoft\Windows\CurrentVersion\Run
HKLM\Software\Microsoft\Windows\CurrentVersion\Run
HKLM\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Run
任务管理器的开关不在这里。它在旁边的 StartupApproved,每个启动项对应一个 12 字节的 REG_BINARY:
HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved\Run
HKLM\...\StartupApproved\Run
HKLM\...\StartupApproved\Run32
HKCU 或 HKLM\...\StartupApproved\StartupFolder
首字节偶数表示启用:注册表项是 0x02,启动文件夹是 0x06。奇数表示关闭,关闭时写成 0x03,第 4 字节起是当前时间的 FILETIME。没有这个值也当成启用。Run 的命令行始终留在原处,只改这 12 个字节,所以和任务管理器看到的是同一状态。
static bool IsEnabled(byte[]? data) =>
data == null || data.Length == 0 || (data[0] & 1) == 0;
static byte[] DisabledBlob()
{
var data = new byte[12];
data[0] = 3;
BitConverter.GetBytes(DateTime.UtcNow.ToFileTime()).CopyTo(data, 4);
return data;
}
启动文件夹是实体 lnk,不是注册表:
%AppData%\Microsoft\Windows\Start Menu\Programs\Startup
%ProgramData%\Microsoft\Windows\Start Menu\Programs\Startup
文件留着,开关同样写到 StartupApproved\StartupFolder,值名是文件名。
Run 之外还有一批注册表位置。这些位置没有 StartupApproved,关闭时先把原值复制到 HKLM\Software\magictools\StartupBackup\Reg\<id>,记下所在根键、路径、值名、类型和数据,再删掉原值。打开时按备份写回,然后删除备份。
| 作用 |
路径 |
| 只跑一次 |
CurrentVersion\RunOnce,含 HKCU、HKLM、WOW6432Node |
| 策略强制 |
CurrentVersion\Policies\Explorer\Run |
| 旧服务式启动 |
RunServices、RunServicesOnce |
| 远程安装留下的 |
Windows NT\CurrentVersion\Terminal Server\Install\...\Run 和 RunOnce |
| 按编号执行 |
RunOnceEx 及 32 位节点 |
| 登录时补装组件 |
Active Setup\Installed Components 里的 StubPath |
| 登录进程 |
Windows NT\CurrentVersion\Winlogon 的 Shell、Userinit |
| 开机原生程序 |
SYSTEM\CurrentControlSet\Control\Session Manager 的 BootExecute |
| 组策略脚本 |
Group Policy\Scripts\Startup、Scripts\Logon |
| 旧式注入 |
Windows NT\CurrentVersion\Windows 的 Run、Load、AppInit_DLLs |
Userinit 和 Shell 是逗号分隔的一段字符串,不能整值删除。只从中去掉这一段,原段放进备份;Userinit 写回时保留末尾逗号。BootExecute 是多字符串,按行删除,autocheck 这种系统行不提供开关。
计划任务不在注册表里开关。用 Schedule.Service 连本机,找到任务后改它的 Enabled。\Microsoft\Windows 下面的系统任务只展示,不参加「全部关闭」。
服务的启动类型在 HKLM\SYSTEM\CurrentControlSet\Services\<名字> 的 Start:2 自动,3 手动,4 禁用。改的时候走 ChangeServiceConfig,不直接改这个 DWORD。关闭前把原来的 Start 和 DelayedAutostart 存到 HKLM\Software\magictools\StartupBackup\Services\<名字>,打开时按备份恢复。
应用商店程序的启动任务在:
HKCU\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\AppModel\SystemAppData\<包名>\StartupTasks\<任务>
State 为 2 是用户允许,1 是用户禁止。关闭写 State=1,并写 LastDisabledTime;打开写 State=2 和 UserEnabledStartupOnce=1。State 为 0 或 4 是策略决定的,不改。
0x02 Windows 自动更新
把 wuauserv 设成禁用后,WaaSMedicSvc 和更新计划任务还会把它改回自动。所以要同时动服务、服务的注册表权限、策略、计划任务和几个会拉起更新的程序。
先改这些服务的启动类型。数字是「恢复时的类型,关闭时的类型」:
("wuauserv", 2, 4) // Windows Update:自动 → 禁用
("dosvc", 2, 4) // 传递优化
("WaaSMedicSvc", 3, 4) // 更新医疗:手动 → 禁用
("UsoSvc", 2, 3) // 更新编排:自动 → 手动
("BITS", 3, 3) // 后台传输,保持手动
("InstallService", 3, 4)
ChangeServiceConfig 写启动类型。关闭时发送停止控制,最多等 15 秒直到状态变成已停止;恢复成自动时再启动。2 是自动,3 是手动,4 是禁用。
仅改启动类型仍会被改回去,接着做四层。
第一层改服务键的权限。键在 HKLM\SYSTEM\CurrentControlSet\Services\<服务名>。先取得所有权,再把 DACL 换成:Everyone、SYSTEM、Administrators 都拒绝 SetValue、创建子键和删除,管理员另有完全控制,其他身份只能读。这样系统组件不能再把 Start 写回 2。下次要修改这个服务,先看访问控制里有没有这条 Deny,有就先恢复成 SYSTEM 和管理员完全控制。
第二层改启动程序。ImagePath 原值备份到 HKLM\SOFTWARE\WindowsUpdateBlocker\ImagePath,然后改成字符串 WubBlocked。只对上面禁用类型是 4 的服务做。恢复时用备份写回并删除备份。
第三层是组策略,位置和值:
HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU
NoAutoUpdate = 1
HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate
DisableWindowsUpdateAccess = 1
HKLM\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings
TrayIconVisibility = 0
恢复时删除前两个值,托盘值改回 1。
第四层关计划任务。通过 Schedule.Service 把这些任务的 Enabled 设为 false:
Microsoft\Windows\WindowsUpdate\*
Microsoft\Windows\UpdateOrchestrator\Schedule Maintenance Work
Microsoft\Windows\UpdateOrchestrator\Schedule Wake To Work
Microsoft\Windows\UpdateOrchestrator\Schedule Work
Microsoft\Windows\UpdateOrchestrator\*
Microsoft\Windows\WaaSMedic\PerformRemediation
带 * 的是整个文件夹。先处理写明全路径的任务,文件夹枚举时跳过它们,避免覆盖刚才的单独设置。
最后挡住几个会自己跑起来的程序。在下面两个键下为每个 exe 建子键,写 Debugger 为 WubBlocked.exe:
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\<exe>
HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\<exe>
包括 WaaSMedic.exe、UsoClient.exe、Windows10Upgrade.exe、MusNotification.exe 等。Windows 启动它们之前会先去运行这个调试器,调试器不存在,程序也就起不来。恢复时只删除值等于 WubBlocked.exe 的 Debugger,别的调试器设置不动。关闭时还可以先结束已经在跑的 MusNotifyIcon.exe、MoUsoCoreWorker.exe 这些通知进程。
0x03 内存优化
这里不结束任何进程。占用率来自 GlobalMemoryStatusEx 的 MemoryLoad,已用内存是总物理内存减去可用物理内存。清理前后各读一次,两次已用的差就是腾出的数量。
Windows 把物理页分成进程正在用的工作集、文件缓存、待机列表和已修改列表。待机列表里的页已经不属于进程,只是留着备用;文件缓存是系统代表所有进程保留的已读文件页。这两部分可以交回去,进程下一次访问时再从磁盘读入。
清理通过 NtSetSystemInformation 完成,调用前用 RtlAdjustPrivilege 打开 SeProfileSingleProcessPrivilege(5)和 SeIncreaseQuotaPrivilege(13)。没有这两个特权,系统直接返回 0xC0000061。
const int SystemFileCacheInformation = 21;
const int SystemMemoryListInformation = 80;
const int SystemFileCacheInformationEx = 81;
const int SystemCombinePhysicalMemoryInformation = 130;
const int SystemRegistryReconciliationInformation = 155;
const int MemoryEmptyWorkingSets = 2;
const int MemoryFlushModifiedList = 3;
const int MemoryPurgeStandbyList = 4;
const int MemoryPurgeLowPriorityStandbyList = 5;
每一项对应一次调用:
| 选项 |
实际动作 |
| 工作集 |
类 80,命令 2。系统修剪各进程工作集,页进入待机列表,进程不退出 |
| 系统文件缓存 |
类 81,失败再试类 21。把缓存工作集的最小值和最大值都设为 -1,缓存被收缩 |
| 低优先级待机列表 |
类 80,命令 5。丢掉优先级最低的备用页 |
| 待机列表 |
类 80,命令 4。清空全部备用页 |
| 已修改页列表 |
类 80,命令 3。要求把改过但还没写盘的页写出去 |
| 已修改文件缓存 |
不走 ntdll。对每个就绪的本地盘、可移动盘打开 \\.\C:,调用 FlushFileBuffers |
| 注册表缓存 |
类 155,只在 Windows 8.1 及更新版本上调用 |
| 合并物理内存 |
类 130,只在 Windows 10 及更新版本上调用,把内容相同的物理页合成一份 |
默认勾选工作集、系统文件缓存、低优先级待机列表、注册表缓存、合并物理内存、已修改文件缓存。自动清理额外排除「待机列表」和「已修改页列表」:这两项会把随后还要使用的页也清掉,接着就发生大量读盘。
自动清理有两个条件,配置在 HKCU\Software\magictools\Memory。占用率达到设定值(限制 50–99)且距离上次至少 30 秒,或者到达设定的分钟间隔(1–720)。两个条件满足任意一个就按当前掩码清理一次。这也是PCL的内存优化的逻辑,我以前听朋友说PCL的内存优化很好用,对这个实现也确实是很感兴趣,就研究了一下,本来还想添加C盘清理,但是C盘确实太过于复杂,我也怕清理到重要的文件损坏系统,所以就先不加了,后续再研究研究项目链接:Magictools。