更快地获取具有多个受信任林和域的网络中数百万个AD组的AD成员身份

时间:2018-02-09 18:45:18

标签: powershell active-directory ldap

首先,我无法理解为什么我需要这些数据,而且我无法了解有关网络的细节。您必须相信我除了PowerShell脚本之外没有其他方法可以获取此数据来运行LDAP查询。

我正在使用具有多个林和多个域的网络。所有森林之间都有信任。我在其中一个森林上登录了一个域,但由于信任,我可以查询所有这些域。

我有一个包含数百万个AD群组的CSV文件。我需要找到数百万AD组中每个人的所有直接成员。很多成员资格都是跨域的,这意味着我不能只使用AD组的member属性,而是必须查询每个域并检查memberOf

我有一个获取此数据的PowerShell脚本。出于各种原因,我不能分享我的代码,但这就是它的作用:

  1. 为我的所有域创建一个System.DirectoryServices.DirectorySearcher个对象数组
  2. 遍历CSV文件,该文件包含每个AD组及其DN
  3. 的列表 对于每个DN
  4. ,循环遍历DirectorySearcher数组,并在memberOfDirectorySearcher)<中查找属于(memberOf=$adGroupDN) AD组的所有对象/ LI>

    代码有效。但由于我正在处理包含数百万个AD组的输入列表,因此脚本非常慢。根据我的测试运行计算,我需要两周多的时间才能获得所需的所有数据。

    我想知道是否有更好/更快的方法来做到这一点?

    我想也许我可以使用线程或其他东西,但我不确定这是否有帮助,也不确定从哪里开始。

    非常感谢任何建议。

    添加一些其他详细信息......

    • 我的输入列表是数百万个唯一的群组
    • 我有多个不同的林/域
    • 我的输入组DN分布在所有林/域
    • 我的输入组DN列表中的组跨越不同的林/域(domain1\group1从我的输入列表中domain2\group2作为成员)
    • 我需要从输入列表中获取组中每个组的完整列表
    • 由于跨域成员身份,我不能依赖我的输入组的member属性。我知道获取它的唯一方法是从输入列表中查询所有组memberOf的每个DC /域。
    • 我只能使用PowerShell
    • 我没有ActiveDirectory模块,只能使用.NET DirectorySearcher
    • 在高级别,我的代码如下所示:

      $arrayOfDirectorySearcherObjectsForEachDCInMyNetwork = ... code to create an array of System.DirectoryServices.DirectorySearcher objects, one for each DC/domain in my network
      
      Foreach ($groupDN in $inputListOfUniqueGroupDNs)
      {
          Foreach ($domain in $arrayOfDirectorySearcherObjectsForEachDCInMyNetwork)
          {
              ...
      
    • 我能想到让它更快的唯一方法是多线程第二个for循环,它使用运行空间同时查询多个DC /域但我无法弄清楚如何做到这一点......

3 个答案:

答案 0 :(得分:2)

如果是一个选项,在域控制器上运行脚本会给你一点点优势。但是否则多线程可能是你最好的选择。

使用Start-Job。有一个例子here

那就是说,我对此表示质疑:

  

很多会员资格都是跨域的,这意味着我不能只使用   AD组的成员属性,而不是每个查询   域并检查memberOf。

小组范围在这里很重要。如果您的所有群组都是通用群组,则无论哪种方式都不会有所影响(无论您是在群组上查看member还是在用户上查看memberOf)。

但重要的是要注意memberOf不会在不同的域上显示域本地组组(即使在同一个林中)。

组中的member属性始终是成员的权威来源。是的,在受信任的域上获取帐户的详细信息有点困难,但可以这样做。

这是一个PowerShell函数,它将拉取组中每个成员的“域\用户名”,包括嵌套组中的那些成员。

function OutputMembers {
    param([string] $groupDn)

    foreach ($m in ([ADSI]("LDAP://" + $groupDn)).member) {
        $member = [ADSI]("LDAP://" + $m)
        $member.objectClass
        if ($member.objectClass -eq "group") {
            #this member is a group so pull the members of that group
            OutputMembers $member.distinguishedName
        } else {
            #"msDS-PrincipalName" is not loaded by default, so we have to tell it to get it
            $member.Invoke("GetInfoEx", @("msDS-PrincipalName"), 0)
            if ([string]::IsNullOrEmpty($member."msDS-PrincipalName")) {
                #member is on a trusted domain, so we have to go look it up
                $sid = New-Object System.Security.Principal.SecurityIdentifier ($member.objectSid[0], 0)
                $sid.Translate([System.Security.Principal.NTAccount]).value
            } else {
                $member."msDS-PrincipalName"
            }
        }
    }
}

通过它,您可以使用每个组的distinguishedName调用该函数,例如:

OutputMembers "CN=MyGroup,OU=Groups,DC=domain,DC=com"

答案 1 :(得分:1)

我认为步骤3中的性能问题可能是嵌入式循环(如果你也寻找间接组成员资格,它甚至可能是递归的):

Foreach ($UserDN in $UserDNs) {
    ...
    Foreach ($GroupDN in $GroupDNs) {
        ...

内循环中的所有内容对于脚本的性能非常重要,因为它将被调用$UserDNs.Count * $GroupDNs.Count次! 我怀疑内部循环中存在大量冗余LDAP查询(对于属于同一组的用户),因此关注它并构建一种对服务器的每个冗余查询的自定义缓存以克服此问题。类似的东西:

$MemberCache = @{}

Function GetMembers([String]$GroupDN) {
    If (!$MemberCache.ContainsKey($GroupDN)) {
        $MemberCache[$GroupDN] = @{}    #HashTables are much faster then using the contains method on an array
        # retrieve all members of the AD group in that DirectorySearcher
        ForEach ($Member in $Members) {$MemberCache[$GroupDN].$Member = $True}
    }
    $MemberCache[$GroupDN]
}

Function IsMember([String]$DN, [String]$GroupDN) {
    (GetMembers($GroupDN)).ContainsKey($DN)
}

一般的想法是,您不应远程重做“查找memberOf DirectorySearcher ((memberOf=$adGroupDN))$adGroupDN AD组中的所有对象{{1 (之前已经查询过的任何组)但是从本地哈希表(缓存)中检索所需的信息。

答案 2 :(得分:0)

这里可以做几个优化。这些优化与Powershell无关,而与算法本身无关。

  1. Powershell不是为执行此类任务而设计的。应该使用C \ C ++或至少C#。
  2. 创建并维护与您查询信息的每个全局编录的RootDse对象的连接,直到完成所有作业。在这种情况下,所有AD查询都将使用一个到AD的单个缓存连接,这可以显着提高性能。
  3. 赞成iRon。为所有查询的组创建缓存。例如,如果查询组A的成员资格而组A是组B的成员,则无需再次查询组A的成员资格。当然,在您的情况下,您不能简单地将所有成员资格存储在内存中,因此您需要创建某种存储成员资格的存储
  4. 并行地从CSV中读取组。制作几个主题。要将其与组缓存组合,您需要运行2个缓存。一个用于已查询的组,另一个用于待处理组,此线程正在查询(以避免在不同线程中对同一组进行双重查询)。这些缓存当然应该是线程安全的。如果一个线程看到另一个线程正在查询该组,则该线程可以跳过当前组并查询任何其他组,并稍后返回。
  5. 对于外部安全主体(来自受信任域的用户和组),请使用SID绑定。您可以从成员DN中提取FSP的sid。直接绑定比DirectorySearcher
  6. 快得多

    请注意:

    1. 对Gabriel Luci赞不绝口。您需要查询成员,而不是memberOf属性,
    2. 不要忘记嵌套组(组A - >组B组&gt;用户U)。用户是B组的成员以及A组。此规则也适用于受信任的域组
    3. 群组可以是彼此的成员。 E. g。 A - &gt; B - &gt; C - &gt; A或甚至A - > B - &gt;答:您必须在脚本中处理此问题