Jump to content

Shorty zum Thema "Performance" beim Skripting gegen AD


Empfohlene Beiträge

Hallo zusammen. Keine Frage, nur ein Tip, wenn's mal schneller gehen soll :-)

 

Wenn man in Active Directory User/Gruppen/Mitgliedschaften auslesen oder bearbeiten will, findet man oft Schleifen, die über Mitglieder oder Mitgliedschaften iterieren und mit den Ergebnissen dann wieder etwas machen. Das läßt sich dramatisch beschleunigen, indem man nicht iteriert, sondern vorher einen passenden LDAP-Filter baut. Und LDAP-Filter dürfen lang sein - sehr lang.

 

Ich hatte das Thema kürzlich, als es um Account-Dubletten im Rahmen von Domänen-Migrationen ging. Das "übliche" Vorgehen wäre wohl, auf der einen Seite alle Accounts zu holen und dann für jeden Account zu prüfen, ob es den auch auf der anderen Seite gibt. Holt man die Accounts der anderen Seite dagegen über einen LDAP-Filter auf die Accounts, die man auf der ersten Seite schon gefunden hat (nur die können ja doppelt vorhanden sein), wird das dramatisch schneller... Getestet mit bis zu 2.000 sAMAccountName im LDAP-Filter, geht problemlos.

 

Die einzige Herausforderung dabei ist, den Input (also das Member- oder MemberOf-Array) passend zu zerlegen. Ich habe mich für die einfachste Variante entschieden, und ich setze auch voraus, daß der CN eindeutig ist. Aber egal wie aufwändig das Konstruieren des LDAP-Filter wird - es wird immer um Größenordnungen schneller gehen als die Objekte einzeln zu holen.

 

Sample Code - $GroupName muss natürlich angepasst werden, wenn das jemand ausprobieren will:

 

$GroupName = 'LDAPFilterGroup'
$Group = Get-ADGroup $GroupName -Properties Member

$NumRepetitions = 5

$ScriptBlocks = @(
    {   $Members = @()
        Foreach ( $Member in $Group.member ) {
            $Members += Get-ADObject $Member
        }
    },
    {   $Members = [Collections.Arraylist]::new()
        Foreach ( $Member in $Group.member ) {
            [void] $Members.Add( ( Get-ADObject $Member ) )
        }
    },
    {   $GroupMembers = $Group.Member | Foreach { ( $_ -Split ',(OU|CN)=' -Replace '^CN=', '' )[0] }
        $LdapFilter = '(|(sAMAccountName=' + ( $GroupMembers -join ')(sAMAccountName=' ) + '))'
        $Members = Get-ADObject -LDAPFilter $LdapFilter
    }
)

Foreach ( $ScriptBlock in $ScriptBlocks ) {
    $Durations = For ( $i=1; $i -le $NumRepetitions; $i++ ) { Measure-Command -Expression $ScriptBlock }
    $Durations | Measure-Object TotalSeconds -Average | Select-Object Average
}

 

Ergebnis in einer Spielumgebung für eine Gruppe mit 1.000 Mitgliedern in Sekunden:

 

Foreach mit @(): 6,5145044
Foreach mit ArrayList: 5,96655574
LDAP-Filter: 0,75933482

 

Muß man glaub nix mehr dazu sagen... [Collections.Arraylist] spart schon bei nur 1.000 Accounts eine halbe Sekunde, aber Variante 3 mit dem "extrem langen LDAP-Filter" ist um den Faktor 10 schneller (der Filter hat knapp 24 kB). Und das wird immer schlimmer für die anderen beiden Methoden, wenn es mehr Mitglieder sind und die Netzwerkverbindung langsamer.

 

Edit: Warum ich auf so was achte? Weil manche unserer Domains 150.000 User, 30.000 OUs und 10.000 GPOs haben. Die Anzahl der Gruppen weiß ich nicht, aber auch irgendwo im 6-stelligen Bereich. Da ist Geschwindigkeit alles...

bearbeitet von daabm
Link zu diesem Kommentar

Kurzer Nachtrag dazu: Wenn der LDAP-Filter zu groß wird, geht das in die Hose. Nach ein paar Tests mit sAMAccountName - sind es mehr als ~40k Einträge (der Filter ist dann etwa 1 MB groß), verweigert der Server die Antwort.

Belastbare Informationen über entsprechende Limits habe ich aber keine gefunden... Wenn jemand was dazu hat, gerne her damit :-)

$LDAPFilter = "(|" + ( ( 1..50000 | Foreach { "(sAMAccountName=User$_)" } ) -join ''  ) + ")"
$LDAPFilter.Length
( Measure-Command { $ADobjects = Get-ADObject -LDAPFilter $LDAPFilter } ).TotalSeconds

Ergebnis:

1158897
Get-ADObject : Es kann keine Verbindung mit dem Server hergestellt werden. Dies liegt möglicherweise daran, dass der Server nicht 
vorhanden oder derzeit ausgefallen ist oder dass darauf nicht Active Directory-Webdienste ausgeführt wird.
In Zeile:3 Zeichen:34
+ ... e-Command { $ADobjects = Get-ADObject -LDAPFilter $LDAPFilter } ).Tot ...
+                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ResourceUnavailable: (:) [Get-ADObject], ADServerDownException
    + FullyQualifiedErrorId : ActiveDirectoryServer:0,Microsoft.ActiveDirectory.Management.Commands.GetADObject
 
0,319832

 

Link zu diesem Kommentar
Am 16.2.2024 um 17:38 schrieb daabm:

Kurzer Nachtrag dazu: Wenn der LDAP-Filter zu groß wird, geht das in die Hose. Nach ein paar Tests mit sAMAccountName - sind es mehr als ~40k Einträge (der Filter ist dann etwa 1 MB groß), verweigert der Server die Antwort.

Belastbare Informationen über entsprechende Limits habe ich aber keine gefunden... Wenn jemand was dazu hat, gerne her damit :-)

$LDAPFilter = "(|" + ( ( 1..50000 | Foreach { "(sAMAccountName=User$_)" } ) -join ''  ) + ")"
$LDAPFilter.Length
( Measure-Command { $ADobjects = Get-ADObject -LDAPFilter $LDAPFilter } ).TotalSeconds

Ergebnis:

1158897
Get-ADObject : Es kann keine Verbindung mit dem Server hergestellt werden. Dies liegt möglicherweise daran, dass der Server nicht 
vorhanden oder derzeit ausgefallen ist oder dass darauf nicht Active Directory-Webdienste ausgeführt wird.
In Zeile:3 Zeichen:34
+ ... e-Command { $ADobjects = Get-ADObject -LDAPFilter $LDAPFilter } ).Tot ...
+                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ResourceUnavailable: (:) [Get-ADObject], ADServerDownException
    + FullyQualifiedErrorId : ActiveDirectoryServer:0,Microsoft.ActiveDirectory.Management.Commands.GetADObject
 
0,319832

 

 

Leider habe ich keine Umgebung mehr mit genug Accounts :-) Ansonsten würde ich genau jetzt bei MS nach den entsprechenden Limits fragen ... Der Call müsste dann ja sogar kostenfrei sein.

Link zu diesem Kommentar

Schreibe einen Kommentar

Du kannst jetzt antworten und Dich später registrieren. Falls Du bereits ein Mitglied bist, logge Dich jetzt ein.

Gast
Auf dieses Thema antworten...

×   Du hast formatierten Text eingefügt.   Formatierung jetzt entfernen

  Only 75 emoji are allowed.

×   Dein Link wurde automatisch eingebettet.   Einbetten rückgängig machen und als Link darstellen

×   Dein vorheriger Inhalt wurde wiederhergestellt.   Editor-Fenster leeren

×   Du kannst Bilder nicht direkt einfügen. Lade Bilder hoch oder lade sie von einer URL.

×
×
  • Neu erstellen...