为什么这个 Powershell ForEach 循环每次迭代都会变慢?

Why does this Powershell ForEach loop get slower with each iteration?

我的代码按预期工作。我真的很想知道是否有人知道为什么我在下面描述的事情会发生。也就是说,如果有人有任何进一步优化例程的想法,我将本着每天都是上学日的精神感激地接受他们!

该脚本正在查询我们所有的域控制器,以获取特定 OU 中所有用户的最新 lastLogon 属性。 (我这样做而不是使用 lastLogonTimeStamp 属性,因为我需要脚本为 运行 时的绝对最新登录。)

在测试期间,为了检查代码是否按照我的预期执行,我添加了一些控制台输出(包含在下面的代码片段中)。

当我这样做时,我注意到在 SECOND ForEach ( $DC in $AllDCs ) 循环的每次迭代中,在嵌套循环写入其第一行控制台输出之前都有一个明显的停顿。暂停的时间随着外层循环的每次迭代而增加,内层循环后续输出的速度也明显下降。在 运行 的过程中,查看十几个 DC 的输出,我估计写入控制台行的速率至少下降了 4 倍。

$AllDCs = Get-ADDomainController -Filter *

$AllRecords = @{}

ForEach ( $DC in $AllDCs ) {

    $UserList = Get-ADUser -filter * -Server $DC -SearchBase $ini.OUDN.NewStartsInternal -SearchScope OneLevel -Properties lastLogon
    $UserList = $UserList | Where { $_.SamAccountName -match $ini.RegEx.PayNo }

    $AllRecords.Add($DC.Hostname,$UserList)

}

$Logons = @{}

ForEach ( $DC in $AllDCs ) {                                   ; this loop is the one I'm talking about
    
    ForEach ( $User in $AllRecords.$($DC.HostName) ) {

        If ( $Logons.ContainsKey($User.SamAccountName) ) {     ;this line amended on advice from mklement0
            
            If ( $Logons.$($User.SamAccountName) -lt $User.lastLogon ) {
            
                $Logons.$($User.SamAccountName) = $User.lastLogon
                Write-Host "Updated $($User.SamAccountName) to $($User.lastLogon)" -ForegroundColor Green

            } Else {

                Write-Host "Ignored $($User.SamAccountName)"

            }

        } Else {

            $Logons.Add( $User.SamAccountName , $User.lastLogon )

            Write-Host "Created $($User.SamAccountName)" -ForegroundColor Yellow

        }

    }

}

我在这里没有任何时间限制,因为我们只讨论了几百个用户和十几个域控制器。无论如何,我已经大大减少了 运行 时间。以前它是循环遍历用户并为每个用户一个一个地查询每个 DC,这毫不奇怪,花费的时间要长得多。

更新:

我从下面的第一条评论中实施了 mklement0 的建议,如果有的话,脚本实际上 运行ning 比以前慢了。外循环迭代之间的延迟更长,内循环的输出似乎受到随机延迟的影响。平均而言,我会说内部循环每秒进行大约 2 到 3 次迭代,在我看来,这对于循环遍历已保存在本地内存中的数据来说非常慢。我知道 PowerShell 以速度慢而著称,但这似乎是个例外。

该脚本通常 运行 在 VM 上运行,所以我在自己的计算机上对其进行了测试,速度慢很多,因此这不是 VM 的资源问题。

更新 2:

我删除了所有 Write-Host 命令并简单地为外循环的每次迭代计时。

首先,删除所有控制台写入显着提高了性能,这是我所期望的,尽管我没有意识到有多少。它轻松地将 运行 时间缩短到原来的五分之一。

就循环时间而言,奇怪的行为仍然存在。在十二次迭代中,前七次在 1 秒内完成,完成最后五次大约需要 35 秒。这种行为每次都或多或少地重复。与可能减慢哈希表查找速度的前七个相比,最后五个 DC 的主机名没有什么不同。

我对现在的 运行 时间感到非常满意,但仍然对这种奇怪的行为感到非常困惑。

所以,这是我对你的代码的看法,我没有改变很多东西,但我觉得这应该更快一点,但我可能错了。

请注意,我使用的是 LDAPFilter = '(LastLogon=*)' 而不是 Filter = '*',因为如果它是一个未跨域复制的属性,则在查询每个域控制器时可能会节省时间。如果不起作用,请将其改回 Filter = '*' :(

避免带入没有LastLogon属性设置的用户,可以节省一些时间。

$AllDCs = Get-ADDomainController -Filter *
$logons = [System.Collections.Generic.Dictionary[string,datetime]]::new()

$params = @{
    LDAPFilter = '(LastLogon=*)' # Use this instead if that didn't work => Filter = '*'
    Server = ''
    SearchBase = $ini.OUDN.NewStartsInternal
    SearchScope = 'OneLevel'
    Properties = 'lastLogon'
}

foreach($DC in $AllDCs)
{
    $params.Server = $DC
    $UserList = Get-ADUser @params
    
    foreach($user in $UserList)
    {
        if($user.samAccountName -notmatch $ini.RegEx.PayNo)
        {
            continue
        }

        if($logons[$user.samAccountName] -lt $user.LastLogon)
        {
            # On first loop iteration should be always entering
            # this condition because
            # $null -lt [datetime]::MaxValue => True AND
            # $null -lt [datetime]::MinValue => True
            # Assuming $user.LastLogon is always a populated attribute
            # which also explains my LDAPFilter = '(LastLogon=*)' from before

            $logons[$user.samAccountName] = $user.LastLogon
        }
    }
}