这个 Powershell 代码片段的哪一部分需要很长时间才能运行?

Yiz*_*rol 1 sorting powershell search active-directory execution-time

我的任务是为我们的 AD env 中的每个用户制作最后一次登录时间的报告,我显然首先向谷歌妈妈询问了一些我可以重新调整用途的东西,但找不到任何可以检查多个域控制器并协调最后一个的东西,然后吐出是否超过了任意设置的日期/天数。

这是代码:

foreach ($user in $usernames) {
    $percentCmpUser = [math]::Truncate(($usernames.IndexOf($user)/$usernames.count)*100)
    Write-Progress -Id 3 -Activity "Finding Inactive Accounts" -Status "$($percentCmpUser)% Complete:" -PercentComplete $percentCmpUser
    $allLogons = $AllUsers | Where-Object {$_.SamAccountName -match $user}
    $finalLogon = $allLogons| Sort-Object LastLogon -Descending |Select-Object -First 1
    if ($finalLogon.LastLogon -lt $time.ToFileTime()) {
        $inactiveAccounts += $finalLogon
    } 
}
Run Code Online (Sandbox Code Playgroud)

$usernames 是大约 6000 个用户名的列表

$AllUsers是一个包含 18000 个用户的列表,其中包含我希望在最终报告中访问的 10 个不同属性。我得到它的方法是为我关心的特定 OU 中的所有用户点击我们 20 个左右的 DC 中的三个。最终的脚本实际上是 6k*20 因为我确实需要点击每个 DC 以确保我不会错过任何用户的登录。

下面是如何$time计算公式为:

$DaysInactive = 60
$todayDate = Get-Date
$time = ($todayDate).Adddays(-($DaysInactive))
Run Code Online (Sandbox Code Playgroud)

每个变量都在脚本中的其他地方使用,这就是我将其分解的原因。

在您提出建议之前LastLogonTimestamp,有人告诉我它不够最新,当我询问将复制时间更改为最新时,我被告知“不,不会发生”。

Search-ADAccount 似乎也没有提供不活跃用户的准确视图。

我愿意接受有关如何使此特定代码段运行得更快或如何使用不同的方法在短时间内获得相同结果的所有建议。

截至目前,为特定 OU 中的所有用户访问每个 DC 大约需要 10-20 秒每个 DC,然后上述代码段需要 30-40 分钟。

Mat*_*sen 5

有几件事很突出,但这里最大的性能杀手可能是这两个语句:

$percentCmpUser = [math]::Truncate(($usernames.IndexOf($user)/$usernames.count)*100)
# and
$allLogons = $AllUsers | Where-Object {$_.SamAccountName -match $user}
Run Code Online (Sandbox Code Playgroud)

...这两个语句都将表现出O(N^2)(或二次方)性能特征——也就是说,每次将输入大小加倍时,所花费的时间都会增加四倍!


  1. Array.IndexOf() 实际上是一个循环

我们来看第一个:

$percentCmpUser = [math]::Truncate(($usernames.IndexOf($user)/$usernames.count)*100)
Run Code Online (Sandbox Code Playgroud)

这可能不是不言而喻的,但是这个方法调用:$usernames.IndexOf() 可能需要在每次执行时遍历整个列表$usernames——当你到达最后一个时$user,它需要遍历并比较$user所有 6000 个项目。

您可以通过两种方式解决此问题:

使用常规for循环:

for($i = 0; $i -lt $usernames.Count; $i++) {
    $user = $usernames[$i]
    $percent = ($i / $usernames.Count) * 100
    # ...
}
Run Code Online (Sandbox Code Playgroud)

完全停止输出进度

Write-Progress真的很慢-即使调用者禁止显示进度输出(例如$ProgressPreference = 'SilentlyContinue'),使用进度流仍然带有开销,在每次循环叫做时尤其如此。

删除Write-Progress完全将删除的要求计算百分比:)

如果您仍然需要输出进度信息,您可以通过仅Write-Progress 偶尔调用来减少一些开销- 例如每 100 次迭代一次:

for($i = 0; $i -lt $usernames.Count; $i++) {
    $user = $usernames[$i]
    if($i % 100 -eq 0){
        $percent = ($i / $usernames.Count) * 100
        Write-Progress -Id 3 -Activity "Finding Inactive Accounts" -PercentComplete $percent
    }
    # ...
}
Run Code Online (Sandbox Code Playgroud)
  1. ... |Where-Object只是一个循环

现在是第二个:

$allLogons = $AllUsers | Where-Object {$_.SamAccountName -match $user}
Run Code Online (Sandbox Code Playgroud)

... 6000 次,powershell 必须枚举所有 18000 个对象$AllUsers并测试它们的Where-Object过滤器。

Where-Object考虑将所有用户加载到哈希表中,而不是使用数组 and :

# Only need to run this once, before the loop
$AllLogonsTable = @{}
$AllUsers |ForEach-Object {
    # Check if the hashtable already contains an item associated with the user name
    if(-not $AllLogonsTable.ContainsKey($_.SamAccountName)){
        # Before adding the first item, create an array we can add subsequent items to
        $AllLogonsTable[$_.SamAccountName] = @()
    }

    # Add the item to the array associated with the username
    $AllUsersTable[$_.SamAccountName] += $_
}

foreach($user in $users){
    # This will be _much faster_ than $AllUsers |Where-Object ...
    $allLogons = $AllLogonsTable[$user]
}
Run Code Online (Sandbox Code Playgroud)

哈希表具有非常快的 查找-通过键查找对象比Where-Object在数组上使用要快得多。