Оптимизировать фильтр Get-ADUser

Nov 29 2020

В AD я пытаюсь определить учетные записи пользователей, в которых одно и то же значение EmployeeID содержится в 2 или более записях. Ниже приведен мой фрагмент кода (кредит: я использую Show-Progressопределенную здесь функцию ), и Get-ADUserодной команде потребовалось более 2 часов, чтобы получить все записи. Остальные шаги (2–5) были выполнены довольно быстро. Завершая работу, я пытаюсь понять, можно ли сделать это более эффективно с помощью 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: Я также попытался сначала экспортировать все учетные записи пользователей в файл csv, а затем выполнить постобработку с помощью Excel, но мне пришлось нахмуриться из-за размера набора данных, и это было как время, так и память.

Любое предложение приветствуется.

Ответы

2 Theo Nov 29 2020 at 16:20

Поскольку мы не знаем, что входит в $propertiesили $selectPropsList, ваш вопрос на самом деле только о том, чтобы выяснить, каким пользователям был выдан один и тот же EmployeeID, верно?
По умолчанию Get-ADUser уже возвращает следующие свойства:

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

Так что все, что вам нужно дополнительно, - это идентификатор EmployeeID. Попытка собрать ОЧЕНЬ много свойств действительно замедляет работу, поэтому сведение к минимуму помогает ускорить процесс.

Затем, используя Show-Progressскрипт, с которым вы связались, вы значительно замедляете его выполнение. Вам действительно нужен индикатор выполнения? Почему бы просто не записать строки с шагами активности прямо в консоль?

Кроме того, соединение всего вместе не помогает и в отделе скорости.

$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уже возвращает только пользовательские объекты, поэтому фильтр LDAP не нужен (ObjectCategory=Person)(objectclass=user). Использование -Filter "EmployeeID -like '*'", вероятно, быстрее

1 mklement0 Nov 29 2020 at 22:27

Этот ответ дополняет полезный ответ Тео и фокусируется на демонстрации прогресса во время операции :

  • Связана Show-Progressфункция , которая является последней в этой записи:

    • имеет явную ошибку в том, что он не передает входные данные конвейера (соответствующая строка случайно закомментирована)

    • концептуально ошибочен в том, что он не использует processблок, что означает, что весь ввод конвейера собирается в первую очередь , прежде чем он будет обработан, что опровергает идею индикатора выполнения.

  • Следовательно, ваши Show-Progressвызовы не будут показывать прогресс, пока предыдущая команда в конвейере не выведет весь свой вывод. Простая альтернатива - разбить конвейер на отдельные команды и просто выдавать одно сообщение о ходе выполнения перед каждой командой, объявляя о следующем этапе обработки (а не о ходе выполнения для каждого объекта), как показано в ответе Тео.

  • Как правило, нет способа показать ход внутренней обработки команды, только ход вывода команды (многообъектного) .

    • Самый простой способ сделать это - ForEach-Objectпозвонить по телефону
      Write-Progress, но при этом возникают две проблемы:

      • Чтобы отобразить индикатор выполнения в процентах , вам необходимо знать, сколько всего объектов будет , что вы должны определить заранее , потому что конвейер не может знать, сколько объектов он получит; ваш единственный вариант - сначала собрать весь вывод (или найти другой способ его подсчета), а затем использовать собранный вывод в качестве ввода конвейера, используя количество объектов в качестве основы для вычисления значения, которое нужно передать Write-Progress -PerCentComplete.

      • Вызов Write-Progressдля каждого объекта , полученным приведут к значительному замедлению общей обработки; компромисс заключается в том, чтобы вызывать его только для каждых N объектов, как показано в этом ответе ; подход там может быть заключен в правильно реализованную функцию, например, Show-Progressкоторая требует передачи общего количества объектов в качестве аргумента и выполняет надлежащую потоковую обработку входных объектов (через processблок); Тем не менее, простое использование кода PowerShell для передачи входных объектов обходится дорого.


Заключение:

Отображение процентного выполнения имеет две неотъемлемые проблемы :

  • Они требуют, чтобы вы знали общее количество объектов для обработки заранее (конвейер не имеет возможности узнать, сколько объектов пройдет через него):

    • Либо: Собрать все объекты процесса в памяти , заранее , если это возможно; количество элементов в коллекции может служить основой для вычислений процента выполнения. Это может не подходить для очень больших наборов входных данных.

    • Или: заранее выполните дополнительный этап обработки, который просто подсчитывает все объекты без фактического их извлечения. Это может оказаться непрактичным с точки зрения добавленного дополнительного времени обработки.

  • Обработка объекта за объектом в коде PowerShell - с помощью ForEach-Objectили расширенного сценария / функции - по своей сути медленная.

    • Вы можете несколько смягчить это, ограничив Write-Progressвызовы для всех N объектов, как показано в этом ответе.

В целом это компромисс между скоростью обработки и возможностью показывать конечному пользователю процент выполнения .