Die praktische Antwort
Verwenden Sie licenseAssignmentStates, wenn Sie nicht nur erklären müssen, welchen SKU ein Benutzer hat, sondern auch, wer oder was ihn zugewiesen hat, ob der Lizenzierungsdienst ihn erfolgreich verarbeitet hat, welche Servicepläne deaktiviert sind und wann sich der Status zuletzt geändert hat. Der folgende Bericht erstellt eine Zeile pro Benutzer, SKU und Zuweisungsquelle, ohne den Mandanten zu ändern.
Erklären Sie jede Quelle
Trennen Sie direkte Zuweisungen von group-based-Zuweisungen und lösen Sie jede Quellgruppe in einen lesbaren Namen auf.
Finden Sie Verarbeitungsfehler
Die Oberflächenzustände ActiveWithError und Error werden zusammen mit dem von Microsoft Graph zurückgegebenen licensing-service-Fehler angezeigt.
Zuordnungsdrift erkennen
Kennzeichnen Sie Benutzer, die das gleiche SKU von mehreren Gruppen oder von einer Gruppe und einer direkten Zuweisung erhalten.
Warum assignedLicenses nicht ausreicht
Die assignedLicenses-Sammlung ist nützlich, um die effektive Lizenz SKUs für einen Benutzer zu überprüfen, es handelt sich jedoch nicht um einen vollständigen Herkunftseintrag. Wenn derselbe SKU von mehreren Gruppen geerbt wird oder von einer Gruppe geerbt und gleichzeitig direkt zugewiesen wird, kann eine effective-licence-Ansicht die betriebliche Komplexität verbergen.
Die Eigenschaft licenseAssignmentStates liefert die fehlenden Details. Microsoft Graph gibt assignedByGroup, skuId, disabledPlans, Status, Fehler und lastUpdatedDateTime für jede aktuelle Zuweisungsquelle zurück. Dies ist eine Momentaufnahme des aktuellen Status, kein historisches Ereignisprotokoll.
Voraussetzungen und Berechtigungen
Installieren Sie Microsoft Graph PowerShell SDK mit Install-Module Microsoft.Graph -Scope CurrentUser. Für eine interaktive delegierte Prüfung fordern Sie User.Read.All, Group.Read.All und LicenseAssignment.Read.All an. Die Mandantenrichtlinie erfordert möglicherweise die Zustimmung des Administrators, und das signed-in-Konto muss über eine Verzeichnisrolle verfügen, die zum Lesen der relevanten Verzeichnis- und Lizenzinformationen berechtigt ist.
- User.Read.All ruft Benutzer und die Eigenschaft non-default licenseAssignmentStates ab.
- Group.Read.All löst die Quellgruppe IDs in lesbare Anzeigenamen auf.
- LicenseAssignment.Read.All liest abonnierte SKUs, sodass der Export skuPartNumber statt nur GUID anzeigen kann.
Verwenden Sie nach Möglichkeit eine dedizierte Audit-Identität read-only und fordern Sie nur die Berechtigungen an, die der Bericht benötigt. Die Group-based-Lizenzierung erfordert außerdem eine berechtigte Berechtigung für jeden profitierenden Benutzer und genügend Produktlizenzen für die einzelnen Benutzer, die von den lizenzierten Gruppen abgedeckt werden.
Wichtige Designgrenzen
Verschachtelte Gruppen verlängern die Lizenzzuweisung nicht
Einer übergeordneten Gruppe zugewiesene Lizenzen werden nicht über die nested-group-Mitgliedschaft angewendet. Prüfen Sie die direkte Mitgliedschaft in der lizenzierten Gruppe. Gehen Sie nicht davon aus, dass eine verschachtelte untergeordnete Gruppe die Lizenz an ihre Benutzer weitergibt.
Eine direkte Zuordnung ist nicht automatisch ein Fehler
Direct-Zuweisungen können für temporären Zugriff, Fachbenutzer add-ons, Pilotbenutzer, break-glass-Prozesse oder dokumentierte Ausnahmen gültig sein. Das Audit sollte ungeklärte Direktzuweisungen identifizieren, anstatt sie blind alle zu eliminieren.
Doppelte Quellen verbrauchen nicht unbedingt doppelte Plätze
Ein Benutzer kann denselben SKU aus mehreren Zuweisungsquellen erhalten. Dabei handelt es sich in der Regel um ein Governance- und Fehlerbehebungsproblem und nicht um einen Beweis dafür, dass zwei Produktsitze derselben Person in Rechnung gestellt werden. Überprüfen Sie die Quellgruppen, die service-plan-Konfiguration und die kommerziellen Zählungen separat.
Was der erweiterte Bericht enthält
- Benutzeranzeigename, UPN, Objekt-ID und Kontostatus.
- SKU Teilenummer und SKU ID.
- Zuweisungstyp Direct oder group-based und Quellgruppe.
- Zuweisungsstatus, Diagrammfehlerwert und deaktiviert service-plan IDs.
- Zeitstempel der letzten Aktualisierung, Überlappungsflag und eine Zusammenfassung aller Quellen für denselben Benutzer und SKU.
PowerShell-Prüfskript
Das folgende Skript ist read-only. Es löst nur Gruppen auf, die tatsächlich als Lizenzquellen auftreten, behält nicht aufgelöste IDs bei und erstellt eine CSV-Zeile pro Benutzer, SKU und Zuweisungsquelle.
# Read-only Microsoft 365 group-based licensing audit
# Requires: Install-Module Microsoft.Graph -Scope CurrentUser
$requiredScopes = @(
"User.Read.All"
"Group.Read.All"
"LicenseAssignment.Read.All"
)
Connect-MgGraph -Scopes $requiredScopes -NoWelcome
$skuLookup = @{}
Get-MgSubscribedSku -All | ForEach-Object {
$skuLookup[$_.SkuId.ToString()] = $_.SkuPartNumber
}
$users = Get-MgUser -All -Property @(
"Id"
"DisplayName"
"UserPrincipalName"
"AccountEnabled"
"LicenseAssignmentStates"
)
$sourceGroupIds = $users.LicenseAssignmentStates |
Where-Object { -not [string]::IsNullOrWhiteSpace($_.AssignedByGroup) } |
ForEach-Object { $_.AssignedByGroup } |
Sort-Object -Unique
$groupLookup = @{}
foreach ($groupId in $sourceGroupIds) {
try {
$group = Get-MgGroup -GroupId $groupId -Property "Id,DisplayName" -ErrorAction Stop
$groupLookup[$groupId] = $group.DisplayName
}
catch {
$groupLookup[$groupId] = $null
}
}
$report = foreach ($user in $users) {
$assignments = @($user.LicenseAssignmentStates)
foreach ($assignment in $assignments) {
$skuId = $assignment.SkuId.ToString()
$sourceGroupId = $assignment.AssignedByGroup
$assignmentType = if ([string]::IsNullOrWhiteSpace($sourceGroupId)) {
"Direct"
} else {
"Group-based"
}
$sameSkuAssignments = @(
$assignments | Where-Object { $_.SkuId.ToString() -eq $skuId }
)
$sourceSummary = $sameSkuAssignments | ForEach-Object {
if ([string]::IsNullOrWhiteSpace($_.AssignedByGroup)) {
"Direct"
} elseif ($groupLookup[$_.AssignedByGroup]) {
"Group: $($groupLookup[$_.AssignedByGroup])"
} else {
"Group ID: $($_.AssignedByGroup)"
}
}
$sourceGroupName = if ($sourceGroupId) {
$groupLookup[$sourceGroupId]
} else {
$null
}
$sourceResolution = if (-not $sourceGroupId) {
"Not applicable"
} elseif ($sourceGroupName) {
"Resolved"
} else {
"Unresolved"
}
[PSCustomObject]@{
DisplayName = $user.DisplayName
UserPrincipalName = $user.UserPrincipalName
UserId = $user.Id
AccountEnabled = $user.AccountEnabled
LicenseSku = $skuLookup[$skuId]
LicenseSkuId = $skuId
AssignmentType = $assignmentType
SourceGroupName = $sourceGroupName
SourceGroupId = $sourceGroupId
SourceResolution = $sourceResolution
AssignmentState = $assignment.State
AssignmentError = $assignment.Error
DisabledPlanIds = ($assignment.DisabledPlans | ForEach-Object { $_.ToString() }) -join ";"
LastUpdatedDateTime = $assignment.LastUpdatedDateTime
SourcesForSameSku = $sameSkuAssignments.Count
HasOverlappingSources = ($sameSkuAssignments.Count -gt 1)
SameSkuSourceSummary = ($sourceSummary -join " | ")
}
}
}
$outputPath = Join-Path -Path (Get-Location) -ChildPath "m365-group-license-audit.csv"
$report |
Sort-Object AssignmentState, AssignmentError, LicenseSku, UserPrincipalName, AssignmentType |
Export-Csv -Path $outputPath -NoTypeInformation -Encoding UTF8
Write-Host "Export complete: $outputPath"
Write-Host "Rows: $($report.Count)"
Write-Host "Errors: $(@($report | Where-Object AssignmentError).Count)"
Write-Host "Direct assignments: $(@($report | Where-Object AssignmentType -eq 'Direct').Count)"
Write-Host "Overlapping sources: $(@($report | Where-Object HasOverlappingSources).Count)"
Write-Host "Unresolved groups: $(@($report | Where-Object SourceResolution -eq 'Unresolved').Count)"Wie das Skript funktioniert
1. Erstellen Sie eine SKU-Suche
Graph gibt die Lizenz SKUs als GUIDs zurück. Get-MgSubscribedSku stellt den skuPartNumber des Mandanten bereit, der einfacher zu filtern und zu diskutieren ist. Der Bericht behält den GUID bei, da es sich um die von Graph verwendete stabile Kennung handelt.
2. Fordern Sie licenseAssignmentStates explizit an
Get-MgUser gibt nur eine Standardteilmenge von Benutzereigenschaften zurück. Das Skript fordert explizit LicenseAssignmentStates an; Wenn Sie es weglassen, kann es zu einem leeren Bericht kommen, selbst wenn Benutzer lizenziert sind.
3. Lösen Sie nur verwendete Quellgruppen auf
Große Mieter können Tausende von Gruppen umfassen. Das Skript sammelt eindeutige assignedByGroup-Werte und löst nur diese IDs auf. Wenn eine Gruppe nicht aufgelöst werden kann, bleibt die ID im Bericht und die Quelle wird mit Unresolved gekennzeichnet.
4. Erkennen Sie überlappende Quellen
Mehr als ein Zuweisungsstatus für denselben Benutzer und SKU setzt HasOverlappingSources auf True. Die Zusammenfassung zeigt dann, ob die Überlappung direkt plus group-based oder mehrere Gruppenquellen ist.
Lesen der Ausgabe
AssignmentType = Direct
Beginnen Sie mit direkten Zuweisungen, wenn der Mandant role-based-Gruppen verwenden soll. Fragen Sie nach einem Eigentümer und einem geschäftlichen Grund. Wenn es sich um eine gültige Ausnahme handelt, dokumentieren Sie ein Überprüfungsdatum. Wenn es sich um eine Legacy-Drift handelt, vergewissern Sie sich, dass eine Ersatzgruppenzuweisung aktiv ist, bevor Sie sie entfernen.
HasOverlappingSources = True
Überprüfen Sie, ob die Überschneidung beabsichtigt ist. Zwei Rollengruppen können berechtigterweise denselben SKU bereitstellen, aber unterschiedliche disabled-plan-Konfigurationen können es schwierig machen, das effektive Ergebnis zu erklären. Notieren Sie die maßgebliche Gruppe für jede Rolle und vereinfachen Sie Quellen, wo dies sicher ist.
SourceResolution = Unresolved
Eine nicht aufgelöste ID kann bedeuten, dass die Gruppe nicht mehr existiert, die Prüfidentität sie nicht lesen kann oder die Verzeichnisreplikation nicht abgeschlossen wurde. Klassifizieren Sie die Zuordnung nicht als verwaist, bis ein entsprechend autorisierter Verzeichnisleser das Objekt überprüft.
AssignmentState = ActiveWithError oder Error
Priorisieren Sie diese Zeilen. Microsoft Graph-Dokumente CountViolation, MutuallyExclusiveViolation, DependencyViolation, ProhibitedInUsageLocationViolation, UniquenessViolation und andere. CSV identifiziert den Benutzer, SKU, Quelle und Status; Bestätigen Sie die genaue Behebung im Microsoft 365 Admin Center und in der aktuellen Microsoft-Dokumentation, bevor Sie eine Lizenz ändern.
DisabledPlanIds
Das gleiche Produkt kann mit verschiedenen deaktivierten Serviceplänen zugewiesen werden. Erstellen Sie für eine tiefergehende Prüfung eine service-plan-Suche aus Get-MgSubscribedSku.ServicePlans und fügen Sie lesbare Plannamen hinzu. Dies ist besonders nützlich, wenn überlappende Gruppen dasselbe SKU unterschiedlich konfigurieren.
Nützliche follow-up-Filter
# Licensing-service failures
$report | Where-Object {
$_.AssignmentState -in @("ActiveWithError", "Error") -or $_.AssignmentError
}
# Direct assignments for enabled accounts
$report | Where-Object {
$_.AssignmentType -eq "Direct" -and $_.AccountEnabled
}
# Same user and SKU assigned by more than one source
$report | Where-Object HasOverlappingSources
# Source groups that could not be resolved
$report | Where-Object SourceResolution -eq "Unresolved"Ein praktischer Sanierungsworkflow
- Exportieren und bewahren Sie den Rohbericht mit einem Zeitstempel auf.
- Triagieren Sie die Zeilen Error und ActiveWithError vor der Governance-Bereinigung.
- Bestätigen Sie die verfügbare Lizenzkapazität und Benutzernutzungsorte.
- Geben Sie jeder gültigen direct-assignment-Ausnahme einen Besitzer und ein Überprüfungsdatum.
- Überprüfungsüberschneidungen, insbesondere wenn Gruppen unterschiedliche Servicepläne deaktivieren.
- Fügen Sie Benutzer zur Zielgruppe hinzu und bestätigen Sie, dass die Zuweisung aktiv ist, bevor Sie die alte Quelle entfernen.
- Re-export nach Änderungen, vergleichen Sie die Anzahl der Ausnahmen und planen Sie eine wiederkehrende read-only-Prüfung.
Bei den aktuellen Richtlinien von Microsoft für Verschiebungen zwischen lizenzierten Gruppen geht es zunächst um das Ziel: Fügen Sie den Benutzer der neuen Gruppe hinzu, bestätigen Sie, dass die Lizenz angewendet wird, und entfernen Sie erst dann den Benutzer aus der ursprünglichen Gruppe. Wenn Sie zuerst die Quelle entfernen, können lizenzierte Dienste vorübergehend entfernt werden, während die Verarbeitung aufholt.
Was dieser Bericht nicht beweist
- Es beweist nicht, dass jeder Auftrag vertragskonform ist.
- Es werden keine Rechnungen berechnet oder Handelsabrechnungen dupliziert.
- Es wird nicht überprüft, ob Benutzer auf alle Workloads im SKU zugreifen sollen.
- Es handelt sich nicht um eine historische Zeitleiste und ersetzt nicht die Produktbedingungen oder Zugriffsbewertungen.
Empfohlenes Betriebsmodell
Führen Sie den Export monatlich und nach größeren Identitäts- oder Lizenzänderungen durch. Verfolgen Sie Fehler, undokumentierte direkte Zuweisungen, sich überschneidende Quellen, ungelöste Gruppen und deaktivierte oder nicht mehr gültige Konten, die aktive Zuweisungen behalten. Das Ziel sind nicht unbedingt null direkte Zuweisungen oder null Überschneidungen. Das Ziel besteht darin, dass jede Ausnahme einen Besitzer, einen Grund und ein Überprüfungsdatum hat – und dass kein Lizenzfehler unsichtbar bleibt.
Microsoft-Quellen
- licenseAssignmentState resource type
- What is group-based licensing in Microsoft Entra ID?
- Assign or unassign licences to a group and manage errors
- Get-MgUser


