Skip to content

fix(registry): 修复 Shell 扩展名称错误解析,新增 MUIVerb 支持#5

Open
tzzs wants to merge 8 commits intomasterfrom
claude/elastic-swirles
Open

fix(registry): 修复 Shell 扩展名称错误解析,新增 MUIVerb 支持#5
tzzs wants to merge 8 commits intomasterfrom
claude/elastic-swirles

Conversation

@tzzs
Copy link
Owner

@tzzs tzzs commented Mar 13, 2026

问题

HKCR\DesktopBackground\shellex\ContextMenuHandlers\DesktopSlideshow 在应用中显示"在我的电池需要替换时发出警告",与菜单项实际含义完全无关。

根本原因:Level 3 DLL 字符串暴力扫描(范围 1-500)命中了 DLL 中与菜单无关的早期资源字符串。

变更内容

PowerShellBridge.ts

  • 移除 Level 3 DLL 字符串扫描(ReadDllStrings),CmHelper C# 源码同步精简(移除 LoadLibraryEx / FreeLibrary / LoadString P/Invoke)
  • Level 2 扩展为遍历 FileDescription / ProductName / InternalName / OriginalFilename 四个 VersionInfo 字段(foreach 取第一个合法值)
  • 原 Level 4(CLSID Default 值)晋升为 Level 3 兜底,彻底消除错误命名
  • buildGetItemsScript 新增 CmShell Add-Type + Resolve-MenuName 函数,经典菜单项名称读取优先级改为 MUIVerb → (Default) → 子键名,支持 @dll,-id 格式间接字符串解析

RegistryService.ts

  • 新增 RegistryCache 缓存层,getMenuItems 优先读缓存,enable/disable 操作后自动失效对应场景缓存
  • 名称兜底层:若 PowerShell 返回以 @ 开头的未解析字符串,自动替换为 subKeyName

测试

  • PowerShellBridge.test.ts:新增 12 个用例,覆盖 MUIVerb、CmShell、Level 3 移除、InternalName/OriginalFilename、CLSID Default 晋升等
  • RegistryService.test.ts:新增 5 个用例,含 DesktopSlideshow 问题场景端到端复现验证
  • MenuManagerService.test.ts:修复预存 mock 缺失(invalidateCachelog.debug

验证方式

  1. 以管理员身份运行 npm start
  2. 切换到「桌面右键」场景,确认 DesktopSlideshow 条目名称合理(键名或 VersionInfo 字段),不再显示"在我的电池需要替换时发出警告"
  3. 确认其他 Shell 扩展条目名称无明显异常
  4. 确认经典菜单项(存在 MUIVerb 的条目)名称正确显示

测试结果

Test Files  9 passed (9)
Tests       77 passed (77)

🤖 Generated with Claude Code

@chatgpt-codex-connector
Copy link

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

tzzs and others added 7 commits March 14, 2026 19:56
**根本原因**
buildGetShellExtItemsScript 的 Level 3 DLL 字符串扫描(范围 1-500)
会命中与菜单项无关的资源字符串,导致 DesktopSlideshow 等条目显示
"在我的电池需要替换时发出警告"等错误名称。

**变更内容**
- PowerShellBridge: 移除 Level 3 DLL 字符串暴力扫描(ReadDllStrings),
  CmHelper C# 源码同步精简(移除 LoadLibraryEx / FreeLibrary / LoadString P/Invoke)
- PowerShellBridge: Level 2 扩展为遍历 FileDescription / ProductName /
  InternalName / OriginalFilename 四个 VersionInfo 字段
- PowerShellBridge: 原 Level 4(CLSID Default 值)晋升为 Level 3 兜底
- PowerShellBridge: buildGetItemsScript 新增 CmShell Add-Type + Resolve-MenuName,
  经典菜单项名称读取优先级改为 MUIVerb → (Default) → 子键名,
  支持 @dll,-id 格式的间接字符串解析
- RegistryService: 新增缓存支持(RegistryCache),getMenuItems 优先读缓存,
  enable/disable 操作后自动失效对应场景缓存
- RegistryService: 名称兜底层 —— 若 PS 返回以 @ 开头的未解析字符串,
  自动替换为 subKeyName

**单元测试**
- PowerShellBridge.test.ts: 新增 12 个用例,覆盖 MUIVerb、CmShell、
  Level 3 移除、InternalName/OriginalFilename、CLSID Default 升级等
- RegistryService.test.ts: 新增 5 个用例,覆盖 @ 前缀名称净化场景
  (含 DesktopSlideshow 问题复现验证)
- MenuManagerService.test.ts: 修复预存 mock 缺失(invalidateCache、log.debug)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- buildGetShellExtItemsScript 从四级降为三级策略
  Level 1: LocalizedString(@ 格式或直接值,过滤泛型 COM 类型描述)
  Level 2: CLSID 默认值
  Level 3: 处理程序键名(最终兜底)
- Format-DisplayName 移除大小写规范化和热键清理(热键已迁至 TS 层)
- buildGetItemsScript 新增 LocalizedDisplayName 第三优先级

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
加载期间快速切换场景时,后续请求不再被丢弃,而是记录为
pendingScene,前一次加载完成后立即执行最新的待切换场景。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
原问题:/&\w/g 先执行,(&V) 中的 &V 被移除,剩余空括号 (),
后续 /\(&\w\)/g 无法匹配,导致"使用 Visual Studio 打开()"。

修复:
1. 调整顺序:/\(&\w\)/g 先于 /&\w/g 执行,整体匹配移除 (&V)
2. 新增 /\(\s*\)/g 空括号兜底,防止顺序问题遗留
3. 补充测试:覆盖 VS Code 打开、个性化、QQ 音乐三个典型场景

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- PowerShellBridge: Resolve-ExtName 在 LocalizedString 与 CLSID Default 之间插入
  Level 1.5 MUIVerb,修复 gvim 等扩展名称退化问题;同时读取 InprocServer32
  DLL 路径并展开环境变量后输出 dllPath 字段
- RegistryService: PsMenuItemRaw 新增 dllPath 字段,getMenuItems 映射时透传
- shared/types: MenuItemEntry 新增 dllPath?: string | null
- mainPage: ShellExt 详情面板拆为"COM 标识符"+ 可选"提供程序 DLL"两行
- 补充 PowerShellBridge/RegistryService 单元测试(共 9 个新用例)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
**核心 bug 修复**
当 handler 键名为 CLSID 格式而 Default 值为非 CLSID 字符串时(如
{90AA3A4E...} 的 Default = "Taskband Pin"),旧代码将 Default 值误用
作 $clsid 传入 Resolve-ExtName,导致 CLSID 正则匹配失败,名称退化为键名。

修复:引入 $actualClsid(键名优先为 CLSID)和 $directName(非 CLSID
Default 值)分离变量,$actualClsid 用于注册表查找和 command 字段输出,
$directName 作为 Resolve-ExtName Level 0 直接名称。

**移除 friendlyNames 硬编码映射表**
原映射表包含中文硬编码字符串('发送到'、'复制到文件夹'等),在 Level 0
优先级最高,屏蔽了 SHLoadIndirectString 的本地化机制,导致非中文系统
显示错误名称。移除后,Level 1 LocalizedString → SHLoadIndirectString
自动按系统语言返回正确本地化名称。

**新增 ms-resource: URI 支持**
- CmHelper.ResolveIndirect 扩展入口过滤,允许 ms-resource: 前缀通过
- LocalizedString、MUIVerb、directName 的 StartsWith 检查同步扩展
- Windows 11 MSIX 封装扩展的裸 ms-resource:// URI 不再作为原始字符串
  返回,而是先尝试 SHLoadIndirectString 解析,失败则静默降级

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Level 0 重构:directName 仅处理间接格式(@dll,-id / ms-resource:),
  普通字符串降级至 Level 3,避免英文技术名抢占本地化结果
- Level 1.7:预建 CommandStore 反向索引,通过 ExplorerCommandHandler
  CLSID 反查 MUIVerb,支持 Taskband Pin 等 ImplementsVerbs 扩展
- Level 2.5:读取 InprocServer32 DLL 的 FileDescription/ProductName,
  过滤泛型描述(shell extension、context menu 等)后作为兜底名称,
  解决 YunShellExt 等第三方扩展无法显示中文产品名的问题
- Level 3:directName 普通字符串兜底,位于所有 CLSID 查询链之后
- 更新测试:调整两个已废弃测试名/断言,新增 Level 2.5 专项测试(共 28 个)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@tzzs tzzs force-pushed the claude/elastic-swirles branch from 8497846 to c42d3aa Compare March 14, 2026 13:34
…n names

Add Test-IsGenericName function to centralize filtering of generic COM/shell descriptions. The function checks for patterns like "context menu", "class" suffix, and placeholders. Update all name resolution levels (1.5 MUIVerb, 2 CLSID Default, 2.5 DLL metadata) to use this common filter. Add corresponding test cases to verify the filtering behavior.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant