在 Windows 资源管理器「详细信息」视图的列里直接看到 .rvt / .rfa / .rte / .rft 的 Revit 版本。
- 不需要点开属性对话框
- 不需要靠图标角标
- 不需要打开 Revit
- 可以排序、可以筛选(
Revit Format >= 2021)
名称 Revit Version Revit Build 工作共享
T5_MBC_CS-KPF-T5A-ZZ-ARC-M3-00606.rvt 2023 20250724_1515(x64) Central
rac_basic_sample_project.rvt 2020 20190109_1515(x64) -
BatchPrintInitial.rvt 2008 20080126_1900 -
从 Releases 下载 RvtVersionSetup.exe(或整个 RVT-V-v1.2.1.zip),双击,UAC 点「是」,装完即可用。
单文件 1.3 MB,DLL / propdesc / 注册工具 / 列监视器全部内嵌,不需要解压、不需要 .NET、不需要联网。
装完直接打开任意含 .rvt 的文件夹 → 切到「详细信息」→ 列会自动出现,不用勾选。
不含 Revit 文件的文件夹不会多出空列。
仓库里不存放编译产物,只有源码。要从源码构建见 四、构建。
卸载:设置 → 应用 → 找「RVT-V · 资源管理器 Revit 版本列」→ 卸载。
或直接跑 C:\Program Files\RvtVersion\RvtVersionSetup.exe /uninstall。
安装程序命令行:
| 参数 | 作用 |
|---|---|
| (无) | 正常安装,自动提权 |
/uninstall |
卸载 |
/quiet |
静默,不等按键 |
/extract <dir> |
只释放载荷,不注册(自检用,不需要管理员) |
日志落在 %TEMP%\RvtVersionSetup.log。
安装时自动注册开机启动(HKCU\...\Run)并立即拉起,用户无需操作。
rvtvwatch.exe --status # 看当前各窗口状态(含 Revit / 列是否可见)
rvtvwatch.exe --once # 只同步一次就退出
rvtvwatch.exe --install # 注册开机启动
rvtvwatch.exe --remove # 取消开机启动
taskkill /IM rvtvwatch.exe /F # 临时停掉轮询 700 ms,但只在窗口集合或路径变化时才真正动 Shell,空闲时 CPU 占用可忽略 (实测常驻内存约 9.5 MB)。
上一版只做了 ShellIconOverlayIdentifiers(图标覆盖层)。覆盖层是在图标角落叠小角标,
和列表列是两套完全独立的 Shell 机制,所以列表里自然什么都没有。
要在列表里显示自定义字段,Windows 只有一条正规路径,三个组件缺一不可:
| 组件 | 作用 | 缺了会怎样 |
|---|---|---|
| Property Handler(COM 原生 DLL) | 按需解析文件,返回属性值 | 列是空白 |
Property Description(.propdesc XML) |
向属性系统声明「存在这个字段」 | 列头右键菜单里找不到这个列 |
| ShellEx 注册 | 把处理器挂到 .rvt 等扩展名上 |
资源管理器不会去调你的 DLL |
三样都注册到 HKLM,所以需要管理员权限。
另外还有两个容易漏的注册位置(本轮踩到过,见排障章节):
| 位置 | 作用 |
|---|---|
HKCR\.rvt\ShellEx\{8895b1c6-...} |
旧式 handler 挂载点 |
HKLM\...\PropertySystem\PropertyHandlers\.rvt |
Win7+ Shell 的主查找路径 |
只写前者 → 列头能出现,但每行全空白。这个症状组合极具误导性。
src/
RevitVersion.Native/ ★ 属性处理器 + 安装程序(C++,交付主路径)
PropKeys.h 自定义 FMTID + 8 个 PROPERTYKEY
RevitCfb.h 原生 OLE2 只读读取器(FAT/MiniFAT/DIFAT)
BasicFileInfo.h 跨版本语义解析(双向对齐 + 字段择优)
PropertyHandler.cpp IPropertyStore / IInitializeWithStream|File
/ IPropertyStoreCapabilities / IPropertyStoreFactory
ShellColumnSync.h ★ 按文件夹条件化列的核心逻辑(可复用)
RvtColumnWatcher.cpp ★ 常驻监视器 rvtvwatch.exe
ShellDiag.cpp 诊断工具 rvtvdiag.exe(注册检查 / 真实视图取证)
Setup.cpp 安装程序主体(提权 / 注册 / 卸载 / 自检)
Setup.rc + Setup.manifest 载荷嵌入 + 应用清单
PropSchemaTool.cpp propdesc 注册 / 注销 / 验证(替代 Add-Type)
HandlerTest.cpp 进程内加载 DLL、枚举 8 个属性(不需注册即可测)
RvtVersion.propdesc 属性描述 schema
RvtVersionHandler.def 只导出 DllGetClassObject / DllCanUnloadNow
build.bat MinGW 构建(一次产出全部六个产物,含安装包)
RevitVersion.Core/ netstandard2.0,零第三方依赖(CLI 复用)
CompoundFileReader.cs 自研最小 OLE2 只读读取器
RevitFileProbe.cs 探针(注意:仍是旧固定偏移实现)
RevitVersionMap.cs 格式号 → 年份映射
RevitVersion.Cli/ net10.0-windows,命令行工具 rvtv.exe
交付主路径是 RevitVersion.Native。RevitVersion.Core / Cli 是早期探索产物,
保留备查;CLI 的固定偏移实现对 2023 文件仍然不准,以原生 DLL 为准。
早期还有过一个 RevitVersion.Shell(图标覆盖层 / IShellFolder2 wrapper 两代方案),
已被属性处理器 + 列监视器完全取代,代码已移除(见 git 历史)。
rvtvdiag.exe 是排障主力,子命令一览:
| 子命令 | 用途 |
|---|---|
--check |
检查全部注册项(CLSID / ShellEx / PropertyHandlers / schema) |
--store <file> |
走 Shell 的真实代码路径读属性并打印 |
--view-cols <dir...> |
真实拉起 Explorer 视图,问 Shell「这个文件夹显示哪些列」 |
--view-cols --persist <dir> |
同上,但会读取已保存的视图状态(验证持久化) |
--set-cols <dir> add|remove |
用自建浏览器改列(不持久化,仅用于对照实验) |
--real-windows [add|remove|force-add|force-remove] |
枚举/操作真实窗口的列 |
--close-windows <子串> |
正常关闭匹配窗口(触发视图状态保存) |
--bag-hashes |
逐个 Bag 检查 ColInfo 哈希与是否含我们的 FMTID(持久化硬证据) |
--list-foldertypes / --dump-cols |
列出 FolderType 与 ColumnList |
--add-col-user / --del-col-user <GUID> |
往 HKCU 写/撤 FolderTypes 覆盖 |
--access <GUID> |
检查某键的读写权限与所有者 |
--probe-user-paths |
探测 HKCU 各候选路径可写性 |
--user-bags / --rvt-bags / --bagmru-raw |
查看用户视图缓存 |
需要 MinGW-w64(g++)。设 MINGW_ROOT 指向其根目录,或把 g++ 放进 PATH。
cd src\RevitVersion.Native
set MINGW_ROOT=C://mingw64
build.bat产物在 out\:RvtVersionHandler.dll、rvtvwatch.exe、rvtvdiag.exe、rvtvpropschema.exe、rvtvhandlertest.exe、RvtVersionSetup.exe。
rvtvhandlertest.exe 不参与安装,只用于不注册 DLL 就验证解析逻辑:
out\rvtvhandlertest.exe "D:\some\folder\some.rvt".NET 部分(RevitVersion.Core / RevitVersion.Cli)单独构建:
dotnet build src\RevitVersion.Core -c Release
dotnet build src\RevitVersion.Cli -c Release编译细节、实现原理、逆向过程、实测数据见 docs/notes.md。
检查注册是否完整(DLL 装好后,用安装器自带的注册检查):
& 'C:\Program Files\RvtVersion\rvtvdiag.exe' --check13 项:文件 / CLSID / InprocServer32 默认值 / 4 个扩展名挂载 / schema 注册 /
属性系统实际可读数 / 真实文件解析。全部 PASS 即为完全就绪。
两种可能,按顺序查:
# A. 注册表里有没有条目指向我们的 propdesc
$b='HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\PropertySystem\PropertySchema'
Get-ChildItem $b | ForEach-Object {
(Get-ItemProperty $_.PSPath).'(default)'
}
# B. 属性系统能否真读到(这个才是关键)
& 'C:\Program Files\RvtVersion\rvtvpropschema.exe' --verify- A 有路径、B 是
0 / 8→ propdesc 结构错了。根节点必须是<schema schemaVersion="1.0">, 见 notes 4.3。改完必须彻底删掉PropertySchema\*里那一条再重新注册(见 notes 4.4)。 - A 没有路径 →
PSRegisterPropertySchema失败,看%TEMP%\RvtVersionSetup.log。
这是本项目踩得最深的坑,根因是 PropertySystem\PropertyHandlers 未注册。
一条命令定位(自带工具,不需要管理员):
& 'C:\Program Files\RvtVersion\rvtvdiag.exe' --check看输出的 汇总: ShellEx N/4, PropertyHandlers M/4:
| 现象 | 含义 | 处理 |
|---|---|---|
PropertyHandlers 0/4 |
直接原因:Shell 取 IPropertyStore 的主查找路径没注册 |
重装(1.1.0 起已自动写) |
| 两行都 4/4,但列仍空 | 属性缓存 | 重启资源管理器 |
PropHandlers 4/4 且 --store 有值 |
已正常,只是没勾列 | 列头右键勾选,或 --columns |
再确认 Shell 真的能取到值(与资源管理器完全相同的代码路径):
& 'C:\Program Files\RvtVersion\rvtvdiag.exe' --store "D:\path\to\file.rvt"期望输出末尾是:
flags=0x01 HANDLERPROPERTIESONLY hr=0x00000000
[HANDLERPROPERTIESONLY] 属性总数 = 8
>> ProductYear = "2023"
结论: Shell 能拿到 Revit Version 值 —— handler 已生效
HANDLERPROPERTIESONLY 返回 hr=0x80004005(E_FAIL)或属性数为 0,
说明 Shell 认为 .rvt 没有 handler —— 就是 PropertyHandlers 的问题。
Windows 有两套并行的注册位置,缺一不可:
| 位置 | 作用 |
|---|---|
HKCR\.rvt\ShellEx\{8895b1c6-b41f-4c1c-a562-0d564250836f} |
旧式接口,部分组件仍查 |
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\PropertySystem\PropertyHandlers\.rvt |
Win7+ Shell 主查找路径 |
只写前者:schema 已注册 → 列头能出现;但 handler 不被调用 → 每一行都空白。 这个组合症状极具误导性,看起来像"解析失败",实际是"根本没被调用"。
必须用 KEY_WOW64_64KEY 写,64 位 Shell 读不到 32 位视图。
按顺序排查:
# 1. InprocServer32 默认值是否真指向 DLL(不能为空,也不能有垃圾 (default) 值)
$k=[Microsoft.Win32.Registry]::LocalMachine.OpenSubKey('SOFTWARE\Classes\CLSID\{5AD3E9FC-A720-47F6-8DFA-9AACD3286E01}\InprocServer32')
"default = [" + $k.GetValue('') + "]"
"names = " + ($k.GetValueNames() -join ' | ')
# 2. 绕过 Shell 的注册配置,直接问 PropertySystem 要值
& 'C:\Program Files\RvtVersion\rvtvdiag.exe' --store "D:\some\folder\some.rvt"- 1 的
default为空,或names里出现(default)这个值名 → 用了Set-ItemProperty -Name '(default)'写默认值(那是错的,会创建普通值)。 重装即可修正。 - 2 有值、1 正常、
--check两行都 4/4,列仍空白 → 重启资源管理器(属性缓存)。 - 2 也全是
props=0→ 解析层问题,见 notes 4.3 / notes 4.4。
UAC 被拒绝。右键 RvtVersionSetup.exe → 「以管理员身份运行」。
先看监视器在不在跑:
tasklist /FI "IMAGENAME eq rvtvwatch.exe"
& 'C:\Program Files\RvtVersion\rvtvwatch.exe' --status--status 的输出会直接告诉你每个窗口的判断结果:
=== 当前窗口状态 ===
窗口 2 个(文件系统视图)
[不变 ] D:\Projects\Tower\RVT 含Revit=是 列可见=是
[不变 ] C:\Users\...\Temp\rvclean 含Revit=否 列可见=否
| 现象 | 原因 | 处理 |
|---|---|---|
| 进程不在 | 被手动 kill,或开机自启被关 | rvtvwatch.exe --install,再手动起一次 |
含Revit=否 但目录里明明有 .rvt |
只看直接子项,不递归;子文件夹里的不算 | 设计如此,见 notes 5.1.1 |
含Revit=是 列可见=否 反复出现 |
该窗口不是「详细信息」视图(图标/列表/平铺没有列概念) | 切到详细信息 |
| 列出现但没值 | 不是监视器的问题,是 handler 没生效 | 回到上一节 --check |
--status 显示「窗口 0 个」 |
没开任何文件夹窗口(桌面不算) | 正常 |
手动验证一次(不改状态,只打印):
& 'C:\Program Files\RvtVersion\rvtvwatch.exe' --once为什么必须常驻:Shell 没有「导航到某文件夹」的公开订阅 API。 所以是 700 ms 轮询,但只在窗口集合/路径变化时才真正调 Shell, 不影响日常使用(内存约 9.5 MB)。
卸载会 taskkill 监视器并清 Run 键,但已经被写进各文件夹 Bag 的列状态不会自动清。
这是 Shell 的持久化设计,不是残留 bug。彻底清:
# 关掉所有资源管理器窗口后
Get-Process explorer | Stop-Process -Force或者手动在列头上把该列取消勾选一次。
上一代 overlay 的注册项未被清除。安装程序会主动清,但需要重启资源管理器才生效:
Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers' |
Where-Object { $_.PSChildName -like '*RvtVersion*' }期望无输出。
InfoTip 已写入 HKLM\SOFTWARE\Classes\SystemFileAssociations\.rvt,
但 Windows 只在部分视图显示,且可能被组策略关闭。
%TEMP%\RvtVersionSetup.log
安装每一步都有记录,含 PSRegisterPropertySchema 的原始 HRESULT。
- Windows 10/11 x64
- 构建:MinGW-w64 16.2(g++ / windres)
- 运行:只依赖 UCRT(系统自带)
- 旧的 .NET 部分:.NET SDK 10.0.402 / .NET Framework 4.8
- docs/notes.md — 逆向过程、实现细节、验证记录、已知限制
MIT