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 并不顶用——实测过,那条路走不通。
两个坑
坑一:用 WScript.Shell 改 .lnk,会把属性存储整段丢掉。之前为了修目标路径,用 CreateShortcut() 打开再 Save(),结果是快捷方式里原本的 System.AppUserModel.ID 凭空消失了。要改这类属性,必须走 IShellLink + IPropertyStore。
坑二(更隐蔽):IPersistFile::Load 必须传 STGM_READWRITE(值 2)。传 0 时属性存储是只读的,SetValue / Commit 会抛 STG_E_ACCESSDENIED——错误信息长得像"权限不够",其实是加载模式不对。为了确认这一点,把 .lnk 复制到可写目录再跑同一段代码,报同样的错,才把嫌疑从权限转到模式上。
用只读副本做隔离实验,是分诊"到底是权限问题还是代码问题"最快的一招。
改法与验收
- 给开始菜单的
Steam.lnk写入System.AppUserModel.ID = Valve.Steam.Client(这一步需要一次管理员授权); - 把固定项的图标从自制的小图指回程序自带的图标;
- 重启资源管理器;改了已固定项的图标还要清
iconcache_*.db才会刷新; - 把鼠标移到屏幕底边唤起任务栏,截图放大对比——白纸变成了正常的 Steam 图标。
留下的东西
把这套逻辑固化成了一个小工具:给它一个 .lnk 路径和一个 AUMID(或一个图标位置),它用 IShellLink 读写、默认 STGM_READWRITE、附带回退备份。踩过的坑写进了脚本头部注释,省得下次再试一遍。
一句话教训
报错信息里的"拒绝访问"未必真的指向权限。先分清"是环境不允许"还是"是我用错了模式",能省掉一整轮无效排查。