Zoptymalizuj filtr Get-ADUser

Nov 29 2020

W usłudze AD próbuję zidentyfikować konta użytkowników, w których ta sama wartość EmployeeID jest umieszczona w 2 lub więcej rekordach. Poniżej znajduje się mój fragment kodu (kredyt: używam Show-Progresszdefiniowanej tutaj funkcji ), a samo Get-ADUserpolecenie zajęło ponad 2 godziny, aby pobrać wszystkie rekordy. Pozostałe kroki (2 do 5) były dość szybkie. Po zakończeniu pracy próbuję się dowiedzieć, czy można to zrobić wydajniej za pomocą PowerShell.

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: Próbowałem też najpierw wyeksportować wszystkie konta użytkowników do pliku csv, a następnie przetworzyć w Excelu, ale musiałem się skrzywić ze względu na rozmiar zbioru danych i było to zarówno przetwarzanie czasu, jak i pamięci.

Każda sugestia jest bardzo ceniona.

Odpowiedzi

2 Theo Nov 29 2020 at 16:20

Ponieważ nie wiemy, co jest w $propertieslub $selectPropsList, twoje pytanie dotyczy tylko ustalenia, którym użytkownikom został wydany ten sam identyfikator pracownika, prawda?
Domyślnie Get-ADUser zwraca już te właściwości:

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

Więc wszystko, czego potrzebujesz, to chyba identyfikator pracownika. Próba zebrania WIELE nieruchomości spowalnia, więc ograniczenie tego do minimum pomaga przyspieszyć działanie.

Następnie, używając Show-Progressskryptu, z którym się łączysz, znacznie spowolnisz wykonywanie skryptu. Czy naprawdę potrzebujesz paska postępu? Dlaczego nie po prostu zapisać wierszy z krokami czynności bezpośrednio na konsoli?

Poza tym łączenie wszystkiego razem nie pomaga też w dziedzinie szybkości.

$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 Get-ADUserzwraca już tylko obiekty użytkownika, więc nie ma potrzeby stosowania filtru LDAP (ObjectCategory=Person)(objectclass=user). Używanie -Filter "EmployeeID -like '*'"jest prawdopodobnie szybsze

1 mklement0 Nov 29 2020 at 22:27

Ta odpowiedź uzupełnia pomocną odpowiedź Theo i koncentruje się na pokazaniu postępów podczas operacji :

  • Związana Show-Progressfunkcja , która jest najnowszym jak to pisze:

    • ma wyraźny błąd polegający na tym, że nie przepuszcza danych wejściowych potoku (odpowiednia linia jest przypadkowo zakomentowana)

    • ma wadę koncepcyjną, ponieważ nie używa processbloku, co oznacza, że wszystkie dane wejściowe z potoku są zbierane jako pierwsze , zanim zostaną przetworzone - co podważa ideę paska postępu.

  • W związku z tym Show-Progresswywołania nie będą pokazywać postępu, dopóki poprzednie polecenie w potoku nie wyświetli wszystkich danych wyjściowych. Prostą alternatywą jest podzielenie potoku na osobne polecenia i po prostu wyemitowanie jednego komunikatu o postępie przed każdym poleceniem, ogłaszając następny etap przetwarzania (zamiast postępu na obiekt), jak pokazano w odpowiedzi Theo.

  • Ogólnie rzecz biorąc, nie ma sposobu, aby pokazać postęp przetwarzania wewnętrznego polecenia , tylko postęp wyjścia polecenia (wielu obiektów) .

    • Najprostszy sposób na zrobienie tego poprzez ForEach-Objectrozmowę, w której dzwonisz
      Write-Progress, wiąże się jednak z dwoma wyzwaniami:

      • Aby wyświetlić pasek postępu w procentach ukończenia , musisz wiedzieć, ile obiektów będzie w sumie , co musisz określić z wyprzedzeniem , ponieważ potok nie może wiedzieć, ile obiektów otrzyma; jedyną opcją jest zebranie najpierw wszystkich danych wyjściowych (lub znalezienie innego sposobu ich policzenia), a następnie użycie zebranych danych wyjściowych jako danych wejściowych potoku, używając liczby obiektów jako podstawy do obliczenia wartości do przekazania Write-Progress -PerCentComplete.

      • Wywołanie Write-Progressdla każdego obiektu otrzymanej spowoduje znaczne spowolnienie ogólnego przetwarzania; kompromisem jest nazywanie go tylko dla każdego N obiektów, jak pokazano w tej odpowiedzi ; podejście tam mogłoby być opakowane w odpowiednio zaimplementowaną funkcję a la, Show-Progressktóra wymaga przekazania całkowitej liczby obiektów jako argumentu i wykonuje prawidłowe strumieniowe przetwarzanie obiektu wejściowego (poprzez processblok); To powiedziawszy, samo użycie kodu PowerShell do przekazywania obiektów wejściowych jest kosztowne.


Wniosek:

Wyświetlacze procentu ukończenia mają dwa nieodłączne problemy :

  • Wymagają one, aby wiedzieć, łączną liczbę obiektów do procesu wcześniej (a rurociąg nie ma możliwości dowiedzenia się, ile obiektów przechodzi przez nią):

    • Albo: Zbierz wszystkie przedmioty do procesu w pamięci , z wyprzedzeniem , jeśli to możliwe; liczba elementów w kolekcji może następnie służyć jako podstawa do obliczeń procentu ukończenia. Może to nie być opcja w przypadku bardzo dużych zestawów danych wejściowych.

    • Lub: Wykonaj wcześniej dodatkowy krok przetwarzania, który po prostu zlicza wszystkie obiekty bez faktycznego ich pobierania. Może to nie być praktyczne ze względu na dodatkowy czas przetwarzania.

  • Przetwarzanie obiekt po obiekcie w kodzie PowerShell - albo poprzez ForEach-Objectalbo zaawansowany skrypt / funkcja - jest z natury powolny.

    • Możesz to nieco złagodzić, ograniczając Write-Progresswywołania do wszystkich N obiektów, jak pokazano w tej odpowiedzi

Ogólnie rzecz biorąc, jest to kompromis między szybkością przetwarzania a możliwością pokazania procentowego postępu użytkownikowi końcowemu.