【问题标题】:How to speed up PowerShell operations on AD objects如何加快对 AD 对象的 PowerShell 操作
【发布时间】:2016-05-23 16:39:50
【问题描述】:

我正在编写一个工作脚本,它将查询我们所有的 AD 域,然后返回具有管理员权限的计算机。下面的脚本可以运行并返回预期的结果,但在我们较大的域中它非常慢。

当处理 23k 或更少的对象时,它只需几分钟(6 分钟左右)即可运行,但当它必须处理 90k+ 时,它会花费数小时。

我是 PowerShell 的新手,不知道这里的哪些操作会使运行时间呈指数增长,所以我无法缩小范围。我的预感是它必须处理 PowerShell 扩展数组以不断添加更多对象的方式?我也在考虑更好地利用管道......但是作为新手并且来自 bash 我不熟悉这个概念以及如何在这段代码中使用它

有什么方法可以加快运行速度,使其运行速度超过几个小时?任何帮助将不胜感激。

$date = Get-Date -uFormat %d-%m-%y

ForEach($domain in $domains)
{
        $all_computers = Get-ADComputer -Server $domain -filter * -Properties enabled | select-object name,enabled,@{name="distinguishedname";expression={$_.DistinguishedName -replace "(CN[^,]+,)"}}
        #Gets all of the objects that are in a group and pulls their names this is to get the admin flag that is appended to names in this group
        $group_name = (Get-ADGroup -Server $domain -Filter{name -like "*admin*"}).Name

        #Counts the devices pulled from the computer query
        $DevNum = $all_computers.count
        echo "Number of devices: " $DevNum > "$domain LARGE $date.txt"

        #Remove servers from the list
        $all_computers = $all_computers | ?{ $_ -NotMatch "Servers" }

        #Counts the number of servers we removed
        $NumSkipped = $DevNum - $all_computers.count

        Switch($all_computers){
            #Finding all of the accounts where both types of admins exist and removing them from the master list
            {$group_name -contains $($_.name + "Admins") -and $group_name -contains $($_.name + "UPEPAdmins")} {$_ | Add-Member "admintype"  "both";Continue}
            #Finding all of the accounts with only exception admins and removing them from the master list
            {$group_name -contains $($_.name + "Admins")} {$_ | Add-Member "admintype" "old";Continue}
            #Finding all of the accounts with only upep admins and removing them from the master list
            {$group_name -contains $($_.name + "UPEPAdmins")} {$_ | Add-Member "admintype" "UPEP";Continue}
            #These accounts have no admin
            default {$_ | Add-Member "admintype" "No"}
        }

        echo "Number of servers skipped: " $NumSkipped >> "$domain LARGE $date.txt"

        echo "Number of workstations: " $all_computers.count >> "$domain LARGE $date.txt"

        echo "Number of Exception admins found: " $($all_computers|?{$_.admintype -match "old|both"}).count >> "$domain LARGE $date.txt"

        echo "Number of UPEP admins found: " $($all_computers|?{$_.admintype -match "upep|both"}).count >> "$domain LARGE $date.txt"

        echo "Number of both UPEP and Exception admins found: " $($all_computers|?{$_.admintype -eq "both"}).count >> "$domain LARGE $date.txt" 

        #output
        $all_computers | Export-Csv "$domain LARGE $date.csv"
}

编辑 1:

更新了代码以反映 SodaWillow、TheMadTechnician 和 删除修剪并用 -replace 替换它会减少一点运行时间。

编辑 2:

将代码更新为正确的解决方案,根据 TheMadTechnician 的建议,我过滤了组以减少其数量,并将组名插入到数组而不是表中。与减少的组数结合使用时,数组的使用显着加快了操作速度。

当前错误:“Both”管理类型正确导出为 CSV,但根本没有在文本文件中报告,我认为这与 if 语句中的逻辑有关。我现在正在看这个

编辑 3:

修复了两个管理类型的逻辑错误,这是这个问题的最终解决方案。 由于私有信息,$domains 变量被声明在此代码块之外。

感谢大家的宝贵时间!

【问题讨论】:

  • 您可能想尝试 LDAP 过滤器和System.DirectoryServices.DirectorySearcher(此处的示例:powershelladmin.com/wiki/…)。测量代码的每个部分所消耗的时间以找到最长的查询,然后从优化这些查询开始(如果您还没有这样做的话)
  • 我尝试在代码块上运行并使用 Measure-Command 来查看我可能在哪里增加了不必要的时间。似乎没有什么是多余的,但放在一起需要很长时间
  • 我会想象 L2 和 L4 的持续时间比其他的要长得多。 L4 可以用Get-ADGroup 重写吗?
  • 是的,那些需要热度。几分钟一片。但没有什么不合理的。看起来修剪(L28 和 L37)是重击者,会用那个子表达式代替修剪的工作吗?我不只是使用 get-adgroup 的原因是,由于 AD 组织不善,我需要的指标信息在 adcomputer 和 ad group 之间分配。
  • 之后有很多处理,需要完全分解和理解以优化脚本。我会尝试潜入:)。

标签: powershell active-directory


【解决方案1】:

嗯,看起来至少你可以从组合一些行中受益。有几个地方你定义了一个变量,然后立即通过过滤自身重新定义它。如:

$servs = $all_computers | Select-Object Name,DistinguishedName
$servs = $servs -Match "Servers" 

可以简化为:

$servs = $all_computers | Select-Object Name,DistinguishedName | Where {$_ -match "Servers"}

现在对 $admin_exist_cnt$upep_admin_exist_cnt 做同样的事情

$admin_exist_cnt = Compare-Object -DifferenceObject $group_name -ReferenceObject $com_name_admin -ExcludeDifferent -IncludeEqual -Property name | select-object @{name="name";expression={$($_.name).toString().TrimEnd("A","d","m","i","n","s")}}

$upep_admin_exist_cnt = Compare-Object -DifferenceObject $group_name -ReferenceObject $com_name_upep -ExcludeDifferent -IncludeEqual -Property name | Select-Object @{name="name";expression={$($_.Name).ToString().TrimEnd("U","P","E","P","A","d","m","i","n","s")}}

然后,在接近尾声的时候,您遍历所有计算机以寻找“两种”管理类型,并从 $all_computers 变量中删除它们,然后再次运行整个过程,并重复执行 4 次。不,不要那样做,改用Switch 命令。它看起来像这样:

Switch($all_computers){
    #Finding all of the accounts where both types of admins exist and removing them from the master list
    {$both_exist_cnt.name -contains $_.name} {$_ | Add-Member "admintype"  "both";Continue}
    #Finding all of the accounts with only exception admins and removing them from the master list
    {$admin_exist_cnt.name -contains $_.name} {$_ | Add-Member "admintype" "old";Continue}
    #Finding all of the accounts with only upep admins and removing them from the master list
    {$upep_admin_exist_cnt.name -contains $_.name} {$_ | Add-Member "admintype" "UPEP";Continue}
    #These accounts have no admin
    default {$_ | Add-Member "admintype" "No"}
}

然后您可以将输出部分缩减为:

#output
$all_computers | Export-Csv test.csv

编辑:好的,回顾一下事情,发现您正在重新定义很多事情。相反,我建议只通过Switch 运行一次,然后计算你的结果。这将大大减少内存消耗,这在小型测试运行中可能无关紧要,但在大量运行时应该会产生相当大的差异。试试这个修改后的脚本,如果您有任何具体问题,请告诉我:

#TODO Fix the server thing and put it in a for loop
$all_computers = Get-ADComputer -Server NW -filter * -Properties enabled | select name,enabled,distinguishedname
#Gets all of the objects that are in a group and pulls their names this is to get the admin flag that is appended to names in this group
$group_name = Get-ADObject -Server NW -Filter{objectCategory -eq "group"} | select name

#Counts the devices pulled from the computer query
#TODO Replace "test.txt" with a descriptive file name
$DevNum = $all_computers.count
echo "Number of devices: " $DevNum > test.txt

#Remove servers from the list
$all_computers = $all_computers | ?{ $_ -NotMatch "Servers" }

#Counts the number of servers we removed
$NumSkipped = $DevNum - $all_computers.count

Switch($all_computers){
    #Finding all of the accounts where both types of admins exist and removing them from the master list
    {$group_name.name -contains $($_.name + "Admins") -and $group_name.name -contains $($_.name + "UPEPAdmins")} {$_ | Add-Member "admintype"  "both";Continue}
    #Finding all of the accounts with only exception admins and removing them from the master list
    {$group_name.name -contains $($_.name + "Admins")} {$_ | Add-Member "admintype" "old";Continue}
    #Finding all of the accounts with only upep admins and removing them from the master list
    {$group_name.name -contains $($_.name + "UPEPAdmins")} {$_ | Add-Member "admintype" "UPEP";Continue}
    #These accounts have no admin
    default {$_ | Add-Member "admintype" "No"}
}

#TODO Replace "test.txt" with a descriptive file name
echo "Number of servers skipped: " $NumSkipped >> test.txt

#Calculating the number of workstations 
echo "Number of workstations: " $all_computers.count >> test.txt

#TODO Replace "test.txt" with a descriptive file name
echo "Number of Exception admins found: " $($all_computers|?{$_.admintype -match "old|both"}).count >> test.txt

#TODO Replace "test.txt" with a descriptive file name
echo "Number of UPEP admins found: " $($all_computers|?{$_.admintype -match "upep|both"}).count >> test.txt

#Find both exception and upep admin names
if($($all_computers|?{$_.admintype -ne "No"}))
{
        echo "Number of both UPEP and Exception Admins: 0" >> test.txt
}else
{
        echo "Number of both UPEP and Exception Admins: " $($all_computers|?{$_.admintype -match "both"}).count >> test.txt
}

#output
$all_computers | Export-Csv test.csv

【讨论】:

  • 我在脚本的一个分支上进行了更改,并在测试台服务器上运行了几次,平均运行时间增加了 6 秒(达到 36 秒)。测试台服务器上的上一次迭代能够在 30 秒内完成。 switch 语句中是否有一些东西可以比我做的更好,因为你失去了重新定义变量的开销?另外,感谢您的时间和帮助,我真的很感激! :)
  • 我重新阅读了您的脚本,并且正在做出一些重大更改,并会尽快回复您。我认为肯定可以做一些改进来加快速度并帮助管理内存以进行更大的迭代。
  • 好的,再次感谢。请注意,我删除了 trim 命令并将其替换为 -replace $upep_admin_exist_cnt = Compare-Object -DifferenceObject $group_name -ReferenceObject $com_name_upep -ExcludeDifferent -IncludeEqual -Property name | Select-Object @{name="name";expression={$($_.Name).ToString() -replace "UPEPAdmins"}} 现在看起来像这样,我将更新我的问题以反映您当前的建议和我的更改!
  • 我希望我能不止一次地支持这个答案,真的很棒。也许您可以在codereview.stackexchange.com 上尝试一下,因为脚本已经运行得很好,尽管在我看来,运行几个小时的脚本对我来说是个问题:) 我真的很好奇这个结果规模很大。
  • 我从未使用过codereview.stackexchange.com,我不熟悉它,但我必须检查一下。是的,我运行了几个小时的最后一个脚本处理了近 700k 个服务器对象、它们接触的每个组(嵌套和嵌套),以及这些组的所有其他成员。
【解决方案2】:

根据您可用的 powershell 版本(iirc 您需要 PSv4),您可以使用列表中的 .where 方法,它要快得多:

$servs = $all_computers.where{$_ -match "Servers"} | Select-Object Name,DistinguishedName

我怀疑之后使用 select 也会快一点,但这只是猜测。

【讨论】:

  • 实际使用 {$_ -eq "Servers"} 可能会更快;)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-11-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-14
  • 1970-01-01
相关资源
最近更新 更多