作者:被爱的小花花_ | 来源:互联网 | 2023-02-11 10:30
我正在使用自定义函数基本上在8TB驱动器(数千个文件)上执行DIR命令(递归文件列表).
我的第一次迭代是:
$results = $PATHS | % {Get-FolderItem -Path "$($_)" } | Select Name,DirectoryName,Length,LastWriteTime
$results | Export-CVS -Path $csvfile -Force -Encoding UTF8 -NoTypeInformation -Delimiter "|"
这导致了一个巨大的$ results变量,并通过强制PowerShell进程将系统降低到爬行速度,以便在处理过程中使用99%-100%的CPU.
我决定使用管道的功能直接写入CSV文件(可能释放内存),而不是保存到中间变量,并提出了这个:
$PATHS | % {Get-FolderItem -Path "$($_)" } | Select Name,DirectoryName,Length,LastWriteTime | ConvertTo-CSV -NoTypeInformation -Delimiter "|" | Out-File -FilePath $csvfile -Force -Encoding UTF8
这似乎工作正常(CSV文件正在增长......并且CPU似乎稳定)但是当CSV文件大小达到~200MB时突然停止,并且控制台的错误是" 管道已经停止 ".
我不确定CSV文件大小与错误消息有什么关系,但我无法使用任何一种方法处理这个大型目录!有关如何允许此过程成功完成的任何建议?
1> wOxxOm..:
Get-FolderItem运行robocopy
以列出文件并将其输出转换为PSObject数组.这是一个缓慢的操作,严格来说,这对于实际任务不是必需的.与foreach 语句相比,流水线操作也增加了很大的开销.在数千或数十万次重复的情况下变得明显.
我们可以将流程加速到任何流水线之外,标准PowerShell cmdlet可以在10秒内在SSD驱动器上写入400,000个文件的信息.
.NET Framework 4或更新版本(自Win8以来,可在Win7/XP上安装)IO.DirectoryInfo
的EnumerateFileSystemInfos,以非阻塞管道方式枚举文件;
PowerShell 3或更新,因为它比PS2整体更快;
foreach
不需要为每个项创建ScriptBlock上下文的语句,因此它比ForEach
cmdlet 快得多
IO.StreamWriter
立即以非阻塞管道方式写入每个文件的信息;
\\?\
前缀技巧解除260个字符的路径长度限制;
手动排队目录进行处理以获取"访问被拒绝"错误,否则会阻止天真的IO.DirectoryInfo枚举;
进度报告.
function List-PathsInCsv([string[]]$PATHS, [string]$destination) {
$prefix = '\\?\' #' UNC prefix lifts 260 character path length restriction
$writer = [IO.StreamWriter]::new($destination, $false, [Text.Encoding]::UTF8, 1MB)
$writer.WriteLine('Name|Directory|Length|LastWriteTime')
$queue = [Collections.Generic.Queue[string]]($PATHS -replace '^', $prefix)
$numFiles = 0
while ($queue.Count) {
$dirInfo = [IO.DirectoryInfo]$queue.Dequeue()
try {
$dirEnumerator = $dirInfo.EnumerateFileSystemInfos()
} catch {
Write-Warning ("$_".replace($prefix, '') -replace '^.+?: "(.+?)"$', '$1')
continue
}
$dirName = $dirInfo.FullName.replace($prefix, '')
foreach ($entry in $dirEnumerator) {
if ($entry -is [IO.FileInfo]) {
$writer.WriteLine([string]::Join('|', @(
$entry.Name
$dirName
$entry.Length
$entry.LastWriteTime
)))
} else {
$queue.Enqueue($entry.FullName)
}
if (++$numFiles % 1000 -eq 0) {
Write-Progress -activity Digging -status "$numFiles files, $dirName"
}
}
}
$writer.Close()
Write-Progress -activity Digging -Completed
}
用法:
List-PathsInCsv 'c:\windows', 'd:\foo\bar' 'r:\output.csv'