如何使用Powershell Pipeline避免大对象?

时间:2017-02-21 20:06:21

标签: powershell export-to-csv pipeline

我正在使用自定义函数在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文件大小与错误消息有什么关系,但是我无法使用任何一种方法处理这个大目录!关于如何让这个过程成功完成的任何建议?

2 个答案:

答案 0 :(得分:5)

Get-FolderItem运行robocopy列出文件并将其输出转换为PSObject数组。这是一个缓慢的操作,严格来说,这对于实际任务来说并不是必需的。与foreach 语句相比,流水线操作也增加了很大的开销。在数千或数十万次重复的情况下变得明显。

我们可以将流程加速到任何流水线操作之外,标准的PowerShell cmdlet可以在10秒内为SSD驱动器上的400,000个文件写入信息。

  1. .NET Framework 4或更新版本(自Win8以来包含,可在Win7 / XP上安装)IO.DirectoryInfo&#39; s EnumerateFileSystemInfos以非阻塞管道方式枚举文件; < / LI>
  2. PowerShell 3或更新,因为它比PS2整体更快;
  3. foreach 声明,它不需要为每个项目创建ScriptBlock上下文,因此它比ForEach cmdlet
  4. 快得多
  5. IO.StreamWriter以非阻塞管道方式立即写出每个文件的信息;
  6. \\?\ prefix trick解除260个字符的路径长度限制;
  7. 手动排队目录以处理过去&#34;访问被拒绝&#34;错误,否则会阻止天真的IO.DirectoryInfo枚举;
  8. 进度报告。
  9. 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'
    

答案 1 :(得分:1)

不要使用robocopy,请使用本机PowerShell命令,如下所示:

$PATHS = 'c:\temp', 'c:\temp2'
$csvfile='c:\temp\listresult.csv'

$PATHS | % {Get-ChildItem $_ -file -recurse } | Select Name,DirectoryName,Length,LastWriteTime | export-csv $csvfile -Delimiter '|' -Encoding UTF8 -NoType

没有纯粹主义者的简短版本:

$PATHS | % {gci $_ -file -rec } | Select Name,DirectoryName,Length,LastWriteTime | epcsv $csvfile -D '|' -E UTF8 -NoT