Introduction : pourquoi revisiter la gestion des hiérarchies dans Entra ID
Dans tout environnement Microsoft 365 d'entreprise, la modélisation fidèle des hiérarchies managériales dans Microsoft Entra ID (anciennement Azure Active Directory) est un prérequis fondamental pour de nombreux scénarios : provisioning automatisé, workflows d'approbation, politiques d'accès conditionnel basées sur le rôle organisationnel, ou encore tableaux de bord RH. Pourtant, extraire ces données de manière fiable et performante via l'API reste un exercice qui cache plusieurs subtilités.
Cet article remplace les approches historiques basées sur le module Azure AD PowerShell (désormais déprécié) et le cmdlet Get-User d'Exchange Online. Nous allons explorer en profondeur comment exploiter Microsoft Graph API via le module Microsoft.Graph pour PowerShell afin de produire un rapport structuré des managers et de leurs rapports directs.
Prérequis indispensable
La pertinence de ce rapport dépend entièrement de la qualité des données dans Entra ID. L'attribut manager doit être correctement renseigné pour chaque compte utilisateur, que ce soit via la synchronisation depuis un Active Directory on-premises (Azure AD Connect / Entra Connect) ou via une API de provisioning (SCIM, HR-driven provisioning depuis Workday ou SAP SuccessFactors).
Architecture de l'approche Microsoft Graph API
Comprendre le modèle de données de la hiérarchie dans Entra ID
Dans le schéma d'objet Entra ID, la relation manager/rapports directs est modélisée comme suit :
- Chaque objet
userpossède une propriété de navigationmanagerqui pointe vers un autre objet de typedirectoryObject. - La collection inverse
directReportsliste tous les utilisateurs dont la propriétémanagerpointe vers l'utilisateur courant. - Ces propriétés ne sont pas retournées par défaut dans les requêtes GET standard sur l'endpoint
/users: elles nécessitent une opération d'expansion ($expand) ou un appel dédié.
Les endpoints Microsoft Graph concernés sont :
GET /users/{id}/manager— retourne le manager d'un utilisateur spécifiqueGET /users/{id}/directReports— retourne la liste des rapports directsGET /users?$expand=manager— retourne les utilisateurs avec leur manager en une seule requête
Permissions Microsoft Graph requises
Pour exécuter les scripts présentés dans cet article, le principal de service ou le compte utilisateur doit disposer des permissions suivantes :
User.Read.All(Application ou Delegated) — lecture des profils utilisateursDirectory.Read.All(Application ou Delegated) — lecture des relations de répertoire, y compris la hiérarchie managériale
Authentification recommandée
Pour des scripts d'administration exécutés en mode non-interactif (tâches planifiées, pipelines CI/CD), privilégiez une App Registration avec des permissions de type Application et un certificat X.509 pour l'authentification, plutôt que des credentials utilisateur interactifs.
Première approche : itération individuelle sur chaque compte
La méthode la plus intuitive consiste à récupérer l'ensemble des comptes utilisateurs licenciés, puis d'appeler le cmdlet Get-MgUserDirectReport pour chacun d'entre eux. Voici l'implémentation complète :
1# Connexion à Microsoft Graph avec les scopes nécessaires2Connect-MgGraph -Scopes "User.Read.All", "Directory.Read.All"3 4[array]$ManagersWithReports = @()5 6# Récupération des comptes membres licenciés avec filtre serveur7[array]$Users = Get-MgUser `8 -Filter "assignedLicenses/`$count ne 0 and userType eq 'Member'" `9 -ConsistencyLevel Eventual `10 -CountVariable Count `11 -All `12 -PageSize 50013 14Write-Host "Nombre total d'utilisateurs licenciés trouvés : $Count"15 16ForEach ($User in $Users) {17 $Reports = Get-MgUserDirectReport -UserId $User.Id -ErrorAction SilentlyContinue18 if ($Reports) {19 $ManagersWithReports += [PSCustomObject]@{20 DisplayName = $User.DisplayName21 UserPrincipalName = $User.UserPrincipalName22 DirectReports = $Reports.Count23 }24 }25}26 27$ManagersWithReports | Sort-Object DirectReports -Descending | Format-Table DisplayName, DirectReportsExemple de sortie :
1DisplayName DirectReports2----------- -------------3Kim Akers (She/Her) 174James Ryan 115Brian Weakliam (Operations) 36Paul Robichaux (Office 365 for IT Pros) 17René Artois 18Chris Bishop 19Tony Redmond 110Andy Ruth (Project Director) 1Problème de performance
Cette approche génère N+1 appels API (un pour récupérer les utilisateurs, puis un par utilisateur pour ses rapports directs). Sur un tenant de 10 000 comptes, cela représente jusqu'à 10 001 requêtes HTTP vers Microsoft Graph, ce qui impacte fortement les délais d'exécution et peut déclencher les mécanismes de throttling (HTTP 429). Cette méthode est acceptable uniquement pour des organisations de moins de 500 utilisateurs.
Approche optimisée : passage unique avec expansion de propriété
Principe du $expand sur la propriété Manager
Microsoft Graph supporte l'opération $expand pour résoudre les propriétés de navigation en une seule requête. Plutôt que d'appeler l'endpoint manager séparément pour chaque utilisateur, nous pouvons instruire Graph de peupler la propriété Manager lors de la récupération des utilisateurs :
1# Passage unique avec expansion de la propriété Manager2[array]$Users = Get-MgUser `3 -Filter "userType eq 'Member'" `4 -ExpandProperty Manager `5 -Select "Id, displayName, Manager, assignedLicenses" `6 -All `7 -PageSize 500 | Where-Object { $_.AssignedLicenses.Count -ne 0 }Pourquoi ce filtre en deux temps ?
Il existe une limitation connue de Microsoft Graph API : l'endpoint /users ne supporte pas la combinaison d'un filtre complexe sur les propriétés de collection (comme assignedLicenses/$count ne 0) avec une opération $expand sur une propriété de navigation. Cette contrainte génère une erreur 400 Bad Request.
La stratégie adoptée est donc la suivante :
- Filtre côté serveur :
userType eq 'Member'— élimine tous les comptes invités (Guest), qui représentent souvent le volume le plus important dans les tenants avec une forte collaboration externe. - Filtre côté client :
Where-Object { $_.AssignedLicenses.Count -ne 0 }— élimine les comptes membres sans licence (comptes de service, comptes désactivés, etc.).
Cette stratégie minimise le volume de données transférées depuis Graph tout en contournant la limitation de l'API.
Structure de l'objet Manager retourné
Après expansion, la propriété Manager de chaque objet utilisateur contient :
1$Users[0].Manager | Format-List2 3DeletedDateTime :4Id : d36b323a-32c3-4ca5-a4a5-2f7b4fbef31c5AdditionalProperties : {[@odata.type, #microsoft.graph.user],6 [accountEnabled, True],7 [businessPhones, System.Object[]],8 [city, NYC]…}Le champ AdditionalProperties contient un dictionnaire de propriétés supplémentaires du compte manager. Pour accéder au nom d'affichage du manager, par exemple :
1$ManagerDisplayName = $Users[0].Manager.AdditionalProperties['displayName']2$ManagerUPN = $Users[0].Manager.AdditionalProperties['userPrincipalName']3$ManagerDepartment = $Users[0].Manager.AdditionalProperties['department']Script complet de génération du rapport
Voici le script de production complet, avec gestion des erreurs, export CSV et affichage structuré :
1#Requires -Modules Microsoft.Graph.Users2 3<#4.SYNOPSIS5 Génère un rapport des managers et de leurs rapports directs depuis Entra ID.6.DESCRIPTION7 Utilise Microsoft Graph API (passage unique avec $expand) pour extraire8 les relations hiérarchiques des comptes membres licenciés.9.NOTES10 Permissions requises : User.Read.All, Directory.Read.All11 Version : 2.0 | Date : 202512#>13 14# --- Connexion à Microsoft Graph ---15Connect-MgGraph -Scopes "User.Read.All", "Directory.Read.All" -NoWelcome16 17Write-Host "Récupération des utilisateurs membres avec expansion du manager..." -ForegroundColor Cyan18 19# --- Passage unique : filtrage serveur + expansion manager ---20[array]$AllUsers = Get-MgUser `21 -Filter "userType eq 'Member'" `22 -ExpandProperty Manager `23 -Select "Id, displayName, userPrincipalName, department, jobTitle, Manager, assignedLicenses" `24 -All `25 -PageSize 500 | Where-Object { $_.AssignedLicenses.Count -ne 0 }26 27Write-Host "Utilisateurs licenciés trouvés : $($AllUsers.Count)" -ForegroundColor Green28 29# --- Construction de la table de hachage des managers ---30$ManagerReport = @{}31 32ForEach ($User in $AllUsers) {33 if ($null -ne $User.Manager) {34 $ManagerId = $User.Manager.Id35 $ManagerDisplayName = $User.Manager.AdditionalProperties['displayName']36 $ManagerUPN = $User.Manager.AdditionalProperties['userPrincipalName']37 38 if (-not $ManagerReport.ContainsKey($ManagerId)) {39 $ManagerReport[$ManagerId] = [PSCustomObject]@{40 ManagerId = $ManagerId41 ManagerName = $ManagerDisplayName42 ManagerUPN = $ManagerUPN43 DirectReportsCount = 044 DirectReports = [System.Collections.Generic.List[string]]::new()45 }46 }47 $ManagerReport[$ManagerId].DirectReportsCount++48 $ManagerReport[$ManagerId].DirectReports.Add($User.DisplayName)49 }50}51 52# --- Mise en forme et affichage du rapport ---53$ReportOutput = $ManagerReport.Values | Sort-Object DirectReportsCount -Descending54 55$ReportOutput | Format-Table ManagerName, ManagerUPN, DirectReportsCount -AutoSize56 57# --- Export CSV ---58$CsvPath = ".\ManagersReport_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv"59$ReportOutput | Select-Object ManagerName, ManagerUPN, DirectReportsCount,60 @{N='DirectReportsList'; E={ $_.DirectReports -join '; ' }} | Export-Csv -Path $CsvPath -NoTypeInformation -Encoding UTF861 62Write-Host "Rapport exporté : $CsvPath" -ForegroundColor Green
Optimisation pour les grands tenants
Pour les organisations de plus de 50 000 comptes, envisagez d'utiliser Microsoft Graph SDK avec des requêtes batch ($batch) pour paralléliser les appels, ou d'exporter les données via Microsoft Entra ID Governance et les Access Reviews qui exposent nativement les données hiérarchiques.
Comparaison des approches disponibles
| Approche | Module/Outil | Appels API | Performance | Statut |
|---|---|---|---|---|
| Get-AzureADUserDirectReports | AzureAD PowerShell | N+1 | Faible | Déprécié |
| Get-User (Exchange Online) | ExchangeOnlineManagement | N+1 | Faible | Fonctionnel |
| Get-MgUserDirectReport (itératif) | Microsoft.Graph | N+1 | Faible | Fonctionnel |
| Get-MgUser -ExpandProperty Manager | Microsoft.Graph | 1 passage | Élevée | Recommandé |
Aller plus loin : requête Graph API native (REST)
Pour les scénarios d'intégration avec des systèmes tiers (Logic Apps, Azure Functions, Power Automate), voici l'équivalent de la requête PowerShell en appel REST direct :
1GET https://graph.microsoft.com/v1.0/users2 ?$filter=userType eq 'Member'3 &$expand=manager($select=id,displayName,userPrincipalName)4 &$select=id,displayName,userPrincipalName,department,assignedLicenses5 &$top=9996 &$count=true7ConsistencyLevel: eventualEt son équivalent en PowerShell via Invoke-MgGraphRequest pour un contrôle total sur la requête :
1$Uri = "https://graph.microsoft.com/v1.0/users" +2 "?`$filter=userType eq 'Member'" +3 "&`$expand=manager(`$select=id,displayName,userPrincipalName)" +4 "&`$select=id,displayName,userPrincipalName,assignedLicenses" +5 "&`$top=999&`$count=true"6 7$Headers = @{ "ConsistencyLevel" = "eventual" }8 9$Response = Invoke-MgGraphRequest -Uri $Uri -Headers $Headers -Method GET10$Users = $Response.value11 12# Pagination manuelle si nécessaire13while ($Response.'@odata.nextLink') {14 $Response = Invoke-MgGraphRequest -Uri $Response.'@odata.nextLink' -Method GET15 $Users += $Response.value16}ConsistencyLevel : eventual
L'en-tête ConsistencyLevel: eventual est obligatoire lorsque vous utilisez $count ou des opérateurs de filtre avancés comme $count ne 0 sur des propriétés de collection. Sans cet en-tête, Graph retourne une erreur 400. Ce header indique à l'API que vous acceptez une légère latence de réplication des données entre les nœuds du service.
Bonnes pratiques pour maintenir la qualité des données hiérarchiques
Un rapport de managers/rapports directs n'est pertinent que si les données sources sont fiables. Voici les recommandations architecturales pour maintenir la qualité :
- Synchronisation depuis AD on-premises : si vous utilisez Microsoft Entra Connect Sync, vérifiez que l'attribut
managerest inclus dans les règles de synchronisation (attributmanagerdans le schéma AD correspond àmanagerdans Entra ID). - HR-driven provisioning : avec Entra ID Provisioning connecté à Workday ou SAP SuccessFactors, mappez explicitement l'attribut
managerdans la configuration de provisioning. - Audits réguliers : planifiez une exécution mensuelle du script pour détecter les comptes sans manager (
$_.Manager -eq $null) qui signalent des données manquantes. - Access Reviews : configurez des Microsoft Entra Access Reviews pour que les managers valident périodiquement les accès de leurs rapports directs — ce processus force également la mise à jour des relations hiérarchiques.
Comptes sans manager
Les utilisateurs dont la propriété manager est nulle ne seront pas inclus dans les rapports directs de qui que ce soit. Dans les workflows automatisés (ex. : offboarding, escalade d'alertes), l'absence de manager peut bloquer des processus critiques. Mettez en place une alerte proactive sur les comptes licenciés sans manager assigné.
Références et ressources complémentaires
- Microsoft Graph API — List users (documentation officielle)
- Microsoft Graph API — Get manager
- Microsoft Graph API — List directReports
- Module Microsoft.Graph PowerShell — référence Get-MgUser
- Script complet sur GitHub — Office 365 for IT Pros
- Article original — Office365itpros.com
- Entra Connect Sync — Attributs synchronisés
- HR-driven provisioning — Workday vers Entra ID



