Inaktive M365 Benutzer finden mit Entra ID & PowerShell ⏱ 11 Min.

Inaktive M365 Benutzer finden mit Entra ID & PowerShell

Ein Konto, das seit Monaten niemand nutzt, bindet eine Lizenz und behält seine Gruppenmitgliedschaften samt Freigaben. Übernimmt ein Angreifer so ein Konto, meldet sich niemand beim Helpdesk, weil es den Benutzer nicht mehr gibt. Entra ID speichert pro Konto drei Anmeldezeitstempel.

Welchen du abfragst, entscheidet, ob dein Bericht die verwaisten Konten findet oder ausgerechnet die ältesten übersieht.

Voraussetzungen

Die Anmeldeaktivität steckt in der Graph-Eigenschaft signInActivity, und Microsoft koppelt sie an eine Premium-Lizenz. Fehlt im Tenant Microsoft Entra ID P1 oder P2, quittiert Graph die Abfrage mit dem Fehler Authentication_RequestFromNonPremiumTenantOrB2CTenant ("Neither tenant is B2C or tenant doesn't have premium license"). Dein Skript bricht damit ab, bevor es eine einzige Zeile schreibt.

P1 ist in Microsoft 365 Business Premium, E3, F1 und F3 enthalten, P2 in Microsoft 365 E5. Tenants mit Business Basic oder Business Standard ohne Zusatzlizenz greifen auf den Nutzungsbericht im Microsoft 365 Admin Center zurück (Abschnitt weiter unten).

Für den Bericht brauchst du keine Schreibrechte, deshalb reicht die Rolle Reports Reader. Sie ist die Rolle mit den geringsten Rechten, die Anmeldeberichte lesen darf.

  • Lizenz: Microsoft Entra ID P1 oder P2
  • Rolle: Reports Reader (Security Reader, Global Reader, Security Administrator und Global Administrator funktionieren ebenfalls)
  • Graph-Berechtigungen: User.Read.All und AuditLog.Read.All
  • Modul: Microsoft.Graph.Users (aktuell 2.40.0, veröffentlicht am 16.09.2026)

Entra schreibt die Zeitstempel gebündelt. Microsoft nennt bis zu 24 Stunden Verzögerung. Für eine 90-Tage-Auswertung spielt das keine Rolle, für die Kontrolle eines Offboardings am selben Tag schon.

Passend dazu: Entra ID | PIM-Rollen zeigt, wie du Admin-Rollen per PIM nur bei Bedarf aktivierst, damit auch die Leserolle für den Bericht nicht dauerhaft vergeben ist, M365 Entra ID | Group.Read.All vs. GroupMember.Read.All erklärt am Beispiel der Gruppen-Scopes, warum du bei Graph immer die kleinste passende Berechtigung anforderst.

Den richtigen Zeitstempel wählen

signInActivity enthält drei Datumswerte, die sich in einem Punkt unterscheiden: ob ein Fehlversuch zählt.

EigenschaftWas sie erfasstStolperfalle
lastSignInDateTimeletzter interaktiver Anmeldeversuch, erfolgreich oder fehlgeschlagenFehlversuche, etwa aus Password Spray, lassen ein ungenutztes Konto aktiv aussehen
lastNonInteractiveSignInDateTimeletzter Versuch eines Clients, sich im Namen des Benutzers anzumelden (Token-Erneuerung), erfolgreich oder fehlgeschlagennur zusammen mit dem interaktiven Wert aussagekräftig
lastSuccessfulSignInDateTimeletzte erfolgreiche Anmeldung, interaktiv oder nicht interaktiverst seit 01.12.2023 befüllt, ältere Anmeldungen hat Microsoft nicht nachgetragen

Für einen Inaktivitätsbericht zählt lastSuccessfulSignInDateTime, weil nur dieser Wert belegt, dass jemand das Konto erfolgreich benutzt hat. Die Einschränkung steckt im Startdatum.

Ein leerer Wert bedeutet deshalb nicht "unbekannt", sondern: Seit dem 1. Dezember 2023, also seit fast drei Jahren, gab es keine erfolgreiche Anmeldung, oder es gab nie eine. Wer leere Werte aus dem Bericht filtert, wirft genau die Konten hinaus, die am längsten brachliegen.

Passend dazu: Microsoft 365 EntraID | Überwachung und Integrität führt durch die Anmeldeprotokolle, in denen du die einzelnen Fehlversuche hinter einem verdächtig frischen lastSignInDateTime nachverfolgst.

Entra Admin Center Filter nutzen

Melde dich mit der Rolle Reports Reader am Microsoft Entra Admin Center an und öffne Entra ID > Users. Über Manage view > Edit columns fügst du mit + Add column die Spalte Last interactive sign-in time hinzu und speicherst die Ansicht.

Danach setzt du über Add filter einen Filter auf Last interactive sign-in time, wählst den Operator <= (less than or equal to) und als Datum einen Stichtag in der Vergangenheit. Für 90 Tage Inaktivität ist das der Tag heute vor 90 Tagen. Ein Datum in der Zukunft liefert dagegen jedes Konto, das sich je angemeldet hat, weil jede Anmeldung vor diesem Datum liegt. Microsoft empfiehlt eine Schwelle von 90 bis 180 Tagen, abhängig von der Organisation.

Der Portalweg hat zwei Schwächen. Er vergleicht nur den interaktiven Zeitstempel, zählt also Fehlversuche mit und meldet Benutzer als inaktiv, die seit Wochen nur über im Hintergrund erneuerte Tokens arbeiten. Und Konten ohne jeden Zeitstempel bieten dem Datumsfilter keinen Vergleichswert, sie prüfst du separat.

Für den Export gibt es zwei Varianten. Der klassische Button Download users schreibt einen festen Attributsatz ohne Anmeldedaten in die CSV. Erst die Vorschau-Variante Download users (Preview) übernimmt die Spalten deiner Ansicht und damit auch die Anmeldezeitstempel. Für einen Bericht, den du regelmäßig ziehst und archivierst, nimmst du trotzdem PowerShell, weil nur dort der Erfolgsstempel und eine reproduzierbare Logik zusammenkommen.

Konto per PowerShell prüfen

Beim Verbinden forderst du beide Scopes an. Ohne AuditLog.Read.All liefert Graph die Anmeldeaktivität nicht aus.

Connect-MgGraph -Scopes 'User.Read.All','AuditLog.Read.All'

Für ein einzelnes Konto löst Graph signInActivity nur über die Objekt-ID auf. Übergibst du den UPN an -UserId und fragst die Anmeldeaktivität mit an, quittiert Graph das mit "Get By Key only supports UserId and the key has to be a valid Guid". Deshalb holst du zuerst die ID:

$user = Get-MgUser -UserId 'max.mustermann@contoso.de'
Get-MgUser -UserId $user.Id -Property id,displayName,signInActivity |
    Select-Object -ExpandProperty SignInActivity |
    Format-List Last*DateTime

Die Ausgabe zeigt alle drei Zeitstempel in UTC. Liegt LastSignInDateTime deutlich nach LastSuccessfulSignInDateTime, gab es danach nur noch Fehlversuche.

Alle inaktiven Konten per Skript ermitteln

Das Skript bewertet jedes Konto nach lastSuccessfulSignInDateTime. Konten ohne diesen Wert bleiben im Bericht und bekommen einen eigenen Status. KeinErfolgSeit2023 bekommt ein Konto mit Anmeldeversuchen, aber ohne erfolgreiche Anmeldung seit Beginn der Erfassung. NieAngemeldet bekommt ein Konto ganz ohne Zeitstempel.

Konten, die jünger als die Schwelle sind, überspringt das Skript, weil sonst jeder Neuzugang, der erst nächste Woche anfängt, als NieAngemeldet im Bericht steht. Microsoft nutzt eine vergleichbare Schonfrist bei den Access Reviews für inaktive Gäste und nimmt dort frisch angelegte Konten aus. Die Spalte VersuchNachStichtag markiert Konten mit Anmeldeversuchen nach dem Stichtag, aber ohne Erfolg. Auf einem Konto, das niemand mehr nutzt, deutet das auf Password Spray oder auf ein vergessenes Gerät mit altem Kennwort hin.

Die Seitengröße steht auf 500, weil Graph beim Abruf von signInActivity maximal 500 Objekte pro Seite liefert. Gegenüber dem Standard von 100 braucht ein Tenant mit 10.000 Konten damit 20 statt 100 Anfragen.

#Requires -Modules Microsoft.Graph.Users
# Voraussetzung: Connect-MgGraph -Scopes 'User.Read.All','AuditLog.Read.All'
param(
    [int]$Tage = 90,
    [string]$Pfad = '.\inaktive-konten.csv'
)

# Entra liefert alle Anmeldezeitstempel in UTC, deshalb rechnet der Stichtag ebenfalls in UTC
$stichtag = (Get-Date).ToUniversalTime().AddDays(-$Tage)

$eigenschaften = 'id', 'displayName', 'userPrincipalName', 'userType', 'accountEnabled',
                 'createdDateTime', 'onPremisesSyncEnabled', 'assignedLicenses', 'signInActivity'

# 500 ist die maximale Seitengröße, sobald signInActivity abgefragt wird
$benutzer = Get-MgUser -All -PageSize 500 -Property $eigenschaften

$ergebnis = foreach ($u in $benutzer) {

    # Neue Konten bekommen eine Schonfrist, sonst erscheint jeder Neuzugang als NieAngemeldet
    if ($u.CreatedDateTime -and $u.CreatedDateTime -gt $stichtag) { continue }

    $aktivitaet = $u.SignInActivity
    $erfolg     = $aktivitaet.LastSuccessfulSignInDateTime

    # Jüngster Anmeldeversuch, interaktiv oder nicht, erfolgreich oder fehlgeschlagen
    $versuch = @($aktivitaet.LastSignInDateTime, $aktivitaet.LastNonInteractiveSignInDateTime) |
        Where-Object { $_ } | Sort-Object -Descending | Select-Object -First 1

    if ($erfolg) {
        if ($erfolg -ge $stichtag) { continue }   # aktiv, nicht berichten
        $status = 'Inaktiv'
    }
    elseif ($versuch) {
        # Versuche vorhanden, aber kein Erfolg seit Beginn der Erfassung am 01.12.2023
        $status = 'KeinErfolgSeit2023'
    }
    else {
        $status = 'NieAngemeldet'
    }

    [pscustomobject]@{
        Anzeigename         = $u.DisplayName
        UPN                 = $u.UserPrincipalName
        Typ                 = $u.UserType
        Aktiviert           = $u.AccountEnabled
        Synchronisiert      = [bool]$u.OnPremisesSyncEnabled
        Lizenziert          = (($u.AssignedLicenses | Measure-Object).Count -gt 0)
        Status              = $status
        LetzterErfolg       = $erfolg
        LetzterVersuch      = $versuch
        VersuchNachStichtag = [bool]($versuch -and $versuch -ge $stichtag)
        Angelegt            = $u.CreatedDateTime
    }
}

$ergebnis = @($ergebnis)
Write-Host ("{0} Konten geprüft, {1} ohne erfolgreiche Anmeldung seit {2:yyyy-MM-dd}" -f
    @($benutzer).Count, $ergebnis.Count, $stichtag) -ForegroundColor Cyan

# Semikolon und UTF-8 mit BOM, damit Excel mit deutschen Regionseinstellungen Spalten und Umlaute sauber einliest
# PowerShell 7: utf8BOM, Windows PowerShell 5.1: UTF8
$ergebnis | Sort-Object Status, LetzterErfolg |
    Export-Csv -Path $Pfad -NoTypeInformation -Delimiter ';' -Encoding utf8BOM

Speichere das Skript als Get-InaktiveKonten.ps1 und rufe es mit der gewünschten Schwelle auf:

.\Get-InaktiveKonten.ps1 -Tage 180 -Pfad .\inaktive-konten-180.csv

Treffer einordnen

Die CSV liefert Kandidaten, gelöscht wird erst nach Prüfung. Vier Spalten entscheiden, wie du mit einem Treffer umgehst.

Typ = Guest gehört in den Gastprozess, weil Gäste über Einladung und Access Reviews verwaltet werden und nicht über das Offboarding der Personalabteilung. Synchronisiert = True deaktivierst du im lokalen Active Directory. Objekte aus der Verzeichnissynchronisierung lassen sich in Entra ID nicht direkt verwalten, die Änderung muss aus dem AD DS kommen und per Sync nach oben laufen.

Aktiviert = True mit Status NieAngemeldet trifft häufig die Benutzerkonten hinter Shared Mailboxes. Microsoft verlangt, die Anmeldung für diese Konten zu blockieren und blockiert zu lassen. Ein solcher Treffer ist deshalb ein eigener Befund, unabhängig von der Inaktivität. Notfallkonten (Break Glass) melden sich planmäßig selten an und landen zuverlässig im Bericht. Nimm sie namentlich aus jeder automatischen Sperre heraus.

Für Cloud-Konten sperrst du zuerst die Anmeldung und widerrufst danach die Refresh Tokens. Genau diese Kombination beschreibt Microsoft für den Entzug von Zugriffen: Anschließend bekommt das Konto keine neuen Tokens mehr, bereits ausgestellte Access Tokens laufen nach standardmäßig einer Stunde ab. Bis der Widerruf greift, können einige Minuten vergehen.

Connect-MgGraph -Scopes 'User.Read.All','User.EnableDisableAccount.All','User.RevokeSessions.All'
Update-MgUser -UserId $user.Id -AccountEnabled:$false
Revoke-MgUserSignInSession -UserId $user.Id

Erst nach einer Wartefrist, in der sich niemand beschwert, entfernst du Lizenzen und löschst das Konto. Gelöschte Benutzer liegen 30 Tage im Papierkorb von Entra ID und lassen sich samt Lizenzen wiederherstellen, danach ist das Konto endgültig weg.

Passend dazu: M365 EntraID | Gastkonten per PowerShell aufräumen übernimmt die Treffer mit Typ Guest und räumt sie per Graph-Skript auf, Microsoft Entra ID | Neuer Revoke Sessions Button ab Februar 2026 zeigt den Widerruf der Sitzungen im Portal, wenn du einzelne Konten ohne PowerShell sperrst.

Ohne Entra ID P1: Nutzungsbericht im Microsoft 365 Admin Center

Ohne P1 bleibt dir der Nutzungsbericht. Öffne im Microsoft 365 Admin Center Reports > Usage, wähle unter Reports Microsoft 365 apps und dann den Tab Active users. Die Tabelle zeigt pro Benutzer das letzte Aktivitätsdatum für Exchange, OneDrive, SharePoint, Viva Engage und Microsoft Teams, auswählbar für 7, 30, 90 oder 180 Tage. Die Rolle Reports Reader reicht auch hier.

Der Bericht misst die Nutzung der Dienste statt der Anmeldungen und hinkt 24 bis 48 Stunden hinterher. Mehr als 180 Tage Rückblick gibt er nicht her. Seit dem 1. September 2021 blendet Microsoft Benutzernamen in diesen Berichten standardmäßig anonymisiert ein. Die Klarnamen schaltet ein Global Administrator unter Settings > Org settings > Services > Reports frei, indem er Conceal user, group, and site names in all reports deaktiviert. Weil der Bericht dann Nutzungsdaten einzelner Beschäftigter zeigt, klärst du das vorher mit Datenschutz und Betriebsrat.

Passend dazu: Datenschutz in Microsoft Entra ID ordnet die Datenschutzeinstellungen im Tenant ein, die du kennen solltest, bevor du personenbezogene Anmelde- und Nutzungsdaten auswertest.

Suche automatisieren

Ein Bericht, den du einmal im Jahr ziehst, findet Konten, die elf Monate offen standen. Mit Microsoft Entra ID Governance oder der Entra Suite übernehmen Lifecycle Workflows diese Arbeit: Der Trigger Sign-in inactivity wertet lastSuccessfulSignInDateTime aus und startet nach der eingestellten Zahl inaktiver Tage die hinterlegten Aufgaben, etwa Send email about user inactivity, Disable user account oder Revoke all refresh tokens for user.

Für Gäste bietet dieselbe Lizenz unter ID Governance > Dashboard > Guest access governance eine Übersicht inaktiver Gäste. Access Reviews können inaktive Gäste zunächst 30 Tage blockieren und danach aus dem Tenant entfernen.

Ohne Governance-Lizenz planst du das Skript als monatliche Aufgabe mit App-Registrierung und Zertifikat. Die Application Permissions User.Read.All und AuditLog.Read.All reichen für den Lesezugriff.

Passend dazu: PowerShell SharePoint Online | App-Only Authentication zeigt eine App-Registrierung mit Zertifikat Schritt für Schritt, und dasselbe Muster brauchst du für den unbeaufsichtigten Lauf des Inaktivitätsskripts.

Fazit

Inaktive Konten findest du in Entra ID zuverlässig, wenn du lastSuccessfulSignInDateTime abfragst und die Lücken dieses Werts kennst. Der Filter im Admin Center ist für einen schnellen Blick brauchbar, arbeitet aber mit dem interaktiven Zeitstempel. Der zählt Fehlversuche mit und blendet Benutzer aus, die nur über Token-Erneuerung aktiv sind. Ein Konto, auf das ein Angreifer per Password Spray zielt, sieht im Portal deshalb aktiver aus als ein Konto, dessen Besitzer nur noch über erneuerte Tokens arbeitet.

Der gefährlichste Fehler in Inaktivitätsskripten ist das stille Aussortieren leerer Werte. Weil Microsoft den Erfolgsstempel erst seit Dezember 2023 erfasst, markieren leere Werte die Konten mit dem längsten Stillstand: ohne erfolgreiche Anmeldung seit fast drei Jahren oder ganz ohne Anmeldung. Wer sie filtert, erzeugt einen Bericht, der sauber aussieht und die riskantesten Konten verschweigt.

Beim Aufräumen zählt die Reihenfolge. Sperren und Sitzungen widerrufen schließt die Angriffsfläche sofort, Lizenz entfernen und Löschen folgen nach einer Wartefrist, weil das 30-Tage-Fenster für die Wiederherstellung sonst schnell verstreicht. Synchronisierte Konten bearbeitest du im lokalen AD, Notfallkonten nimmst du aus jeder Automatik heraus.

Die Lizenzfrage bleibt der Haken. Ohne P1 liefert Graph keine Anmeldeaktivität, und der Nutzungsbericht ersetzt sie nur grob, weil er Dienstnutzung statt Anmeldungen misst und nach 180 Tagen endet. Wer Business Standard betreibt, sollte das beim nächsten Lizenzgespräch als Argument für Business Premium oder für P1 als Einzellizenz mitnehmen. Und wer Governance-Lizenzen hat, sollte die Suche einem Lifecycle Workflow übergeben, weil ein Workflow nicht vergisst, den Bericht zu ziehen.

Weiterlesen: M365 Entra ID | Warum P1 oder P2 für Sicherheit sorgen liefert die Argumente für das Lizenzgespräch, weil die Anmeldeaktivität nur eine von mehreren Sicherheitsfunktionen ist, die an P1 hängen.


Teilen:
Noch keine Kommentare

Sei der Erste und starte die Diskussion mit einem hilfreichen Beitrag.

Kommentar hinterlassen

Dein Beitrag wird vor der Veröffentlichung kurz geprüft — fachlich, respektvoll und auf den Punkt ist hier genau richtig.

E-Mail Adresse wird nicht veröffentlicht.