M365 EntraID | Gastkonten per PowerShell aufräumen ⏱ 9 Min.

M365 EntraID | Gastkonten per PowerShell aufräumen

M365 Gastkonten automatisiert löschen

Gastzugänge in Microsoft 365 wachsen in den meisten Tenants organisch und völlig unkontrolliert. Ein Klick in Microsoft Teams oder SharePoint reicht aus und ein externer Partner erhält ein Gastkonto im eigenen Entra ID. Das eigentliche Problem entsteht nach Projektabschluss. Die Zusammenarbeit endet, die Konten bleiben aktiv.

Ein kompromittierter Partner-Tenant bedeutet bei aktiven Alt-Zugängen direkten Zugriff auf deine Unternehmensdaten. Offene Gastkonten ohne strukturiertes Lifecycle-Management sind ein vermeidbares, aber häufig ignoriertes Sicherheitsrisiko. Angreifer nutzen diese sogenannten Standing Privileges für laterale Bewegungen. Wenn das Heimatkonto eines Gasts durch Phishing übernommen wird, hat der Angreifer einen gültigen Token für deinen Tenant. Deine lokalen MFA-Richtlinien greifen oft ins Leere, sofern keine strikten Cross-Tenant Access Policies konfiguriert sind. Die einzige effektive Verteidigungslinie nach innen: das rigorose Löschen verwaister Konten.

Die Architektur der B2B-Kollaboration

Wie funktioniert die B2B-Zusammenarbeit technisch? Entra ID legt für jeden eingeladenen externen Benutzer ein Stub-Objekt mit dem Attribut UserType "Guest" im lokalen Verzeichnis an. Die primäre Authentifizierung findet jedoch im Heimat-Tenant des Gasts statt. Entra ID stellt nach erfolgreichem Login im Quell-Tenant ein Ticket für den Ziel-Tenant aus.

Du hast als Administrator des Ziel-Tenants keine direkte Kontrolle über die Passwortkomplexität oder die Session-Lifetime im Heimat-Tenant. Eine direkte Folge: Sicherheitslücken bei deinen Partnern werden zu deinen eigenen. Je länger ein ungenutztes Gastkonto existiert, desto höher ist die Wahrscheinlichkeit, dass es durch Fluktuation beim Partnerunternehmen verwaist und angreifbar wird.

Inaktivität messen: Die SignInActivity-Eigenschaft

Um Gastkonten datenbasiert aufzuräumen, benötigst du einen verlässlichen Indikator für Inaktivität. Microsoft Graph bietet hierfür die Eigenschaft signInActivity. Dieser Datensatz speichert den Zeitstempel der letzten Anmeldung (lastSignInDateTime).

Dabei gibt es einen technischen Fallstrick. Wenn ein Gast die Einladung per E-Mail erhält, diese aber nie annimmt, ist der Wert für lastSignInDateTime leer. Das Konto ist also eine reine Karteileiche. Ein Bereinigungsskript muss zwingend das Erstellungsdatum (createdDateTime) als Fallback prüfen. Liegt beides außerhalb unseres definierten Zeitfensters, ist das Konto ein Kandidat für die Löschung. Für den Zugriff auf diese speziellen Audit-Daten benötigt PowerShell erweiterte Rechte. Ein reines Leserecht für Benutzerobjekte reicht nicht aus.

Ein zweiter Fallstrick betrifft nicht die Berechtigung, sondern die Lizenz. signInActivity liefert nur dann Werte, wenn dein Tenant über eine Entra ID P1 oder P2 Lizenz verfügt. Fehlt sie, bleibt das Feld bei jedem Benutzer leer, unabhängig von den vergebenen Graph-Rechten. Das Skript würde dann bei jedem Gastkonto, das älter als 90 Tage ist, auf das Erstellungsdatum zurückfallen, egal wie aktiv der Gast tatsächlich noch arbeitet. Ohne diese Lizenz drohst du also, aktive Partnerkonten zu löschen, nur weil sie älter als dein Schwellenwert sind. Prüfe die Lizenzlage deines Tenants deshalb, bevor du dich auf die Ergebnisse verlässt.

Der schnelle Check: Ad-hoc-Verbindung per Delegated Scopes

Für eine manuelle, einmalige Bereinigung nutzt du den interaktiven Login. Das Microsoft Graph PowerShell SDK erlaubt den Verbindungsaufbau mit der direkten Anforderung von Berechtigungen, den sogenannten Scopes. Ein typischer Aufruf für einen administrativen Check sieht so aus:

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

Beim Aufruf öffnet sich ein Browser-Fenster. Du meldest dich mit deinem Admin-Konto an und stimmst den angeforderten Rechten zu. Dieser Ansatz ist praktisch für die Fehleranalyse zwischendurch. Für eine regelmäßige, automatisierte Ausführung eignet sich diese Methode jedoch nicht. Ein geplanter Task auf einem Server kann keinen interaktiven MFA-Prompt bedienen. Der Skript-Lauf bricht unweigerlich ab.

Automatisierung richtig umsetzen: Die App-Registrierung

Um den Bereinigungsprozess als unbeaufsichtigten Hintergrund-Job auszuführen, benötigt Entra ID eine maschinelle Identität. Du löst dieses Architektur-Problem über eine App-Registrierung.

Die App-Registrierung bündelt die Authentifizierung und die exakten API-Rechte. Das Skript fordert beim Login keine dynamischen Rechte mehr über Parameter an. Es authentifiziert sich als Applikation. Microsoft Graph liest die fest hinterlegten Rechte der Applikation im Entra ID aus und stellt den passenden Access Token aus.

Navigiere im Azure Portal zu den App-Registrierungen und lege einen neuen Eintrag an.

Unter dem Menüpunkt API-Berechtigungen wählst du Microsoft Graph. Da kein menschlicher Benutzer vor dem Bildschirm sitzt, zwingt uns das OAuth-Protokoll zur Nutzung von Anwendungsberechtigungen (Application Permissions). Wähle hier User.Read.All sowie AuditLog.Read.All. Ein Administrator muss diese sensiblen Rechte im Anschluss über den Button für die Admin-Zustimmung mandantenweit freigeben.

Die App-Registrierung ist eingerichtet. Jetzt benötigt das Skript einen Schlüssel, um sich als exakt diese App auszuweisen. Erstelle unter Zertifikate & Geheimnisse einen neuen geheimen Mandantenschlüssel (Client Secret).

Kopiere den Wert direkt in deinen Passwort-Manager. Das Portal blendet diesen String nach dem Neuladen der Seite unwiderruflich aus. Notiere dir zusätzlich die Anwendungs-ID (Client-ID) und die Verzeichnis-ID (Tenant-ID) aus der Übersicht der App.

How-To: Verwaiste Gastkonten per App-Only PowerShell entfernen

Der Aufruf von Connect-MgGraph ändert sich durch den App-Only-Flow. Die Cmdlet-Signatur verlangt Client-ID und Client Secret nicht als einzelne Parameter, sondern gekapselt in einem PSCredential-Objekt: Die Client-ID wandert ins Benutzername-Feld, das Secret als SecureString ins Passwort-Feld. Das Skript läuft ab jetzt komplett ohne interaktive Prompts. Aus diesem Grund verzichtet der angepasste Code auf die manuelle Bestätigungsabfrage per Konsoleneingabe. Der Job liest, filtert und löscht in einem vollautomatisierten Durchlauf. Das Ziel des Workflows: Alle Gastkonten auslesen, prüfen, ob sie älter als 90 Tage sind und seit 90 Tagen keinen Login mehr verzeichnet haben. Anschließend generiert das Skript einen CSV-Report zur Dokumentation und entfernt die betroffenen Konten aus dem Verzeichnis.

Install-Module -Name Microsoft.Graph.Users -Scope CurrentUser -Force
Install-Module -Name Microsoft.Graph.Authentication -Scope CurrentUser -Force
#Requires -Modules Microsoft.Graph.Authentication, Microsoft.Graph.Users

# App-Registrierungsdaten
$TenantId = "deine-tenant-id"
$ClientId = "deine-app-id"
$ClientSecret = "dein-app-secret-wert"

# Secret in einen SecureString konvertieren und als PSCredential kapseln
$SecureSecret = ConvertTo-SecureString -String $ClientSecret -AsPlainText -Force
$ClientSecretCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $ClientId, $SecureSecret

# Benötigt Entra ID P1 Lizenz im Tenant für das Auslesen der SignInActivity
try {
    Import-Module Microsoft.Graph.Authentication -ErrorAction Stop
    Import-Module Microsoft.Graph.Users -ErrorAction Stop
}
catch {
    Write-Error "Fehler beim Laden der Graph-Module. Bitte prüfen Sie die Installation: $_"
    exit
}

try {
    # Verbindung per App-Only Flow herstellen
    Connect-MgGraph -TenantId $TenantId -ClientSecretCredential $ClientSecretCredential -ErrorAction Stop
}
catch {
    Write-Error "Fehler bei der Verbindung zu Microsoft Graph: $_"
    exit
}

# Parameter für die Inaktivität definieren
$DaysInactive = 90
$ThresholdDate = (Get-Date).AddDays(-$DaysInactive)
$ExportPath = "C:\temp\InactiveGuests_$((Get-Date).ToString('yyyyMMdd')).csv"

Write-Host "Suche per App-Identität nach inaktiven Gastkonten..."

try {
    # Alle Gäste inklusive Audit-Eigenschaften abrufen
    $AllGuests = Get-MgUser -Filter "userType eq 'Guest'" -Property "Id, DisplayName, Mail, UserPrincipalName, CreatedDateTime, SignInActivity" -All -ErrorAction Stop

    # Pipeline-optimierte Zuweisung (vermeidet Performance-Probleme durch +=)
    $InactiveGuests = foreach ($Guest in $AllGuests) {
        $LastSignIn = $Guest.SignInActivity.LastSignInDateTime
        $Created = $Guest.CreatedDateTime
        $IsStale = $false

        if ($null -ne $LastSignIn) {
            # Prüfung auf letzte Anmeldung
            if ($LastSignIn -lt $ThresholdDate) {
                $IsStale = $true
            }
        }
        else {
            # Fallback auf Erstellungsdatum für ungenutzte Einladungen
            if ($Created -lt $ThresholdDate) {
                $IsStale = $true
            }
        }

        if ($IsStale) {
            [PSCustomObject]@{
                Id         = $Guest.Id
                Name       = $Guest.DisplayName
                Email      = $Guest.Mail
                UPN        = $Guest.UserPrincipalName
                Created    = $Created
                LastSignIn = $LastSignIn
            }
        }
    }

    if ($null -eq $InactiveGuests -or $InactiveGuests.Count -eq 0) {
        Write-Host "Keine inaktiven Gastkonten gefunden. Der Tenant ist sauber." -ForegroundColor Green
        Disconnect-MgGraph
        exit
    }

    # CSV-Verzeichnis prüfen und ggf. anlegen
    $ExportDir = Split-Path $ExportPath
    if (-not (Test-Path $ExportDir)) {
        New-Item -ItemType Directory -Path $ExportDir | Out-Null
    }

    # CSV für Audit-Zwecke sichern
    $InactiveGuests | Export-Csv -Path $ExportPath -NoTypeInformation -Delimiter ";"
    Write-Host "Gefundene inaktive Konten: $($InactiveGuests.Count). Starte Bereinigung..." -ForegroundColor Yellow

    # Automatisierte Entfernung (weicher Papierkorb, 30 Tage Wiederherstellungsfenster)
    foreach ($User in $InactiveGuests) {
        Write-Host "Entferne Gastkonto: $($User.Email)..." -ForegroundColor Cyan
        try {
            Remove-MgUser -UserId $User.Id -ErrorAction Stop
        }
        catch {
            Write-Warning "Fehler beim Entfernen von $($User.Email): $_"
        }
    }
}
catch {
    Write-Error "Ein Fehler ist während der Skriptausführung aufgetreten: $_"
}
finally {
    Disconnect-MgGraph -ErrorAction SilentlyContinue
    Write-Host "Verbindung getrennt. Bereinigung abgeschlossen." -ForegroundColor Green
}

Fehlerbehebung und Latenzen

Wenn das Skript einen Berechtigungsfehler wirft, fehlen meist die mandantenweiten Freigaben für die Graph API in der App-Registrierung. Prüfe, ob der grüne Haken bei der Admin-Zustimmung im Entra ID gesetzt ist. Ein weiterer technischer Aspekt betrifft die Verzögerung von Graph. Wenn sich ein Benutzer anmeldet, kann es laut Microsoft bis zu 24 Stunden dauern, bis signInActivity diesen Login über die API zurückmeldet. Für einen Cleanup-Job auf Basis von 90 Tagen ist diese Latenz jedoch völlig irrelevant.

Lifecycle-Management erfordert klare Prozesse

Die manuelle Pflege von Entra ID Gastkonten skaliert in keinem Unternehmen. Das bereitgestellte PowerShell-Skript löst das akute Problem der verwaisten Konten und verkleinert die Angriffsfläche deines Tenants messbar. Es zwingt Administratoren dazu, periodisch einen Blick auf die Kollaborations-Ressourcen zu werfen und die Altlasten vergangener Projekte konsequent abzutrennen.

Dennoch hat dieser skriptbasierte Ansatz eine konzeptionelle Schwäche. Er verlagert die Verantwortung für den Lebenszyklus komplett in die IT-Abteilung. Die IT weiß in der Regel nicht, ob ein externer Berater nach sechs Monaten Pause nicht vielleicht doch nächste Woche wieder Zugriff auf die Projektdaten benötigt. Ein per Remove-MgUser entferntes Gastkonto landet zunächst nur im Papierkorb von Entra ID und lässt sich dort 30 Tage lang per Restore-MgUser oder über das Entra Admin Center wiederherstellen. Reagiert niemand innerhalb dieses Fensters, verfällt das Konto endgültig und der Benutzer muss komplett neu eingeladen werden. Die Chat-Historien in Teams bleiben zwar erhalten, Berechtigungen auf SharePoint-Ebene müssen aber komplett neu vergeben werden.

Für Unternehmen ab einer gewissen Größe führt daher kein Weg an Entra ID Identity Governance vorbei. Mit sogenannten Access Reviews lässt sich dieser gesamte Prozess an die Fachabteilungen delegieren. Das System fragt dann automatisch den Projektleiter nach der Notwendigkeit des Zugriffs. Erst wenn der Projektleiter verneint oder nicht reagiert, greift der Löschmechanismus. Für Reviews auf Basis von Inaktivität reicht eine reine P2-Lizenz allerdings nicht aus. Microsoft verlangt hierfür zusätzlich die Entra ID Governance Lizenz, die auf P1 oder P2 aufsetzt. Genau diese Lizenz benötigst du auch für den nativen Inactive-Guests-Report im Identity Governance Dashboard, der einen Großteil der hier gezeigten Logik bereits fertig mitbringt. Wer diese Governance-Lizenz nicht besitzt, für den bleibt die Kombination aus einer App-Registrierung, einem sauberen Graph-Skript und einem CSV-Report als geplanter Task der einzige gangbare Weg zur Risikominderung.

QuelleThemaURL
Microsoft LearnConnect-MgGraph Parameterreferenz (ClientSecretCredential)https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.authentication/connect-mggraph
Microsoft LearnsignInActivity Lizenz- und Berechtigungsanforderunghttps://learn.microsoft.com/en-us/graph/api/user-list
Microsoft LearnLog-Latenz signInActivityhttps://learn.microsoft.com/en-us/entra/identity/monitoring-health/reference-log-latency
Microsoft LearnSoft Delete vs. Hard Delete für Benutzerhttps://learn.microsoft.com/en-us/entra/architecture/recover-from-deletions
Microsoft LearnAccess Reviews Lizenzanforderunghttps://learn.microsoft.com/en-us/entra/id-governance/access-reviews-overview
Microsoft LearnInactive-Guests-Report, Lizenzanforderunghttps://learn.microsoft.com/en-us/entra/identity/users/clean-up-stale-guest-accounts
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.