1. Le module ActiveDirectory
# Sur un serveur (contrôleur de domaine ou serveur membre)
Install-WindowsFeature -Name RSAT-AD-PowerShell
# Sur un poste d'administration Windows 10/11
Add-WindowsCapability -Online -Name "Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0"
Import-Module ActiveDirectory
Get-Command -Module ActiveDirectory | Measure-Object # plus de 140 cmdlets
Get-ADDomain
Get-ADDomainController -Filter *
Le vocabulaire de l’annuaire
| Terme | Signification |
|---|---|
| DN (Distinguished Name) | Chemin complet d’un objet : CN=Marie Durand,OU=Profs,DC=lycee,DC=local |
| OU | Unité d’organisation : un conteneur, sur lequel on applique des GPO et des délégations |
| SamAccountName | Identifiant de connexion court (mdurand) |
| UPN | Identifiant au format adresse (mdurand@lycee.local) |
| Groupe de sécurité | Porteur des droits ; on affecte les droits aux groupes, jamais aux comptes |
2. Rechercher dans l’annuaire
Get-ADUser -Identity mdurand -Properties *
Get-ADUser -Filter "Department -eq 'Informatique'" -Properties Department, Title
# Rechercher dans une OU précise
Get-ADUser -SearchBase "OU=Eleves,DC=lycee,DC=local" -Filter * |
Select-Object Name, SamAccountName, Enabled
# Filtre LDAP, plus puissant
Get-ADUser -LDAPFilter "(&(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2))"
Get-ADGroup -Filter "Name -like 'GRP_*'"
Get-ADGroupMember -Identity "GRP_Profs" | Select-Object Name, objectClass
Get-ADComputer -Filter * -Properties LastLogonDate | Select-Object Name, OperatingSystem, LastLogonDate
3. Créer et modifier
$motDePasse = Read-Host "Mot de passe initial" -AsSecureString
New-ADUser -Name "Marie Durand" `
-GivenName "Marie" -Surname "Durand" `
-SamAccountName "mdurand" `
-UserPrincipalName "mdurand@lycee.local" `
-Path "OU=Profs,DC=lycee,DC=local" `
-AccountPassword $motDePasse `
-ChangePasswordAtLogon $true `
-Enabled $true
Set-ADUser -Identity mdurand -Department "Mathématiques" -Title "Professeur"
Add-ADGroupMember -Identity "GRP_Profs" -Members mdurand
Move-ADObject -Identity (Get-ADUser mdurand).DistinguishedName -TargetPath "OU=Archives,DC=lycee,DC=local"
Disable-ADAccount -Identity mdurand
Set-ADAccountPassword -Identity mdurand -Reset -NewPassword (Read-Host -AsSecureString)
La même écriture avec le splatting du niveau 2 est plus lisible et se prête mieux à un script :
$parametres = @{
Name = "Marie Durand"
GivenName = "Marie"
Surname = "Durand"
SamAccountName = "mdurand"
UserPrincipalName = "mdurand@lycee.local"
Path = "OU=Profs,DC=lycee,DC=local"
AccountPassword = $motDePasse
ChangePasswordAtLogon = $true
Enabled = $true
}
New-ADUser @parametres
Unités d’organisation
New-ADOrganizationalUnit -Name "BTS-SIO" -Path "OU=Eleves,DC=lycee,DC=local" -ProtectedFromAccidentalDeletion $true
Get-ADOrganizationalUnit -Filter * | Select-Object Name, DistinguishedName
L’option -ProtectedFromAccidentalDeletion est activée par défaut et doit le rester : elle a sauvé bien des annuaires.
4. Création en masse depuis un CSV
C’est le scénario type de la rentrée scolaire. Le script suit toujours la même progression : lire, contrôler, simuler, exécuter, journaliser.
# eleves.csv : Nom;Prenom;Classe
$eleves = Import-Csv "C:\Temp\eleves.csv" -Delimiter ";" -Encoding UTF8
$rapport = foreach ($e in $eleves) {
$identifiant = (Remove-Accent ($e.Prenom.Substring(0,1) + $e.Nom)).ToLower() -replace "[^a-z0-9]", ""
if (Get-ADUser -Filter "SamAccountName -eq '$identifiant'") {
[PSCustomObject]@{ Identifiant = $identifiant; Etat = "Déjà existant" }
continue
}
$ou = "OU=$($e.Classe),OU=Eleves,DC=lycee,DC=local"
if (-not (Get-ADOrganizationalUnit -Filter "DistinguishedName -eq '$ou'")) {
[PSCustomObject]@{ Identifiant = $identifiant; Etat = "OU manquante : $($e.Classe)" }
continue
}
try {
New-ADUser -Name "$($e.Prenom) $($e.Nom)" -GivenName $e.Prenom -Surname $e.Nom `
-SamAccountName $identifiant -UserPrincipalName "$identifiant@lycee.local" `
-Path $ou -AccountPassword $motDePasseInitial -ChangePasswordAtLogon $true `
-Enabled $true -ErrorAction Stop
Add-ADGroupMember -Identity "GRP_Eleves" -Members $identifiant
[PSCustomObject]@{ Identifiant = $identifiant; Etat = "Créé" }
}
catch {
[PSCustomObject]@{ Identifiant = $identifiant; Etat = "Erreur : $($_.Exception.Message)" }
}
}
$rapport | Export-Csv "C:\Temp\rapport-creation.csv" -Delimiter ";" -Encoding UTF8 -NoTypeInformation
$rapport | Group-Object Etat | Select-Object Count, Name
5. Auditer l’annuaire
# Comptes actifs jamais connectés depuis 90 jours
$limite = (Get-Date).AddDays(-90)
Get-ADUser -Filter "Enabled -eq '$true'" -Properties LastLogonDate, whenCreated |
Where-Object { $_.LastLogonDate -lt $limite -or -not $_.LastLogonDate } |
Select-Object Name, SamAccountName, LastLogonDate, whenCreated |
Sort-Object LastLogonDate
# Mots de passe qui n'expirent jamais
Get-ADUser -Filter "PasswordNeverExpires -eq '$true' -and Enabled -eq '$true'" |
Select-Object Name, SamAccountName
# Groupes privilégiés
foreach ($g in "Admins du domaine", "Administrateurs de l'entreprise", "Opérateurs de compte") {
Get-ADGroupMember -Identity $g -Recursive -ErrorAction SilentlyContinue |
Select-Object @{N = "Groupe"; E = { $g }}, Name, objectClass
}
# Ordinateurs inactifs
Get-ADComputer -Filter * -Properties LastLogonDate, OperatingSystem |
Where-Object LastLogonDate -lt (Get-Date).AddDays(-180) |
Select-Object Name, OperatingSystem, LastLogonDate
6. Côté serveur
Get-WindowsFeature | Where-Object Installed | Select-Object Name, DisplayName
Install-WindowsFeature -Name DHCP -IncludeManagementTools
Get-SmbShare
New-SmbShare -Name "Partage-SIO" -Path "D:\Partages\SIO" -FullAccess "LYCEE\GRP_Profs"
Get-SmbShareAccess -Name "Partage-SIO"
Get-GPO -All | Select-Object DisplayName, GpoStatus, ModificationTime
Get-GPOReport -Name "GPO-Eleves" -ReportType Html -Path C:\Temp\gpo.html
7. Mini-projet : la campagne de rentrée
8. Bilan
Exercices corrigés
Ces corrigés supposent une maquette avec le domaine lycee.local. Adapte les noms distinctifs (DC=…) à ton environnement : c’est l’erreur la plus fréquente lors du premier essai.
Exercice 6.1 — Explorer l’annuaire
Voir le corrigé
# 1.
Get-ADDomain | Select-Object Name, DNSRoot, DistinguishedName
Get-ADDomainController -Filter * | Select-Object Name, IPv4Address, OperatingSystem
# 2.
Get-ADUser -Filter * -Properties Enabled | Group-Object Enabled | Select-Object Name, Count
# 3.
Get-ADUser -SearchBase "OU=Eleves,DC=lycee,DC=local" -Filter * -Properties LastLogonDate |
Select-Object Name, SamAccountName, LastLogonDate | Sort-Object LastLogonDate
# 4.
Get-ADGroupMember "Admins du domaine" |
Get-ADUser -Properties LastLogonDate |
Select-Object Name, SamAccountName, LastLogonDate
Explication
Group-Object Enabled donne en une ligne le décompte actifs/désactivés. Le chaînage Get-ADGroupMember | Get-ADUser -Properties LastLogonDate fonctionne car les objets renvoyés portent l’identité attendue par la cmdlet suivante.
Exercice 6.2 — Rentrée des classes
Voir le corrigé
[CmdletBinding()]
param(
[Parameter(Mandatory)][string]$FichierCsv,
[string]$OuRacine = "OU=Eleves,DC=lycee,DC=local",
[switch]$Simulation
)
function Remove-Accent {
param([string]$Texte)
($Texte.Normalize([Text.NormalizationForm]::FormD) -replace "\p{Mn}", "")
}
$motDePasse = Read-Host "Mot de passe initial commun (maquette uniquement)" -AsSecureString
$eleves = Import-Csv $FichierCsv -Delimiter ";" -Encoding UTF8
$rapport = foreach ($e in $eleves) {
if (-not $e.Nom -or -not $e.Prenom -or -not $e.Classe) {
[PSCustomObject]@{ Ligne = "$($e.Nom) $($e.Prenom)"; Etat = "Ligne incomplète" }
continue
}
$base = (Remove-Accent ($e.Prenom.Substring(0,1) + $e.Nom)).ToLower() -replace "[^a-z0-9]", ""
$identifiant = $base
$n = 1
while (Get-ADUser -Filter "SamAccountName -eq '$identifiant'" -ErrorAction SilentlyContinue) {
$n++
$identifiant = "$base$n"
}
$ouClasse = "OU=$($e.Classe),$OuRacine"
$groupe = "GRP_$($e.Classe)"
if ($Simulation) {
[PSCustomObject]@{ Identifiant = $identifiant; Ou = $ouClasse; Groupe = $groupe; Etat = "Simulation" }
continue
}
if (-not (Get-ADOrganizationalUnit -Filter "DistinguishedName -eq '$ouClasse'")) {
New-ADOrganizationalUnit -Name $e.Classe -Path $OuRacine
}
if (-not (Get-ADGroup -Filter "Name -eq '$groupe'")) {
New-ADGroup -Name $groupe -GroupScope Global -GroupCategory Security -Path $ouClasse
}
try {
New-ADUser -Name "$($e.Prenom) $($e.Nom)" -GivenName $e.Prenom -Surname $e.Nom `
-SamAccountName $identifiant -UserPrincipalName "$identifiant@lycee.local" `
-Path $ouClasse -AccountPassword $motDePasse -ChangePasswordAtLogon $true `
-Enabled $true -ErrorAction Stop
Add-ADGroupMember -Identity $groupe -Members $identifiant
[PSCustomObject]@{ Identifiant = $identifiant; Ou = $ouClasse; Groupe = $groupe; Etat = "Créé" }
}
catch {
[PSCustomObject]@{ Identifiant = $identifiant; Ou = $ouClasse; Groupe = $groupe; Etat = "Erreur : $($_.Exception.Message)" }
}
}
$rapport | Export-Csv "rapport-rentree.csv" -Delimiter ";" -Encoding UTF8 -NoTypeInformation
$rapport | Group-Object Etat | Select-Object Count, Name
Explication
Le script cherche un identifiant libre dans l’annuaire, et non dans le seul fichier : c’est indispensable puisque des comptes existent déjà. Le mode simulation renvoie exactement les mêmes objets que le mode réel, ce qui permet de comparer avant d’agir.
Pour aller plus loin
Ajoute une colonne Etat dans un fichier de reprise : en relançant le script avec ce fichier, tu ne retraites que les lignes en erreur.
Exercice 6.3 — Audit
Voir le corrigé
# 1.
$limite = (Get-Date).AddDays(-90)
Get-ADUser -Filter "Enabled -eq '$true'" -Properties LastLogonDate, whenCreated |
Where-Object { -not $_.LastLogonDate -or $_.LastLogonDate -lt $limite } |
Select-Object Name, SamAccountName, LastLogonDate, whenCreated
# 2.
"Admins du domaine", "Administrateurs de l'entreprise", "Administrateurs du schéma" | ForEach-Object {
$groupe = $_
Get-ADGroupMember -Identity $groupe -Recursive -ErrorAction SilentlyContinue |
Select-Object @{N = "Groupe"; E = { $groupe }}, Name, objectClass
}
# 3.
Get-ADUser -Filter * -Properties Description |
Where-Object { $_.Description -match "(?i)(mot de passe|mdp|pass)" } |
Select-Object Name, SamAccountName, Description
# 4.
$tous = Get-ADUser -Filter * -Properties Enabled, LastLogonDate
[PSCustomObject]@{
Actifs = ($tous | Where-Object Enabled).Count
Desactives = ($tous | Where-Object { -not $_.Enabled }).Count
Dormants90j = ($tous | Where-Object { $_.Enabled -and (-not $_.LastLogonDate -or $_.LastLogonDate -lt $limite) }).Count
Admins = (Get-ADGroupMember "Admins du domaine" -Recursive).Count
}
Explication
-Recursive sur Get-ADGroupMember déplie les groupes imbriqués : un compte peut être administrateur sans figurer directement dans le groupe. C’est précisément ce que cherchent les attaquants, et ce que les audits oublient.
Pour aller plus loin
# Version indépendante de la langue, par identifiant de sécurité bien connu
$domaine = (Get-ADDomain).DomainSID.Value
Get-ADGroup -Identity "$domaine-512" | Get-ADGroupMember -Recursive
Mini-projet
Voir le corrigé
function New-MotDePasse {
param([int]$Longueur = 14)
$alphabet = [char[]]"abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789!@#%*"
-join (1..$Longueur | ForEach-Object { $alphabet | Get-Random })
}
# Dans la boucle de création :
$clair = New-MotDePasse
$securise = ConvertTo-SecureString $clair -AsPlainText -Force
New-ADUser @parametres -AccountPassword $securise -ChangePasswordAtLogon $true
# Fichier de remise, à imprimer puis détruire
[PSCustomObject]@{ Identifiant = $identifiant; MotDePasseInitial = $clair } |
Export-Csv "remise-eleves.csv" -Append -Delimiter ";" -Encoding UTF8 -NoTypeInformation
Ce fichier de remise est le point sensible du script : il contient des mots de passe en clair. Il doit être créé dans un dossier à accès restreint, imprimé puis supprimé immédiatement, et jamais envoyé par messagerie. Comme les comptes exigent un changement à la première connexion, sa durée de vie utile est de quelques heures.
Explication
Un mot de passe différent par élève est la seule approche acceptable : un mot de passe commun signifie que chacun peut se connecter au compte des autres pendant les premières heures. L’alphabet exclut les caractères ambigus (I, l, 1, O, 0) pour limiter les erreurs de saisie lors de la remise.
Pour aller plus loin
Pour aller plus loin, génère un courrier de remise individuel (un fichier par élève) plutôt qu’un tableau global : la distribution est alors plus facile à cloisonner.
🧠 Testez vos connaissances
5 questions. Répondez puis cliquez sur « Corriger ». Rien n’est enregistré : c’est de l’auto-évaluation.
© 2026 Bruno Jousseaume. Ce cours est publié sous licence CC BY-NC-SA 4.0 : vous pouvez le partager et l’adapter en citant l’auteur et un lien vers cette page, sans usage commercial, et en partageant vos adaptations sous la même licence.