Microsoft-365-Lizenzen optimieren: ungenutzte Lizenzen aufspüren und Kosten senken (mit PowerShell)
Wie Sie mit dem Microsoft Graph PowerShell SDK ungenutzte Microsoft-365-Lizenzen finden: inaktive und deaktivierte Konten, lizenzierte Shared Mailboxen, Mehrfachzuweisungen und E5-Downgrade-Kandidaten, mit sicherem Entfernen ohne Datenverlust.
Geprüft am 30.09.2026

In vielen Microsoft-365-Tenants belegen ausgeschiedene Mitarbeitende, deaktivierte Konten oder Shared Mailboxen weiterhin Lizenzen. Diese Anleitung zeigt, wie Sie mit dem Microsoft Graph PowerShell SDK einen Lizenz-Report erstellen, ungenutzte Lizenzen finden und sauber entfernen, ohne Postfachdaten zu verlieren.
Voraussetzungen
- Microsoft-365-Tenant; Rolle Globaler Leser für die Reports, Lizenzadministrator bzw. Benutzeradministrator für Änderungen
- Microsoft Entra ID P1 oder P2 im Tenant (in Microsoft 365 Business Premium, E3 und E5 enthalten): Ohne P1 liefert Graph keine Anmeldeaktivität (
signInActivity) für Schritt 3 - PowerShell 7.x (empfohlen) oder Windows PowerShell 5.1
- Microsoft Graph PowerShell SDK:
Install-Module Microsoft.Graph -Scope CurrentUser - Exchange Online Management Modul (für Shared-Mailbox-Abfragen):
Install-Module ExchangeOnlineManagement - Ausgabepfad
C:\Reports\muss existieren und Schreibrechte besitzen - Für unbeaufsichtigte Ausführung: App-Registrierung in Entra ID mit Client-Secret oder Zertifikat sowie den Graph-Anwendungsberechtigungen
User.Read.All,Organization.Read.AllundAuditLog.Read.All
Schritt 1: Verbindung herstellen und Module prüfen
Stellen Sie sicher, dass die Module in aktueller Version installiert sind. MSOnline funktioniert seit 2025 nicht mehr, AzureAD ist ebenfalls eingestellt; verwenden Sie beide nicht mehr.
# Module aktualisieren
Update-Module Microsoft.Graph -Scope CurrentUser
Update-Module ExchangeOnlineManagement -Scope CurrentUser
# Interaktive Anmeldung (MFA-fähig)
Connect-MgGraph -Scopes 'User.Read.All', 'Organization.Read.All', 'AuditLog.Read.All'
# Ausgabepfad anlegen, falls nicht vorhanden
if (-not (Test-Path 'C:\Reports')) { New-Item -ItemType Directory -Path 'C:\Reports' }
Verifizieren: Get-MgContext | Select-Object Account, Scopes zeigt Ihr Konto und die drei Scopes. Fehlt ein Scope, melden Sie sich mit Disconnect-MgGraph ab und erneut an; beim ersten Mal ist eine Administratorzustimmung nötig.
Schritt 2: Gebuchte SKUs und freie Lizenzen überblicken
Die Differenz zwischen PrepaidUnits.Enabled (bezahlte Plätze) und ConsumedUnits (zugewiesene Plätze) ergibt die freien Lizenzen, die Sie bei der nächsten Vertragsanpassung reduzieren können.
# Alle gebuchten SKUs mit freien Lizenzen anzeigen
Get-MgSubscribedSku -All | Select-Object SkuPartNumber, SkuId,
@{N='Gesamt';E={$_.PrepaidUnits.Enabled}},
@{N='Verbraucht';E={$_.ConsumedUnits}},
@{N='Frei';E={$_.PrepaidUnits.Enabled - $_.ConsumedUnits}} |
Sort-Object Frei -Descending | Format-Table -AutoSize
Häufige SKU-Bezeichnungen im Überblick:
| SkuPartNumber | Produkt |
|---|---|
| SPE_E3 | Microsoft 365 E3 |
| SPE_E5 | Microsoft 365 E5 |
| SPB | Microsoft 365 Business Premium |
| O365_BUSINESS_PREMIUM | Microsoft 365 Business Standard |
| O365_BUSINESS_ESSENTIALS | Microsoft 365 Business Basic |
| ENTERPRISEPACK | Office 365 E3 |
| O365_BUSINESS | Microsoft 365 Apps for Business |
Verifizieren: Die Tabelle listet jede gebuchte SKU mit Gesamt-, Verbraucht- und Frei-Wert; die Summen entsprechen der Übersicht im Microsoft 365 Admin Center unter Abrechnung > Lizenzen.
Schritt 3: Inaktive Benutzer mit Lizenzen finden
Als inaktiv gilt ein Konto, das sich seit mehr als 90 Tagen nicht angemeldet hat. Die Eigenschaft SignInActivity muss explizit per -Select angefordert werden und erfordert Entra ID P1 sowie AuditLog.Read.All. Für den $count-Filter sind -ConsistencyLevel eventual und -CountVariable nötig.
# Inaktive Benutzer (>90 Tage keine Anmeldung) mit Lizenzen
$cutoff = (Get-Date).AddDays(-90)
Get-MgUser -Filter 'assignedLicenses/$count ne 0' `
-ConsistencyLevel eventual -CountVariable anzahl -All `
-Select UserPrincipalName,DisplayName,AccountEnabled,SignInActivity,AssignedLicenses |
Where-Object {
$_.SignInActivity.LastSuccessfulSignInDateTime -lt $cutoff -or
$_.SignInActivity.LastSuccessfulSignInDateTime -eq $null
} |
Select-Object UserPrincipalName, DisplayName, AccountEnabled,
@{N='LetzteAnmeldung';E={$_.SignInActivity.LastSuccessfulSignInDateTime}} |
Export-Csv -Path 'C:\Reports\InaktiveLizenzierte.csv' -NoTypeInformation -Encoding UTF8
Ebenfalls schnell zu finden: deaktivierte Konten, die noch eine Lizenz belegen.
# Deaktivierte Konten mit aktiver Lizenz
Get-MgUser -Filter "assignedLicenses/`$count ne 0 and accountEnabled eq false" `
-ConsistencyLevel eventual -CountVariable anzahl -All `
-Select UserPrincipalName,DisplayName,AccountEnabled,AssignedLicenses |
Export-Csv -Path 'C:\Reports\DeaktivierteMitLizenz.csv' -NoTypeInformation -Encoding UTF8
Verifizieren: Ist die Spalte LetzteAnmeldung in InaktiveLizenzierte.csv bei allen Zeilen leer, fehlt Entra ID P1 oder der Scope AuditLog.Read.All. Gleichen Sie Stichproben mit den Anmeldeprotokollen im Entra Admin Center ab.
Schritt 4: Analysieren Sie direkte und gruppenbasierte Lizenzzuweisungen
Lizenzen weisen Sie im Microsoft 365 Admin Center zu; das Entra Admin Center bietet dafür keine Bearbeitung mehr. Über die Graph-Eigenschaft LicenseAssignmentStates erkennen Sie, ob eine Lizenz direkt oder über eine Gruppe vergeben wurde. Sie ist in Graph v1.0 verfügbar, muss aber per -Property angefordert werden.
# Direkte vs. gruppenbasierte Lizenzzuweisung prüfen
Get-MgUser -All `
-Property UserPrincipalName,DisplayName,LicenseAssignmentStates |
ForEach-Object {
$user = $_
$_.LicenseAssignmentStates | ForEach-Object {
[PSCustomObject]@{
UPN = $user.UserPrincipalName
SkuId = $_.SkuId
ZuweisungsArt = if ($_.AssignedByGroup) { "Gruppe: $($_.AssignedByGroup)" } else { 'Direkt' }
Fehler = $_.Error
}
}
} | Export-Csv -Path 'C:\Reports\LizenzZuweisungsPfad.csv' -NoTypeInformation -Encoding UTF8
Anschließend erkennen Sie Mehrfachzuweisungen: Ein Benutzer erhält dieselbe SKU direkt und über eine Gruppe oder über mehrere Gruppen. Er verbraucht dabei nur eine Lizenz, doch das Entfernen aus der Gruppe gibt sie nicht frei. Entfernen Sie die direkte Zuweisung, wenn die Gruppe die Lizenz steuern soll.
# Doppelzuweisungen (Direkt + Gruppe für gleiche SKU) erkennen
$doppelt = Get-MgUser -All -Property UserPrincipalName,LicenseAssignmentStates |
ForEach-Object {
$u = $_
$skus = $_.LicenseAssignmentStates | Group-Object SkuId
$skus | Where-Object { $_.Count -gt 1 } | ForEach-Object {
[PSCustomObject]@{ UPN = $u.UserPrincipalName; SkuId = $_.Name; Anzahl = $_.Count }
}
}
$doppelt | Export-Csv -Path 'C:\Reports\Doppellizenzen.csv' -NoTypeInformation -Encoding UTF8
Verifizieren: LizenzZuweisungsPfad.csv enthält je Benutzer und SKU eine Zeile mit Direkt oder Gruppe: <ID>. Einträge in der Spalte Fehler ungleich None weisen auf Zuweisungsprobleme hin (siehe Troubleshooting).
Schritt 5: Lizenzierte Shared Mailboxen aufspüren
Freigegebene Postfächer (Shared Mailboxen) benötigen bis 50 GB keine Lizenz. Ausnahmen: Archiv-Postfach, Beweissicherungsverfahren (Litigation Hold) oder mehr als 50 GB erfordern eine Lizenz. Dieser Schritt nutzt Exchange Online und Microsoft Graph.
# Shared Mailboxen mit zugewiesener Lizenz finden
# Erfordert: Connect-ExchangeOnline + Connect-MgGraph
Connect-ExchangeOnline
$sharedMBX = Get-Mailbox -RecipientTypeDetails SharedMailbox -ResultSize Unlimited |
Select-Object -ExpandProperty UserPrincipalName
$sharedMBX | ForEach-Object {
$u = Get-MgUser -UserId $_ -Property UserPrincipalName,DisplayName,AssignedLicenses
if ($u.AssignedLicenses.Count -gt 0) {
[PSCustomObject]@{ UPN = $u.UserPrincipalName; Lizenzen = ($u.AssignedLicenses.SkuId -join '; ') }
}
} | Export-Csv -Path 'C:\Reports\SharedMBXmitLizenz.csv' -NoTypeInformation -Encoding UTF8
Prüfen Sie vor dem Entfernen die Postfachgröße (Get-EXOMailboxStatistics -Identity $upn | Select-Object TotalItemSize), ob ein Archiv oder Litigation Hold aktiv ist, und blockieren Sie die Anmeldung des Kontos (Update-MgUser -UserId $upn -AccountEnabled:$false). Testen Sie das Entfernen zuerst an einem einzelnen Postfach.
# Lizenz von einer einzelnen Shared Mailbox entfernen
$upn = 'sharedbox@contoso.com'
$licenses = (Get-MgUser -UserId $upn).AssignedLicenses.SkuId
Set-MgUserLicense -UserId $upn -RemoveLicenses @($licenses) -AddLicenses @()
# Lizenzen in Massenverarbeitung aus CSV entfernen
$users = Import-Csv -Path 'C:\Reports\SharedMBXmitLizenz.csv'
foreach ($row in $users) {
$licenses = (Get-MgUser -UserId $row.UPN).AssignedLicenses.SkuId
Set-MgUserLicense -UserId $row.UPN -RemoveLicenses @($licenses) -AddLicenses @()
Write-Host "Lizenz entfernt: $($row.UPN)"
}
Verifizieren: (Get-MgUser -UserId $upn -Property AssignedLicenses).AssignedLicenses.Count liefert 0; das Postfach bleibt in Outlook für die Berechtigten erreichbar. Bei Gruppenlizenzen meldet Set-MgUserLicense einen Fehler: Entfernen Sie dann das Konto aus der Lizenzgruppe.
Schritt 6: Downgrade-Kandidaten von E5 auf E3 identifizieren
Die folgende Abfrage listet alle Benutzer mit E5-Lizenz. Ob ein Downgrade möglich ist, entscheiden Sie nach Prüfung der genutzten Dienste (z. B. Defender for Endpoint P2, Power BI Pro, Entra ID P2) – das Skript liefert die Kandidatenliste.
# E5-Nutzer auflisten (Downgrade-Kandidaten-Basis)
$e5Sku = Get-MgSubscribedSku -All | Where-Object { $_.SkuPartNumber -eq 'SPE_E5' }
Get-MgUser -Filter "assignedLicenses/any(x:x/skuId eq $($e5Sku.SkuId))" `
-ConsistencyLevel eventual -CountVariable e5Count -All |
Select-Object UserPrincipalName, DisplayName |
Export-Csv -Path 'C:\Reports\E5Nutzer.csv' -NoTypeInformation -Encoding UTF8
Write-Host "E5-Nutzer gesamt: $e5Count"
Verifizieren: Die ausgegebene Anzahl entspricht dem Wert Verbraucht für SPE_E5 aus Schritt 2.
Die Einsparung je Downgrade ergibt sich aus der Differenz Ihrer vertraglichen Preise für E5 und E3. Prüfen Sie vorher, ob Sicherheitsfunktionen aus E5 (etwa Defender for Office 365 P2 oder Entra ID P2 für PIM) im Tenant genutzt werden.
Schritt 7: Fehlende UsageLocation prüfen
Ein fehlender UsageLocation-Wert blockiert gruppenbasierte Lizenzzuweisungen vollständig. Das Konto bleibt trotz Gruppenmitgliedschaft ohne Lizenz.
# Benutzer ohne Nutzungsstandort finden
Get-MgUser -Filter 'assignedLicenses/$count ne 0' -ConsistencyLevel eventual -CountVariable anzahl -All `
-Select UserPrincipalName,DisplayName,UsageLocation |
Where-Object { -not $_.UsageLocation } |
Export-Csv -Path 'C:\Reports\OhneUsageLocation.csv' -NoTypeInformation -Encoding UTF8
Verifizieren: Nach dem Setzen mit Update-MgUser -UserId <UPN> -UsageLocation 'DE' taucht das Konto beim erneuten Lauf nicht mehr auf.
Schritt 8: Spar-Rechnung aufstellen
Sobald alle CSV-Reports vorliegen, beziffern Sie das Einsparpotenzial: Anzahl freigebbarer Lizenzen je SKU multipliziert mit Ihrem vertraglichen Preis. Zählen Sie die Zeilen je Report:
Get-ChildItem C:\Reports\*.csv | ForEach-Object {
[PSCustomObject]@{ Report = $_.Name; Zeilen = (Import-Csv $_.FullName).Count }
} | Format-Table -AutoSize
| Fundstelle | Report | Einsparung |
|---|---|---|
| Inaktive Konten >90 Tage | InaktiveLizenzierte.csv | ja, nach Rücksprache mit der Fachabteilung |
| Deaktivierte Konten mit Lizenz | DeaktivierteMitLizenz.csv | ja, nach Klärung der Postfachaufbewahrung |
| Shared Mailboxen mit Lizenz | SharedMBXmitLizenz.csv | ja, sofern unter 50 GB, ohne Archiv und Hold |
| Mehrfachzuweisungen | Doppellizenzen.csv | nein, nur Bereinigung |
| E5-Downgrade | E5Nutzer.csv | Preisdifferenz E5/E3 je Konto |
Bei Jahresverträgen reduzieren Sie die Anzahl meist nur zur Verlängerung; bereinigen Sie deshalb vorher.
Verifizieren: Die Zeilenzahlen je Report stimmen mit Ihrer Tabelle überein; nach der Bereinigung zeigt Schritt 2 entsprechend mehr freie Lizenzen.
Troubleshooting / Typische Fehler
- „Request_UnsupportedQuery“ bei
$count-Filtern:-ConsistencyLevel eventualoder-CountVariablefehlt. Beide Parameter sind nötig, wenn erweiterte OData-Filter mit$countgenutzt werden. - SignInActivity ist immer
$null: Die Eigenschaft muss per-Selectangefordert werden und setzt Entra ID P1 sowieAuditLog.Read.Allvoraus. - „ProhibitedInUsageLocationViolation“ bei Gruppenlizenzen: Der Benutzer hat keinen
UsageLocation-Wert gesetzt. Lösung siehe Schritt 7; die Gruppe weist die Lizenz danach automatisch zu. - „MutuallyExclusiveViolation“: Ein Benutzer ist Mitglied zweier Gruppen mit inkompatiblen Service-Plänen (z. B. Skype for Business Plan 1 und Plan 2). Fehlerdetails finden Sie im Entra-Überwachungsprotokoll oder in
LicenseAssignmentStates.Error(Schritt 4). - Lizenz bleibt nach Entfernen aus der Gruppe bestehen: Es existiert zusätzlich eine direkte Zuweisung, die Microsoft nicht automatisch entfernt. Lösung:
Set-MgUserLicense -RemoveLicenses @($directSkuId) -AddLicenses @()für alle betroffenen Nutzer. - MSOnline-Befehle (
Set-MsolUserLicense) funktionieren nicht mehr: MSOnline ist eingestellt; nutzen SieSet-MgUserLicenseundGet-MgUser. LicenseAssignmentStatesist leer: Die Eigenschaft wird nur mit-Property ...,LicenseAssignmentStateszurückgegeben.- Postfachdaten nach Lizenzentfernung verloren: Nach dem Entzug der Exchange-Lizenz bleibt ein Benutzerpostfach 30 Tage erhalten und wird dann gelöscht. Sichern Sie die Daten vorher oder setzen Sie einen Hold bzw. eine Aufbewahrungsrichtlinie, solange das Postfach noch lizenziert ist (
Set-Mailbox -Identity <UPN> -LitigationHoldEnabled $true), und löschen Sie danach das Konto; das Postfach wird dann zum inaktiven Postfach.
Häufige Fragen
Kann ich einer Shared Mailbox komplett die Lizenz entziehen?
Ja, unter den Bedingungen aus Schritt 5: unter 50 GB, kein Archiv, kein Litigation Hold, Anmeldung blockiert.
Wie erkenne ich, ob eine Lizenz direkt oder per Gruppe zugewiesen wurde?
Mit dem Skript aus Schritt 4: Ist AssignedByGroup leer, ist die Lizenz direkt zugewiesen, sonst steht dort die ID der Gruppe.
Was passiert mit den Postfachdaten, wenn ich einem inaktiven Benutzer die Lizenz entziehe?
Es bleibt 30 Tage erhalten und wird danach gelöscht. Wie Sie die Daten als inaktives Postfach bewahren, steht im Troubleshooting unter „Postfachdaten nach Lizenzentfernung verloren“.
Wie groß ist das typische Einsparpotenzial?
Das hängt von Fluktuation und Offboarding ab; die Rechnung zeigt Schritt 8.
Kann ich die Preiserhöhung durch eine frühzeitige Verlängerung umgehen?
Nein, nur aufschieben: Microsoft hat die Preisanpassung zum 1. Juli 2026 umgesetzt; bestehende Verträge behalten ihre Konditionen bis zur nächsten Verlängerung. Danach gelten die neuen Preise. Bereinigen Sie die Lizenzanzahl daher vor jedem Verlängerungstermin.
Welche Berechtigungen brauche ich für den vollständigen Lizenz-Report?
Lesen: Rolle Globaler Leser mit User.Read.All, Organization.Read.All und AuditLog.Read.All. Ändern: Lizenzadministrator mit User.ReadWrite.All. Für Schritt 5 zusätzlich eine Exchange-Rolle mit Leserechten auf Empfänger.
Was ist mit dem neuen Microsoft 365 E7?
Microsoft 365 E7 („Frontier Suite“, seit Mai 2026 verfügbar) bündelt Microsoft 365 E5, Microsoft 365 Copilot, die Microsoft Entra Suite und Agent 365. Für die Bereinigung ändert sich nichts: Die Skripte erkennen E7 wie jede andere SKU in Schritt 2.
Fazit
Führen Sie die Lizenzprüfung mindestens einmal im Quartal aus, idealerweise als geplante Aufgabe mit App-Registrierung. Die CSV-Dateien dienen als Grundlage für die Vertragsverlängerung. Wer wissen möchte, wie Lizenzen in Entra ID über Gruppen zentral gesteuert werden, findet in der Anleitung zu Entra ID: Benutzer, Gruppen und Lizenzen verwalten den passenden Einstieg. Zur Absicherung der Admin-Konten siehe MFA und Conditional Access in Entra ID.
Weiterführende Anleitungen und Quellen
- Entra ID: Benutzer, Gruppen und Lizenzen verwalten
- MFA und Conditional Access in Microsoft 365 / Entra ID einrichten
- PowerShell-Grundlagen für Windows-Admins
- Gruppenrichtlinien (GPO): Grundlagen und Praxis
Quellen: Microsoft Learn – View licensed and unlicensed Microsoft 365 users with PowerShell · Microsoft Learn – Resolve group license assignment problems · Microsoft Learn – user resource (Graph v1.0) · o365reports.com – Find Unused Licenses in Microsoft 365 Using PowerShell · Microsoft Learn – About shared mailboxes


