The Challenge of License Management at Scale

Managing Microsoft 365 licenses individually becomes unwieldy as organizations grow. Each time an employee joins, changes roles, or leaves, IT must manually update licenses. Group-based licensing solves this by automatically assigning licenses based on group membership.

Prerequisites

  • Azure AD Premium P1 or P2 license
  • Global Administrator or License Administrator role
  • PowerShell modules: AzureAD or Microsoft.Graph

Step 1: Audit Current License Assignments

First, identify all licenses currently assigned individually:

# Connect to Azure AD
Connect-AzureAD

# Get all users with direct license assignments
$users = Get-AzureADUser -All $true | Where-Object {$_.AssignedLicenses.Count -gt 0}

# Export current state
$licenseReport = @()
foreach ($user in $users) {
    foreach ($license in $user.AssignedLicenses) {
        $licenseReport += [PSCustomObject]@{
            UserPrincipalName = $user.UserPrincipalName
            DisplayName = $user.DisplayName
            SkuId = $license.SkuId
        }
    }
}
$licenseReport | Export-Csv "DirectLicenseAssignments.csv" -NoTypeInformation

Step 2: Map SKU IDs to Friendly Names

Create a mapping of Microsoft SKU IDs to group names:

# Define license mapping
$licenseMapping = @{
    "DYN365_BUSCENTRAL_ESSENTIAL" = "License-DynamicsBusinessCentralEssential"
    "EXCHANGESTANDARD" = "License-ExchangeOnlinePlan1"
    "ENTERPRISEPACK" = "License-O365E3"
    "AAD_PREMIUM_P2" = "License-EntraIDP2"
    "POWER_BI_PRO_CE" = "License-PowerBIPro"
    "PROJECTPROFESSIONAL" = "License-PlannerProjectPlan3"
    "POWERAPPS_PER_APP" = "License-PowerAppsPerAppBaseline"
    "POWERAUTOMATE_ATTENDED_RPA" = "License-PowerAutomatePremium"
}

Step 3: Create License Groups

foreach ($license in $licenseMapping.GetEnumerator()) {
    $groupName = $license.Value
    
    # Check if group exists
    $existingGroup = Get-AzureADGroup -Filter "DisplayName eq '$groupName'"
    
    if (-not $existingGroup) {
        # Create new group
        New-AzureADGroup `
            -DisplayName $groupName `
            -MailEnabled $false `
            -SecurityEnabled $true `
            -MailNickname $groupName.Replace(" ", "")
        
        Write-Host "Created group: $groupName" -ForegroundColor Green
    }
}

Step 4: Assign Licenses to Groups

# Get available licenses
$subscribedSkus = Get-AzureADSubscribedSku

foreach ($license in $licenseMapping.GetEnumerator()) {
    $skuPartNumber = $license.Key
    $groupName = $license.Value
    
    # Find the SKU
    $sku = $subscribedSkus | Where-Object {$_.SkuPartNumber -eq $skuPartNumber}
    
    if ($sku) {
        # Get the group
        $group = Get-AzureADGroup -Filter "DisplayName eq '$groupName'"
        
        # Create license assignment
        $licensesToAssign = New-Object -TypeName Microsoft.Open.AzureAD.Model.AssignedLicense
        $licensesToAssign.SkuId = $sku.SkuId
        
        $licensesToRemove = New-Object -TypeName Microsoft.Open.AzureAD.Model.AssignedLicenses
        $licensesToRemove.AddLicenses = $licensesToAssign
        
        # Assign license to group
        Set-AzureADGroupLicense -ObjectId $group.ObjectId -AssignedLicenses $licensesToRemove
        
        Write-Host "Assigned $skuPartNumber to group $groupName" -ForegroundColor Green
    }
}

Step 5: Migrate Users to Group-Based Licensing

# Process each user
foreach ($user in $users) {
    $userLicenses = Get-AzureADUserLicenseDetail -ObjectId $user.ObjectId
    
    foreach ($userLicense in $userLicenses) {
        # Find corresponding group
        $groupName = $licenseMapping[$userLicense.SkuPartNumber]
        
        if ($groupName) {
            $group = Get-AzureADGroup -Filter "DisplayName eq '$groupName'"
            
            # Add user to group
            Add-AzureADGroupMember -ObjectId $group.ObjectId -RefObjectId $user.ObjectId
            
            Write-Host "Added $($user.UserPrincipalName) to $groupName" -ForegroundColor Yellow
        }
    }
}

Step 6: Verify and Remove Direct Assignments

Wait for license processing (usually 2-4 hours), then verify:

# Verify group license assignment
foreach ($groupName in $licenseMapping.Values) {
    $group = Get-AzureADGroup -Filter "DisplayName eq '$groupName'"
    $members = Get-AzureADGroupMember -ObjectId $group.ObjectId
    
    foreach ($member in $members) {
        $licenses = Get-AzureADUserLicenseDetail -ObjectId $member.ObjectId
        
        foreach ($license in $licenses) {
            if ($license.AssignmentPaths -contains "Group") {
                Write-Host "$($member.UserPrincipalName) has group-assigned $($license.SkuPartNumber)" -ForegroundColor Green
            }
        }
    }
}

# Remove direct assignments (after verification)
foreach ($user in $users) {
    $licensesToRemove = New-Object -TypeName Microsoft.Open.AzureAD.Model.AssignedLicenses
    $licensesToRemove.RemoveLicenses = $user.AssignedLicenses.SkuId
    
    Set-AzureADUserLicense -ObjectId $user.ObjectId -AssignedLicenses $licensesToRemove
}

Best Practices

1. Use Descriptive Naming Conventions

Prefix license groups with “License-” for easy identification.

2. Implement Role-Based Groups

Create groups based on job roles:

# Example: Sales team gets E3 + Power BI
$salesGroup = New-AzureADGroup -DisplayName "Role-Sales" -MailEnabled $false -SecurityEnabled $true
Add-AzureADGroupMember -ObjectId (Get-AzureADGroup -Filter "DisplayName eq 'License-O365E3'").ObjectId -RefObjectId $salesGroup.ObjectId
Add-AzureADGroupMember -ObjectId (Get-AzureADGroup -Filter "DisplayName eq 'License-PowerBIPro'").ObjectId -RefObjectId $salesGroup.ObjectId

3. Monitor License Usage

# Create usage report
$usageReport = @()
foreach ($sku in Get-AzureADSubscribedSku) {
    $usageReport += [PSCustomObject]@{
        License = $sku.SkuPartNumber
        Total = $sku.PrepaidUnits.Enabled
        Assigned = $sku.ConsumedUnits
        Available = $sku.PrepaidUnits.Enabled - $sku.ConsumedUnits
    }
}
$usageReport | Format-Table -AutoSize

4. Handle License Conflicts

When users need multiple licenses with conflicting services:

# Disable specific services
$disabledPlans = @("TEAMS1", "YAMMER_ENTERPRISE")
$license = New-Object -TypeName Microsoft.Open.AzureAD.Model.AssignedLicense
$license.SkuId = $sku.SkuId
$license.DisabledPlans = $disabledPlans

Automation with Azure Automation

Create a runbook for automatic license assignment based on attributes:

param(
    [Parameter(Mandatory=$true)]
    [string]$UserPrincipalName
)

# Connect using Managed Identity
Connect-AzureAD -Identity

$user = Get-AzureADUser -ObjectId $UserPrincipalName
$department = $user.Department

# Department-based assignment
$departmentMapping = @{
    "Sales" = @("License-O365E3", "License-PowerBIPro")
    "Engineering" = @("License-O365E3", "License-VisioPlan1", "License-PlannerProjectPlan3")
    "Finance" = @("License-O365E3", "License-DynamicsBusinessCentralEssential")
}

if ($departmentMapping.ContainsKey($department)) {
    foreach ($groupName in $departmentMapping[$department]) {
        $group = Get-AzureADGroup -Filter "DisplayName eq '$groupName'"
        Add-AzureADGroupMember -ObjectId $group.ObjectId -RefObjectId $user.ObjectId
    }
}

Conclusion

Group-based licensing transforms license management from a manual, error-prone process to an automated, scalable solution. By implementing this approach, organizations can ensure consistent licensing, reduce administrative overhead, and improve compliance tracking.