首页 > 解决方案 > Powershell 调用 Robocopy 以获取前 32 个最大文件的总大小不起作用

问题描述

下面的脚本是 DFS 设置和 Robocopy 命令的组合,用于列出文件服务器中前 32 个最大的文件。

我需要针对其他 10 个服务器执行以下代码,每个服务器都有大约3-4 百万个文件。

$results = Get-DfsrMembership | ForEach-Object {
    $_ | Select-Object -Property `
                       @{ n = 'Server - IP'; e = { "$($_.ComputerName) [$((Resolve-DnsName -Name $_.ComputerName -Type A).IPAddress)]" } },
                       @{ n = 'Staging Path Quota GB'; e = { ($_.StagingPathQuotaInMB / 1000) } },
                       @{ n = 'Top 32 Largest Files Size'; e = { (robocopy /L /E /ndl /njh /njs /bytes $_.ContentPath nocopy | %{ if ($_ -match "New File\W*(\d*)\W*([\w:\\\.]*)") { [int64]$matches[1] } } | sort -Descending | select -first 32 | measure -sum | select -expand Sum) / 1gb } },
                       GroupName,
                       ContentPath,
                       State
}
$results | Sort-Object 'Top 32 Largest Files Size'

但是,上述脚本的结果缺少前 32 个最大文件大小列的值,因为它们都是 0。

当我执行此部分时:

服务器 A 中的 RDP 会话

(robocopy /L /E /ndl /njh /njs /bytes C:\DFS\Share nocopy | %{ if ($_ -match "New File\W*(\d*)\W*([\w:\\\.]*)") { [int64]$matches[1] } } | sort -Descending | select -first 32 | measure -sum | select -expand Sum) / 1gb }

服务器 B 中的 RDP 会话

(robocopy /L /E /ndl /njh /njs /bytes X:\DFS-Dir\Shared nocopy | %{ if ($_ -match "New File\W*(\d*)\W*([\w:\\\.]*)") { [int64]$matches[1] } } | sort -Descending | select -first 32 | measure -sum | select -expand Sum) / 1gb }

上面返回每个相应命令提示符 shell 上的数字。

标签: powershell

解决方案


我认为您的意思是当您在 RDP 会话中运行它时它可以工作,但它在 PowerShell 代码中不起作用。

$noEmpties = [StringSplitOptions]::RemoveEmptyEntries

(( robocopy /L /E /ndl /njh /njs /np /nc /bytes C:\temp2 nocopy | 
ForEach-Object{ [Int64]$_.Split(" `t", $noEmpties)[0] } | 
Sort-object -Descending )[0..31] | 
Measure-Object -Sum).Sum /1gb

以上是您所做工作的简化。它应该使用更少的管道和Select-Object命令运行得更快一些。您可能还会考虑/MT:xrobocopy 参数。过去,我将日志记录结果与多线程混合,但是在测试这种情况时,它似乎可以工作。当然,如果性能是一个问题。

注意:我假设性能是一个问题,否则Get-ChildItem编写起来会容易得多。

$matches方法有效,但阅读起来很复杂……我在 robocopy 命令中添加了/np&/nc以使解析也更容易一些。

现在当然它只会发出一个数字。该数字是最大的 32 个文件的总和。

我也不确定你需要第一个ForEach,我想你可以直接进入Select-Object命令......

如果您有超出此范围的问题,我认为您应该在表达式运行时查看表达式内部发生了什么。不同的结果可能是由于运行时的不同条件造成的,例如$_可能不同。尝试在您的代码中放置一个断点或使用编辑器,并在您移动时逐步测试所有值和表达式。这可能有助于识别问题。


更新:

我没有 DFS 资源来测试您的确切场景,但我向您的原始代码提供了一个自定义对象并且它确实有效。

我使用相同的方法来测试我早期方法的含糖版本:

$noEmpties = [StringSplitOptions]::RemoveEmptyEntries

$Props =
@(
    @{ n = 'Server - IP'; e = { "$($_.ComputerName) [$((Resolve-DnsName -Name $_.ComputerName -Type A).IPAddress)]" } }
    @{ n = 'Staging Path Quota GB'; e = { ( $_.StagingPathQuotaInMB / 1000 ) } },
    @{ 
        n = 'Top 32 Largest Files Size'
        e = {
            ( (Robocopy /L /E /NDL /NJH /NJS /NP /NC /Bytes C:\temp2 nocopy | 
            ForEach-Object{ [Int64]$_.Split(" `t", $noEmpties)[0] } | 
            Sort-object)[-1..-32] | 
            Measure-Object -Sum).Sum /1gb 
            }
    }
    'GroupName'
    'ContentPath'
    'State'
)

$results = Get-DfsrMembership  | 
Select-Object $Props |
Sort-Object 'Top 32 Largest Files Size'

这似乎奏效了。对于我自己的研究,我在执行主管道之前将表达式预制在一个数组中。那只是一种代码隔离方法。在这种情况下,提高可读性将在调试时大有帮助。使用你最喜欢的隔离方法;它可以很容易地移动到一个函数并从表达式中调用。

注意:您的原始表达式在我的测试中有效

在某一时刻,我确实返回了所有 0,这是因为我未能分配$noEmpties[StringSplitOptions]::RemoveEmptyEntries. 这进一步让我觉得表达式中发生了一些意想不到的事情。我不能完全解决它,但如果它仍然是一个问题,你可以求助于调试。或者,如果我的样本在您的环境中存在同样的问题。


更新:

感谢您接受@Theo 的好答案,但我想指出一些事情。虽然我仍然不确定为什么某些远程条件会产生零,但我所有的测试都是本地的,所以你可以将我的表达式与 Theo 的Invoke-Command方法一起使用。我提到的原因;我的方法具有复合性能优势。

当运行大约 5000 个文件时,Theo 的方法平均为 501 毫秒,而我的平均为 465 毫秒。在您提到的 3-4 百万个文件中,36 毫秒的其他微不足道的差异可能会相当复杂。

这不是我想出的最快方法,请查看:

$noEmpties = [StringSplitOptions]::RemoveEmptyEntries

[Int64[]]$Sizes = 
Robocopy /L /E /NDL /NJH /NJS /NP /NC /BYTES C:\temp2 nocopy | 
ForEach-Object{ $_.Split(" `t", $noEmpties)[0] } 
[Int64[]]::Sort($Sizes)
(($Sizes[-1..-32] | Measure-Object -Sum).Sum) / 1gb

这真的很酷。通过类型约束数组,我将所有值强制为[Int64]. 无需即时转换它们。然后我在数组类上使用了静态排序方法[Int[]],结果证明它比Sort-Object. 我也确实找到了证实这一点的文件。我相信数组切片方法通常比 快,但我发现用任何类型的手动求和循环Select-Object替换都没有优势。Measure-Object

注意:我怀疑该.Split()方法将有助于处理您的 其他问题。尽管也可能存在基于 RegEx 的方法。

现在在任何一种方法中,我都能够通过使用.SubString()而不是拆分方法来获得更多的性能。这有点棘手,因为有些空白字符是制表符,有些是空格。

[Int64[]]$Sizes = Robocopy /L /E /NDL /NJH /NJS /NP /NC /Bytes C:\temp2 nocopy | ForEach-Object{ $_.Substring(0,14) } 
[Int64[]]::Sort($Sizes)
(($Sizes[-1..-32] | Measure-Object -Sum).Sum) / 1gb

有几个看似随机的案例似乎不起作用,但总的来说它似乎可靠。如果有的话,您可能必须使用引用的字符串索引。该.split()方法更可靠,但如果对性能角度感兴趣,我想添加此示例。

最后一件事;你实际上可以使用Get-ChildItem

((Get-ChildItem \\?\C:\temp2 -File -Recurse | 
ForEach-Object{ $_.Length } |
Sort-Object)[-1..-32] |
Measure-Object -Sum).Sum/1gb

然而,在大约 5000 个文件的同一组中,平均大约 1230 毫秒的速度要慢得多。您可以在此处获得有关网络前缀语法的更多信息\\?\这里是示例


推荐阅读