Entra ID (Azure AD) — Gérer utilisateurs, MFA et groupes via Microsoft Graph
Introduction
Faitza_Entra.ps1 regroupe les opérations courantes sur Entra ID (Azure AD) via Microsoft Graph :
lecture d'utilisateurs, vérification MFA, logs de connexion, désactivation/suppression de comptes et gestion des groupes.
Toutes les fonctions utilisent Get-Faitza_Modul_Headers -resource "graph" pour obtenir un Bearer token valide.
Voir l'article dédié : Token Bearer pour les APIs Microsoft en PowerShell.
Get-Faitza_Entra_User
Droits requis — User.Read.All · Directory.Read.All
Recherche un ou plusieurs utilisateurs Entra ID. Supporte la sélection de propriétés (-property),
le filtrage OData (-filter), l'expand natif et manuel (memberOf, transitiveMemberOf),
la pagination automatique, et un filtre post-expand via -filterExpand.
function Get-Faitza_Entra_User {
[CmdletBinding()]
param ([string]$user, [string[]]$property, [string[]]$filter, [string[]]$expand, [scriptblock]$filterExpand)
try {
Write-Host "$($PSStyle.Background.Red)>>> Get-Faitza_Entra_User <<<$($PSStyle.Reset)"
$expandManual = @("memberOf", "transitiveMemberOf")
$allResults = $null
$headers = Get-Faitza_Modul_Headers -resource "graph"
if ($property -notcontains "id") { $property += "id" }
$select = $property -join ","
$expandNative = if ($expand) { @($expand | Where-Object { $_ -notin $expandManual }) } else { @() }
$expandStr = if ($expandNative.Count -gt 0) { $expandNative -join "," } else { $null }
if ($user) {
$user = $user.Replace("#", "%23")
$uri = "https://graph.microsoft.com/v1.0/users?`$filter=(userPrincipalName eq '$user' or mail eq '$user')&`$select=$select&`$count=true"
if ($expandStr) { $uri += "&`$expand=$expandStr" }
$response = Invoke-RestMethod -Method Get -Uri $uri -Headers $headers -ErrorAction Stop
if ($response.value.Count -gt 0) { $allResults = @($response.value[0]) }
} else {
$allResults = @()
$uri = "https://graph.microsoft.com/v1.0/users?`$select=$select&`$top=999&`$count=true"
if ($filter -and $filter.Count -gt 0) { $filterStr = [uri]::EscapeDataString(($filter -join " and ")); $uri += "&`$filter=$filterStr" }
if ($expandStr) { $uri += "&`$expand=$expandStr" }
do {
$response = Invoke-RestMethod -Method Get -Uri $uri -Headers $headers -ErrorAction Stop
$allResults += $response.value
$uri = if ($response.'@odata.nextLink') { $response.'@odata.nextLink' } else { $null }
} while ($uri)
}
if ($allResults -and $expand) {
$expandToProcess = @($expand | Where-Object { $_ -in $expandManual })
foreach ($expandProp in $expandToProcess) {
foreach ($res in $allResults) {
if (-not $res.id) { continue }
$expandArray = @()
$expandUri = "https://graph.microsoft.com/v1.0/users/$($res.id)/$expandProp`?`$top=999"
do {
try { $expandRes = Invoke-RestMethod -Method Get -Uri $expandUri -Headers $headers -ErrorAction Stop; if ($expandRes.value) { $expandArray += $expandRes.value }; $expandUri = if ($expandRes.'@odata.nextLink') { $expandRes.'@odata.nextLink' } else { $null } }
catch { $expandUri = $null }
} while ($expandUri)
if ($filterExpand -and $expandArray.Count -gt 0) { $expandArray = @($expandArray | Where-Object $filterExpand) }
$res | Add-Member -MemberType NoteProperty -Name $expandProp -Value $expandArray -Force
}
}
}
if ($null -eq $allResults -or ($allResults -is [array] -and $allResults.Count -eq 0)) { return $null }
if ($user -and $allResults.Count -eq 1) { return $allResults[0] }
return $allResults
} catch {
throw "Erreur Get-Faitza_Entra_User : $_"
} finally {
Write-Host "$($PSStyle.Background.Red)<<< Get-Faitza_Entra_User >>>$($PSStyle.Reset)"
}
}
# Un utilisateur précis
Get-Faitza_Entra_User -user "[email protected]" `
-property @("displayName","accountEnabled","signInActivity")
# Tous les utilisateurs avec leurs groupes
Get-Faitza_Entra_User `
-property @("displayName","userPrincipalName") `
-expand @("transitiveMemberOf")
Get-Faitza_Entra_User_Login
Droits requis — AuditLog.Read.All · User.Read.All
Extrait les informations de connexion d'un objet utilisateur (dernière connexion interactive et non-interactive,
nombre de jours d'inactivité). Prend en entrée un objet retourné par Get-Faitza_Entra_User
avec la propriété signInActivity.
function Get-Faitza_Entra_User_Login {
[CmdletBinding()]
param ($user)
try {
Write-Host "$($PSStyle.Background.Red)>>> Get-Faitza_Entra_User_Login <<<$($PSStyle.Reset)"
if ($null -eq $user) { throw "Le parametre -user est obligatoire pour verifier le login." }
$user_lastsuccess = if ($user.signInActivity.lastSuccessfulSignInDateTime) { [datetime]$user.signInActivity.lastSuccessfulSignInDateTime } else { "Jamais" }
$user_lastnonint = if ($user.signInActivity.lastNonInteractiveSignInDateTime) { [datetime]$user.signInActivity.lastNonInteractiveSignInDateTime } else { "Jamais" }
$user_jours = if ($user_lastsuccess -ne "Jamais") { [math]::Round((New-TimeSpan -Start $user_lastsuccess -End (Get-Date)).TotalDays, 1) } else { 999 }
return [PSCustomObject]@{ UPN = $user.userPrincipalName; DerniereConnexion = if ($user_lastsuccess -ne "Jamais") { $user_lastsuccess.ToString("dd/MM/yyyy HH:mm") } else { "Jamais" }; JoursDepuis = $user_jours; DerniereNonInteractive = if ($user_lastnonint -ne "Jamais") { $user_lastnonint.ToString("dd/MM/yyyy HH:mm") } else { "Jamais" } }
} catch {
throw "Erreur Get-Faitza_Entra_User_Login : $_"
} finally {
Write-Host "$($PSStyle.Background.Red)<<< Get-Faitza_Entra_User_Login >>>$($PSStyle.Reset)"
}
}
$u = Get-Faitza_Entra_User -user "[email protected]" `
-property @("userPrincipalName","signInActivity")
Get-Faitza_Entra_User_Login -user $u
Get-Faitza_Entra_User_Disabled
Droits requis — AuditLog.Read.All · User.Read.All
Wrapper de Get-Faitza_Entra_User_Login qui filtre d'abord les UPN exclus avant d'analyser
l'activité. Utilisé pour itérer sur une liste d'utilisateurs et ignorer les comptes de service ou les comptes à exclure.
function Get-Faitza_Entra_User_Disabled {
[CmdletBinding()]
param ($user, [string[]]$ExcludedUPNs = @())
try {
Write-Host "$($PSStyle.Background.Red)>>> Get-Faitza_Entra_User_Disabled <<<$($PSStyle.Reset)"
if ($null -eq $user) { throw "Le parametre -user est obligatoire." }
if ($ExcludedUPNs -contains $user.userPrincipalName.ToLower().Trim()) { return $null }
return Get-Faitza_Entra_User_Login -user $user
} catch {
throw "Erreur Get-Faitza_Entra_User_Disabled : $_"
} finally {
Write-Host "$($PSStyle.Background.Red)<<< Get-Faitza_Entra_User_Disabled >>>$($PSStyle.Reset)"
}
}
$exclusions = @("[email protected]", "[email protected]")
$users | ForEach-Object {
Get-Faitza_Entra_User_Disabled -user $_ -ExcludedUPNs $exclusions
} | Where-Object { $_ -and $_.JoursDepuis -gt 90 }
Get-Faitza_Entra_MFA
Droits requis — UserAuthenticationMethod.Read.All
Vérifie les méthodes d'authentification MFA enregistrées pour un utilisateur. Retourne un booléen
mfa (vrai si Authenticator ou FIDO2 est présent) et la liste des types détectés.
function Get-Faitza_Entra_MFA {
[CmdletBinding()]
param ([string]$userId)
try {
Write-Host "$($PSStyle.Background.Red)>>> Get-Faitza_Entra_MFA <<<$($PSStyle.Reset)"
if ([string]::IsNullOrWhiteSpace($userId)) { throw "Le parametre -userId est obligatoire pour verifier le MFA." }
$headers = Get-Faitza_Modul_Headers -resource "graph"
$uri = "https://graph.microsoft.com/v1.0/users/$userId/authentication/methods"
$user_mfa = $false
$mfa_types = @()
$response = Invoke-RestMethod -Method Get -Uri $uri -Headers $headers
if ($null -ne $response.value) {
$type_map = @{
'#microsoft.graph.microsoftAuthenticatorAuthenticationMethod' = 'App';
'#microsoft.graph.fido2AuthenticationMethod' = 'FIDO2';
'#microsoft.graph.phoneAuthenticationMethod' = 'Telephone';
'#microsoft.graph.softwareOathAuthenticationMethod' = 'TOTP';
'#microsoft.graph.windowsHelloForBusinessAuthenticationMethod' = 'Windows Hello';
'#microsoft.graph.emailAuthenticationMethod' = 'Email';
'#microsoft.graph.temporaryAccessPassAuthenticationMethod' = 'TAP'
}
foreach ($method in $response.value) {
$type = $method.'@odata.type'
if ($type_map.ContainsKey($type)) {
$mfa_types += $type_map[$type]
if ($type -match 'microsoftAuthenticatorAuthenticationMethod|fido2AuthenticationMethod') {
$user_mfa = $true
}
}
}
}
return @{ mfa = $user_mfa; types = $mfa_types }
} catch {
throw "Erreur Get-Faitza_Entra_MFA : $_"
} finally {
Write-Host "$($PSStyle.Background.Red)<<< Get-Faitza_Entra_MFA >>>$($PSStyle.Reset)"
}
}
$result = Get-Faitza_Entra_MFA -userId "[email protected]"
# $result.mfa → $true / $false
# $result.types → @("App", "FIDO2")
Disable-Faitza_Entra_user
Droits requis — User.ReadWrite.All
Désactive un compte Entra ID (accountEnabled = $false). Accepte un UPN, une adresse mail
ou un GUID. En cas d'échec avec un UPN, résout l'ID via Get-Faitza_Entra_User et réessaie.
Vérifie la désactivation après PATCH.
function Disable-Faitza_Entra_user {
[CmdletBinding()]
param ([string]$userId)
try {
Write-Host "$($PSStyle.Background.Red)>>> Disable-Faitza_Entra_user <<<$($PSStyle.Reset)"
if ([string]::IsNullOrWhiteSpace($userId)) { throw "Le parametre -userId est obligatoire pour la desactivation." }
$headers = Get-Faitza_Modul_Headers -resource "graph"
$body = @{ accountEnabled = $false } | ConvertTo-Json
$TargetId = $userId.Trim()
$isGuid = $TargetId -match "^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$"
if (-not $isGuid) {
try { Invoke-RestMethod -Method Patch -Uri "https://graph.microsoft.com/v1.0/users/$TargetId" -Headers $headers -Body $body -ErrorAction Stop | Out-Null }
catch {
$EntraUser = Get-Faitza_Entra_User -user $TargetId -property @('id', 'userPrincipalName')
if ($null -eq $EntraUser) { throw "Utilisateur definitivement introuvable dans Entra ID avec la valeur : $TargetId" }
$TargetId = $EntraUser.id
Invoke-RestMethod -Method Patch -Uri "https://graph.microsoft.com/v1.0/users/$TargetId" -Headers $headers -Body $body | Out-Null
}
} else { Invoke-RestMethod -Method Patch -Uri "https://graph.microsoft.com/v1.0/users/$TargetId" -Headers $headers -Body $body | Out-Null }
$user_test = Get-Faitza_Entra_User -user $TargetId -property @('accountEnabled')
if ($user_test.accountEnabled -eq $true) { throw "Test de verification echoue pour $TargetId, le compte est toujours actif." }
return $true
} catch {
throw "Erreur Disable-Faitza_Entra_user : $_"
} finally {
Write-Host "$($PSStyle.Background.Red)<<< Disable-Faitza_Entra_user >>>$($PSStyle.Reset)"
}
}
Disable-Faitza_Entra_user -userId "[email protected]"
Remove-Faitza_Entra_user
Droits requis — User.ReadWrite.All
Supprime définitivement un utilisateur Entra ID. Accepte UPN, mail ou GUID, résout l'ID si nécessaire. Attend 2 secondes puis vérifie que l'utilisateur n'est plus visible dans Graph.
function Remove-Faitza_Entra_user {
[CmdletBinding()]
param ([string]$userId)
try {
Write-Host "$($PSStyle.Background.Red)>>> Remove-Faitza_Entra_user <<<$($PSStyle.Reset)"
if ([string]::IsNullOrWhiteSpace($userId)) { throw "Le parametre -userId est obligatoire pour la suppression." }
$headers = Get-Faitza_Modul_Headers -resource "graph"
$TargetId = $userId.Trim()
$isGuid = $TargetId -match "^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$"
if (-not $isGuid) {
try { Invoke-RestMethod -Method Delete -Uri "https://graph.microsoft.com/v1.0/users/$TargetId" -Headers $headers -ErrorAction Stop | Out-Null }
catch { $EntraUser = Get-Faitza_Entra_User -user $TargetId -property @('id', 'userPrincipalName'); if ($null -eq $EntraUser) { throw "Utilisateur definitivement introuvable dans Entra ID avec la valeur : $TargetId" }; $TargetId = $EntraUser.id; Invoke-RestMethod -Method Delete -Uri "https://graph.microsoft.com/v1.0/users/$TargetId" -Headers $headers | Out-Null }
} else { Invoke-RestMethod -Method Delete -Uri "https://graph.microsoft.com/v1.0/users/$TargetId" -Headers $headers | Out-Null }
Start-Sleep -Seconds 2
$user_test = Get-Faitza_Entra_User -user $TargetId -property @('id')
if ($null -ne $user_test) { throw "Test de verification echoue pour $TargetId, le compte est toujours visible dans Entra." }
return $true
} catch {
throw "Erreur Remove-Faitza_Entra_user : $_"
} finally {
Write-Host "$($PSStyle.Background.Red)<<< Remove-Faitza_Entra_user >>>$($PSStyle.Reset)"
}
}
Remove-Faitza_Entra_user -userId "[email protected]"
Get-Faitza_Entra_Groupe_Member
Droits requis — Group.Read.All · GroupMember.Read.All
Retourne les membres d'un ou plusieurs groupes Entra ID. Le switch -Recursive descend
dans les sous-groupes. Déduplique les utilisateurs via leur ID. Retourne data (liste d'utilisateurs)
et erreur (groupes introuvables).
function Get-Faitza_Entra_Groupe_Member {
[CmdletBinding()]
param ([string[]]$GroupName, [switch]$Recursive)
try {
Write-Host "$($PSStyle.Background.Red)>>> Get-Faitza_Entra_Groupe_Member <<<$($PSStyle.Reset)"
if (-not $GroupName -or $GroupName.Count -eq 0) { throw "Le parametre GroupName est vide." }
$headers = Get-Faitza_Modul_Headers -resource "graph"
$uniqueUsers = @{}
$processedGroupIds = @{}
$erreur = @()
foreach ($name in $GroupName) {
try {
$groupUri = if ($name -match "@") { "https://graph.microsoft.com/v1.0/groups?`$filter=mail eq '$name'&`$select=id,displayName,mail" } else { "https://graph.microsoft.com/v1.0/groups?`$filter=displayName eq '$name'&`$select=id,displayName,mail" }
$groupResponse = Invoke-RestMethod -Method Get -Uri $groupUri -Headers $headers
if ($groupResponse.value.Count -eq 0) { $erreur += "'$name' introuvable"; continue }
$Queue = [System.Collections.Generic.Queue[PSCustomObject]]::new()
$Queue.Enqueue($groupResponse.value[0])
while ($Queue.Count -gt 0) {
$currentGroup = $Queue.Dequeue()
if ($processedGroupIds.ContainsKey($currentGroup.id)) { continue }
$processedGroupIds[$currentGroup.id] = $true
$membersUri = "https://graph.microsoft.com/v1.0/groups/$($currentGroup.id)/members?`$count=true&`$select=id,displayName,givenName,surname,mail,mobilePhone,accountEnabled&`$top=999"
do {
$membersResponse = Invoke-RestMethod -Method Get -Uri $membersUri -Headers $headers
if ($membersResponse.value) {
foreach ($member in $membersResponse.value) {
if ($member.'@odata.type' -eq "#microsoft.graph.user") { if (-not $uniqueUsers.ContainsKey($member.id)) { $uniqueUsers[$member.id] = $member } }
elseif ($member.'@odata.type' -eq "#microsoft.graph.group" -and $Recursive) { $Queue.Enqueue($member) }
}
}
$membersUri = if ($membersResponse.'@odata.nextLink') { $membersResponse.'@odata.nextLink' } else { $null }
} while ($membersUri)
}
} catch { $erreur += "Erreur avec $name : $_" }
}
return @{ data = @($uniqueUsers.Values); erreur = $erreur }
} catch {
throw "Erreur Get-Faitza_Entra_Groupe_Member : $_"
} finally {
Write-Host "$($PSStyle.Background.Red)<<< Get-Faitza_Entra_Groupe_Member >>>$($PSStyle.Reset)"
}
}
$result = Get-Faitza_Entra_Groupe_Member `
-GroupName @("GRP-IT", "GRP-Finance") `
-Recursive
# $result.data → liste des utilisateurs
# $result.erreur → groupes introuvables
Get-Faitza_Entra_Groupe_Name
Droits requis — Group.Read.All
Résout le displayName d'un groupe à partir de son GUID. Utile pour afficher un nom lisible
quand on ne dispose que d'un identifiant.
function Get-Faitza_Entra_Groupe_Name {
[CmdletBinding()]
param ([string]$GroupId)
try {
Write-Host "$($PSStyle.Background.Red)>>> Get-Faitza_Entra_Groupe_Name <<<$($PSStyle.Reset)"
if (-not $GroupId) { throw "Le parametre GroupId est vide." }
$Headers = Get-Faitza_Modul_Headers -resource "graph"
$Response = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/groups/$($GroupId.Trim())?`$select=displayName" -Headers $Headers -Method Get -ErrorAction Stop
if ($null -eq $Response.displayName) { throw "Groupe non trouve" }
return $Response.displayName
} catch {
throw "Erreur Get-Faitza_Entra_Groupe_Name : $_"
} finally {
Write-Host "$($PSStyle.Background.Red)<<< Get-Faitza_Entra_Groupe_Name >>>$($PSStyle.Reset)"
}
}
Get-Faitza_Entra_Groupe_Name -GroupId "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
Add-Faitza_Entra_UserToGroup
Droits requis — GroupMember.ReadWrite.All
Ajoute un utilisateur à un groupe Entra ID via l'endpoint /groups/{id}/members/$ref.
Prend le GUID de l'utilisateur et le GUID du groupe.
function Add-Faitza_Entra_UserToGroup {
[CmdletBinding()]
param ([string]$UserId, [string]$GroupId)
try {
Write-Host "$($PSStyle.Background.Red)>>> Add-Faitza_Entra_UserToGroup <<<$($PSStyle.Reset)"
if ([string]::IsNullOrWhiteSpace($UserId)) { throw "Le parametre -UserId est obligatoire." }
if ([string]::IsNullOrWhiteSpace($GroupId)) { throw "Le parametre -GroupId est obligatoire." }
$Headers = Get-Faitza_Modul_Headers -resource "graph"
$body = @{ "@odata.id" = "https://graph.microsoft.com/v1.0/directoryObjects/$UserId" } | ConvertTo-Json
Invoke-RestMethod -Method Post -Uri "https://graph.microsoft.com/v1.0/groups/$GroupId/members/`$ref" -Headers $Headers -Body $body -ErrorAction Stop | Out-Null
return $true
} catch {
throw "Erreur Add-Faitza_Entra_UserToGroup : $_"
} finally {
Write-Host "$($PSStyle.Background.Red)<<< Add-Faitza_Entra_UserToGroup >>>$($PSStyle.Reset)"
}
}
Add-Faitza_Entra_UserToGroup `
-UserId "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" `
-GroupId "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy"
// Commentaires
Aucun commentaire pour l'instant.