Praktická odpověď
licenseAssignmentStates použijte, když potřebujete vysvětlit nejen to, které SKU uživatel má, ale kdo nebo co jej přidělil, zda jej licenční služba úspěšně zpracovala, které plány služeb jsou zakázány a kdy se naposledy změnil stav. Níže uvedená sestava vytvoří jeden řádek na uživatele, SKU, a zdroj přiřazení bez úpravy tenanta.
Vysvětlete každý zdroj
Oddělte přímá přiřazení od přiřazení group-based a přeložte každou skupinu zdrojů na čitelný název.
Najděte chyby zpracování
Stavy Surface ActiveWithError a Error spolu s chybou licensing-service vrácenou aplikací Microsoft Graph.
Zjistit posun zadání
Označte uživatele, kteří obdrží stejný SKU z několika skupin nebo ze skupiny, a přímé přiřazení.
Proč assignedLicenses nestačí
Kolekce assignedLicenses je užitečná pro kontrolu platné licence SKUs u uživatele, ale nejedná se o úplný záznam o původu. Pokud je stejný SKU zděděn z více skupin nebo zděděn ze skupiny a zároveň přiřazen přímo, může pohled effective-licence skrýt provozní složitost.
Vlastnost licenseAssignmentStates poskytuje chybějící podrobnosti. Microsoft Graph vrací assignedByGroup, skuId, disabledPlans, stav, chybu a lastUpdatedDateTime pro každý aktuální zdroj přiřazení. Toto je snímek aktuálních stavů, nikoli protokol historických událostí.
Předpoklady a oprávnění
Nainstalujte Microsoft Graph PowerShell SDK s Install-Module Microsoft.Graph -Scope CurrentUser. Pro interaktivní delegovaný audit požádejte User.Read.All, Group.Read.All a LicenseAssignment.Read.All. Zásady nájemce mohou vyžadovat souhlas správce a účet signed-in musí mít adresářovou roli, která může číst relevantní adresář a licenční informace.
- User.Read.All načte uživatele a vlastnost non-default licenseAssignmentStates.
- Group.Read.All převádí zdrojovou skupinu IDs na čitelné zobrazované názvy.
- LicenseAssignment.Read.All čte předplacené SKUs, takže export může zobrazit skuPartNumber namísto pouze GUID.
Kde je to možné, použijte vyhrazenou identitu auditu read-only a požádejte pouze o oprávnění, která zpráva potřebuje. Licencování Group-based také vyžaduje oprávněné oprávnění pro každého zvýhodněného uživatele a dostatek licencí na produkty pro jedinečné uživatele zahrnuté v licencovaných skupinách.
Důležité konstrukční limity
Vnořené skupiny nerozšiřují přiřazení licence
Licence přiřazené nadřazené skupině se neuplatňují prostřednictvím členství nested-group. Audit přímého členství v licencované skupině; nepředpokládejte, že vnořená podřízená skupina předává licenci svým uživatelům.
Přímé zadání není automaticky chybou
Přiřazení Direct mohou být platná pro dočasný přístup, specialisty add-ons, pilotní uživatele, procesy break-glass nebo zdokumentované výjimky. Audit by měl odhalit nevysvětlená přímá zadání namísto slepého odstranění všech.
Duplicitní zdroje nemusí nutně spotřebovávat duplicitní místa
Uživatel může přijmout stejný SKU z několika zdrojů přiřazení. Obvykle se jedná o problém správy a řešení problémů, nikoli o důkaz, že dvě místa produktu jsou účtována stejné osobě. Prohlédněte si skupiny zdrojů, konfiguraci service-plan a komerční počty samostatně.
Co obsahuje rozšířený přehled
- Zobrazované jméno uživatele, UPN, ID objektu a stav účtu.
- Číslo dílu SKU a ID SKU.
- Typ přiřazení Direct nebo group-based a zdrojová skupina.
- Stav přiřazení, chybová hodnota grafu a zakázáno service-plan IDs.
- Časové razítko poslední aktualizace, příznak překrytí a souhrn všech zdrojů pro stejného uživatele a SKU.
Skript auditu PowerShell
Následující skript je read-only. Vyřeší pouze skupiny, které se skutečně vyskytují jako zdroje licencí, zachová nevyřešené IDs a vytvoří jeden řádek CSV na uživatele, SKU a zdroj přiřazení.
# 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)"Jak skript funguje
1. Vytvořte vyhledávání SKU
Graph vrátí licenci SKUs jako GUIDs. Get-MgSubscribedSku dodává tenantův skuPartNumber, který lze snadněji filtrovat a diskutovat o něm. Sestava uchovává GUID, protože je to stabilní identifikátor používaný Graph.
2. Požádejte explicitně o licenseAssignmentStates
Get-MgUser vrací pouze výchozí podmnožinu uživatelských vlastností. Skript výslovně požaduje LicenseAssignmentStates; vynechání může vytvořit prázdnou sestavu, i když uživatelé mají licenci.
3. Vyřešte pouze používané skupiny zdrojů
Velcí nájemci mohou obsahovat tisíce skupin. Skript shromažďuje jedinečné hodnoty assignedByGroup a řeší pouze tyto hodnoty IDs. Pokud skupinu nelze vyřešit, ID zůstane v sestavě a zdroj bude označen Unresolved.
4. Detekce překrývajících se zdrojů
Více než jeden stav přiřazení pro stejného uživatele a SKU nastavuje HasOverlappingSources na True. Souhrn pak ukazuje, zda je překrytí přímé plus group-based, nebo několik skupinových zdrojů.
Čtení výstupu
AssignmentType = Direct
Začněte s přímým přiřazením, když má tenant používat skupiny role-based. Zeptejte se na vlastníka a obchodní důvod. Pokud se jedná o platnou výjimku, zdokumentujte datum revize. Pokud se jedná o starší posun, před odstraněním ověřte, že je aktivní přiřazení náhradní skupiny.
HasOverlappingSources = True
Zkontrolujte, zda je překrytí záměrné. Dvě skupiny rolí mohou legitimně poskytovat stejné SKU, ale různé konfigurace disabled-plan mohou ztížit vysvětlení efektivního výsledku. Zaznamenejte si autoritativní skupinu pro každou roli a zjednodušte zdroje tam, kde je to bezpečné.
SourceResolution = Unresolved
Nevyřešené ID může znamenat, že skupina již neexistuje, identita auditu ji nemůže přečíst nebo nebyla dokončena replikace adresáře. Neklasifikujte přiřazení jako osiřelé, dokud náležitě autorizovaný program pro čtení adresářů neověří objekt.
AssignmentState = ActiveWithError nebo Error
Upřednostněte tyto řádky. Microsoft Graph dokumenty CountViolation, MutuallyExclusiveViolation, DependencyViolation, ProhibitedInUsageLocationViolation, UniquenessViolation a další. CSV identifikuje uživatele, SKU, zdroj a stav; před změnou licence potvrďte přesnou nápravu v centru pro správu Microsoft 365 a aktuální dokumentaci společnosti Microsoft.
DisabledPlanIds
Ke stejnému produktu lze přiřadit různé servisní plány, které jsou zakázány. Pro hlubší audit sestavte vyhledávání service-plan z Get-MgSubscribedSku.ServicePlans a přidejte čitelné názvy plánů. To je zvláště užitečné, když překrývající se skupiny konfigurují stejné SKU odlišně.
Užitečné filtry follow-up
# 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"Praktický postup nápravy
- Exportujte a uchovávejte nezpracovaný přehled s časovým razítkem.
- Seřiďte řádky Error a ActiveWithError před vyčištěním správy.
- Potvrďte dostupnou kapacitu licencí a umístění uživatelů.
- Přidělte každé platné výjimce direct-assignment vlastníka a datum kontroly.
- Zkontrolujte překrývání, zejména tam, kde skupiny zakazují různé plány služeb.
- Před odstraněním starého zdroje přidejte uživatele do cílové skupiny a potvrďte, že je přiřazení aktivní.
- Re-export po změnách porovnejte počty výjimek a naplánujte opakovaný audit read-only.
Aktuální pokyny společnosti Microsoft pro přesuny mezi licencovanými skupinami jsou nejprve cílem: přidejte uživatele do nové skupiny, potvrďte, že je použita licence, a teprve poté odeberte uživatele z původní skupiny. První odstranění zdroje může dočasně odebrat licencované služby, zatímco zpracování dohání.
Co tato zpráva nedokazuje
- Neprokazuje, že každé zadání je smluvně v souladu.
- Nevypočítává faktury ani neduplikuje obchodní vyúčtování.
- Neověřuje, zda by uživatelé měli přistupovat ke každému pracovnímu zatížení v SKU.
- Nejedná se o historickou časovou osu a nenahrazuje podmínky produktu ani přístupové recenze.
Doporučený provozní model
Spusťte export měsíčně a po velkých změnách identity nebo licencí. Sledujte chyby, nezdokumentovaná přímá přiřazení, překrývající se zdroje, nevyřešené skupiny a deaktivované nebo opuštěné účty, které si uchovávají aktivní přiřazení. Cílem nemusí být nutně nulová přímá přiřazení nebo nulové překrývání. Cílem je, aby každá výjimka měla vlastníka, důvod a datum kontroly – a aby žádná licenční chyba nezůstala neviditelná.
Zdroje společnosti Microsoft
- licenseAssignmentState resource type
- What is group-based licensing in Microsoft Entra ID?
- Assign or unassign licences to a group and manage errors
- Get-MgUser


