Optimieren Sie den Get-ADUser-Filter

Nov 29 2020

In AD versuche ich, Benutzerkonten zu identifizieren, bei denen derselbe EmployeeID-Wert in zwei oder mehr Datensätzen angegeben ist. Unten ist mein Code (Kredit: Ich verwende eine hierShow-Progress definierte Funktion ) und der Befehl allein hat mehr als 2 Stunden gebraucht, um alle Datensätze abzurufen. Die anderen Schritte (2 bis 5) waren ziemlich schnell. Während ich die Arbeit abgeschlossen habe, versuche ich zu wissen, ob dies mit PowerShell effizienter hätte durchgeführt werden können.Get-ADUser

Get-ADUser -LDAPFilter "(&(ObjectCategory=Person)(objectclass=user)(employeeid=*))" -Properties $properties -Server $server_AD_GC -ResultPageSize 1000 | 
    # *ISSUE HERE*
    #    The Get-ADUser extract process seems to work very slow.
    #    However, it is important to note that the above command will be retrieving more than 200K records
    # NOTE: I've inferred that employeeid is an indexed attribute and is replicated to GlobalCatalogs and hence have used it in the filter
    Show-Progress -Activity "(1/5) Getting AD Users ..." |
select $selectPropsList -OutVariable results_UsersBaseSet | Group-Object EmployeeID | Show-Progress -Activity "(2/5) Grouping on EmployeeID ..." | ? { $_.Count -gt 1 } | 
    Show-Progress -Activity "(3/5) Filtering only dup EmpID records ..." | 
select -Exp Group | 
    Show-Progress -Activity "(4/5) UnGrouping ..." | 
Export-Csv "C:\Users\me\op_GetADUser_w_EmpID_Dupes_EntireForest - $([datetime]::Now.ToString("MM-dd-yyyy_hhmmss")).csv" -NoTypeInformation |
    Show-Progress -Activity "(5/5) Exporting ..." | 
Out-Null

PS: Ich habe auch versucht, zuerst alle Benutzerkonten in eine CSV-Datei zu exportieren und dann mit Excel nachzubearbeiten, aber ich musste wegen der Größe des Datensatzes die Stirn runzeln und es war sowohl Zeit- als auch Speicherprobleme.

Jeder Vorschlag wird sehr geschätzt.

Antworten

2 Theo Nov 29 2020 at 16:20

Da wir nicht wissen, was in $propertiesoder ist $selectPropsList, geht es bei Ihrer Frage wirklich nur darum herauszufinden, an welche Benutzer dieselbe EmployeeID ausgegeben wurde, oder?
Standardmäßig gibt Get-ADUser bereits folgende Eigenschaften zurück:

DistinguishedName, Enabled, GivenName, Name, ObjectClass, ObjectGUID, SamAccountName, SID, Surname,UserPrincipalName

Alles, was Sie extra brauchen, ist die EmployeeID, denke ich. Der Versuch, VIELE Eigenschaften zu sammeln, verlangsamt sich. Wenn Sie dies auf ein Minimum beschränken, können Sie die Dinge beschleunigen.

Wenn Show-ProgressSie das Skript verwenden , mit dem Sie verknüpft sind, verlangsamen Sie die Ausführung des Skripts erheblich. Benötigen Sie wirklich einen Fortschrittsbalken? Warum nicht einfach die Zeilen mit Aktivitätsschritten direkt in die Konsole schreiben?

Auch in der Geschwindigkeitsabteilung hilft es nicht, alles zusammen zu leiten.

$server_AD_GC = 'YourServer' $selectPropsList = 'EmployeeID', 'Name', 'SamAccountName', 'Enabled'
$outFile = "C:\Users\me\op_GetADUser_w_EmpID_Dupes_EntireForest - $([datetime]::Now.ToString("MM-dd-yyyy_hhmmss")).csv"

Write-Host "Step (1/4) Getting AD Users ..." 
$users = Get-ADUser -Filter "EmployeeID -like '*'" -Properties EmployeeID -Server $server_AD_GC -ResultPageSize 1000

Write-Host "Step (2/4) Grouping on EmployeeID ..."
$dupes = $users | Group-Object -Property EmployeeID | Where-Object { $_.Count -gt 1 } Write-Host "Step (3/4) Collecting duplicates ..." $result = foreach ($group in $dupes) {
    $group.Group | Select-Object $selectPropsList
}

Write-Host "Step (4/4) Exporting ..."
$result | Export-Csv -Path $outFile -NoTypeInformation

Write-Host  "All done" -ForegroundColor Green

PS gibt Get-ADUserbereits nur Benutzerobjekte zurück, sodass der LDAP-Filter nicht erforderlich ist (ObjectCategory=Person)(objectclass=user). Die Verwendung -Filter "EmployeeID -like '*'"ist wahrscheinlich schneller

1 mklement0 Nov 29 2020 at 22:27

Diese Antwort ergänzt Theos hilfreiche Antwort und konzentriert sich darauf , den Fortschritt während der Operation zu zeigen :

  • Die verknüpfte Show-ProgressFunktion , die zum jetzigen Zeitpunkt die neueste ist:

    • hat einen direkten Fehler , da es keine Pipeline-Eingaben weiterleitet (die entsprechende Zeile wurde versehentlich auskommentiert)

    • ist konzeptionell insofern fehlerhaft, als es keinen processBlock verwendet, was bedeutet, dass alle Pipeline-Eingaben zuerst gesammelt werden , bevor sie verarbeitet werden - was die Idee eines Fortschrittsbalkens zunichte macht.

  • Daher Show-Progresszeigen Ihre Aufrufe erst dann den Fortschritt an, wenn der vorherige Befehl in der Pipeline die gesamte Ausgabe ausgegeben hat. Eine einfache Alternative besteht darin , die Pipeline in separate Befehle zu brechen und emittieren einfach eine Fortschrittsmeldung vor jedem Befehl, um die nächste ankündigte Stufe der Verarbeitung (nicht pro-Objekt - progress) , wie in Theo Antwort gezeigt.

  • Im Allgemeinen gibt es keine Möglichkeit, den Fortschritt der befehlsinternen Verarbeitung anzuzeigen, sondern nur den Fortschritt der Ausgabe eines Befehls (mit mehreren Objekten) .

    • Der einfachste Weg, dies über einen ForEach-ObjectAnruf zu tun , bei dem Sie anrufen
      Write-Progress, bringt jedoch zwei Herausforderungen mit sich:

      • Um ein zeigen Prozent-complete Fortschrittsbalken, müssen Sie wissen , wie viele es Objekte in insgesamt sein wird , die Sie bestimmen , müssen vor der Zeit , weil eine Pipeline kann nicht wissen , wie viele Objekte , die es empfangen wird; Sie können nur die gesamte Ausgabe zuerst erfassen (oder einen anderen Weg finden, um sie zu zählen) und dann die erfasste Ausgabe als Pipeline-Eingabe verwenden, wobei die Anzahl der Objekte als Grundlage für die Berechnung des zu übergebenden Werts verwendet wird Write-Progress -PerCentComplete.

      • Der Aufruf Write-Progressfür jedes Objekt empfängt in einer führen deutliche Verlangsamung der Gesamtverarbeitung; Ein Kompromiss besteht darin, es nur für alle N Objekte aufzurufen, wie in dieser Antwort gezeigt . Der dortige Ansatz könnte in eine ordnungsgemäß implementierte Funktion a la Show-Progresseingebunden werden, die die Übergabe der Gesamtobjektzahl als Argument erfordert und eine ordnungsgemäße Streaming- Eingabeobjektverarbeitung (über einen processBlock) durchführt. Die bloße Verwendung von PowerShell-Code zum Weiterleiten von Eingabeobjekten ist jedoch kostspielig.


Fazit:

Prozentuale Fortschrittsanzeigen weisen zwei inhärente Probleme auf :

  • Sie erfordern, dass Sie die Gesamtzahl der zu verarbeitenden Objekte im Voraus kennen (eine Pipeline kann nicht wissen, wie viele Objekte sie passieren werden):

    • Entweder: Sammeln Sie alle Objekte Prozess im Speicher , im Voraus , wenn möglich; Die Anzahl der Elemente in der Sammlung kann dann als Grundlage für die prozentual vollständigen Berechnungen dienen. Dies ist möglicherweise keine Option bei sehr großen Eingabesätzen.

    • Oder: Führen Sie einen zusätzlichen Verarbeitungsschritt voraus , dass nur zählt alle Objekte ohne sie tatsächlich abruft. Dies ist hinsichtlich der zusätzlichen Verarbeitungszeit möglicherweise nicht praktikabel.

  • Die objektweise Verarbeitung in PowerShell-Code - entweder über ForEach-Objectoder über ein erweitertes Skript / eine erweiterte Funktion - ist von Natur aus langsam.

    • Sie können dies etwas abmildern, indem Sie Write-ProgressAufrufe auf alle N Objekte beschränken, wie in dieser Antwort gezeigt

Insgesamt ist dies ein Kompromiss zwischen der Verarbeitungsgeschwindigkeit und der Fähigkeit , dem Endbenutzer den prozentualen Fortschritt anzuzeigen.