Création des GG
Introduction
Nous allons attaquer la partie des groupes globaux, les créer automatiquement via un script, et y ajouter des membres avec le même principe.
Tableau modèle
Il nous faut d'abord le fichier qui servira de modèle, que l'on mettra à jour, pour faire les modifications.
Le gros avantage est que ce fichier peut-être divisé par service, et distribué aux responsables et directeurs de services pour avoir des infos toujours à jour, sans donner accès à une console d'administration.
Il faut ensuite exporter, ou convertir ce tableau en csv, encodage UTF8, séparateur ";" point virgule.
On obtient alors ce fichier :
GG;Membre
ORG_GG_DIRECTION;
ORG_GG_FINANCE;
ORG_GG_RH;
ORG_GG_IT;
ORG_GG_DIRECTION;Alice MARTIN
ORG_GG_DIRECTION;Thomas ROBERT
ORG_GG_FINANCE;Julie LAMBERT
ORG_GG_FINANCE;Nicolas PETIT
ORG_GG_RH;Sophie DURAND
ORG_GG_RH;Marc LEFEVRE
ORG_GG_IT;Lucas MOREAU
ORG_GG_IT;Emma GARNIER
ORG_GG_IT;Hugo CHEVALIER
ORG_GG_COMMUNICATION;
ORG_GG_COMMUNICATION;Clara BERTRAND
ORG_GG_LOGISTIQUE;
ORG_GG_LOGISTIQUE;Antoine RENAUD
Création des GG
Voici à présent le script qui permet de créer tous les GG, dans un seul et même endroit, dans OU=GG,OU=AGDLP,DC=domain,DC=local :
<#
===============================================================================
SCRIPT : 5.1 - Creation_GG.ps1 (Version 2-passes, robuste)
OBJET : Creation des Groupes Globaux (GG) + alignement de leurs membres
===============================================================================
PRINCIPE :
PASS A : Creer tous les groupes globaux (GG et sous-GG) avant ajout des membres
PASS B : Vider contenu de chaque GG puis injecter les membres (GG puis utilisateurs)
AVANTAGES :
- Plus aucune erreur "groupe introuvable"
- Les sous-GG peuvent apparaitre apres les parents dans le CSV
- Nettoyage des caracteres parasites (nbsp, guillemets, espaces)
- Sorties visibles sans accents (compatibilite console)
===============================================================================
#>
Param(
[string]$CSVPath = "C:\AGDLP\ORG_ARBO_GDL.csv",
[string]$OUPath = "OU=GG,OU=AGDLP,DC=domain,DC=local",
[string]$LogFile = "C:\AGDLP\AD_Group_Update_Errors.txt"
)
$ErrorActionPreference = "Stop"
# ----------------------------------------------------------------------------
# 0. Module AD
# ----------------------------------------------------------------------------
try { Import-Module ActiveDirectory -ErrorAction Stop }
catch {
Write-Error "Module ActiveDirectory non disponible. Installer RSAT."
exit 1
}
# ----------------------------------------------------------------------------
# 1. Reset fichier log (UTF-8 BOM)
# ----------------------------------------------------------------------------
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($LogFile, "", $utf8Bom)
# ----------------------------------------------------------------------------
# 2. Lecture CSV GG
# ----------------------------------------------------------------------------
if (-not (Test-Path $CSVPath)) {
Write-Error "Fichier CSV introuvable : $CSVPath"
exit 1
}
$data = Import-Csv -Path $CSVPath -Delimiter ";" |
Where-Object { $_.GG -and $_.GG.Trim() -ne "" }
if ($data.Count -eq 0) {
Write-Host "Aucun groupe global a traiter."
exit 0
}
# ----------------------------------------------------------------------------
# 3. Petite fonction de nettoyage (supprime espaces HTML, nbsp, guillemets)
# ----------------------------------------------------------------------------
function Clean-Cell([string]$s) {
if (-not $s) { return "" }
$s = $s -replace " ", " "
$s = $s -replace "\u00A0", " "
$s = $s -replace "\u200B", ""
$s = $s -replace "[«»“”‘’]", ""
return ($s -replace "\s+", " ").Trim()
}
foreach ($row in $data) {
$row.GG = Clean-Cell $row.GG
$row.Membre = Clean-Cell $row.Membre
}
# ----------------------------------------------------------------------------
# 4. PASS A : CREATION DE TOUS LES GROUPES (GG + sous-GG references)
# ----------------------------------------------------------------------------
# Ensemble de tous les groupes a creer
$allGG = New-Object System.Collections.Generic.HashSet[string] ([StringComparer]::OrdinalIgnoreCase)
# Tous les GG declares
$data | ForEach-Object { $null = $allGG.Add($_.GG) }
# Tous les GG references comme Membre
foreach ($row in $data) {
if ($row.Membre -and ($row.Membre -like "*_GG_*")) {
$null = $allGG.Add($row.Membre)
}
}
# Correction : pas de .ToArray(), on convertit proprement en tableau PowerShell
$allGGList = @($allGG)
$totalA = $allGGList.Count
$indexA = 0
Write-Host "Pass A : Creation des GG (sans membres) - Total: $totalA"
foreach ($ggName in $allGGList) {
$indexA++
Write-Progress -Activity "Pass A - Creation GG" -Status "$indexA / $totalA" -PercentComplete (($indexA/$totalA)*100)
if ([string]::IsNullOrWhiteSpace($ggName)) { continue }
$exists = Get-ADGroup -Filter "SamAccountName -eq '$ggName'" `
-SearchBase $OUPath `
-ErrorAction SilentlyContinue
if ($exists) { continue }
try {
New-ADGroup -Name $ggName -SamAccountName $ggName -Path $OUPath `
-GroupScope Global -GroupCategory Security -ErrorAction Stop | Out-Null
Write-Host "Cree : $ggName"
}
catch {
$msg = "Erreur creation du groupe $ggName : $_`r`n"
[System.IO.File]::AppendAllText($LogFile, $msg, $utf8Bom)
Write-Warning "Creation impossible : $ggName (voir log)"
}
}
Write-Progress -Activity "Pass A - Creation GG" -Completed
# ----------------------------------------------------------------------------
# 5. PASS B : VIDER et RE-INJECTER les membres
# ----------------------------------------------------------------------------
$groups = $data | Group-Object -Property GG
$totalB = $groups.Count
$indexB = 0
Write-Host ""
Write-Host "Pass B : Injection des membres dans chaque GG"
foreach ($grp in $groups) {
$indexB++
Write-Progress -Activity "Pass B - Inj. membres" `
-Status "$indexB / $totalB - $($grp.Name)" `
-PercentComplete (($indexB/$totalB)*100)
$GroupName = Clean-Cell $grp.Name
if (-not $GroupName) { continue }
# ------------------------------------------------------
# 5.1 Reset membres du GG
# ------------------------------------------------------
try {
$existing = Get-ADGroup -Identity $GroupName -ErrorAction Stop
$members = Get-ADGroupMember -Identity $GroupName -ErrorAction SilentlyContinue
if ($members) {
Remove-ADGroupMember -Identity $GroupName -Members $members `
-Confirm:$false -ErrorAction SilentlyContinue
}
}
catch {
# Si pour une raison obscure il n existe pas, on tente creation tardive
try {
New-ADGroup -Name $GroupName -SamAccountName $GroupName -Path $OUPath `
-GroupScope Global -GroupCategory Security -ErrorAction Stop | Out-Null
}
catch {
$msg = "Erreur creation tardive du groupe $GroupName : $_`r`n"
[System.IO.File]::AppendAllText($LogFile, $msg, $utf8Bom)
Write-Warning "Impossible de traiter $GroupName (voir log)"
continue
}
}
# ------------------------------------------------------
# 5.2 Ajouter les membres GROUPE (tous existent depuis Pass A)
# ------------------------------------------------------
foreach ($line in $grp.Group) {
$m = Clean-Cell $line.Membre
if (-not $m) { continue }
if ($m -notlike "*_GG_*") { continue }
$child = Get-ADGroup -Identity $m -ErrorAction SilentlyContinue
if (-not $child) {
$msg = "Groupe membre introuvable (apres Pass A) : $m (parent: $GroupName)`r`n"
[System.IO.File]::AppendAllText($LogFile, $msg, $utf8Bom)
Write-Warning "GG introuvable : $m -> ignore"
continue
}
Add-ADGroupMember -Identity $GroupName -Members $child -ErrorAction SilentlyContinue
Write-Host " + GG : $m"
}
# ------------------------------------------------------
# 5.3 Ajouter les membres UTILISATEUR
# ------------------------------------------------------
foreach ($line in $grp.Group) {
$m = Clean-Cell $line.Membre
if (-not $m) { continue }
if ($m -like "*_GG_*") { continue }
$user = Get-ADUser -Filter { DisplayName -eq $m } -ErrorAction SilentlyContinue
if (-not $user) {
$msg = "Utilisateur introuvable : $m (groupe : $GroupName)`r`n"
[System.IO.File]::AppendAllText($LogFile, $msg, $utf8Bom)
Write-Warning "Utilisateur introuvable : $m -> ignore"
continue
}
Add-ADGroupMember -Identity $GroupName -Members $user.SamAccountName `
-ErrorAction SilentlyContinue
Write-Host " + USR: $m"
}
}
Write-Progress -Activity "Pass B - Inj. membres" -Completed
Write-Host ""
Write-Host "Traitement GG termine avec succes. Voir le log si necessaire : $LogFile" -ForegroundColor Green
Si les utilisateurs existes, il se retrouves dans les groupes GG qui leurs correspondant dans le tableau.
J'ai pendant un moment pensé à faire une ligne par GG et séparer les utilisateurs par une ",", mais cela n'aurait pas été pratique pour les éventuels utilisateurs d'autres services.