Pour trouver les utilisateurs Microsoft 365 inactifs, rapprochez deux éléments conservés à des endroits distincts : les licences attribuées à chaque compte, et la date de la dernière connexion réussie de ce compte. Microsoft Graph détient les deux sur l'objet utilisateur, donc un seul appel Get-MgUser qui sélectionne assignedLicenses et signInActivity vous donne une ligne par compte licencié avec sa date de dernière connexion. Comparez ces lignes à un seuil que votre équipe fixe à l'avance, généralement 30, 60 ou 90 jours, et chaque compte au-delà de la limite est un siège payant sans aucune preuve d'utilisation derrière lui. Le script ci-dessous fait exactement cela et écrit un CSV que vous pouvez emmener en revue. Copiez-le, exécutez-le, gardez-le.

C'est le versant connexion du gaspillage de licences. Le guide compagnon sur comment trouver les licences Microsoft 365 inutilisées couvre l'autre versant, l'écart entre ce que vous avez acheté et ce que vous avez attribué. Cet écart est le plus facile. Celui-ci, le siège attribué à un compte réel que personne n'utilise, représente en général le chiffre le plus élevé et la conversation la plus difficile.

Commencez par l'arithmétique, car elle décide si cela vaut un après-midi

Faites le calcul d'abord, avec vos propres chiffres plutôt qu'avec la référence de quelqu'un d'autre. Sur un parc de 300 utilisateurs, si 4 pour cent des comptes licenciés ne se sont pas connectés depuis un trimestre, cela fait 12 sièges. Douze sièges au prix que vous payez réellement par utilisateur et par mois, multiplié par douze mois, donne le montant annuel. À £20 le siège, cela fait £2,880 par an ; à £30, cela fait £4,320. Changez n'importe quelle donnée d'entrée et la réponse bouge, et c'est bien là l'intérêt : vous pouvez vérifier ce calcul de tête, et votre responsable financier aussi.

Deux choses rendent cela digne d'être poursuivi dans une organisation de 50 à 600 utilisateurs. Cela se répète chaque mois jusqu'à ce que quelqu'un agisse, donc ne rien faire produit un effet cumulé. Et ce n'est presque jamais le travail de quelqu'un. La finance voit une facture stable et suppose qu'elle est correcte ; l'informatique voit un décompte de licences et suppose que la finance l'a approuvé. C'est ainsi que des sièges dormants survivent pendant des années.

Pourquoi la page Licenses ne peut pas répondre à cette question

Le premier réflexe est le centre d'administration Microsoft 365, sous Billing puis Licenses, qui affiche chaque produit avec le nombre de sièges achetés et le nombre de sièges attribués. C'est une vérification utile de deux minutes et vous devriez la faire. Notez qu'elle agrège par produit, tandis que Billing puis Your products liste une ligne par abonnement, si bien que les deux pages ne concorderont pas toujours au premier coup d'œil.

Ce qu'aucune des deux ne vous dira, c'est si un siège attribué sert à quelque chose. Ces pages sont construites autour du droit d'usage, pas autour du comportement. Un compte créé pour un prestataire il y a deux ans, toujours activé, détenant toujours un siège complet, et sans connexion depuis, y ressemble exactement à votre utilisateur le plus actif.

Reports puis Usage vous rapproche du but. Le rapport Active users affiche une date de dernière activité par charge de travail pour Exchange, OneDrive, SharePoint et Teams, ainsi que les licences attribuées. Il mérite d'être connu, et il répond à une question différente : savoir si quelqu'un a touché une application, pas si l'identité s'est authentifiée. Deux choses à anticiper. Les données sont généralement disponibles sur les 7, 30, 90 et 180 derniers jours et peuvent mettre un jour ou plus à apparaître. Et les noms d'utilisateurs sont masqués par défaut, donc si le rapport affiche des lignes anonymisées, un administrateur doit décocher l'option « Conceal user, group, and site names in all reports » sous Settings, Org Settings, Services, Reports.

Définissez ce que signifie inactif avant de lancer quoi que ce soit

C'est l'étape que les équipes sautent, et elle décide si votre rapport survivra au contact avec le reste de l'entreprise. Trente jours est agressif et balaie les congés parentaux, les arrêts maladie de longue durée, les congés sabbatiques et le personnel saisonnier. Quatre-vingt-dix jours laisse un trimestre entier de gaspillage s'écouler avant que quoi que ce soit ne soit signalé. Soixante est un premier passage raisonnable dans un parc de taille moyenne. Il n'y a pas de bonne réponse, et quiconque vous dit le contraire, éditeurs compris, vous vend sa valeur par défaut.

Ce qui compte, c'est que le seuil soit une politique écrite dont votre équipe est propriétaire, avec des exceptions nommées, avant que le premier rapport ne parvienne à qui que ce soit. Un rapport sans seuil convenu produit une dispute. Un rapport avec un seuil produit des actions.

Les données de connexion derrière la réponse

Microsoft Entra ID enregistre l'activité de connexion sur chaque objet utilisateur, et Graph l'expose sur la ressource user de la v1.0 via la propriété signInActivity. Elle porte une dernière connexion interactive, une dernière connexion non interactive et une dernière connexion réussie, chacune associée à un identifiant de requête. Interactive signifie qu'une personne a saisi des identifiants ou approuvé une invite. Non interactive couvre un client qui renouvelle un jeton en arrière-plan, ce qui explique qu'un compte puisse paraître actif dans un champ et éteint dans un autre.

La raison d'utiliser cela plutôt que de fouiller les journaux de connexion tient à la rétention. Les journaux de connexion et d'audit sont conservés 7 jours dans l'offre gratuite et 30 jours avec Entra ID P1 ou P2, donc le journal seul ne peut pas vous renseigner sur un écart de 90 jours. La propriété signInActivity est différente : Entra la conserve aussi longtemps que l'objet utilisateur existe. C'est la bonne source pour cette question.

Quatre contraintes pratiques avant de lancer quoi que ce soit :

  • Elle n'est renvoyée que si vous la sélectionnez explicitement. Un Get-MgUser simple ne l'inclura pas, et la propriété est tout simplement absente plutôt que de déclencher une erreur, ce qui est la raison la plus fréquente pour laquelle on conclut que la donnée manque.
  • Sa lecture nécessite AuditLog.Read.All en plus de User.Read.All. Sans la portée d'audit, les données de licence reviennent et le champ de connexion revient vide, ce qui donne l'impression que tous les comptes sont inactifs.
  • Elle nécessite Microsoft Entra ID P1 ou P2. Sur un tenant sans l'un des deux, attendez-vous à ce que la propriété soit indisponible, alors vérifiez ce point avant de déboguer une colonne vide.
  • Une valeur vide n'est pas une preuve de non-usage. La dernière connexion réussie n'est enregistrée que depuis décembre 2023 et n'a pas fait l'objet d'un remplissage rétroactif, et la valeur peut accuser un retard allant jusqu'à 24 heures. Traitez une valeur vide comme « à examiner », jamais comme « à supprimer ».

Côté annuaire, vous n'avez pas besoin du rôle Global Administrator. Reports Reader est le rôle le moins privilégié capable de lire les journaux d'audit et de connexion, et c'est le bon à utiliser : il s'agit d'une tâche de reporting et elle ne devrait justifier pour personne la détention d'un rôle en écriture.

Le script

C'est tout. Il se connecte, construit une table de correspondance pour que la sortie affiche des noms de plans plutôt que des GUID, récupère chaque compte licencié avec son activité de connexion, le compare à votre seuil, et écrit un CSV.

# Inactive licensed users report
# Requires the Microsoft.Graph PowerShell SDK
Connect-MgGraph -Scopes "User.Read.All","AuditLog.Read.All","Organization.Read.All"
$InactiveDays = 60
$Cutoff = (Get-Date).AddDays(-$InactiveDays)
# Map SKU GUIDs to readable plan names
$SkuMap = @{}
Get-MgSubscribedSku | ForEach-Object { $SkuMap[$_.SkuId] = $_.SkuPartNumber }
# signInActivity is only returned when explicitly selected
$Users = Get-MgUser -All -Property "id,displayName,userPrincipalName,accountEnabled,userType,createdDateTime,assignedLicenses,signInActivity"
$Report = foreach ($u in $Users) {
    if (-not $u.AssignedLicenses -or $u.AssignedLicenses.Count -eq 0) { continue }
    if ($u.UserType -eq 'Guest') { continue }
    $last = $u.SignInActivity.LastSuccessfulSignInDateTime
    if (-not $last) { $last = $u.SignInActivity.LastSignInDateTime }
    if ($last -and $last -ge $Cutoff) { continue }
    [pscustomobject]@{
        DisplayName       = $u.DisplayName
        UserPrincipalName = $u.UserPrincipalName
        Enabled           = $u.AccountEnabled
        Created           = $u.CreatedDateTime
        LastSignIn        = $last
        DaysSinceSignIn   = if ($last) { [int](New-TimeSpan -Start $last -End (Get-Date)).TotalDays } else { 'Never' }
        Licences          = (($u.AssignedLicenses.SkuId | ForEach-Object { $SkuMap[$_] }) -join '; ')
    }
}
$Report | Sort-Object Created |
    Export-Csv -Path ".\inactive-licensed-users.csv" -NoTypeInformation -Encoding UTF8
"{0} licensed accounts with no sign-in in {1} days" -f @($Report).Count, $InactiveDays

Lisez-le une fois avant de l'exécuter. Il ignore les comptes sans licence, parce qu'un compte dormant sans licence ne coûte rien, et il ignore les invités, parce qu'ils suivent des règles différentes et domineraient sinon la sortie. Il privilégie la dernière connexion réussie et se rabat sur la dernière connexion interactive, de sorte qu'il se comporte correctement sur les comptes dont la connexion réussie précède le démarrage de ce champ en décembre 2023. Il compare les lignes au seuil dans PowerShell plutôt que dans la requête Graph : signInActivity peut être filtré côté serveur, mais pas en combinaison avec une autre propriété filtrable, donc le faire côté client garde les conditions de licence et d'invité au même endroit. Un compte qui ne s'est jamais connecté apparaît comme Never plutôt que comme une valeur vide, parce que c'est un constat différent de celui d'un compte dormant de longue date.

La boucle en quatre mouvements. Le script couvre les deux premiers. Les deux derniers relèvent du jugement, et c'est là que l'argent se déplace vraiment.

Ce que le rapport fait généralement remonter, et la décision que chaque cas exige

Presque chaque premier passage produit les quatre mêmes catégories, et chacune appelle une décision différente d'une personne différente.

Des partants jamais complètement traités. Un compte encore activé, encore licencié, parfois des mois après le départ de la personne. C'est le constat à la plus forte valeur et au plus faible risque, et c'est un échec de processus plutôt qu'un problème de licences. Retirez la licence, bloquez la connexion, et traitez correctement la boîte aux lettres et les fichiers. La checklist de départ Microsoft 365 détaille l'ordre complet des opérations, ce qui compte parce qu'effectuer ces étapes dans le mauvais ordre fait perdre des données.

Des comptes de service et des comptes partagés qui détiennent des sièges complets. Ils n'affichent souvent aucune connexion interactive alors qu'ils sont réellement utilisés par une application. N'y touchez pas tant que vous n'avez pas trouvé le propriétaire. Demandez ce dont le compte a réellement besoin, car un siège de productivité complet dépasse fréquemment de loin la réponse.

Des modules complémentaires en double ou qui se recoupent. Des comptes portant un module complémentaire qu'un plan de base plus récent inclut déjà. Le compte est actif, il n'apparaît donc jamais comme dormant, mais le module complémentaire est un poids mort. Trier votre sortie par la colonne des licences les fait ressortir rapidement.

De véritables personnes réellement inactives. Quelqu'un en congé longue durée, quelqu'un qui a changé de rôle, quelqu'un intégré sur un plan qu'il n'a jamais utilisé. C'est la catégorie qui exige un manager dans la conversation plutôt qu'un script, et l'issue est souvent un déclassement plutôt qu'un retrait. Notre guide sur Business Premium, E3 et E5 est une référence utile pour savoir ce qu'un plan plus léger coûte réellement à l'utilisateur.

Un avertissement avant d'agir sur tout cela. Vérifiez d'où vient chaque licence avant de la retirer à l'utilisateur. Les attributions basées sur des groupes sont gérées au niveau du groupe, et la page Licenses affiche le nom du groupe plutôt que ses membres, si bien qu'un siège que vous retirez d'un compte peut discrètement revenir par l'appartenance au groupe et l'économie n'atteint jamais la facture.

Si les quatre catégories ci-dessus vous semblent déjà familières, vous pouvez voir le même rapport tourner en continu sur votre propre tenant avec un essai EtherInsights de 14 jours, sans maintenance de script.

La limite honnête d'un simple script

Le script est bon. C'est aussi un instantané, et c'est là son plafond. Quelqu'un doit penser à l'exécuter. Quelqu'un doit l'interpréter, parce qu'un CSV de 40 comptes n'est pas une décision, c'est 40 décisions. Quelqu'un doit relancer les propriétaires, appliquer les changements, puis prouver à la finance que le nombre de sièges a réellement baissé au renouvellement, ce qui est encore un autre travail. Sautez deux mois et le parc a bougé sous vos pieds.

Ce n'est pas un argument contre le fait de le scripter. Exécutez-le cette semaine quoi qu'il arrive. C'est un argument pour rester lucide sur le quatrième mois, quand la personne qui l'a écrit est occupée et que le rapport cesse discrètement d'être lancé. Dans une organisation de 50 à 600 utilisateurs, la contrainte est rarement la capacité. C'est que personne n'est propriétaire de la tâche récurrente.

La place d'EtherInsights

EtherInsights est la version continue de ce que ce script fait une fois. Il rapproche les licences de l'activité réelle selon un calendrier, fait remonter les sièges dormants et jamais utilisés comme des constats assortis d'un propriétaire nommé et d'une action suivante plutôt que comme un tableur, et suit l'avant et l'après pour qu'une récupération arrive à votre revue financière comme une preuve plutôt que comme une affirmation. Pour l'ensemble du cycle de vie, depuis un siège dormant signalé jusqu'au départ d'un partant traité avant que sa licence ne soit refacturée, la gestion des licences Microsoft 365 et le départ des partants est le chemin qui boucle la boucle.

Cela coûte £0.79 par utilisateur actif avec un essai de 14 jours, vous pouvez donc appliquer l'arithmétique du début de cet article à votre propre tenant avant de vous engager. Si vous devez apporter un chiffre plutôt qu'une méthode à un détenteur de budget, c'est en général le moyen le plus rapide d'en obtenir un. Le gaspillage de licences n'est qu'une ligne dans un tableau plus large, c'est pourquoi il s'inscrit dans l'optimisation des coûts cloud, et pourquoi prouver l'économie compte autant que la trouver.

Fixez le seuil, exécutez le script, agissez immédiatement sur les partants, et posez une date dans le calendrier pour le mois prochain. Si cette date ne cesse de glisser, vous avez trouvé le vrai problème, et il n'est pas technique.

Découvrez la gestion des licences Microsoft 365 et le départ des partants pour voir les sièges dormants, le départ des partants et la piste de preuves traités comme un seul processus continu au lieu d'un rappel mensuel.