Script PowerShell synchronisation Active Directory avec une liste SharePoint 6

Ayant mis en place un Intranet grâce à l’outil aussi élégant que puissant Microsoft Sharepoint 2013 Foundation je peux vous dire que une bonne mise en place nécessite forcément un passage par le PowerShell et ses applets SharePoint associés. Ici, nous avons mis en place dans notre site principal une liste de contacts, mais étant trop lourde à mettre à jour, il fallait un moyen modulable et centralisé de garder la liste à jour sans se fouler (comme un fainéant :p )

Dans un réseau Microsoft-based, on peut considérer que le centre d’opération, c’est l’Active Directory ! La ou sont créés nos utilisateurs, et ou ils s’authentifient. Nous avons donc renseigné pour chaque utilisateur les coordonnées de bases comme le téléphone interne, ligne externe, adresse mail, service, … et je me suis lancé dans la réalisation d’un script qui permet de synchroniser des utilisateurs donnés (en fonction d’un filtre) avec ma liste de contacts (ajout, suppression ou modification, tout est géré!).

Comment ça fonctionne ? Dans les grandes lignes comme ceci :

  1. On récupère les attributs LDAP des utilisateurs concernés dans l’Active Directory
  2. On les structure et ont les place dans un fichier .csv
  3. On récupère le contenu de la liste des contacts sur le site SharePoint
  4. On compare les éléments de la liste avec ceux du fichier .csv fraichement généré
  5. Si des utilisateurs n’existent pas dans la liste de contacts, ils sont ajoutés à la liste, si ils existent, ils sont mis à jour et si ils n’existent pas dans le fichier .csv, ils sont supprimés de la liste de contacts.

Vous pourrez télécharger le script directement dans la section téléchargement si vous voulez faire ça en mode speed (il est plutôt bien commenté) ou alors lisez la suite ou je vais expliquer comment je procède (sans trop entrer dans les détails quand même). Conseil : des connaissances scripting (bash/batch) ou directement de PowerShell, et une pincée de connaissance LDAP / Active Directory seront d’une grande aide pour la compréhension et pour le paramétrage.

Avant toute chose, je tiens à partager un lien qui m’a énormément aidé à assimiler les fondement de PowerShell, que je n’avait jamais utilisé jusqu’alors : http://my-powershell.fr/aide-memoire-powershell

Ceci étant dit, je vous propose de décomposer mon script.

Décomposition du script

Je commence par démarrer le timer qui va me permettre de mesure le temps d’exécution de mon script.

$executionTime = [Diagnostics.Stopwatch]::StartNew()

Ensuite je vais ajouter le ‘ »snapin » SharePoint de manière à pouvoir utiliser toutes les méthodes propres à SharePoint :

$ver = $host | select version

if ($ver.Version.Major -gt 1) {
    $host.Runspace.ThreadOptions = "ReuseThread"
}

Add-PsSnapin Microsoft.SharePoint.PowerShell

Fonction de génération de mon fichier .csv

Je commence ma fonction (avant je met la date et le chemin de mon fichier de log)

$logFile = "C:\SharePointData\scripts_sharepoint_console\script_synchro_SP_AD.log" Get-Date -Format F >> $logFile
#fonction qui va taper dans l'AD et générer un csv formaté
function generateCsvFile() {

Voici le contenu de ma fonction :

La je créé la structure de mon fichier .csv

 "Nom,Prenom,Interne,Portable,Direct,Mail,Pole,Ville" > test.csv

Et ensuite je pose mon filtre de recherche des utilisateurs, qui va se faire si ce son des utilisateurs (category = user), si ils ont le champ « department » de rempli, et si le compte n’est pas désactivé. Pour ce point vous pourriez ne pas comprendre aux premiers abords ce dernier filtre, mais en gros, les attributs à true ou false d’un utilisateur dans l’AD sont cumulatifs, c’est à dire que pour chaque attribut correspond un bit (1 ou 0). Le paramètre « userAccountControl » est le 2ème bit, donc j’applique un masque au nombre, et si il s’avère que le deuxième bit est à 1, je n’importe pas l’utilisateur, car il est désactivé.

$strFilter = "(&(objectCategory=User)(department=*)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))

Puis je défini la base de mon annuaire LDAP :

$strDomain = "LDAP://OU=Postes et utilisateurs,dc=mysite,dc=local"

Enfin, je passe à la création des mes objets pour la recherche, la mon objet de requete :

try {
    $objDomain = New-Object System.DirectoryServices.DirectoryEntry($strDomain)
} catch {
    " : LDAP: Impossible d'établir la connexion avec : " + $strDomain >> $logFile
}

Je créé mon objet de recherche :

try {
    $objSearcher = New-Object System.DirectoryServices.DirectorySearcher
    $objSearcher.SearchRoot = $objDomain
    $objSearcher.PageSize = 1000
    $objSearcher.Filter = $strFilter
    $objSearcher.SearchScope = "Subtree"
} catch {
    " : L'objet DirectorySearcher n'a pas pu être instancié " >> $logFile
}

Et je fais mon tableau, qui contiendra les attributs LDAP à récupérer par utilisateur lors de la recherche :

$tabProplist = "cn", "sn", "givenName", "ipPhone", "mobile", "telephoneNumber", "mail", "department", "l"

Ensuite je parcours mon tableau précédent pour ajouter chaque ligne dans les propriétés à importer dans le requêteur.

foreach ($i in $tabPropList){$objSearcher.PropertiesToLoad.Add($i)}

Puis, on lance la recherche et on stocke les résultats dans un tableau :

$tabResults = $objSearcher.FindAll()

Naturellement,  je parcoure mon tableau et j’ajoute toutes les propriétées récuperées sur une ligne séparées par des virgules, puis je saute une ligne (afin de respecter la structure d’un fichier .csv) :

foreach ($objResult in $tabResults) {
            $objItem = $objResult.Properties 

            # -replace rajoute un tiret dans les noms composés espacés (ex: Jean Baptiste devient Jean-Baptiste)
            $strToAppend = ""
            $strToAppend += $objItem.sn
            $strToAppend += ','
            $strToAppend += $objItem.givenname -replace " ","-"
            $strToAppend += ','
            $strToAppend += $objItem.ipphone
            $strToAppend += ','
            $strToAppend += $objItem.mobile
            $strToAppend += ','
            $strToAppend += $objItem.telephonenumber
            $strToAppend += ','
            $strToAppend += $objItem.mail
            $strToAppend += ','
            $strToAppend += $objItem.department
            $strToAppend += ','
            $strToAppend += $objItem.l

            $strToAppend >> test.csv
    } 
}

Fonction de remplissage de la liste des contacts

Je commence ma fonction de remplissage de ma liste de contacts :

function populateSPList() {

et je définit mes paramètres :

#url du site SP et nom de la liste à modifier
 $strUrl = "http://srv-sp2013-vm/sites/mysite"
 $strListName = "Liste contacts"

Ensuite je définit la liste des attributs à récupérer pour chaque objets de la liste :

$strFilterProperty = "ListItems", "Name", "Title"

Ensuite j’instancie mon objet de type SPSite avec $strUrl pour pouvoir avoir un « handle » de ma collection de site :

try {
    $objSite = New-Object Microsoft.SharePoint.SPSite($strUrl)
 } catch {
    " : Impossible d'ouvrir SPSite à l'adresse : " + $strUrl >> $logFile
 }

Puis j’ouvre mon site Web

try {
    $objWeb = $objSite.OpenWeb()
} catch {
    " : OpenWeb() à échoué sur : " + $strUrl >> $logFile
}

Maintenant, je vais instancier un objet List qui contiendra ma liste Sharepoint de nom $strListName :

 $objList = $objWeb.Lists[$strListName]

Et je vais importer le fichier .csv créé précédemment dans une variable :

$fileCsv = Import-Csv test.csv

Puis je vais créer un requêteur qui interprète les requêtes CAML

try {
    $spQuery = New-Object Microsoft.SharePoint.SPQuery
} catch {
    " : Impossible de créer le requêteur SPQuery " >> $logFile
}

Je forge ma requête CAML. Ici, je prend simplement tout mes objets pour lesquels l’attribut Titre n’est pas NULL, donc tout :

Note : J’ai essayé de forger une requête précise pour récupérer seulement les utilisateurs qui avaient le nom et prénom égaux à ceux qui étaient dans le CSV de façon à supprimer les autres, mais ma requête n’a jamais voulue fonctionner, bien que je l’ai validée sur les logiciels CAML Helper et CAML Viewer (qui permettent de créer facilement ce type de requêtes).

$camlQuery = "<Query><Where><IsNotNull><FieldRef Name='Title' /></IsNotNull></Where></Query>"

Puis je créé ma requête au sens objet du terme :

$spQuery.Query = $camlQuery

Et j’insère la requête dans le requêteur :

try {
    $spSearchedItem = $objList.GetItems($spQuery)
} catch {
    " : Erreur a la requete CAML : " + $spQuery >> $logFile
}

Une fois que nos utilisateurs venus de la liste SharePoint sont dans la variable $spSearchedItem, on va boucler dessus et pour chaque élément, si le nom et le prénom sont les même, on met simplement à jour les informations, sans recréer d’utilisateur dans la liste. Si en revanche le nom et le prénom ne sont pas trouvés, on le créée :

foreach($row in $fileCsv) {

  $strTrouve="Non"
  foreach($currentUserListed in $spSearchedItem)
  {
       if(($currentUserListed["Title"] -eq $row.Nom) -and ($currentUserListed["FirstName"] -eq $row.Prenom)) 
       {
            $strTrouve="Oui"
            $currentUserListed["Num_x00e9_ro_x0020_interne"] = $row.Interne
            $currentUserListed["WorkPhone"] = $row.Direct
            $currentUserListed["CellPhone"] = $row.Portable
            $currentUserListed["Email"] = $row.Mail
            $currentUserListed["JobTitle"] = $row.Pole
            $currentUserListed["WorkCity"] = $row.Ville
            $currentUserListed.Update()
       } 
 }
 if($strTrouve -eq "Non") 
        {
            $itemToAdd = $objList.Items.Add()
            $itemToAdd["Nom"] = $row.Nom
            $itemToAdd["Prénom"] = $row.Prenom
            $itemToAdd["Nom complet"] = $row.Prenom + " " + $row.Nom
            $itemToAdd["Numéro interne"] = $row.Interne
            $itemToAdd["Téléphone professionnel"] = $row.Direct
            $itemToAdd["Numéro de téléphone mobile"] = $row.Portable
            $itemToAdd["Adresse de messagerie"] = $row.Mail
            $itemToAdd["Département"] = $row.Pole
            $itemToAdd["Ville"] = $row.Ville
            $itemToAdd.Update() 
        }
    }

Et puis on va parcourir cette fois la liste et dans la liste le fichier .csv, afin de détécter dans l’autre sens, si des contacts qui existent dans la liste et qui n’éxistent pas dans le fichier .csv sont présents. Si tel est le cas, on va aussi les virer, pour avoir notre fichier .csv (donc notre Active Directory) maitre des opérations (c’est lui qui synchronise et pas l’inverse) :

foreach($currentUserListed in $spSearchedItem) {
     $strTrouve="Non"
     foreach($row in $fileCsv) {
         if(($row.Nom -eq $currentUserListed["Title"]) -and ($row.Prenom -eq $currentUserListed["FirstName"]))
         {
             $strTrouve="Oui"
         }
     }
     if($strTrouve -eq "Non") {
         $objList.Items.GetItemById($currentUserListed["ID"]).Delete()
         " : L'objet " + $currentUserListed["Title"] + $currentUserListed["FirstName"] + " a été supprimé de la liste SP" >> $logFile
     }
}

Voila, normalement votre liste SharePoint est à jour et reflète fidèlement les informations saisies dans l’AD. Maintenant je termine en écrivant dans le fichier de log le nombre d’objets présents dans la liste après les opérations, je détruit mes objets inutilisés (vlan poubelle :p ) et j’arrête le timer afin d’écrire le temps d’exécution du programme dans le log.

if($objList.Count>1) {
       " : Il existe " + $objList.Count-1 + " doublon(s) de la liste à peupler" >> $logFile
    }
    " : Nombre d'éléments remontés : " + $objList.Items.Count >> $logFile

    $objSite.Dispose()
    $objWeb.Dispose()
}

################## execution des fonctions #############################################

generateCsvFile

populateSPList

$executionTime.Stop()

" : Execution terminée en " + $executionTime.Elapsed.TotalSeconds + " secondes." >> $logFile
## FIN

Les deux lignes generateCsvFile et populateSPList sont les appels à mes deux fonctions.

Voila j’espère avoir aidé certains qui voulaient un truc du genre :)

6 thoughts on “Script PowerShell synchronisation Active Directory avec une liste SharePoint

  1. Répondre Willy juil 14, 2015 15 h 16 min

    Bonjour,

    Merci pour le partage de cette fabuleuse procédure.

    Je ne suis pas du tout spécialiste du Powershell et de la programmation en général, mais mon besoin est de créer des listes d’utilisateurs pour leur attribuer des permissions dans différents sous-site Sharepoint 2013 (de manière à ne pas les rentrer manuellement)

    Comment faire simplement a partir de fichiers csv (contenant Nom,Prenom,mail) pour ajouter des listes utilisateurs aux différents groupes des sous-sites (membres,visiteurs,propriétaires)

    Merci beaucoup pour votre aide

    • Répondre Alexandre juil 15, 2015 13 h 32 min

      Bonjour,


      $Users = Get-Content "C:\tmp\Data\Users.csv"

      $web = Get-SPWeb -identity $siteCollectionUrl
      foreach ($User in $Users) {
      $web.EnsureUser($User)
      Set-SPUser -Identity $User -Web $siteCollectionUrl -Group $group
      }

      ou $web est le contexte de votre site SharePoint, et $group le nom du groupe SharePoint auquel vous voulez ajouter les utilisateurs.

      Ici le script va simplement parcourir les lignes du fichier csv et les interpréter comme des objets, il faut que dans les fichier csv se trouvent les prénom/noms de utilisateurs.
      EnsureUser va permettre de vérifier si l’utilisateur existe dans la base, si ce n’est pas le cas, il le crée.

      J’espère avoir pu aider,

  2. Répondre Arno sept 14, 2016 13 h 25 min

    Bonjour,
    Je ne parviens pas à récupérer votre script.
    D’avance merci.
    Arno

  3. Répondre Margaux jan 26, 2017 12 h 08 min

    Bonjour,

    Avez-vous recréé un lien de téléchargement?

    Merci,

    Margaux

Laisser un commentaire