Otimize o filtro Get-ADUser

Nov 29 2020

No AD, estou tentando identificar contas de usuário em que o mesmo valor EmployeeID é preenchido em 2 ou mais registros. Abaixo está o meu pedaço de código (Crédito: estou usando uma Show-Progressfunção definida aqui ) e o Get-ADUsercomando sozinho levou mais de 2 horas para buscar todos os registros. As outras etapas (2 a 5) foram bem rápidas. Enquanto concluo o trabalho, estou tentando saber se isso poderia ter sido feito de forma mais eficiente com o 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: Eu também tentei primeiro exportar todas as contas de usuário para um arquivo csv e depois pós-processar com o Excel, mas tive que franzir a testa por causa do tamanho do conjunto de dados e isso exigia tanto tempo quanto memória.

Qualquer sugestão é muito apreciada.

Respostas

2 Theo Nov 29 2020 at 16:20

Como não sabemos o que está em $propertiesou $selectPropsList, sua pergunta é apenas sobre descobrir para quais usuários o mesmo EmployeeID foi emitido, certo?
Por padrão, Get-ADUser já retorna estas propriedades:

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

Então, tudo que você precisa extra é o EmployeeID, eu acho. Tentar coletar MUITAS propriedades torna mais lento, então manter isso no mínimo ajuda a acelerar as coisas.

Em seguida, usando o Show-Progressscript ao qual você vinculou, você tornará a execução do script consideravelmente mais lenta. Você realmente precisa de uma barra de progresso? Por que não simplesmente escrever as linhas com etapas de atividades diretamente no console?

Além disso, colocar tudo junto também não ajuda no departamento de velocidade.

$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-ADUserjá retorna apenas objetos de usuário, portanto, não há necessidade do filtro LDAP (ObjectCategory=Person)(objectclass=user). Usar -Filter "EmployeeID -like '*'"é provavelmente mais rápido

1 mklement0 Nov 29 2020 at 22:27

Esta resposta complementa a resposta útil de Theo e se concentra em mostrar o progresso durante a operação :

  • A função vinculadaShow-Progress , que é a mais recente no momento desta redação:

    • tem um bug total , em que não passa a entrada do pipeline (a linha relevante é acidentalmente comentada)

    • é conceitualmente defeituoso por não usar um processbloco, o que significa que toda a entrada do pipeline é coletada primeiro , antes de ser processada - o que frustra a ideia de uma barra de progresso.

  • Portanto, suas Show-Progresschamadas não mostrarão o progresso até que o comando anterior no pipeline produza toda a sua saída. Uma alternativa simples é dividir o pipeline em comandos separados e simplesmente emitir uma mensagem de progresso antes de cada comando, anunciando o próximo estágio de processamento (em vez de progresso por objeto) conforme mostrado na resposta de Theo.

  • Geralmente, não há como mostrar o progresso do processamento interno do comando , apenas o progresso da saída de um comando (multi-objeto) .

    • A maneira mais simples de fazer isso por meio de uma ForEach-Objectchamada em que você liga
      Write-Progress, mas que vem com dois desafios:

      • Para mostrar uma barra de progresso de porcentagem concluída , você precisa saber quantos objetos haverá no total , o que deve ser determinado com antecedência , porque um pipeline não pode saber quantos objetos receberá; sua única opção é coletar toda a saída primeiro (ou encontrar alguma outra maneira de contá-la) e, em seguida, usar a saída coletada como entrada do pipeline, usando a contagem de objetos como base para calcular o valor a ser transmitido Write-Progress -PerCentComplete.

      • Chamando Write-Progresspara cada objeto recebido irá resultar em uma desaceleração significativa do processamento global; um compromisso é chamá-lo apenas para todos os N objetos, como mostrado nesta resposta ; a abordagem pode ser envolvida em uma função implementada adequadamente a la Show-Progressque requer a passagem da contagem total de objetos como um argumento e executa o processamento de objeto de entrada de streaming adequado (por meio de um processbloco); Dito isso, o mero ato de usar o código do PowerShell para passar objetos de entrada é caro.


Conclusão:

As exibições de progresso de porcentagem concluída têm dois problemas inerentes :

  • Eles exigem que você saiba o número total de objetos a serem processados ​​de antemão (um pipeline não tem como saber quantos objetos passarão por ele):

    • Ou: Colete todos os objetos para processar na memória , de antemão , se possível; a contagem de elementos na coleção pode então servir como base para os cálculos de porcentagem completa. Isso pode não ser uma opção com conjuntos de entrada muito grandes.

    • Ou: Execute uma etapa de processamento extra antecipadamente que simplesmente conta todos os objetos, sem realmente recuperá-los. Isso pode não ser prático em termos de tempo de processamento adicional adicionado.

  • O processamento de objeto por objeto no código do PowerShell - por meio de ForEach-Objectum script / função avançada - é inerentemente lento.

    • Você pode atenuar isso limitando as Write-Progresschamadas a todos os N objetos, conforme mostrado nesta resposta

No geral, é uma troca entre a velocidade de processamento e a capacidade de mostrar o progresso percentual completo ao usuário final.