个人技术笔记

2026-10-08 · 排错记录 排错与验收

任务栏那个白图标:一次 AUMID 属性存储的排错

现象

任务栏上出现两个 Steam:一个是早就固定好的,图标正常;另一个是正在运行的客户端窗口,显示成一张白纸。

先排除掉的

  • 不是图标缓存——重建过、资源管理器也重启过;
  • 不是快捷方式坏了——目标路径与图标位置都指向正确文件;
  • 不是图标文件不存在——注册表里那条映射指向的 .ico 确实在盘上;
  • 不是"没写 AUMID 映射"——HKCU\Software\Classes\AppUserModelId\<id> 两条都写了 DisplayName 和 IconUri,图标依旧是白纸。

拿到一手事实,而不是继续猜

与其继续试,不如先问系统:这个窗口到底是哪个身份。用 SHGetPropertyStoreForWindow(hwnd) 读窗口属性存储里的 PKEY_AppUserModel_ID,结论很干脆——

steamwebhelper  [SDL_app]  "Steam"  AUMID = Valve.Steam.Client

问题于是变成一句话:系统要按这个 AUMID 去找图标,但没有任何一个快捷方式带着这个 AUMID。而光往注册表写 IconUri 并不顶用——实测过,那条路走不通。

任务栏图标解析链路:带 AUMID 的窗口,系统去找带同一 AUMID 的快捷方式并沿用其图标;找不到就只剩一张白纸
白图标的来源:系统按 AUMID 去找快捷方式,找不到就只剩一张白纸。

两个坑

坑一:用 WScript.Shell 改 .lnk,会把属性存储整段丢掉。之前为了修目标路径,用 CreateShortcut() 打开再 Save(),结果是快捷方式里原本的 System.AppUserModel.ID 凭空消失了。要改这类属性,必须走 IShellLink + IPropertyStore。

坑二(更隐蔽):IPersistFile::Load 必须传 STGM_READWRITE(值 2)。传 0 时属性存储是只读的,SetValue / Commit 会抛 STG_E_ACCESSDENIED——错误信息长得像"权限不够",其实是加载模式不对。为了确认这一点,把 .lnk 复制到可写目录再跑同一段代码,报同样的错,才把嫌疑从权限转到模式上。

用只读副本做隔离实验,是分诊"到底是权限问题还是代码问题"最快的一招。

改法与验收

  1. 给开始菜单的 Steam.lnk 写入 System.AppUserModel.ID = Valve.Steam.Client(这一步需要一次管理员授权);
  2. 把固定项的图标从自制的小图指回程序自带的图标;
  3. 重启资源管理器;改了已固定项的图标还要清 iconcache_*.db 才会刷新;
  4. 把鼠标移到屏幕底边唤起任务栏,截图放大对比——白纸变成了正常的 Steam 图标。

留下的东西

把这套逻辑固化成了一个小工具:给它一个 .lnk 路径和一个 AUMID(或一个图标位置),它用 IShellLink 读写、默认 STGM_READWRITE、附带回退备份。踩过的坑写进了脚本头部注释,省得下次再试一遍。

一句话教训

报错信息里的"拒绝访问"未必真的指向权限。先分清"是环境不允许"还是"是我用错了模式",能省掉一整轮无效排查。

← 回到笔记列表