Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Групповое лицензирование в Microsoft Entra ID, часть Microsoft Entra, доступно через портал Azure. Существуют полезные задачи, которые можно выполнять с помощью командлетов Microsoft Graph PowerShell.
В этой статье мы рассмотрим некоторые примеры использования Microsoft Graph PowerShell.
Предупреждение
Эти примеры предоставляются только для демонстрационных целей. Мы рекомендуем протестировать их на меньшем масштабе или в отдельной тестовой среде, прежде чем полагаться на них в рабочей среде. Вы можете изменить примеры в соответствии с требованиями конкретной среды.
Прежде чем начать запуск командлетов, сначала подключитесь к вашей организации, выполнив командлет Connect-MgGraph
.
Назначение лицензий группе
Лицензирование на основе групп обеспечивает удобный способ управления назначением лицензий. Вы можете назначить одну или несколько лицензий продукта группе, а эти лицензии назначаются всем членам группы.
# Import the Microsoft.Graph.Groups module
Import-Module Microsoft.Graph.Groups
# Define the group ID - replace with your actual group ID
$groupId = "11111111-1111-1111-1111-111111111111"
# Create a hashtable to store the parameters for the Set-MgGroupLicense cmdlet
$params = @{
AddLicenses = @(
@{
# Remove the DisabledPlans key as we don't need to disable any service plans
# Specify the SkuId of the license you want to assign
SkuId = "11111111-1111-1111-1111-111111111111" }
)
# Keep the RemoveLicenses key empty as we don't need to remove any licenses
RemoveLicenses = @(
)
}
# Call the Set-MgGroupLicense cmdlet to update the licenses for the specified group
# Replace $groupId with the actual group ID
Set-MgGroupLicense -GroupId $groupId -BodyParameter $params
Просмотр лицензий продуктов, назначенных группе
The [Get-MgGroup](/powershell/module/microsoft.graph.groups/get-mggroup) cmdlet can be used to retrieve the group object and check the *AssignedLicenses* property: it lists all product licenses currently assigned to the group.
```powershell
# Define the group ID
$groupId = "99c4216a-56de-42c4-a4ac-e411cd8c7c41"
# Get the group with the specified ID and its assigned licenses
$group = Get-MgGroup -GroupId $groupId -Property "AssignedLicenses"
# Extract the assigned licenses
$assignedLicenses = $group | Select-Object -ExpandProperty AssignedLicenses
# Extract the SKU IDs from the assigned licenses
$skuIds = $assignedLicenses | Select-Object -ExpandProperty SkuId
# For each SKU ID, get the corresponding SKU part number
$skuPartNumbers = $skuIds | ForEach-Object {
$skuId = $_
$subscribedSku = Get-MgSubscribedSku | Where-Object { $_.SkuId -eq $skuId }
$skuPartNumber = $subscribedSku | Select-Object -ExpandProperty SkuPartNumber
$skuPartNumber
}
# Output the SKU part numbers
$skuPartNumbers
Вот как выглядят эти результаты:
SkuPartNumber
-------------
ENTERPRISEPREMIUM
EMSPREMIUM
Получить все группы с назначенными лицензиями
Вы можете просмотреть все группы со всеми назначенными лицензиями, выполнив следующую команду:
Get-MgGroup -All -Property Id, MailNickname, DisplayName, GroupTypes, Description, AssignedLicenses | Where-Object {$_.AssignedLicenses -ne $null }
Можно просмотреть дополнительные сведения о назначенных продуктах:
# Get all groups with assigned licenses
$groups = Get-MgGroup -All -Property Id, MailNickname, DisplayName, GroupTypes, Description, AssignedLicenses | Where-Object {$_.AssignedLicenses -ne $null }
# Process each group
$groupInfo = foreach ($group in $groups) {
# For each group, get the SKU part numbers of the assigned licenses
$skuPartNumbers = foreach ($skuId in $group.AssignedLicenses.SkuId) {
$subscribedSku = Get-MgSubscribedSku | Where-Object { $_.SkuId -eq $skuId }
$subscribedSku.SkuPartNumber
}
# Create a custom object with the group's object ID, display name, and license SKU part numbers
[PSCustomObject]@{
ObjectId = $group.Id
DisplayName = $group.DisplayName
Licenses = $skuPartNumbers -join ', '
}
}
$groupInfo
Вот как выглядят эти результаты:
Id DisplayName AssignedLicenses
-- ----------- ----------------
7023a314-6148-4d7b-b33f-6c775572879a EMS E5 – Licensed users EMSPREMIUM
cf41f428-3b45-490b-b69f-a349c8a4c38e PowerBi - Licensed users POWER_BI_STANDARD
962f7189-59d9-4a29-983f-556ae56f19a5 O365 E3 - Licensed users ENTERPRISEPACK
c2652d63-9161-439b-b74e-fcd8228a7074 EMSandOffice {ENTERPRISEPREMIUM,EMSPREMIUM}
Просмотр всех отключённых лицензий тарифного плана, назначенных группам
$groups = Get-MgGroup -All
$groupsWithLicenses = @()
foreach ($group in $groups) {
$licenses = Get-MgGroup -GroupId $group.Id -Property "AssignedLicenses, Id, DisplayName" |
Select-Object AssignedLicenses, DisplayName, Id
if ($licenses.AssignedLicenses) {
foreach ($license in $licenses.AssignedLicenses) {
$skuId = $license.SkuId
$disabledPlans = $license.DisabledPlans
$skuDetails = Get-MgSubscribedSku | Where-Object { $_.SkuId -eq $skuId }
$skuPartNumber = $skuDetails.SkuPartNumber
$disabledPlanDetails = @()
if ($disabledPlans.Count -gt 0) {
foreach ($planId in $disabledPlans) {
$planDetails = $skuDetails.ServicePlans | Where-Object { $_.ServicePlanId -eq $planId }
if ($planDetails) {
$disabledPlanDetails += "$($planDetails.ServicePlanName) ($planId)"
}
}
} else {
$disabledPlanDetails = "None"
}
$groupsWithLicenses += [PSCustomObject]@{
GroupObjectId = $group.Id
GroupName = $group.DisplayName
SkuId = $skuId
SkuPartNumber = $skuPartNumber
DisabledPlans = ($disabledPlanDetails -join ", ")
}
}
}
}
Export to CSV
$csvPath = "$env:USERPROFILE\Documents\GroupLicenses.csv"
$groupsWithLicenses | Export-Csv -Path $csvPath -NoTypeInformation -Encoding UTF8
Write-Host "Export completed: $csvPath"
Получение статистики для групп с лицензиями
# Import User Graph Module
Import-Module Microsoft.Graph.Users
# Authenticate to MS Graph
Connect-MgGraph -Scopes "User.Read.All", "Directory.Read.All", "Group.ReadWrite.All"
#get all groups with licenses
$groups = Get-MgGroup -All -Property LicenseProcessingState, DisplayName, Id, AssignedLicenses | Select-Object displayname, Id, LicenseProcessingState, AssignedLicenses | Select-Object DisplayName, Id, AssignedLicenses -ExpandProperty LicenseProcessingState | Select-Object DisplayName, State, Id, AssignedLicenses | Where-Object {$_.State -eq "ProcessingComplete"}
$groupInfoArray = @()
# Filter the groups to only include those that have licenses assigned
$groups = $groups | Where-Object {$_.AssignedLicenses -ne $null}
# For each group, get the group name, license types, total user count, licensed user count, and license error count
foreach ($group in $groups) {
$groupInfo = New-Object PSObject
$groupInfo | Add-Member -MemberType NoteProperty -Name "Group Name" -Value $group.DisplayName
$groupInfo | Add-Member -MemberType NoteProperty -Name "Group ID" -Value $group.Id
$groupInfo | Add-Member -MemberType NoteProperty -Name "License Types" -Value ($group.AssignedLicenses | Select-Object -ExpandProperty SkuId)
$groupInfo | Add-Member -MemberType NoteProperty -Name "Total User Count" -Value (Get-MgGroupMember -GroupId $group.Id -All | Measure-Object).Count
$groupInfo | Add-Member -MemberType NoteProperty -Name "Licensed User Count" -Value (Get-MgGroupMember -GroupId $group.Id -All | Where-Object {$_. LicenseProcessingState -eq "ProcessingComplete"} | Measure-Object).Count
$groupInfo | Add-Member -MemberType NoteProperty -Name "License Error Count" -Value (Get-MgGroupMember -GroupId $group.Id -All | Where-Object {$_.LicenseProcessingState -eq "ProcessingFailed"} | Measure-Object).Count
$groupInfoArray += $groupInfo
}
# Format the output and print it to the console
$groupInfoArray | Format-Table -AutoSize
Получить все группы с ошибками лицензий
# Get all groups that have assigned licenses
$groups = Get-MgGroup -All -Property DisplayName, Id, AssignedLicenses |
Where-Object { $_.AssignedLicenses -ne $null } |
Select-Object DisplayName, Id, AssignedLicenses
# Initialize an array to store group information
$groupInfo = @()
# Iterate over each group
foreach ($group in $groups) {
$groupId = $group.Id
$groupName = $group.DisplayName
# Initialize counters for total members and members with license errors
$totalCount = 0
$licenseErrorCount = 0
# Get all members of the group that have license errors
$members = Get-MgGroupMemberWithLicenseError -GroupId $groupId
# Process each member
foreach ($member in $members) {
$totalCount++
# If the member has a license error (indicated by a non-empty Id), increment the error count
if (![string]::IsNullOrEmpty($member.Id)) {
$licenseErrorCount++
}
}
# Create a custom object with the group's information and counts
$groupInfo += [PSCustomObject]@{
GroupName = $groupName
GroupId = $groupId
TotalUserCount = $totalCount
LicenseErrorCount = $licenseErrorCount
}
}
# Display the groups with licensing errors
$groupInfo | Where-Object { $_.LicenseErrorCount -gt 0 } | Format-Table -Property GroupName, GroupId, TotalUserCount, LicenseErrorCount
Получить всех пользователей с ошибками лицензий в группе
Зная группу, в которой есть ошибки, связанные с лицензиями, вы можете просмотреть список всех пользователей, у которых возникли эти ошибки. Пользователю также могут отображаться ошибки из других групп. Но в этом примере результаты ограничены только ошибками, относящимися к данной группы. Для этого мы проверяем свойство ReferencedObjectId каждой записи IndirectLicenseError пользователя.
# Import necessary modules
Import-Module Microsoft.Graph.Users
Import-Module Microsoft.Graph.Groups
# Specify the group ID you want to check
$groupId = "ENTER-YOUR-GROUP-ID-HERE"
# Authenticate to Microsoft Graph
Connect-MgGraph -Scopes "Group.Read.All", "User.Read.All"
# Get the specified group
$group = Get-MgGroup -GroupId $groupId -Property DisplayName, Id, AssignedLicenses
Write-Host "Checking license errors for group: $($group.DisplayName)" -ForegroundColor Cyan
# Initialize output array
$groupInfoArray = @()
# Get all members from the group and check their license status
$groupMembers = Get-MgGroupMember -GroupId $group.Id -All
$errorCount = 0
# Process each member
foreach ($memberId in $groupMembers.Id) {
# Get user details
$user = Get-MgUser -UserId $memberId -Property DisplayName, Id, LicenseAssignmentStates
# Check for license errors
$licenseErrors = $user.LicenseAssignmentStates | Where-Object {
$_.AssignedByGroup -eq $groupId -and $_.Error -ne "None"
}
if ($licenseErrors) {
$errorCount++
$userInfo = [PSCustomObject]@{
GroupName = $group.DisplayName
GroupId = $group.Id
UserName = $user.DisplayName
UserId = $user.Id
Error = ($licenseErrors.Error -join ", ")
ErrorSubcode = ($licenseErrors.ErrorSubcode -join ", ")
}
$groupInfoArray += $userInfo
}
}
# Summary
Write-Host "Found $errorCount users with license errors in group $($group.DisplayName)" -ForegroundColor Yellow
# Format the output and print it to the console
if ($groupInfoArray.Length -gt 0) {
$groupInfoArray | Format-Table -AutoSize
}
else {
Write-Host "No License Errors"
}
Получение всех пользователей с ошибками лицензий в организации в целом
Чтобы просмотреть список всех пользователей с ошибками лицензий из одной или нескольких групп, используйте следующий скрипт. Сценарий выводит по одной строке на пользователя на каждую ошибку лицензии, что позволяет четко определить источник каждой ошибки.
# Connect to Microsoft Graph
Connect-MgGraph -Scopes "User.Read.All", "Directory.Read.All", "Organization.Read.All"
# Retrieve all SKUs in the tenant
$skus = Get-MgSubscribedSku -All | Select-Object SkuId, SkuPartNumber
# Retrieve all users in the tenant with required properties
$users = Get-MgUser -All -Property AssignedLicenses, LicenseAssignmentStates, DisplayName, Id, UserPrincipalName
# Initialize an empty array to store the user license information
$allUserLicenses = @()
foreach ($user in $users) {
# Initialize a hash table to track all assignment methods for each license
$licenseAssignments = @{}
$licenseErrors = @()
# Loop through license assignment states
foreach ($assignment in $user.LicenseAssignmentStates) {
$skuId = $assignment.SkuId
$assignedByGroup = $assignment.AssignedByGroup
$assignmentMethod = if ($assignedByGroup -ne $null) {
# If the license was assigned by a group, get the group name
$group = Get-MgGroup -GroupId $assignedByGroup
if ($group) { $group.DisplayName } else { "Unknown Group" }
} else {
# If the license was assigned directly by the user
"User"
}
# Check for errors in the assignment state and capture them
if ($assignment.Error -ne $null -or $assignment.ErrorSubcode -ne $null) {
$errorDetails = @{
Error = $assignment.Error
ErrorSubcode = $assignment.ErrorSubcode
SkuId = $skuId
AssignedBy = $assignmentMethod
}
$licenseErrors += $errorDetails
}
# Ensure all assignment methods are captured
if (-not $licenseAssignments.ContainsKey($skuId)) {
$licenseAssignments[$skuId] = @($assignmentMethod)
} else {
$licenseAssignments[$skuId] += $assignmentMethod
}
}
# Process assigned licenses
foreach ($skuId in $licenseAssignments.Keys) {
# Get SKU details from the pre-fetched list
$sku = $skus | Where-Object { $_.SkuId -eq $skuId } | Select-Object -First 1
$skuPartNumber = if ($sku) { $sku.SkuPartNumber } else { "Unknown SKU" }
# Sort and join the assignment methods
$assignmentMethods = ($licenseAssignments[$skuId] | Sort-Object -Unique) -join ", "
# Clean up license errors to make them more legible
$errorDetails = if ($licenseErrors.Count -gt 0) {
$errorMessages = $licenseErrors | Where-Object { $_.SkuId -eq $skuId } | ForEach-Object {
# Check if error or subcode are empty, and filter them out
if ($_Error -ne "None" -and $_.ErrorSubcode) {
"$($_.AssignedBy): Error: $($_.Error) Subcode: $($_.ErrorSubcode)"
} elseif ($_Error -ne "None") {
"$($_.AssignedBy): Error: $($_.Error)"
} elseif ($_.ErrorSubcode) {
"$($_.AssignedBy): Subcode: $($_.ErrorSubcode)"
}
}
# Join filtered error messages into a clean output
$errorMessages -join "; "
} else {
"No Errors"
}
# Construct a custom object to store the user's license information
$userLicenseInfo = [PSCustomObject]@{
UserId = $user.Id
UserDisplayName = $user.DisplayName
UserPrincipalName = $user.UserPrincipalName
SkuId = $skuId
SkuPartNumber = $skuPartNumber
AssignedBy = $assignmentMethods
LicenseErrors = $errorDetails
}
# Add the user's license information to the array
$allUserLicenses += $userLicenseInfo
}
}
# Export the results to a CSV file
$path = Join-path $env:LOCALAPPDATA ("UserLicenseAssignments_" + [string](Get-Date -UFormat %Y%m%d) + ".csv")
$allUserLicenses | Export-Csv $path -Force -NoTypeInformation
# Display the location of the CSV file
Write-Host "CSV file generated at: $((Get-Item $path).FullName)"
Примечание.
Этот скрипт извлекает список всех лицензированных пользователей в вашей среде, показывающий, какие лицензии назначены и метод назначения. В результатах, где "AssignedBy" отображает "Пользователь", это указывает на прямое назначение лицензии. Где "SkuPartNumber" отображает "неизвестный SKU", это указывает на то, что конкретная лицензия SKU деактивирована в арендаторе. Скрипт экспортирует полные результаты в CSV-файл в локальной папке AppData для дальнейшего анализа.
Как проверить, назначена ли лицензия пользователю напрямую или унаследована от группы?
# Retrieve all SKUs in the tenant
$skus = Get-MgSubscribedSku -All | Select-Object SkuId, SkuPartNumber
# Retrieve all users in the tenant with required properties
$users = Get-MgUser -All -Property AssignedLicenses, LicenseAssignmentStates, DisplayName, Id, UserPrincipalName
# Initialize an empty array to store the user license information
$allUserLicenses = @()
foreach ($user in $users) {
# Initialize a hash table to track all assignment methods for each license
$licenseAssignments = @{}
# Loop through license assignment states
foreach ($assignment in $user.LicenseAssignmentStates) {
$skuId = $assignment.SkuId
$assignedByGroup = $assignment.AssignedByGroup
$assignmentMethod = if ($assignedByGroup -ne $null) {
# If the license was assigned by a group, get the group name
$group = Get-MgGroup -GroupId $assignedByGroup
if ($group) { $group.DisplayName } else { "Unknown Group" }
} else {
# If the license was assigned directly by the user
"User"
}
# Ensure all assignment methods are captured
if (-not $licenseAssignments.ContainsKey($skuId)) {
$licenseAssignments[$skuId] = @($assignmentMethod)
} else {
$licenseAssignments[$skuId] += $assignmentMethod
}
}
# Process assigned licenses
foreach ($skuId in $licenseAssignments.Keys) {
# Get SKU details from the pre-fetched list
$sku = $skus | Where-Object { $_.SkuId -eq $skuId } | Select-Object -First 1
$skuPartNumber = if ($sku) { $sku.SkuPartNumber } else { "Unknown SKU" }
# Sort and join the assignment methods
$assignmentMethods = ($licenseAssignments[$skuId] | Sort-Object -Unique) -join ", "
# Construct a custom object to store the user's license information
$userLicenseInfo = [PSCustomObject]@{
UserId = $user.Id
UserDisplayName = $user.DisplayName
UserPrincipalName = $user.UserPrincipalName
SkuId = $skuId
SkuPartNumber = $skuPartNumber
AssignedBy = $assignmentMethods
}
# Add the user's license information to the array
$allUserLicenses += $userLicenseInfo
}
}
# Export the results to a CSV file
$path = Join-path $env:LOCALAPPDATA ("UserLicenseAssignments_" + [string](Get-Date -UFormat %Y%m%d) + ".csv")
$allUserLicenses | Export-Csv $path -Force -NoTypeInformation
# Display the location of the CSV file
Write-Host "CSV file generated at: $((Get-Item $path).FullName)"
Удалите прямые лицензии у пользователей с групповыми лицензиями.
Целью этого сценария является удаление ненужных прямых лицензий для пользователей, которые уже унаследовали такую же лицензию от группы (например, при переходе на групповое лицензирование).
Примечание.
Чтобы гарантировать, что пользователи не теряют доступ к службам и данным, важно убедиться, что прямые назначенные лицензии не предоставляют больше функциональных возможностей служб, чем унаследованные лицензии. В настоящее время невозможно использовать PowerShell для определения служб, включенных с помощью унаследованных лицензий, а также прямых лицензий. Поэтому скрипт использует минимальный уровень служб, которые, как известно, наследуются от групп, чтобы проверить и убедиться, что пользователи не испытывают непредвиденной потери службы.
Переменные
- $GroupLicenses: Представляет лицензии, назначенные группе.
- $GroupMembers: Содержит членов группы.
- $UserLicenses: Содержит лицензии непосредственно, назначенные пользователю.
- $DirectLicensesToRemove: Сохраняет лицензии, которые необходимо удалить от пользователя.
# Define the group ID containing the assigned license
$GroupId = "objectID of Group"
# Force all errors to be terminating errors
$ErrorActionPreference = "Stop"
# Get the group's assigned licenses
$Group = Get-MgGroup -GroupId $GroupId -Property AssignedLicenses
$GroupLicenses = $Group.AssignedLicenses.SkuId
if (-not $GroupLicenses) {
Write-Host "No licenses assigned to the specified group. Exiting script."
return
}
# Get all members of the group
$GroupMembers = Get-MgGroupMember -GroupId $GroupId -All
foreach ($User in $GroupMembers) {
$UserId = $User.Id
# Get user's assigned licenses
$UserData = Get-MgUser -UserId $UserId -Property DisplayName,Mail,UserPrincipalName,AssignedLicenses
$UserLicenses = $UserData.AssignedLicenses.SkuId
# Identify direct licenses that match the group's assigned licenses
$DirectLicensesToRemove = @()
foreach ($License in $UserLicenses) {
if ($GroupLicenses -contains $License) {
$DirectLicensesToRemove += $License
}
}
# Print user info before taking action
Write-Host ("{0,-40} {1,-25} {2,-40} {3}" -f $UserData.Id, $UserData.DisplayName, $UserData.Mail, $UserData.UserPrincipalName)
# Skip users who have no direct licenses matching the group
if ($DirectLicensesToRemove.Count -eq 0) {
Write-Host "No direct licenses to remove. (Only inherited licenses detected)"
Write-Host "------------------------------------------------------"
continue
}
# Attempt to remove direct licenses
try {
Write-Host "Removing direct license(s)..."
Set-MgUserLicense -UserId $UserId -RemoveLicenses $DirectLicensesToRemove -AddLicenses @() -ErrorAction Stop
Write-Host "✅ License(s) removed successfully."
}
catch {
$ErrorMessage = $_.Exception.Message
if ($ErrorMessage -match "User license is inherited from a group membership") {
Write-Host "⚠️ Skipping removal - License is inherited from a group."
} else {
Write-Host "❌ Unexpected error: $ErrorMessage"
}
}
Write-Host "------------------------------------------------------"
}
Write-Host "Script execution complete."
Следующие шаги
Дополнительные сведения о наборе функций для управления лицензиями с помощью групп см. в следующих статьях: