基于组的许可管理 PowerShell 示例

可通过 Azure 门户获取 Microsoft Entra ID(Microsoft Entra 的一部分)中基于组的许可。 使用 Microsoft Graph PowerShell Cmdlet 可执行一些有用的任务。

本文将介绍使用 Microsoft Graph PowerShell 的一些示例。

警告

这些示例仅供演示使用。 在生产环境中依赖它们之前,建议先在较小的规模或单独的测试环境中测试它们。 可以修改示例以满足特定环境的要求。

开始运行 cmdlet 前,请务必首先通过运行 Connect-MgGraph cmdlet- 来连接到组织。

将许可证分配给组

基于组的许可提供了一种便捷的方式来管理许可证分配。 可以将一个或多个产品许可证分配给组,这些许可证将分配给该组的所有成员。

# 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

获取组中含有许可证错误的所有用户

如果某个组中包含一些许可证相关的错误,现在可以列出受这些错误影响的所有用户。 用户还可能遇到来自其他组的错误。 但在此示例中,通过检查用户的每个 IndirectLicenseError 条目的 ReferencedObjectId 属性,我们仅将结果限于与所涉及组相关的错误。

# 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”显示为“User”时,它表示直接分配许可证。 如果“SkuPartNumber”显示“未知 SKU”,则表示租户中禁用了特定的许可证 SKU。 该脚本将完整结果导出到本地 AppData 文件夹中的 CSV 文件,以便进一步分析。

检查用户许可证是直接分配还是继承自组

# 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."

后续步骤

若要详细了解用于通过组进行许可证管理的功能集,请参阅以下文章: