Jump to content

Shorty zum Thema "Performance" beim Skripting gegen AD


Der letzte Beitrag zu diesem Thema ist mehr als 180 Tage alt. Bitte erstelle einen neuen Beitrag zu Deiner Anfrage!

Empfohlene BeitrÀge

Geschrieben (bearbeitet)

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
  • Danke 5
Geschrieben

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

 

Geschrieben
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.

Geschrieben (bearbeitet)

Was mich vor allem angesichts der Person des Urhebers wundert, ist, dass die Tests und die Misere am Ende mit ADWS und nicht mit LDAP erfolgten :-) @daabm  Du kannst das Ergebnis doch bestimmt auch mit dem DirectorySearcher reproduzieren - erhöhen sich da die Limits vielleicht?

bearbeitet von cj_berlin
Geschrieben

Klar kann ich das - gelegentlich... :-) Du aber auch.

 

Die Motivation war aber nicht, diejenigen zu erreichen, die System.DirectoryServices nutzen. Sondern die, die mit den "gĂ€ngigen" Standardvorgehen bei Powershell ĂŒber Dinge iterieren. Und dabei zu weit rechts filtern 😂 Diesen Mal nur nicht so offensichtlich.

Der letzte Beitrag zu diesem Thema ist mehr als 180 Tage alt. Bitte erstelle einen neuen Beitrag zu Deiner Anfrage!

Erstelle ein Benutzerkonto oder melde dich an, um zu kommentieren

Du musst ein Benutzerkonto haben, um einen Kommentar verfassen zu können

Benutzerkonto erstellen

Neues Benutzerkonto fĂŒr unsere Community erstellen. Es ist einfach!

Neues Benutzerkonto erstellen

Anmelden

Du hast bereits ein Benutzerkonto? Melde dich hier an.

Jetzt anmelden
×
×
  • Neu erstellen...