För att hitta inaktiva Microsoft 365-användare kopplar du ihop två saker som Microsoft håller på separata platser: licenserna som är tilldelade varje konto, och datumet då kontot senast loggade in med lyckat resultat. Microsoft Graph håller båda på användarobjektet, så ett enda Get-MgUser-anrop som väljer assignedLicenses och signInActivity ger dig en rad per licensierat konto med dess senaste inloggningsdatum. Ställ raderna mot en gräns som ditt team kommer överens om i förväg, vanligtvis 30, 60 eller 90 dagar, och varje konto ovanför strecket är en betald plats utan några belägg för användning bakom sig. Skriptet nedan gör precis det och skriver en CSV som du kan ta med in i en genomgång. Kopiera det, kör det, behåll det.
Det här är inloggningssidan av licensslöseri. Den kompletterande guiden om hur du hittar oanvända Microsoft 365-licenser täcker den andra sidan, glappet mellan vad du har köpt och vad du har tilldelat. Det glappet är det enkla. Det här, platsen som är tilldelad ett riktigt konto som ingen använder, är vanligtvis det större talet och det svårare samtalet.
Börja med räknestycket, för det avgör om detta är värt en eftermiddag
Gör summan först, med dina egna siffror snarare än någon annans jämförelsetal. I en miljö med 300 användare, om 4 procent av de licensierade kontona inte har loggat in på ett kvartal, är det 12 platser. Tolv platser gånger vad du faktiskt betalar per användare och månad, gånger tolv månader, är årssiffran. Vid £20 per plats är det £2,880 om året; vid £30 blir det £4,320. Ändra någon indata och svaret rör sig, vilket är hela poängen: du kan kontrollera detta i huvudet och det kan din ekonomiansvarige också.
Två saker gör det värt att jaga i en organisation med 50 till 600 användare. Det återkommer varje månad tills någon agerar, så att inte göra något ackumuleras. Och det är nästan aldrig någons uppgift. Ekonomi ser en stabil faktura och antar att den stämmer; IT ser ett licensantal och antar att ekonomi godkände det. Så överlever vilande platser i åratal.
Varför sidan Licenses inte kan svara på detta
Det självklara första stoppet är administrationscentret för Microsoft 365, under Billing och sedan Licenses, som visar varje produkt med hur många platser du har köpt och hur många som är tilldelade. Det är en användbar tvåminuterskontroll och du bör göra den. Observera att den aggregerar per produkt, medan Billing och sedan Your products listar en rad per prenumeration, så de två sidorna kommer inte alltid att stämma överens vid en snabb blick.
Vad ingen av dem berättar för dig är om en tilldelad plats gör något. De sidorna är byggda kring behörighet, inte kring beteende. Ett konto som skapades för en konsult för två år sedan, fortfarande aktiverat, fortfarande med en full plats, och utan inloggning sedan dess, ser identiskt ut där jämfört med din flitigaste användare.
Reports och sedan Usage tar dig närmare. Rapporten Active users visar ett datum för senaste aktivitet per arbetsbelastning för Exchange, OneDrive, SharePoint och Teams, tillsammans med de tilldelade licenserna. Den är värd att känna till, och den besvarar en annan fråga: om någon rörde en app, inte om identiteten autentiserade över huvud taget. Två saker att vänta sig. Data är i allmänhet tillgängliga över de senaste 7, 30, 90 och 180 dagarna och kan ta ett dygn eller mer att dyka upp. Och användarnamn döljs som standard, så om rapporten visar anonymiserade rader måste en administratör avmarkera alternativet "Conceal user, group, and site names in all reports" under Settings, Org Settings, Services, Reports.
Kom överens om vad inaktiv betyder innan du kör något
Det här är steget som team hoppar över, och det avgör om din rapport överlever kontakten med resten av verksamheten. Trettio dagar är aggressivt och sveper med föräldraledighet, långtidssjukskrivning, tjänstledighet och säsongsanställda. Nittio dagar låter ett helt kvartals slöseri löpa innan något flaggas. Sextio är en rimlig första omgång i en medelstor miljö. Det finns inget rätt svar, och den som säger att det gör det, leverantörer inräknade, säljer dig sitt eget standardvärde.
Det som spelar roll är att gränsen är en nedskriven policy som ditt team äger, med namngivna undantag, innan den första rapporten går till någon. En rapport utan en överenskommen tröskel skapar en diskussion. En rapport med en tröskel skapar åtgärder.
Inloggningsdata bakom svaret
Microsoft Entra ID registrerar inloggningsaktivitet mot varje användarobjekt, och Graph exponerar den på v1.0-användarresursen som egenskapen signInActivity. Den bär en senaste interaktiv inloggning, en senaste icke-interaktiv inloggning, och en senaste lyckad inloggning, var och en parad med ett begärande-ID. Interaktiv betyder att en person skrev in autentiseringsuppgifter eller godkände en prompt. Icke-interaktiv täcker en klient som uppdaterar en token i bakgrunden, vilket är varför ett konto kan se aktivt ut i ett fält och dött i ett annat.
Skälet att använda detta i stället för att gräva i inloggningsloggarna är lagringstid. Inloggnings- och granskningsloggar sparas i 7 dagar på den kostnadsfria nivån och 30 dagar med Entra ID P1 eller P2, så loggen ensam kan inte berätta något om ett glapp på 90 dagar. Egenskapen signInActivity är annorlunda: Entra lagrar den så länge användarobjektet finns. Det är rätt källa för den här frågan.
Fyra praktiska begränsningar innan du kör något:
- Den returneras bara när du uttryckligen väljer den. Ett vanligt
Get-MgUsertar inte med den, och egenskapen saknas helt enkelt i stället för att ge ett fel, vilket är det vanligaste skälet till att folk drar slutsatsen att data saknas. - Att läsa den kräver
AuditLog.Read.Allvid sidan avUser.Read.All. Utan granskningsscopen returneras licensdata medan inloggningsfältet kommer tillbaka tomt, vilket ser ut som att varje konto är inaktivt. - Den kräver Microsoft Entra ID P1 eller P2. I en miljö utan någon av dem, räkna med att egenskapen är otillgänglig, så kontrollera det innan du felsöker en tom kolumn.
- Ett tomt värde är inget bevis på att kontot inte används. Senaste lyckade inloggning har bara registrerats sedan december 2023 och fylldes inte i retroaktivt, och värdet kan släpa efter med upp till 24 timmar. Behandla tomt som "undersök", aldrig som "ta bort".
På katalogsidan behöver du inte Global Administrator. Reports Reader är den roll med minst behörighet som kan läsa gransknings- och inloggningsloggar, och det är den rätta att använda: det här är en rapporteringsuppgift och bör inte vara ett skäl för någon att inneha en skrivroll.
Skriptet
Det här är hela saken. Det ansluter, bygger en uppslagstabell så att utdata läses i abonnemangsnamn snarare än GUID:er, hämtar varje licensierat konto med dess inloggningsaktivitet, ställer det mot din gräns, och skriver en 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
Läs det en gång innan du kör det. Det hoppar över olicensierade konton, eftersom ett vilande olicensierat konto inte kostar något, och det hoppar över gäster, eftersom de följer andra regler och annars skulle dominera utdata. Det föredrar den senaste lyckade inloggningen och faller tillbaka på den senaste interaktiva inloggningen, så att det beter sig förnuftigt på konton vars lyckade inloggning ligger före startpunkten för det fältet i december 2023. Det åldrar raderna i PowerShell snarare än i Graph-frågan: signInActivity kan filtreras på serversidan, men inte i kombination med någon annan filtrerbar egenskap, så att göra det på klientsidan håller licens- och gästvillkoren på samma ställe. Ett konto som aldrig har loggat in visas som Never snarare än som tomt, eftersom det är ett annat fynd än ett långvarigt vilande konto.
Loopen i fyra drag. Skriptet täcker de två första. De två sista är omdöme, och det är där pengarna faktiskt rör sig.
Vad rapporten vanligtvis visar, och beslutet som var och en kräver
Nästan varje första körning ger samma fyra kategorier, och var och en behöver ett annat beslut från en annan person.
Avgångna medarbetare som aldrig avregistrerades fullt ut. Ett konto som fortfarande är aktiverat, fortfarande licensierat, ibland månader efter att personen slutade. Det här är fyndet med högst värde och lägst risk, och det är ett processfel snarare än ett licensieringsfel. Ta bort licensen, blockera inloggning, och hantera postlådan och filerna ordentligt. Checklistan för avregistrering i Microsoft 365 har hela ordningsföljden, vilket spelar roll eftersom att göra stegen i fel ordning innebär att data går förlorade.
Tjänste- och delade konton som håller fulla platser. De visar ofta ingen interaktiv inloggning alls samtidigt som de verkligen används av en applikation. Rör dem inte förrän du har hittat ägaren. Fråga vad kontot faktiskt behöver, eftersom en full produktivitetsplats ofta är långt mer än svaret.
Dubbletter eller överlappande tillägg. Konton som bär ett tillägg som ett senare basabonnemang redan innehåller. Kontot är aktivt, så det dyker aldrig upp som vilande, men tillägget är dödvikt. Att sortera dina utdata på licenskolumnen lyfter fram dessa snabbt.
Verkligt sysslolösa riktiga människor. Någon som är långtidsledig, någon som bytt roll, någon som introducerades på ett abonnemang de aldrig använde. Det här är kategorin som behöver en chef i samtalet snarare än ett skript, och utfallet är ofta en nedgradering snarare än ett borttagande. Vår guide till Business Premium, E3 och E5 är en användbar referens för vad ett lättare abonnemang faktiskt kostar användaren.
En varning innan du agerar på något av det. Kontrollera var varje licens kom ifrån innan du drar av den från användaren. Gruppbaserade tilldelningar hanteras vid gruppen, och sidan Licenses visar gruppnamnet snarare än dess medlemmar, så en plats du tar bort från ett konto kan tyst komma tillbaka via gruppmedlemskap och besparingen når aldrig fakturan.
Om de fyra kategorierna ovan redan känns bekanta kan du se samma rapport köra kontinuerligt mot din egen tenant med en 14-dagars provperiod av EtherInsights, utan något skriptunderhåll.
Den ärliga gränsen för att köra detta som ett skript
Skriptet är bra. Det är också en ögonblicksbild, och det är dess tak. Någon måste komma ihåg att köra det. Någon måste tolka det, eftersom en CSV med 40 konton inte är ett beslut, det är 40 beslut. Någon måste jaga ägarna, tillämpa ändringarna, och sedan bevisa för ekonomi att platsantalet faktiskt sjönk vid förnyelsen, vilket är ett eget jobb igen. Missa två månader och miljön har rört sig under dig.
Det är inget argument mot att skripta det. Kör det den här veckan oavsett. Det är ett argument för att vara klarögd inför månad fyra, när personen som skrev det har fullt upp och rapporten tyst slutar köras. I en organisation med 50 till 600 användare är begränsningen sällan förmåga. Det är att ingen äger den återkommande uppgiften.
Var EtherInsights passar in
EtherInsights är den kontinuerliga versionen av det som skriptet gör en gång. Det stämmer av licenser mot verklig aktivitet enligt ett schema, lyfter fram vilande och aldrig använda platser som fynd med en namngiven ägare och en nästa åtgärd snarare än som ett kalkylblad, och följer före och efter så att en återtagning når din ekonomigenomgång som bevis snarare än som ett påstående. För hela livscykeln, från att en vilande plats flaggas till att en avgången medarbetare avregistreras innan licensen faktureras igen, är licenshantering och avregistrering för Microsoft 365 vägen som sluter loopen.
Det kostar £0.79 per aktiv användare med en provperiod på 14 dagar, så du kan köra räknestycket högst upp i den här artikeln mot din egen tenant innan du binder dig till något. Om du behöver ge en budgetansvarig ett tal snarare än en metod är det vanligtvis den snabbaste vägen till ett. Licensslöseri är en rad i en bredare bild, vilket är varför det sitter inuti kostnadsoptimering i molnet, och varför att bevisa besparingen spelar lika stor roll som att hitta den.
Kom överens om tröskeln, kör skriptet, agera på de avgångna omedelbart, och sätt ett datum i kalendern för nästa månad. Om det datumet fortsätter att glida har du hittat det verkliga problemet, och det är inte ett tekniskt.
Utforska licenshantering och avregistrering för Microsoft 365 för att se vilande platser, avregistrering av avgångna medarbetare och bevisspåret hanteras som en enda kontinuerlig process i stället för en månatlig påminnelse.
