Récemment, j'ai été amené à devoir utiliser certaines catégories très peu utilisées d'Active Directory dans le cadre d'un projet de Workflow que je développe. J'ai utilisé la partie "Gestionnaire" que l'on trouve sur les fiches utilisateur. ("Manager" version Shakespearienne)

AD%20GEST

En me servant des informations fournies par le service RH du client, j'ai pu créer des UO représentatives de l'organisation interne des différents services, et j'ai pu scripter la modification en masse de chaque compte utilisateur renseigné afin d'y ajouter le nom du service et le code interne du service. Après déplacement de chaque utilisateur dans l'UO correspondante, je me suis servi de la partie graphique pour ajouter les gestionnaires de chaque utilisateur.

Et la m'est venue l'idée... Puisque chaque utilisateur est dans une UO, que chaque utilisateur possède un responsable, et que chaque responsable possède un responsable jusqu'au plus haut niveau hiérarchique... comment faire pour avoir une vue toujours à jour de l'organigramme interne de l'entreprise?

Je voyais déjà quelle partie graphique utiliser pour afficher la hiérarchie : un TreeView.

Vous n'avez jamais vu de TreeView? Mais si, je vous l'assure.

Voici ce qu'est un TreeView Windows Forms.

TV%20TEST

Déjà plus parlant, n'est ce pas?

Alors tout ça c'est bien, on veut utiliser un outil graphique pour mieux s'en rendre compte, mais ça ne réponds toujours pas à diverses questions :

  • Comment délimiter le niveau hiérarchique d'un employé? (à savoir, est-il au niveau 0, directeur ou indépendant de toute hiérarchie, niveau 1, répondant directement au directeur, niveau 2, répondant à un niveau 1, etc.)
  • Comment différencier un utilisateur de niveau 0 d'un compte système (qui par défaut n'a pas de manager)?

    C'est ce que nous allons voir!

Mais commençons par le commencement :

Récupérer les valeurs utiles

Nous avons besoin tout d'abord de la liste intégrale des utilisateurs. Cependant, nous ne voulons pas forcément voir les comptes désactivés... Et nous avons besoin d'informations supplémentaires qui ne sont pas renvoyées par la commande Get-Aduser de base. Par exemple, si nous avons renseigné le département et le secteur des utilisateurs, il peut être utile de les récupérer afin que notre organigramme soit plus "parlant". Lorsque nous précisons un manager sur une fiche AD, automatiquement est crée une nouvelle propriété pour le manager : directreports . Directreports renvoie la liste des subordonnés de l'utilisateur. Celle ci ne sera pas utilisée dans notre script, mais peut être utile et mérite d'être au moins présentée.

Notre commande est donc la suivante :

$all = get-aduser -filter 'enabled -eq $true' -Properties manager,department

Nous aurons également besoin de l'intégralité des managers, afin de savoir si un utilisateur n'a pas de manager parce qu'il est directeur (et donc a des utilisateurs qui l'ont désigné en tant que tel) ou simplement parce que sa fiche n'est pas remplie. Pour cela, nous allons reprendre le retour de la commande précédente, et grouper selon le manager.

$managegroups = ($all | select-object -Property samaccountname,name,manager | Group-Object -Property manager)

Enfin, nous allons créer un petit algorithme afin de connaître le "niveau" hiérarchique de l'utilisateur au sein de son entreprise (Plus le niveau est proche de 0, plus l'utilisateur est haut dans la hiérarchie A.D.). Cet algorithme prendre les utilisateur un par un, regarder leur manager, puis le manager du manager et ainsi de suite jusqu’à ce qu'il n'y ait plus de manager ou qu'il rencontre un problème.

Un problème?

Oui, un problème. En effet, le fonctionnement d'Active Directory permet de faire des "boucles" de manager. En temps normal, un Manager A est le supérieur d'un manager B qui est lui même responsable d'un utilisateur C, comme marqué sur ce schéma.

ABC

Maintenant imaginez qu'une erreur soit dans votre chaine organisationnelle, et que A soit renseigné comme manager de B et que B soit renseigné comme manager de A, comme sur le schéma ci-dessous. Schéma déjà moins classique de management

ABA

Et bien dans ce schéma, notre programme tomberait sur une boucle infinie. Il passerait de A à B, puis à A et encore B sans jamais s'arrêter, car il n'y aurait aucun moment ou il n'y aurait aucun manager. Il faut donc vérifier que ce genre de schéma ne se présente pas, et "casser" la boucle dès lors qu'elle est détectée.

    #création d'un tableau contenant les résultats
    $userslevel = New-Object System.Collections.ArrayList
    #Lancement de la boucle pour toutes les entrées dans $all (variable définie au préalable)
    foreach ($utilisateur in $all)
    {
        #On enregistre le manager dans la variable $loop
        $loop = $utilisateur.manager
        #On définit le niveau hierarchique à 0
        $level = 0
        #On crée un array vide
        $array =@()
        #Et tant que le manager n'est pas vide...
        While($loop -ne $null)
        {
            #on augmente d'un niveau hierarchique
            $level++
            #On ajoute le nom du manager dans le tableau
            $array += $loop
            #récupère le manager de l'utilisateur / S / S+1 / S+x afin de déterminer l'endroit dans l'arborescence
            $loop = ($all | Where-Object {$_.distinguishedname -eq $loop}).manager 
            #Si le manager est déja dans le tableau alors il y'a une boucle!
            if ($array -contains $loop)
            {
            #on avertit juste de la présence d'une boucle (utile pour connaitre le problème)
            "$($utilisateur.samaccountname) boucle sur $loop"
            #on vide le tableau
            $array.Clear()
            #On casse la boucle
            break
            }
        }
        #Si le niveau hierarchique est à 0 et que le nom de l'utilisateur est contenu dans la liste des managers...
        if (($level -eq 0) -and ($managegroups.name.contains($utilisateur.distinguishedname)) -ne $false)
        {
            #On ajoute un objet dans le tableau avec les propriétés suivantes
            $userslevel.Add([PSCUSTOMOBJECT]@{"name" = $utilisateur.name;"samaccountname" = $utilisateur.samaccountname;"Manager" = "Direction";"level" = $level;"Department" = $utilisateur.department}) | Out-Null
        }
        #Sinon, s'il est à 0
        elseif ($level -eq 0)
        {
           #On ajoute un objet dans le tableau avec les propriétés suivantes 
           $userslevel.Add([PSCUSTOMOBJECT]@{"name" = $utilisateur.name;"samaccountname" = $utilisateur.samaccountname;"Manager" = $nomanagerstring;"level" = $level;"Department" = $utilisateur.department}) | Out-Null
        }
        #Et dans les autres cas...
        else
        {
            # On ajoute un objet avec les propriétés suivantes
            $userslevel.Add([PSCUSTOMOBJECT]@{"name" = $utilisateur.name;"samaccountname" = $utilisateur.samaccountname;"Manager" = $utilisateur.Manager;"level" = $level;"Department" = $utilisateur.department}) | Out-Null
        }

    }

A la suite de cette petite boucle, nous aurons donc une liste de chaque utilisateur avec leur place dans la hiérarchie A.D. Il ne nous reste plus qu'à mettre cela en place en introduisant chaque utilisateur comme noeud du treeview.

    foreach ($level in $groupedlevel){

        foreach ($utilisateur in $level.Group)
            {

                $ispresent = $null
                #Si le manager de l'utilisateur (pas celui de l'AD, celui crée auparavant dans la boucle précedente) est vide ...
                if ($utilisateur.Manager -eq $nomanagerstring)
                {
                    #On ajoute un enfant dans le noeud $nomanagerstring du treeview
                    $treeView1.nodes[$nomanagerstring].nodes.add($utilisateur.samaccountname,$utilisateur.name) | Out-Null
                }
                else
                {
                    #Sinon, on prend l'utilisateur AD correspondant au nom du manager
                    $managername = $all | Where-Object {$_.distinguishedname -eq $utilisateur.Manager}
                    #On recherche dans les noeuds du treeview le samaccountname 
                    $ispresent = $treeView1.Nodes.Find($managername.samaccountname,$true)
                    #S'il en trouve 0 ou aucun ...
                    if ($($ispresent.count) -eq 0 -or ($ispresent -eq $null))
                    {
                        #On ajoute un noeud portant l'identifiant samaccountname, avec en texte le nom et le département de l'utilisateur
                        $treeView1.Nodes.Add($utilisateur.samaccountname,"$($utilisateur.name) - $($utilisateur.department)") | Out-Null
                    }
                    else
                    {
                        #Sinon on ajoute au noeud portant le même nom la valeur de l'enfant
                        $ispresent[0].nodes.add($utilisateur.samaccountname,"$($utilisateur.name) - $($utilisateur.department)") | Out-Null
                    }
                }
            }

    }

Script final

Vous pouvez télécharger le script sur mon espace git personnel.

$nomanagerstring = "Sans Manager"
$windowname = "Vue hierarchique de l'A.D."
$all = get-aduser -filter 'enabled -eq $true' -Properties manager,department,directreports
$arraylist = New-Object System.Collections.ArrayList
$managegroups = ($all | select-object -Property samaccountname,name,manager | Group-Object -Property manager)
$loop = $null
#J'ajoute les données du treeview à l'aide d'une fonction. C'est pour cela qu'elle est déclarée avant le restant du script
function Add-Treeview {
    $treeView1.Nodes.Add($nomanagerstring,$nomanagerstring) | Out-Null
    $userslevel = New-Object System.Collections.ArrayList
    foreach ($utilisateur in $all)
    {

        $loop = $utilisateur.manager
        #"Manager de début  pour $($utilisateur.samaccountname) : $loop"
        #$loop -eq $null
        $level = 0
        $array =@()
        While($loop -ne $null)
        {
            $level++
            $array += $loop
            $loop = ($all | Where-Object {$_.distinguishedname -eq $loop}).manager #récupère le manager de l'utilisateur / S / S+1 / S+x afin de déterminer l'endroit dans l'arborescence
            if ($array -contains $loop)
            {
            "$($utilisateur.samaccountname) boucle sur $loop"
            $array.Clear()
            break
            }
        }
        if (($level -eq 0) -and ($managegroups.name.contains($utilisateur.distinguishedname)) -ne $false)
        {
            $userslevel.Add([PSCUSTOMOBJECT]@{"name" = $utilisateur.name;"samaccountname" = $utilisateur.samaccountname;"Manager" = "Direction";"level" = $level;"Department" = $utilisateur.department}) |Out-Null
        }
        elseif ($level -eq 0)
        {
            $userslevel.Add([PSCUSTOMOBJECT]@{"name" = $utilisateur.name;"samaccountname" = $utilisateur.samaccountname;"Manager" = $nomanagerstring;"level" = $level;"Department" = $utilisateur.department}) | Out-Null
        }
        else
        {
            $userslevel.Add([PSCUSTOMOBJECT]@{"name" = $utilisateur.name;"samaccountname" = $utilisateur.samaccountname;"Manager" = $utilisateur.Manager;"level" = $level;"Department" = $utilisateur.department}) | Out-Null
        }

    }

    $userslevel = $userslevel | Sort-Object -Property level

    $groupedlevel = $userslevel | Group-Object -Property level
    foreach ($level in $groupedlevel){

        foreach ($utilisateur in $level.Group)
            {

                $ispresent = $null
                if ($utilisateur.Manager -eq $nomanagerstring)
                {
                    $treeView1.nodes[$nomanagerstring].nodes.add($utilisateur.samaccountname, $utilisateur.name) | Out-Null
                }
                else
                {
                    $managername = $all | Where-Object {$_.distinguishedname -eq $utilisateur.Manager}
                    $ispresent = $treeView1.Nodes.Find($managername.samaccountname,$true)
                    #@($utilisateur.name,$ispresent.count) | ft
                    if ($($ispresent.count) -eq 0 -or ($ispresent -eq $null))
                    {
                        $treeView1.Nodes.Add($utilisateur.samaccountname,"$($utilisateur.name) - $($utilisateur.department)") | Out-Null
                    }
                    else
                    {
                        $ispresent[0].nodes.add($utilisateur.samaccountname,"$($utilisateur.name) - $($utilisateur.department)") | Out-Null
                    }
                }
            }

    }
    #Trier le treeview c'est mieux :)
    $treeView1.Sorted = $true
}

#################################################################

#region Import the Assemblies
[reflection.assembly]::loadwithpartialname("System.Windows.Forms") | Out-Null
[reflection.assembly]::loadwithpartialname("System.Drawing") | Out-Null
#endregion

#region Generated Form Objects
$form1 = New-Object System.Windows.Forms.Form
$treeView1 = New-Object System.Windows.Forms.TreeView
$InitialFormWindowState = New-Object System.Windows.Forms.FormWindowState
#endregion Generated Form Objects

#region Generated Form Code
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Height = 407
$System_Drawing_Size.Width = 558
$form1.ClientSize = $System_Drawing_Size
$form1.DataBindings.DefaultDataSourceUpdateMode = 0
$form1.Name = "form1"
$form1.Text = $windowname

$treeView1.DataBindings.DefaultDataSourceUpdateMode = 0
$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 24
$System_Drawing_Point.Y = 127
$treeView1.Location = $System_Drawing_Point
$treeView1.Name = "treeView1"
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Height = 268
$System_Drawing_Size.Width = 522
$treeView1.Size = $System_Drawing_Size
$treeView1.TabIndex = 0

Add-Treeview

$form1.Controls.Add($treeView1)

$InitialFormWindowState = $form1.WindowState

$form1.add_Load($OnLoadForm_StateCorrection)

$form1.ShowDialog()| Out-Null

Et voici le résultat final !

AD%20SAMPLE

Désormais, il ne reste plus qu'a configurer une action de clic droit avec un menu contextuel pour effectuer des opérations à un ou plusieurs utilisateurs! (Déverrouillage, renommage, etc.)

Ajouter un commentaire

Article précédent Article suivant