2026/10/11 23:26:08

PowerShell脚本批量统计子文件夹大小,轻松排查C盘空间占用

PowerShell脚本批量统计子文件夹大小,轻松排查C盘空间占用 我把PowerShell这个标题相关的所有细节都整理好了内容围绕“批量统计子文件夹大小”展开从原理、脚本、实操到排坑全程覆盖写完大概6000多字可以直接拿去发。Windows下想批量统计子文件夹大小我第一个想到的就是PowerShell。它随系统自带、脚本可复用、能一次性输出所有子目录的占用情况解决“右键属性一个个点”这种原始低效的痛点。这篇文章适合运维、开发、普通办公用户看尤其是经常发现C盘莫名其妙满掉又不想装第三方磁盘分析工具的人。PowerShell脚本本身不复杂我会把原理、代码、坑一次性讲透让你不仅能跑通还能按自己的需求改。1. 为什么推荐用PowerShell做批量统计1.1 传统方式到底有多痛很多人遇到“哪个文件夹占空间大”这个问题第一反应是打开资源管理器对着子文件夹一个个右键看属性。文件夹少还好要是目录下面有二三十个子文件夹一个个点过去、等属性弹窗加载完、再抄下数字整个过程少说也要十几分钟。更麻烦的是Windows资源管理器的“属性”窗口一次只能看一个文件夹没法排序、没法导出、也没法对比。有人会想到用CMD的dir命令但dir本身也不直接给文件夹大小dir /s /a 输出的是一大堆文件列表最后才给你一个总计字节数。想统计多个子文件夹就得写for循环嵌套CMD的语法写起来又绕又容易错批处理变量延迟展开那一套能把人逼疯。还有人会选择下载第三方工具比如TreeSize、WinDirStat、du命令之类。这些工具确实好用但问题在于公司电脑未必允许安装软件下载工具本身也有安全风险而且很多工具是图形界面没法自动化和定时执行。你只是想“每周看一次目录占用情况”每次都手动打开工具点一下反而更麻烦。1.2 PowerShell方案的三个核心优势PowerShell做批量统计子文件夹大小强在三个地方。第一是零依赖。Windows 7及以上系统出厂自带Windows PowerShell 5.1不需要装任何额外软件也没有下载和执行第三方的风险。哪怕是最小化的服务器系统Server CorePowerShell也是默认组件。这意味着你可以把脚本丢到任何一台Windows机器上直接跑。第二是对象化输出。PowerShell不像CMD那样只能处理纯文本它的命令输出的是结构化对象可以直接排序、筛选、分组、导出CSV。统计完结果顺手就能变成表格或Excel文件而不是一堆需要人工再加工的字符串。第三是可组合、可复用。你可以把统计逻辑封装成函数存到PowerShell配置文件profile里以后在任何目录直接调用一条命令就能得到结果。也可以挂到计划任务里每周自动跑一次把结果写成报告完全不需要人工介入。这一点对于运维场景特别实用。1.3 动手前先确认环境在写脚本之前建议先确认三件事能省掉后续各种莫名其妙的问题。首先是版本。系统自带的Windows PowerShell 5.1和可独立安装的PowerShell 7.xpwsh在日常命令语法上基本通用本文脚本两个版本都能跑。但你如果装了PowerShell 7记得用 pwsh 命令启动而不是在普通窗口里敲powershell两者是不同的程序。其次是打开方式。最简单的Win X 菜单里选“Windows PowerShell (管理员)”或者直接按 Win 键输入 powershell 回车。还有个小技巧在资源管理器地址栏输入 powershell 并回车会在当前目录直接打开PowerShell窗口统计哪个目录就先进哪个目录非常方便。如果只是查询文件夹大小普通权限就够不用非开管理员但涉及到执行策略修改、读取别人家目录或者系统保护目录时才需要管理员权限。最后是执行策略。默认情况下PowerShell禁止运行.ps1脚本但不影响你在窗口里手动敲命令。如果你想把脚本存成文件来跑第一次会看到“因为在此系统中禁止执行脚本”的报错。那时候执行以下命令即可只影响当前用户Set-ExecutionPolicy RemoteSigned -Scope CurrentUserRemoteSigned的意思是本地创建的脚本可以运行从网上下载的脚本必须带数字签名。这是官方推荐的安全策略不建议直接改成Unrestricted。2. 核心命令与原理解析2.1 统计一条命令就能读懂PowerShell统计文件夹大小本质上就两个核心cmdlet配合Get-ChildItem负责递归列出所有文件Measure-Object负责把所有文件的Length属性求和。Get-ChildItem -Path D:\Data -Recurse -File | Measure-Object -Property Length -Sum这条命令看起来短但里面每个参数都值得说清楚。-Recurse表示递归遍历所有子目录-File表示只看文件不看目录避免把文件夹对象也算进去-Property Length指定要对文件的Length属性做聚合计算-Sum表示我们要的是总和而不是计数。跑完会输出一个对象里面有Count和Sum两个核心字段Count是文件数量Sum是所有文件的字节数总和。这里有个新手容易踩的坑Measure-Object输出的Sum属性单位是字节Byte。一个稍微大点的目录随便就是几十个GB直接看原始数字完全不直观。所以实际使用中要先换算成GB、MB或者TB这一点后面脚本里会专门处理。2.2 为什么统计结果可能对不上假设你统计C:\Users\你的用户名\Downloads这个目录跑出来的结果比右键属性里看到的少了几百MB。不用慌大概率是隐藏文件的问题。Get-ChildItem在默认情况下不会返回隐藏文件和系统文件而资源管理器的属性对话框是把它们算进去的。比如Downloads目录里可能有个AppData软链接、隐藏的desktop.ini或者系统级的回收站文件这些都会被PowerShell默认漏掉。解决办法很直接加上-Force参数。Get-ChildItem -Path D:\Data -Recurse -File -Force-Force在PowerShell里表示强行包含隐藏项。统计文件夹占用空间时建议一律加上这个参数不然结果会偏低。另外还有一种情况会导致统计结果比属性对话框“多”那就是符号链接和硬链接。如果一个目录里放了指向别处的目录符号链接比如很多网盘客户端的“同步文件夹”快捷方式Get-ChildItem -Recurse会顺着链接一层层钻进去把链接目标里的文件也统计进来。这算不算“这个文件夹的大小”从占用磁盘的角度看不算从“在这个目录里能看到哪些内容”的角度看算。所以要看你的业务需求。如果你只想统计真实占用的空间需要在递归时排除ReparsePoint重解析点代码里可以这样写Get-ChildItem -Path D:\Data -Recurse -File -Force | Where-Object { $_.Attributes -notmatch ReparsePoint }2.3 两种实现思路效率差距巨大前面说的单命令统计只能统计整个目录的总大小但标题是“批量统计子文件夹大小”所以我们要的是指定一个根目录把根目录下每个一级子目录的大小分别算出来。实现思路有两种我强烈建议你理解它们之间的区别因为它直接决定脚本在大目录下是“能用”还是“卡死”。第一种思路是“循环递归”。用Get-ChildItem先拿到根目录下所有一级子目录然后对每一个子目录分别做一次递归统计$folders Get-ChildItem -Path $root -Directory foreach ($folder in $folders) { $size (Get-ChildItem -Path $folder.FullName -Recurse -File -Force -ErrorAction SilentlyContinue | Measure-Object Length -Sum).Sum }这种写法逻辑清晰、容易理解但性能很差。假设根目录下面有20个子目录每个子目录里的文件总量是100万个那么这个脚本实际上会把同一批文件翻来覆去地递归20次每次都从头扫描一遍磁盘。磁盘IO是最大的瓶颈文件越多越明显。数据量大时这种写法能把几分钟的事情拖到几十分钟甚至更久。第二种思路是“一次遍历按路径分组”。先对整个根目录做一次完整的递归把所有文件对象拿回来然后按“这个文件属于哪个一级子目录”进行分组统计$files Get-ChildItem -Path $root -Recurse -File -Force -ErrorAction SilentlyContinue $files | Group-Object { ($_.FullName.Substring($root.Length 1) -split \\)[0] } | ForEach-Object { # $_ 是每个分组$_.Name 是分组依据一级子目录名 # $_.Group 是组内所有文件对象 }这样整个根目录只递归扫描一次之后的所有统计都是内存里的对象运算速度提升非常明显。实际测试中几万个文件的场景下两种方式差别可能不到一两秒但到了几十万甚至上百万文件的场景差距就是“等到怀疑人生”和“三十秒出结果”的区别。当然第二种思路也有缺点——如果你要统计的不是“一级子目录”而是“所有层级的每个独立文件夹”那一次遍历分组就不合适了因为分组依据会变得非常复杂。对于最常见的使用场景根目录下哪些文件夹占空间最大好决定删哪些一级子目录就够了。如果要统计所有层级我建议直接用第一种思路只是别把它用在超大目录上。2.4 一个真正的性能核弹直接调.NET API如果你对速度有极致要求比如要统计的目录有几百万文件PowerShell的cmdlet仍然会显得啰嗦。Get-ChildItem每次枚举文件都要构造完整的FileInfo对象这个对象的信息太全了反而成了负担。微软在.NET里提供的System.IO.Directory类可以绕过PowerShell的部分开销用纯.NET方法直接枚举[System.IO.Directory]::EnumerateFiles($path, *, [System.IO.SearchOption]::AllDirectories)EnumerateFiles是惰性求值边遍历边返回路径字符串配合foreach循环累加文件长度会快很多。但代价是代码可读性下降且错误处理要自己搞权限报错不会自动吞掉你得包一层try/catch。我的建议是一般场景用Get-ChildItem就好别为了那点性能把代码搞复杂只有当你确实感觉到“这个脚本跑得太慢慢到不能接受”的时候再用.NET方式优化。运维行业有个原则先让功能跑起来再让速度提上去。3. 可直接抄作业的完整脚本3.1 先想清楚你要哪种统计结果很多人在网上抄脚本跑出来不对问题往往不是脚本本身而是需求没说清楚。统计子文件夹大小至少有两种形态A. 只要根目录下第一层子文件夹各自的大小通常用来快速定位“哪个大目录该清理”。B. 要目录树中所有层级的每个文件夹各自的大小通常用来做全面的空间分析报告。A用“一次遍历分组”B用“循环递归统计”。下面两个脚本分别是这两种形态的实现你可以直接复制改成自己的路径和需求。3.2 基础版逐个递归统计每个子目录这个版本逻辑直观适合目录层级不深、文件量不大的场景。我在脚本里加了空值保护因为如果一个文件夹里没有任何文件Measure-Object的Sum会是$null直接参与数学运算会报错。$root D:\Data # 改成你要统计的根目录 $folders Get-ChildItem -Path $root -Directory -Force $result foreach ($folder in $folders) { $size (Get-ChildItem -Path $folder.FullName -Recurse -File -Force -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum if ($null -eq $size) { $size 0 } [PSCustomObject]{ 文件夹名 $folder.Name 完整路径 $folder.FullName 大小GB [math]::Round($size / 1GB, 2) 文件数 $folder | Get-ChildItem -Recurse -File -Force -ErrorAction SilentlyContinue | Measure-Object | Select-Object -ExpandProperty Count } } $result | Sort-Object 大小GB -Descending | Format-Table -AutoSize注意最后一行用Format-Table -AutoSize可以让表格自适应宽度避免长路径被截断显示成省略号。如果你想把结果导出成文件把最后一行换成$result | Sort-Object 大小GB -Descending | Export-Csv -Path 目录大小统计.csv -NoTypeInformation -Encoding UTF8-Encoding UTF8在Windows PowerShell 5.1下是带BOM的UTF-8Excel打开中文不会乱码。如果你用的是PowerShell 7默认的UTF8不带BOMExcel打开中文虽然有可能会乱码但那属于Excel的历史遗留问题可以在导入时手动选择UTF-8编码。需要说明的是这个基础脚本有一个明显的性能缺陷为了统计“文件数”我又对每个文件夹做了一次完整的递归遍历等于整体耗时翻倍。如果文件数不是你关心的指标就把脚本里“文件数”那一行删掉只保留大小统计。3.3 优化版一次遍历按顶层目录分组如果你的根目录文件量大、子目录多强烈建议用这个版本。核心逻辑已经说过了一次性拿回所有文件Group-Object按一级子目录归属分组再对每个分组求和。整个磁盘只扫描一遍。$root D:\Data $allFiles Get-ChildItem -Path $root -Recurse -File -Force -ErrorAction SilentlyContinue $result $allFiles | Group-Object { ($_.FullName.Substring($root.Length 1) -split \\)[0] } | ForEach-Object { $bytes ($_.Group | Measure-Object -Property Length -Sum).Sum if ($null -eq $bytes) { $bytes 0 } [PSCustomObject]{ 子目录名 $_.Name 大小GB [math]::Round($bytes / 1GB, 2) 大小MB [math]::Round($bytes / 1MB, 2) 文件数 $_.Count } } $result | Sort-Object 大小GB -Descending | Format-Table -AutoSize字符串分割那行是理解这个脚本的关键。一个Windows路径长这样D:\Data\ProjectA\report\2024\summary.xlsx。$_.FullName.Substring($root.Length 1)把前缀的D:\Data\去掉变成ProjectA\report\2024\summary.xlsx再按反斜杠\分割成数组取第[0]个元素得到ProjectA。这就把文件归类到了它所属的第一层子目录下。注意路径中如果有空格也没关系因为我们是按字符切不涉及命令解析。这里有一个边界情况要提醒根目录下的某些文件是直接放在根目录本身、不在任何子文件夹里的比如D:\Data\readme.txt。它们分割后的第一个元素是readme.txt会被当做一个“子目录名”。如果你希望根目录直接下的散文件单独归为“根目录”可以多加一层判断判断文件路径里是否还包含反斜杠。但对于绝大多数清理场景这种散文件数量很少不处理影响不大。3.4 人味儿十足的格式化函数统计结果如果用字节自己看一个是数字太长每次输出一串“28526471235”也没法一眼感知大小。我习惯在脚本里放一个大小格式化函数直接输出“12.35 GB”这种人类友好的格式function Format-Size { param([double]$bytes) if ($bytes -ge 1TB) { return {0:N2} TB -f ($bytes / 1TB) } if ($bytes -ge 1GB) { return {0:N2} GB -f ($bytes / 1GB) } if ($bytes -ge 1MB) { return {0:N2} MB -f ($bytes / 1MB) } if ($bytes -ge 1KB) { return {0:N2} KB -f ($bytes / 1KB) } return {0} B -f $bytes }然后在上面两个脚本的[PSCustomObject]里加上一列“可读大小 Format-Size -bytes $bytes”输出的表格一眼就能看懂。这个函数我经常存进PowerShell profile里任何终端随时可以调用属于那种“写一次吃一年”的通用工具。3.5 扩展Robocopy其实也算一条路PowerShell不是唯一的选择Windows自带的robocopy也能统计目录大小而且速度不慢。虽然robocopy的主要用途是文件复制但它有一个“列目录但不复制”的模式。你可以对每个子文件夹执行这样的命令robocopy 子文件夹路径 任意空目录 /L /S /NJH /NJS /BYTES/L是“只列出不复制”/S递归子目录/NJH和/NJS关掉头尾信息/BYTES让输出的统计信息按字节显示。命令跑完robocopy会在末尾输出类似“Bytes : 1.234 g”这样的统计行。如果你会用脚本循环每个子文件夹再解析那行文本也能得到结果。但我个人不推荐用它做主力方案原因有两个。一是robocopy的输出是给人类看的文本不是结构化的对象你要从一堆报告行里正则匹配提取数字脚本写起来既笨拙又容易受本地化语言影响。二是robocopy统计的是“复制任务需要处理的数据量”它可能会因为文件属性、重解析点、dedup等机制和你的预期有偏差。PowerShell的对象管道明显更干净。robocopy这条方案的价值在于如果哪天PowerShell的Get-ChildItem因为系统权限问题连枚举都做不到robocopy反而能靠管理员权限硬扛过去。知道有这条路就行。4. 常见问题与排查技巧实录4.1 统计出来的数字和右键属性“对不上”这是后台被问最多的问题。原因通常有三个解决办法也各有对应。第一个是隐藏文件漏统计加-Force即可。第二个是符号链接被递归穿透导致比预期大用Where-Object { $_.Attributes -notmatch ReparsePoint }排除。第三个是权限不足某些子目录比如系统卷信息、其他用户配置文件根本打不开Get-ChildItem会报一堆红色错误。这时要在统计命令末尾加-ErrorAction SilentlyContinue让脚本跳过访问不了的目录继续往后跑同时你要接受结果会比真实值略小这个事实。如果你怀疑某个关键目录被跳过可以先单独对该目录执行一次不带SilentlyContinue的Get-ChildItem看清楚报错再决定要不要用管理员身份重跑。4.2 文件量太大脚本跑了一个小时还没结束这是典型的“循环递归”写法导致的性能问题。如果已经用了优化版“一次遍历分组”还是慢那瓶颈就不在脚本逻辑而在磁盘IO本身。特别大的目录几十万文件首次统计时操作系统没有对应的文件索引缓存只能实打实扫一遍磁盘。这时候几个建议一是用机械硬盘的话尽量让脚本在磁盘空闲时段跑二是加上进度提示至少知道卡在哪儿而不是干瞪眼等三是如果统计对象是备份目录或归档目录考虑用文件系统级别的备份报告替代实时扫描。加个最简单的进度提示可以这样写$count 0 foreach ($file in $allFiles) { $count if ($count % 1000 -eq 0) { Write-Host 已处理 $count 个文件... } }实际改的时候把整个统计逻辑放在循环里一次搞定不要一边遍历一边又去套后台递归那会雪上加霜。4.3 中文文件夹名显示成乱码PowerShell窗口默认可能不是UTF-8编码中文会显示成各种方块或问号。如果你只是想把统计结果输出到CSV那只要在Export-Csv里指定-Encoding UTF85.1版是带BOM的UTF87.x要加上编码名称用Excel打开一般都没问题。如果你想在窗口里直接看中文列表有两个办法一是换Windows Terminal它的默认编码就是UTF-8Windows 11上已经自带了二是先执行chcp 65001切换控制台代码页再重新打开PowerShell窗口。这里有个坑在PowerShell里执行chcp 65001会把当前窗口的代码页切成UTF-8但这个改动到自己所在的窗口就重置了别指望一劳永逸。如果脚本文件本身是以UTF-8无BOM格式保存的并且里面有中文路径PowerShell 5.1读取时可能把中文字符识别成乱码导致路径找不到。保存.ps1文件时建议在VSCode或Notepad里显式选择“UTF-8 with BOM”编码。PowerShell 7则没有这个问题因为7.x默认就走UTF-8。4.4 PowerShell窗口的文字复制粘贴技巧这个看起来是小事但很多人卡在这。PowerShell 5.1窗口默认启用“快速编辑模式”你直接用鼠标左键拖选文字然后按回车或者右键就能拷贝到剪贴板。粘贴时直接在窗口里点右键即可。也就是说选中即复制右键即粘贴不需要像CMD那样先打开菜单找“标记”。如果你用的是Windows Terminal复制是CtrlShiftC粘贴是CtrlShiftV注意不是普通的CtrlC/V因为CtrlC在终端里是中断命令的意思。刚上手的人很容易犯这个错误辛辛苦苦选中一堆文字按了一下CtrlC结果命令全被取消了。4.5 执行策略、版本差异和安全意识有人脚本跑不起来是因为Open PowerShell时用的是命令行窗口直接粘贴一段Set-ExecutionPolicy命令。如果你连“是否要更改为执行策略”的提示都没看到大概率是没开管理员权限。执行策略的默认级别Trusted和实际生效级别是分用户的当前用户设置过的会覆盖本机默认设置。所以排查的时候先看当前会话的设置Get-ExecutionPolicy -List输出会列出MachinePolicy、UserPolicy、Process、CurrentUser、LocalMachine几个作用域CurrentUser生效优先级很高。解决方式就是前面提到的Set-ExecutionPolicy RemoteSigned -Scope CurrentUser。最后提醒一句任何脚本都建议你自己看懂每行在做什么再执行尤其是网上抄来的。PowerShell的执行策略本质上是“防止你双击了一个来路不明的脚本就运行”而不是“保证你运行了微软代码就一定安全”。统计目录大小的脚本本身不可能有恶意逻辑但你要养成的习惯是先打开.ps1文件读一遍再用编辑器或PowerShell ISE逐步调试最后才整段跑。结尾几个实用经验我自己用这套脚本统计过几次服务器的共享盘最大的感受是统计不重要重要的是统计完怎么处理。我每次都会顺手在输出里加上文件数和“最后修改时间距今超过N天的文件总大小”这两个衍生维度。比如同样占10GB的目录一个全是最近在用的项目文件另一个三年没动过的归档文件夹清理优先级的判断完全不同。既然PowerShell已经拿回了所有文件对象额外算这两个指标只是多几行代码的事。还有一个小技巧把前面优化版脚本的根目录参数改成$env:TEMP或者$env:LOCALAPPDATA\Temp就能快速看系统临时目录的占用改成$env:USERPROFILE\Downloads就能看下载文件夹有多大。当你觉得C盘莫名其妙满掉又不想装任何软件时这套脚本加一个CSV导出就是最轻量的“应急磁盘分析工具”。走一遍结果一目了然该清哪个目录立刻就有数了。