Optimiser le filtre Get-ADUser

Nov 29 2020

Dans AD, j'essaie d'identifier les comptes d'utilisateurs où la même valeur EmployeeID est renseignée dans 2 enregistrements ou plus. Voici mon morceau de code (crédit: j'utilise une Show-Progressfonction définie ici ) et la Get-ADUsercommande seule a pris plus de 2 heures pour récupérer tous les enregistrements. Les autres étapes (2 à 5) ont été assez rapides. Pendant que j'ai terminé le travail, j'essaie de savoir si cela aurait pu être fait plus efficacement avec 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: J'ai également essayé d'exporter d'abord tous les comptes d'utilisateurs dans un fichier csv, puis de post-traiter avec Excel, mais j'ai dû froncer les sourcils à cause de la taille de l'ensemble de données et c'était à la fois un temps et une mémoire crunch.

Les suggestions sont grandement appréciées.

Réponses

2 Theo Nov 29 2020 at 16:20

Puisque nous ne savons pas ce qu'il y a dans $propertiesou $selectPropsList, votre question est en réalité de savoir à quels utilisateurs le même EmployeeID a été émis, n'est-ce pas?
Par défaut, Get-ADUser renvoie déjà ces propriétés:

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

Donc, tout ce dont vous avez besoin en plus est le EmployeeID, je suppose. Essayer de collecter BEAUCOUP de propriétés ralentit, donc garder cela au strict minimum permet d'accélérer les choses.

Ensuite, en utilisant le Show-Progressscript auquel vous avez lié, vous ralentirez considérablement l'exécution du script. Avez-vous vraiment besoin d'une barre de progression? Pourquoi ne pas simplement écrire les lignes avec les étapes de l'activité directement sur la console?

De plus, tout assembler n'aide pas non plus dans le département de la vitesse.

$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-ADUserrenvoie déjà uniquement les objets utilisateur, il n'est donc pas nécessaire d'utiliser le filtre LDAP (ObjectCategory=Person)(objectclass=user). L'utilisation -Filter "EmployeeID -like '*'"est probablement plus rapide

1 mklement0 Nov 29 2020 at 22:27

Cette réponse complète la réponse utile de Theo et se concentre sur les progrès réalisés pendant l'opération :

  • La fonction liéeShow-Progress , qui est la dernière à ce jour:

    • a un bogue pur et simple , en ce qu'il ne passe pas l'entrée du pipeline (la ligne correspondante est accidentellement commentée)

    • est conceptuellement défectueux en ce qu'il n'utilise pas de processbloc, ce qui signifie que toutes les entrées du pipeline sont collectées en premier , avant d'être traitées - ce qui va à l'encontre de l'idée d'une barre de progression.

  • Par conséquent, vos Show-Progressappels n'indiqueront pas la progression tant que la commande précédente du pipeline n'aura pas sorti toute sa sortie. Une alternative simple est de diviser le pipeline en commandes séparées et d'émettre simplement un message de progression avant chaque commande, annonçant l' étape suivante du traitement (plutôt que la progression par objet) comme indiqué dans la réponse de Theo.

  • Généralement, il n'y a aucun moyen d'afficher la progression du traitement interne à la commande , uniquement la progression de la sortie d'une commande (multi-objet) .

    • Le moyen le plus simple de le faire via un ForEach-Objectappel dans lequel vous appelez
      Write-Progress, mais cela comporte deux défis:

      • Pour afficher une barre de progression en pourcentage achevé , vous devez savoir combien d'objets il y aura au total , ce que vous devez déterminer à l' avance , car un pipeline ne peut pas savoir combien d'objets il recevra; votre seule option est de collecter d'abord toute la sortie (ou de trouver une autre façon de la compter), puis d'utiliser la sortie collectée comme entrée de pipeline, en utilisant le nombre d'objets comme base de calcul de la valeur à transmettre Write-Progress -PerCentComplete.

      • L'appel Write-Progresspour chaque objet reçu entraînera un ralentissement significatif du traitement global; un compromis est de ne l'appeler que pour tous les N objets, comme le montre cette réponse ; l'approche pourrait être enveloppée dans une fonction correctement implémentée a la Show-Progressqui nécessite de passer le nombre total d'objets en tant qu'argument et effectue un traitement d'objet d'entrée en continu approprié (via un processbloc); Cela dit, le simple fait d'utiliser du code PowerShell pour transmettre des objets d'entrée est coûteux.


Conclusion:

Les affichages de progression en pourcentage terminé présentent deux problèmes inhérents :

  • Ils nécessitent que vous connaissiez au préalable le nombre total d'objets à traiter (un pipeline n'a aucun moyen de savoir combien d'objets le traverseront):

    • Soit: collecter tous les objets à traiter en mémoire , au préalable , si possible; le nombre d'éléments de la collection peut ensuite servir de base aux calculs du pourcentage achevé. Cela peut ne pas être une option avec de très grands ensembles d'entrées.

    • Ou: Effectuez au préalable une étape de traitement supplémentaire qui ne fait que compter tous les objets sans les récupérer. Cela peut ne pas être pratique en termes de temps de traitement supplémentaire ajouté.

  • Le traitement objet par objet dans le code PowerShell - via ForEach-Objectou via un script / une fonction avancé - est intrinsèquement lent.

    • Vous pouvez atténuer cela quelque peu en limitant les Write-Progressappels à tous les N objets, comme indiqué dans cette réponse

Dans l'ensemble, il s'agit d'un compromis entre la vitesse de traitement et la possibilité d'afficher le pourcentage de progression achevé à l'utilisateur final.