写一个巡检脚本,检查系统关键文件在不在。跑到某个系统文件时,Test-Path 返回 False——脚本判定"文件缺失",告警弹了出来,你以为出了事故,开始翻是不是被误删了。结果去机器上一看:文件好端端躺在那儿,系统也运转如常,什么都没缺。
这个"文件在,但检查说不在"的假阴性,坑过不少人。
现象
具体表现:
- 脚本里用"测试路径"这类存在性检查去判断某个系统文件,返回
False; - 你以为文件被删了或损坏了,甚至准备报事故;
- 实际去看,文件确实存在,而且系统功能完全正常(它本来就是系统在用的东西);
- 换不同的检查方式,有的工具说"在"、有的说"不在",结果不一致。
典型的受害者是页面文件(pagefile.sys)、休眠文件(hiberfil.sys)这类系统级、开机就被占用的文件。
根因
关键在于:这类系统文件被内核独占打开了。
页面文件由内存管理器在系统启动时就打开并长期独占持有——它要在里面直接做内存换页的读写,不能被别的进程随意打开、移动或删除。休眠文件同样由休眠子系统独占。处于这种"独占打开"状态的文件,用户态的程序去做属性查询或存在性检查时,会遇到假阴性:查询被拒绝、或返回一个"找不到"的结果。
为什么会这样?因为用户态的"测试路径"这类 API,底层是通过"取文件属性"来判断存在性的。对一个被内核独占的文件,这个取属性的操作不能正常完成——有的是访问被拒,有的实现把"无法获取"直接映射成了"不存在"。"无法访问"和"不存在",在返回值上被混成了一类,这就是假阴性的来源。
要特别注意的是:这不是文件损坏,也不是权限不够(即便你用管理员身份也一样),而是存在性检查这种方式本身,对内核独占文件不适用。
解决
不要用存在性检查去判断这类系统文件。改用专门针对它的系统接口或配置命令。
查页面文件——用页面文件自己的用量接口,而不是看文件在不在:
# 列出页面文件的配置与实际用量
Get-CimInstance Win32_PageFileUsage | Select-Object Name, AllocatedBaseSize, CurrentUsage, PeakUsage
# 或看系统级别的虚拟内存配置
Get-CimInstance Win32_PageFileSetting
AllocatedBaseSize 就是页面文件的大小,有这个记录、大小合理,就说明它是配置好且在用的,不需要靠"文件存在吗"来判断。
查休眠文件——用电源配置命令:
# 查看休眠是否可用;/a 列出所有睡眠状态(含休眠是否启用)
powercfg /a
休眠处于可用状态,就说明休眠文件是就绪的。
如果只是想看它的大小,列目录时带上"包括隐藏/系统文件"的参数,直接读它的大小:
Get-ChildItem C:\ -Force -ErrorAction SilentlyContinue |
Where-Object { $_.Name -in 'pagefile.sys','hiberfil.sys' } |
Select-Object Name, Length
-Force 让列表包含隐藏和系统文件(这类文件通常同时带隐藏+系统属性)。但要注意:能不能列出、能不能取到大小,仍受独占状态影响,所以判断"有没有、用没用"时,优先用上面那两类专门接口,而不是列目录。
延伸与预防
这条坑的通用价值,是**"存在性检查"这类判断本身的不可靠性**。
用户态的"文件在不在",本质上是一次属性探测,而属性探测会受到各种因素干扰:内核独占、权限、重解析点、网络路径不可达、句柄被占用……任何一环让查询失败,返回值都可能退化成"不存在"。把"查不到"当成"没有",是很多误报的根源。
所以遇到"检测说不存在,但人眼看着在",第一反应不该是"文件丢了",而应该是**"这个检查方式可靠吗"**。换一个更贴近真实意图的接口——想知道页面文件用没用,就查它的用量;想知道服务在不在,就查服务的状态;想知道端口监听没监听,就查监听表——用"目标对象自己的接口"去问,而不是用通用的方式去推断。
预防上,写巡检脚本时对系统级对象留个心眼:不用通用的存在性检查去判断那些"开机就被占用"的东西;返回值里也要区分"不存在"和"无法访问"这两种情况,别让它们在日志里变成同一个结论。