Zoptymalizuj filtr Get-ADUser
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-Progress
zdefiniowanej tutaj funkcji ), a samo Get-ADUser
polecenie 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
Ponieważ nie wiemy, co jest w $properties
lub $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-Progress
skryptu, 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-ADUser
zwraca 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
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
process
bloku, 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-Progress
wywoł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-Progress
dla 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-Progress
która wymaga przekazania całkowitej liczby obiektów jako argumentu i wykonuje prawidłowe strumieniowe przetwarzanie obiektu wejściowego (poprzezprocess
blok); 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.