前言
对于部分Windows用户来说,出于个性化或系统盘污染等其它方面考虑,可能会有重定向桌面地址的需求。
对于常规用户来说,Windows的资源管理器自带的桌面的UserAssist面板配置(即右键桌面
文件夹项的属性面板)已经能够满足此要求。利用面板的位置
栏,能够方便地完成对桌面的迁移,具体操作见下图:
NOTE
需要注意的是,当使用上述方法时,请确保注册表项完好。若用户默认的桌面
ShellFolder项被删除或被篡改破坏,位置
栏将不会被继承。此时需还原注册表或通过下文所列方法完成桌面重定向。
探索:如何去界面化完成桌面重定向?
从注册表切入
由于对于一般用户而言,将问题精准定位是困难的,故下文假定阅读者有一定的搜索能力并对Windows的资源管理器与注册表有一定了解。
方向①:借助搜索引擎,如何操作?
一般情况下,在使用搜索引擎直搜“切换桌面地址”或其它关键词后,都能够得到以下答案:
定位注册表项:
HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders
HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders
修改Desktop键值为重定向后的桌面地址
重启Explorer.exe(即资源管理器)
通过实践不难验证该方法是有效的。
然而不可否认的是,使用该方法相较于利用GUI界面迁移桌面不仅更低效,且存在一定的风险,故而意义并不大。(当注册表未损坏时)
不过通过上述流程经过简单的分析可以额外注意到以下内容:
- 桌面是依靠Explorer.exe渲染的
- Explorer.exe通过读取上述注册表项的键值来定位桌面的地址
- 独立改动注册表并不会主动引发Explorer.exe的刷新
- ☆☆☆☆☆
桌面
与我的文档
等其他可重定向的文件夹可能同属于一类事物,即Shell Folder
方向②:基于已知的大方向,如何自行探索?
前置经验:
- Explorer.exe提供包含桌面在内的所有文件系统浏览服务
- SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer为Explorer.exe独属的注册表项
基于上述经验,以下给出两类探索方法:
(一)ProcessMonitor 监控法
该方法逻辑在于监控桌面重定向
实际发生过程中系统范畴内相关对象的一切行为来定位关联的变量。
方法采用Microsoft Sysinternals Utilities列表提供的Process Monitor工具完成对进程行为的监控。
简单熟悉Process Monitor的操作方法后,考虑到非编程方法方便直接操作的只有注册表,故而将监控的目标设定为Explorer.exe
,Operation
筛选为包含 Reg 且包含 Key
。再根据经验判断,能够显式的与变化关联的应当只有写操作,对于注册表来说即RegSetKeyValue
与RegCreateKey
方法。
完成以上设定后,操作系统自带的桌面移动面板,监控确定按钮按下
到桌面完成迁移
时间段内的行为。
以下为监控期间发生的行为(仅供参考):
因为重定向是地址字符串的变动,所以优先考虑注册表键值类型为REG_SZ
或REG_EXPAND_SZ
的键。可以发现变动的恰好是User Shell Folders
、Shell Folders
两项的Desktop
值。其后浏览其他键值变更,可以简单判断并非是影响目标的关键因素。此时可以确定该两项注册表项即为影响桌面定位的关键。
手动在regedit
修改两项的关联键值,并尝试手动F5
刷新,发现并未桌面并未更新。重启资源管理器后完成刷新。
重复监控过程,观察Explorer.exe
在注册表之外的其它操作,可以发现在上述注册表项修改完之后Explorer.exe
直接进行了文件映射的创建。此时基本可以判断在注册表项更新后或是更新前,存在某项操作通知了资源管理器操作的发生,而该操作并未被Process Monitor捕获,故无法继续使用非编程方法完成桌面的重定向。
(二)猜测 + 黑箱方法
尝试定位影响桌面路径的因素时,几个关键词是必不可少的。
合理利用Explorer
、Desktop
、Folder
、${当前桌面路径}
、${重定向后桌面路径}
几个关键字搜索筛选出可能相关的表项,并考察桌面重定向前后表项键值的变更情况,同样不难筛选出Shell Folders
、User Shell Folders
两个关联项。
后续操作同上。
总结
上述几种方法都能够较快的筛选出关联注册表项,但是没有编程方法的介入,都难以像Windows自带面板提供的操作那样完成高效、无缝切换。
从WINAPI切入
在方法①中,可以注意到桌面
跟一个名为Shell Folder
的对象脱不开关系。通过对MS DOC的查阅可以知道,Shell Folder
是Shell对象范畴内对文件夹的一类拓展,其扩大了文件夹的范畴,允许通过自定义的具体实现将异形对象抽象为文件夹实例。其中链接向物理路径的Shell Folder
实现于系统内置的Shell File System Folder
接口。
Shell Folder在微软文档中并无专门的篇幅描述,相关的样例代码也给得不多。对于初次接触到WINAPI Shell编程的人,庞大的框架与零散的API可能导致思路梳理困难,流程运行逻辑不清。
如若实在需要实例代码,可以尝试访问HotExample进行搜索。
此外,本文不会对相关内容进行过多阐述。
在Windows中,每一个Shell实例(通常)都有GUID描述符,对于Shell Folder
,其GUID可以在WINSDK - knownfolders.h
中找到。
在其中可以看到Desktop
的GUID为{B4BFCC3A-DB2C-424C-B029-7FE99A87C641}
。转向对注册表的访问,可以在SOFTWARE\Classes\CLSID
下找到该项。
浏览子项与键值可以发现,Desktop
继承自{0E5AAE11-A475-4c5b-AB00-C66DE400274E}
也即Shell File System Folder
,而从Instance\InitPropertyBag\TargetKnownFolder
可以知道,该Shell Folder
(即Desktop
)是指向同名实例的。
需要提及的是,Instance\InitPropertyBag\TargetFolderPath
与TargetKnownFolder
一样都是用于指定文件夹路径的,不同的是,前者指定的是具体路径,而后者指定了预定义的Folder ID
。
不过,借助相关资料和注册表搜索可以发现,此处的{B4BFCC3A-DB2C-424C-B029-7FE99A87C641}
并非是一个自引用。在CLSID
下定义的Shell Folder
最终会引用在SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\FolderDescriptions
中定义的Folder ID
,最终传递给Explorer.exe
。
以下是二者的注册表项层级结构:
+ CLSID\{B4BFCC3A-DB2C-424C-B029-7FE99A87C641}
- DescriptionID
- System.IsPinnedToNameSpaceTree
+ DefaultIcon
- (Default): C:\Windows\system32\imageres.dll,-183
+ InProcServer32
- (Default): C:\Windows\system32\shell32.dll
- ThreadingModel: Both
+ Instance
- CLSID: {0E5AAE11-A475-4c5b-AB00-C66DE400274E}
+ InitPropertyBag
- Attributes
- TargetKnownFolder: {B4BFCC3A-DB2C-424C-B029-7FE99A87C641}
+ ShellFolder
- Attributes
- FolderValueFlags
- SortOrderIndex
+ FolderDescriptions\{B4BFCC3A-DB2C-424C-B029-7FE99A87C641}
- Attributes
- Category
- Icon: C:\Windows\system32\imageres.dll,-183
- LocalizedName: @C:\Windows\system32\shell32.dll,-21769
- Name: Desktop
- PreCreate
- PublishExpandedPath
- RelativePath: Desktop
- Roamable
+ PropertyBag
- NoCustomize
相较于TargetFolderPath
的直接,TargetKnownFolder
则是绕了点弯来实现目标的定位。
从黑箱方法大致可以推断Explorer.exe
对Shell Folder
处理流程如下(不保证为系统实际情形):
FolderDescription\<GUID>\Name
将定义Known Folder
的名称,而Explorer.exe
将通过该名称在Shell Folder
、User Shell Folder
中检索得到其链接的路径。
了解上述流程之后,其实就可以发现重定向文件夹只有两步操作:
- 变更
Known Folder
的目标路径- 通知资源管理器
Known Folder
已经发生变化
在Shell对象编程中,有专门的方法SHChangeNotify
用于通知Shell对象相关事件的发生。
仔细阅读文档后,对于方法第一参数的选择可以是SHCNE_UPDATEDIR
或者SHCNE_UPDATEITEM
。
/** path is the re-located path of target known folder */
SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_FLUSH | SHCNF_PATH, path, nullptr);
手动修改注册表项并使用上述代码完成对更新事件的分发,发现桌面依旧没有变化。由上述操作的总结,那么在假定事件成功分发的情况下,则可以怀疑被分发的事件并不实际存在,即:“变更 Known Folder 的目标路径”并未真正完成。
查阅官方文档发现存在专门的方法SHSetKnownFolderPath
完成对Known Folder
目标路径的设置。
重写核心代码如下:
#include <shlobj.h>
bool SwitchDesktopTo(const wchar_t *path) {
auto hr = SHSetKnownFolderPath(FOLDERID_Desktop, 0, nullptr, path);
if (!SUCCEEDED(hr)) return false;
LPITEMIDLIST list = nullptr;
SHGetKnownFolderIDList(FOLDERID_Desktop, 0, nullptr, &list);
if (!list) return false;
SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_FLUSH | SHCNF_IDLIST, list, nullptr);
ILFree(list);
return true;
}
目标达成,重定向的效果与系统面板提供的方法并无肉眼上的差别。
后期使用Process Monitor对该过程进行监控发现,注册表项的变动依旧限定在Shell Folder
与User Shell Folder
之内,故大致可以推断之前失败的原因在于SHSetKnownFolderPath
除了修改表项键值之外还在方法内部做了额外的操作;也正是因为该部分操作未被纳入手动hack,又导致了SHChangeNotify
没有成功地分发有效事件。
写在最后
到此为止,通过桌面重定向的探索,包含桌面实例在内的一系列IShellFolder
接口实现的外部结构大致清朗的。但是不论是相关注册表项的各键值的含义还是内部handler实现都处于相对模糊的状态。
该部分再通过像本文这样浅显的方法探究就不适合了。归根到底,要搞清楚Shell Object
,就得深入Windows Shell开发,就得通读文档,多联系多思考。
本文描述的Desktop
等同类文件夹作为Shell File System Folder
接口实例的实现,近似于一个简单的路径“快捷方式”,相比于子系统文件系统、共享文件夹、网络邻居这类高级实例还差得远。
共勉!