Optimizar el filtro Get-ADUser

Nov 29 2020

En AD, estoy tratando de identificar cuentas de usuario donde el mismo valor de EmployeeID se completa en 2 o más registros. A continuación se muestra mi fragmento de código (crédito: estoy usando una Show-Progressfunción definida aquí ) y el Get-ADUsercomando solo ha tardado más de 2 horas en recuperar todos los registros. Los otros pasos (2 a 5) han sido bastante rápidos. Mientras he completado el trabajo, estoy tratando de saber si esto podría haberse hecho de manera más eficiente con 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

PD: También intenté exportar primero todas las cuentas de usuario a un archivo csv y luego postprocesar con Excel, pero tuve que fruncir el ceño debido al tamaño del conjunto de datos y fue tanto tiempo como memoria.

Cualquier sugerencia es muy apreciada.

Respuestas

2 Theo Nov 29 2020 at 16:20

Dado que no sabemos qué hay en $propertieso $selectPropsList, su pregunta es realmente solo sobre averiguar a qué usuarios se les ha emitido el mismo EmployeeID, ¿verdad?
De forma predeterminada, Get-ADUser ya devuelve estas propiedades:

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

Entonces, todo lo que necesita adicional es el EmployeeID, supongo. Tratar de recolectar MUCHAS propiedades se ralentiza, por lo que mantener esto al mínimo ayuda a acelerar las cosas.

A continuación, al utilizar el Show-Progressscript al que se ha vinculado, ralentizará considerablemente la ejecución del script. ¿Realmente necesitas tener una barra de progreso? ¿Por qué no escribir simplemente las líneas con los pasos de la actividad directamente en la consola?

Además, conectar todo junto tampoco ayuda en el departamento de velocidad.

$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-ADUserya devuelve solo objetos de usuario, por lo que no es necesario el filtro LDAP (ObjectCategory=Person)(objectclass=user). Usar -Filter "EmployeeID -like '*'"es probablemente más rápido

1 mklement0 Nov 29 2020 at 22:27

Esta respuesta complementa la útil respuesta de Theo y se enfoca en mostrar el progreso durante la operación :

  • La función vinculadaShow-Progress , que es la más reciente a la fecha de este escrito:

    • tiene un error absoluto , ya que no pasa la entrada de la tubería (la línea relevante se comenta accidentalmente)

    • es conceptualmente defectuoso porque no usa un processbloque, lo que significa que toda la entrada de la canalización se recopila primero , antes de procesarse, lo que anula la idea de una barra de progreso.

  • Por lo tanto, sus Show-Progressllamadas no mostrarán el progreso hasta que el comando anterior en la canalización haya generado toda su salida. Una alternativa simple es dividir la canalización en comandos separados y simplemente emitir un mensaje de progreso antes de cada comando, anunciando la siguiente etapa de procesamiento (en lugar del progreso por objeto) como se muestra en la respuesta de Theo.

  • Generalmente, no hay forma de mostrar el progreso del procesamiento interno del comando , solo el progreso de la salida de un comando (multi-objeto) .

    • La forma más sencilla de hacerlo es mediante una ForEach-Objectllamada en la que llamas
      Write-Progress, pero eso conlleva dos desafíos:

      • Para mostrar una barra de progreso de porcentaje completo , necesita saber cuántos objetos habrá en total , lo que debe determinar con anticipación , porque una canalización no puede saber cuántos objetos recibirá; su única opción es recopilar primero toda la salida (o encontrar otra forma de contarla) y luego usar la salida recopilada como entrada de canalización, utilizando el recuento de objetos como base para calcular el valor a pasar Write-Progress -PerCentComplete.

      • Llamar Write-Progresspor cada objeto recibido resultará en una ralentización significativa del procesamiento general; un compromiso es llamarlo solo para cada N objetos, como se muestra en esta respuesta ; el enfoque podría estar envuelto en una función correctamente implementada a la Show-Progressque requiere pasar el recuento total de objetos como un argumento y realiza un procesamiento de entrada de objetos de flujo adecuado (a través de un processbloque); Dicho esto, el mero hecho de usar código PowerShell para pasar objetos de entrada es costoso.


Conclusión:

Las pantallas de progreso de porcentaje completo tienen dos problemas inherentes :

  • Requieren que conozca el número total de objetos a procesar de antemano (una canalización no tiene forma de saber cuántos objetos pasarán a través de ella):

    • O bien: recopile todos los objetos para procesar en la memoria , de antemano , si es posible; el recuento de elementos de la colección puede servir como base para los cálculos de porcentaje completo. Puede que esta no sea una opción con conjuntos de entrada muy grandes.

    • O: Realice un paso de procesamiento adicional de antemano que simplemente cuente todos los objetos sin recuperarlos realmente. Esto puede no ser práctico en términos del tiempo de procesamiento adicional agregado.

  • El procesamiento objeto por objeto en el código de PowerShell , ya sea mediante ForEach-Objectun script / función avanzada o mediante un script o función avanzada , es inherentemente lento.

    • Puede mitigar eso un poco limitando las Write-Progressllamadas a cada N objetos, como se muestra en esta respuesta

En general, es una compensación entre la velocidad de procesamiento y la capacidad de mostrar el progreso porcentual completo al usuario final.