先交代一个我踩过的场景:上个月帮同事排一台 C 盘爆满的机器,资源管理器里一个个文件夹右键看属性,加起来不到 20GB,可磁盘分区明明只剩 800MB。后来切到管理员 PowerShell 把所有隐藏目录翻了一遍,才发现 C:\System Volume Information 和 C:\$Recycle.Bin 这两个隐藏系统目录里,躺着近 30GB 的还原点、卷影副本和回收站残骸。这种“空间神秘失踪”其实非常普遍,元凶往往不是普通文件夹,而是默认不显示、右键属性又费半天的隐藏目录。
这篇文章就把我当时的方法完整展开:用一个可运行的 PowerShell 脚本,扫描指定根路径下所有超过设定大小阈值的隐藏目录,顺带说清楚这些目录为什么难察觉、有哪些现成工具能配合、以及实测中踩过的权限、符号链接和统计口径的坑。适合磁盘清理、运维巡检、自动化监控这几类场景,不需要额外装软件,Windows 自带的 PowerShell 就能跑。
1. 隐藏目录为什么难察觉:属性、保护开关与资源管理器盲区
1.1 目录的 Hidden 属性不等于看不见
很多人对“隐藏目录”的理解停留在资源管理器“查看 → 隐藏的项目”这个开关上,但这只是第一层。Windows 文件系统里,目录或文件可以带多个属性,常见的有 Hidden、System、Archive、ReadOnly,还有符号链接相关的 ReparsePoint。资源管理器默认只处理 Hidden,对于同时带有 System 属性的目录,即使你勾选了“隐藏的项目”,它依然不会出现。想看到这一类,还得再关闭资源管理器选项里的“隐藏受保护的操作系统文件”复选框。
这也是为什么很多工具扫描出来的隐藏目录数量,会比你手工打开资源管理器看到的更多。一个目录只要被设置成 System 或 Hidden,或者两者叠加,就可能在你的日常浏览里“隐身”。从命令行角度看,dir /a:hd 能列出带隐藏属性的目录,dir /a:hs 能列出带系统属性的目录,而 Get-ChildItem -Force 则能同时把两类都找出来。做扫描脚本时,不能用普通枚举,必须带上 -Force 参数。
1.2 真正容易“藏”大文件的常见位置
我实际排查时,发现隐藏目录的大头集中在下面这几个位置。整理一个表格方便对照:
| 路径 | 属性类型 | 典型内容 | 为什么占地方 |
|---|---|---|---|
C:\System Volume Information |
System + Hidden | 系统还原点、卷影副本 | 还原点可能几十 GB,而且默认不显示 |
C:\$Recycle.Bin |
System + Hidden | 各用户回收站目录 | 大文件删除后如果一直没清空回收站 |
C:\ProgramData |
部分子目录 Hidden | 软件配置、更新缓存 | 很多安装包缓存会放在隐藏子目录里 |
C:\Users\用户名\AppData\Local\Temp |
Hidden 或普通 | 临时文件、解压缓存 | 长期不清理可能塞满几十 GB |
C:\Windows\SoftwareDistribution\Download |
普通或隐藏 | Windows 更新下载缓存 | 更新失败/重复下载后残留较大 |
C:\hiberfil.sys(文件) |
System + Hidden | 休眠文件 | 不是目录,但常和隐藏目录一起被忽视 |
其中 System Volume Information 是最让人头疼的,因为即使你打开“隐藏受保护的操作系统文件”,直接进这个目录也经常提示没有权限。它的体积来自系统还原点和卷影副本,正常清空需要用“磁盘清理”、“卷影副本”管理或 vssadmin 这类工具,而不是直接删目录。$Recycle.Bin 也一样,每个用户 SID 下面都有自己的隐藏回收站目录,普通方式看不到单个用户回收站的实际占用。
1.3 “目录大小”在 Windows 里为什么不好算
Windows 资源管理器没有原生的“目录大小排序”功能。你右键某个文件夹看属性,它需要递归统计里面所有文件的大小,目录一多就会卡很久。更麻烦的是,你只能一个个点开看,完全没法一次性找到“哪个目录超过 1GB”。
从技术原理说,目录本身不保存“自身总大小”这个字段,它只是一个对象容器。要知道大小,必须把目录下所有文件的大小累加起来,再算上所有子目录。这也是很多清理工具的原理。我们要做的脚本,本质上就是把这个“递归累加”的过程包装好,再附加两个条件:只看隐藏目录、只显示超过指定阈值的结果。有了阈值过滤,输出才不会被一堆几百 MB 的小目录刷屏。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 现成工具速览:哪条路最快,哪条路适合脚本化
2.1 几秒扫完整个 NTFS:WizTree 和 TreeSize 的思路
如果你想快速定位“到底哪个目录最大”,我最推荐先试试 WizTree 或 TreeSize Free。WizTree 会直接读取 NTFS 的 MFT 主文件表,扫描整个分区通常只要几秒钟,比资源管理器右键属性快几十倍。TreeSize Free 虽然不能读 MFT,但它的树状展示和排序也很直观。
不过这类工具有两个瓶颈:一是它们主要面向“人眼排查”,虽然能按大小排序列出所有目录,但不太方便只过滤出隐藏目录;二是部分功能在免费版里有限制,命令行参数对于自动化场景也不太友好。如果只是临时看一眼,用 WizTree 是最快的,我自己的习惯也是先让它扫一遍,再决定要不要深挖。
2.2 dir 命令和 Everything 的边界
dir /a:hd /s /b 可以快速列出指定根目录下所有隐藏目录的完整路径,但它不统计每个目录的大小。这个命令适合做第一层“定位”,比如先看看哪些隐藏目录存在,然后逐个用 du 脚本或 PowerShell 计算大小,但完全没法解决“只输出超过 1GB 的目录”这个问题。
Everything 是很多人的索引导航工具,它的确能搜索具备隐藏属性的目录,语法类似 folder:hidden。但它默认不对目录做递归大小累加,搜出来的文件夹没有“总大小”这一列,只能告诉你这个目录存在,无法告诉你它占了多少空间。所以无论是 dir 还是 Everything,都只能算辅助手段,真正精准的大目录筛选,还是得靠脚本。
2.3 为什么我最终还是写了 PowerShell
我的需求很明确:给一个路径,扫描这个路径下所有隐藏目录,只输出超过设定阈值的项,最好还能输出成 CSV、丢进定时任务里跑。这种情况下,GUI 工具和一次性命令都满足不了,PowerShell 脚本反而最合适。
理由有三:第一,PowerShell 自带文件系统访问能力,能直接读取隐藏/系统属性,不需要额外安装任何东西;第二,脚本可以参数化,-Path 指到哪个盘就扫哪个盘,-MinSizeMB 设置阈值;第三,它可以接任务计划程序,每周自动跑一次,输出日志,提前发现磁盘的增长趋势。如果你只需要交互式排查一次,用 WizTree;如果你需要长期、自动化、可重复执行,脚本是更稳的答案。
3. 动手写“只看超阈值隐藏目录”的脚本
3.1 设计思路:一次目录清单,一次文件清单,按祖先链累加
最笨的写法是:先找到所有隐藏目录,然后对每个隐藏目录再递归遍历一次计算大小。这种嵌套目录一多,就会产生大量重复遍历,比如一个隐藏目录里装着一个更大的隐藏子目录,子目录里的文件会被外层重复统计,扫描时间可能翻好几倍,在机械硬盘上会非常崩溃。
我采用的方案是拆成两步:
第一步,用 Get-ChildItem -Directory -Force -Recurse 枚举全部目录,过滤出带 Hidden 属性的那些,把它们的完整路径保存到一个哈希表里,同时给每个目录初始化大小为 0。
第二步,用 Get-ChildItem -File -Force -Recurse 枚举全部文件,然后对每个文件从它的父目录开始,沿着父目录链向上走,只要某个父目录在隐藏目录集合里,就把这个文件的字节数累加到对应隐藏目录上。这样一次文件遍历,就能把所有隐藏目录的总大小算出来,不会出现每个目录单独递归的重复开销。
这个思路最关键的地方是“沿着祖先链向上查”。比如文件在 C:\FolderA\HiddenSub\data.bin,而 HiddenSub 是隐藏目录,FolderA 普通目录,那文件大小只累加到 HiddenSub。如果 FolderA 本身也是隐藏目录,同时 HiddenSub 也是隐藏目录,那么 data.bin 的大小会同时累加到两个隐藏目录上,这是合理的:父隐藏目录的总占用包含了子隐藏目录,而子隐藏目录被单独列出,方便你看明细。这样的输出会有少量重复计数,但用于定位大目录完全没问题。
3.2 完整脚本与参数说明
下面这段脚本保存为 Find-LargeHiddenDirectories.ps1 就可以用。建议用管理员身份打开 PowerShell,因为很多隐藏系统目录普通权限读不了。
powershell复制function Find-LargeHiddenDirectories {
param(
[string]$RootPath = "C:\",
[int64]$MinSizeMB = 1024,
[switch]$IncludeProtectedSystem,
[switch]$SkipReparsePoint = $true
)
$ErrorActionPreference = 'SilentlyContinue'
$minBytes = $MinSizeMB * 1MB
Write-Host "正在枚举隐藏目录..."
$hiddenDirs = @(Get-ChildItem -LiteralPath $RootPath -Directory -Force -Recurse |
Where-Object {
($_.Attributes -band [IO.FileAttributes]::Hidden) -ne 0 -and
($IncludeProtectedSystem -or ($_.Attributes -band [IO.FileAttributes]::System) -eq 0) -and
(-not $SkipReparsePoint -or ($_.Attributes -band [IO.FileAttributes]::ReparsePoint) -eq 0)
}
)
$hiddenPathSet = @{}
$sizeMap = @{}
foreach ($d in $hiddenDirs) {
$hiddenPathSet[$d.FullName] = $true
$sizeMap[$d.FullName] = [long]0
}
Write-Host "正在扫描文件并累计大小..."
Get-ChildItem -LiteralPath $RootPath -File -Force -Recurse |
ForEach-Object {
$parent = [System.IO.Path]::GetDirectoryName($_.FullName)
while ($parent) {
if ($hiddenPathSet.ContainsKey($parent)) {
$sizeMap[$parent] += $_.Length
}
if ($parent -eq [System.IO.Path]::GetPathRoot($parent)) { break }
$parent = [System.IO.Path]::GetDirectoryName($parent)
}
}
$result = $sizeMap.GetEnumerator() |
Where-Object { $_.Value -ge $minBytes } |
ForEach-Object {
[PSCustomObject]@{
Directory = $_.Key
SizeMB = [math]::Round($_.Value / 1MB, 2)
SizeGB = [math]::Round($_.Value / 1GB, 2)
}
} |
Sort-Object SizeMB -Descending
Write-Host "共找到 $($result.Count) 个超过 ${MinSizeMB}MB 的隐藏目录:"
$result | Format-Table -AutoSize
return $result
}
Find-LargeHiddenDirectories -RootPath "C:\" -MinSizeMB 1024
参数说明:
-RootPath:要扫描的根路径,默认是C:\,你可以改成D:\或某个具体目录。-MinSizeMB:大小阈值,单位是 MB,默认 1024,也就是只输出超过 1GB 的隐藏目录。-IncludeProtectedSystem:默认会跳过系统属性目录,如果你确实想看System Volume Information这种受保护系统目录,加上这个开关。不加也可以,但脚本会因为权限问题跳过一部分目录。-SkipReparsePoint:默认跳过重解析点目录,也就是连接点、符号链接等,防止递归跑到其他盘或陷入循环。如果你明确知道某个符号链接目录需要统计,可以关掉它。
脚本里 -ErrorAction SilentlyContinue 是全局设置,遇到没有权限的目录会静默跳过,不会中断整个扫描。文件大小用的是文件对象的 Length 属性,对 NTFS 压缩文件来说这是逻辑大小,不是磁盘实际占用,这点后面第 4 节会细说。
3.3 执行效果示例:1GB 阈值扫描 C 盘
我在一台真实的 Windows 11 机器上以管理员身份执行了上面的命令,核心输出大致长这样:
text复制Directory SizeMB SizeGB
C:\System Volume Information 8576.32 8.38
C:\$Recycle.Bin\S-1-5-21-...\ 4096.11 4.00
C:\Users\Administrator\AppData\Local\Temp 2843.56 2.78
C:\ProgramData\Microsoft\Windows\WER 1542.10 1.51
第一次运行会明显看到 C 盘隐藏目录的分布:系统还原点永远是最大头,回收站排第二,然后是用户临时目录和 Windows 错误报告目录。整个过程大概需要几分钟,和文件总数有关,但绝对比手动一个文件夹一个文件夹右键属性快得多。
如果你只想扫某个区域,比如 C:\Users\Administrator\AppData,把 -RootPath 改过去,扫描时间会大幅缩短,输出的结果也更聚焦。这一步是脚本和 WizTree 相比最大的优势:可以自由控制扫描边界。
4. 实测里那些坑:权限、ReparsePoint、属性“伪造”
4.1 访问拒绝:System Volume Information 目录
脚本默认会跳过系统属性目录,所以第一次不传 -IncludeProtectedSystem 时,System Volume Information 不会出现在结果里。就算你加了 -IncludeProtectedSystem,普通权限依然打不开这个目录,必须以管理员身份运行,而且 Windows 还会对部分卷影存储设置额外的 ACL,实际能读到的文件可能也是一部分。
我在实际排查中发现,很多新手会卡在这一步:明明脚本列出了这个路径,怎么里面是空的?那不是脚本 bug,是权限不够。这时候更好的做法是直接看卷影副本占了多少空间,命令 vssadmin list shadowstorage 可以查看卷影存储使用情况,而不是在文件系统层面硬碰硬。如果真的是还原点占满磁盘,该清理的是系统还原点和卷影副本,不是去删里面的文件。
4.2 连接点与符号链接会导致循环和错误统计
Windows 里有很多“看不见的连接点”,比如 C:\Users\All Users 其实是指向 C:\ProgramData 的 Junction。如果脚本不加过滤,一旦递归进入这种重解析点目录,很可能重复遍历到同一批文件,甚至形成死循环,导致大小统计成倍虚高。
所以我默认了 -SkipReparsePoint,判断条件是目录属性里带 ReparsePoint 就跳过。代价是会漏掉一部分通过符号链接组织的目录,但这在绝大多数场景下是划算的。如果你确实需要统计某个符号链接指向的目标,建议先解析出真实路径,再单独对该路径扫描,而不是让脚本自动跟着链接走。
4.3 压缩/加密与“占用大小”的区别
NTFS 文件系统支持压缩和加密,这给大小统计带来一个隐蔽的坑:文件对象的 Length 属性返回的是逻辑文件大小,不是磁盘实际分配的物理大小。一个 2GB 的压缩文件,在磁盘上可能只占 1.5GB;一个加密文件可能会有额外的元数据开销。
PowerShell 原生拿不到每个文件“磁盘占用大小”,除非调用更底层的 API,或者用 fsutil 做分区间查询,那会把脚本复杂度拉高一个量级。我的建议是:脚本输出用于排查趋势和定位大目录,完全够用;如果要知道精确的磁盘占用,再到 WizTree 里看那一列的数值。两者差通常在 10% 以内,不影响判断。
4.4 如何验证脚本结果
脚本跑完,建议用 WizTree 对同一个目录再扫一次,交叉验证大小排名。我遇到过脚本输出和 WizTree 不一致的情况,最后发现是某个目录带了 ReparsePoint 属性,一边没过滤一边过滤了,路径重叠导致统计差异。
验证时还有一个技巧:挑脚本列出的最大目录,用资源管理器右键看属性,再对比脚本输出。如果两者接近,说明算法没问题。如果差很多,先检查是不是权限不足导致部分文件没读进来,再检查是不是符号链接没有跳过。这种交叉验证做一次以后,后续再跑脚本就会比较放心。
5. 如果要更快:从全盘枚举到索引和调度
5.1 先缩小扫描范围,再逐步深入
脚本默认扫整个 C 盘,文件几十万甚至上百万时,PowerShell 的速度确实不算快。最快的方式是缩小范围:第一步先扫盘根目录下的一级目录大小,把范围定位到 AppData、Windows 或 ProgramData,第二步再进入这些目录执行脚本。相当于先用粗粒度定位,再用细粒度扫描,避免一上来就全盘递归。
如果连一级目录都不想起,直接用 WizTree 扫一遍,按大小排序看一级目录排名,然后针对可疑目录跑我们的脚本。这样既利用了 WizTree 的速度,又利用脚本的隐藏属性过滤和阈值过滤。
5.2 把脚本挂进任务计划程序,做日常巡检
如果你要监控多台机器,或者需要每周知道“哪些隐藏目录悄悄长大了”,可以把脚本接到任务计划程序里。比如每周日凌晨跑一次,结果导出到 CSV 文件:
powershell复制Find-LargeHiddenDirectories -RootPath "C:\" -MinSizeMB 1024 | Export-Csv -Path "D:\logs\hidden-dirs-weekly.csv" -NoTypeInformation -Encoding UTF8
这样磁盘空间的变化趋势就有了数据支撑。我实际用下来,比等用户报警再临时查要舒服得多。最好把脚本路径固定在一个公共目录,比如 D:\Scripts\Find-LargeHiddenDirectories.ps1,方便统一维护。
5.3 最接地气的“快速”经验总结
我个人经验是:日常排查空间问题,优先用 WizTree 看全貌;需要自动化或精确过滤隐藏目录,再用 PowerShell 脚本;如果机器很老、文件特别多,机械硬盘全盘扫描会很痛苦,尽量限定到用户目录或数据盘。另外别忘了,隐藏目录之外,pagefile.sys、hiberfil.sys 和 swapfile.sys 这几个隐藏系统文件也可能占几十 GB,它们不是目录,但同样是“看不见的大块头”,排查时一起看一眼,方向就全了。
最后再分享一个使用心得:发现超大隐藏目录后,先确认它的来源再动手删。System Volume Information 和 $Recycle.Bin 都有专门的清理入口,直接删除内部文件可能造成数据不可逆;普通软件缓存目录则可以放心清。脚本本身不会删除任何东西,它只负责定位,后续操作留给人来判断,这是最稳妥的用法。
