Providers/Entra/Public/Set-EntraLicense.ps1

function Set-EntraLicense {
    <#
    .EXTERNALHELP TestEnvironment-Help.xml
    .SYNOPSIS
        Assigns licences by group and directly, so the assignment path is ambiguous on purpose
    #>


    [CmdletBinding(SupportsShouldProcess)]
    [OutputType('EntraLicenseAssignment')]
    param(
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$SkuPartNumber,

        [Parameter()]
        [switch]$ShowProgress,

        [Parameter()]
        [switch]$PassThru
    )

    $connection = Get-EntraConnection
    $cache = @{}

    $skus = @((Invoke-EntraRequest -Method GET -Path '/subscribedSkus').value)
    if (-not $skus) {
        Write-Error "The tenant reports no subscribed SKUs, so nothing can be licensed." -ErrorAction Stop
        return
    }

    $available = @($skus | Where-Object { ($_.prepaidUnits.enabled - $_.consumedUnits) -gt 0 })

    if ($SkuPartNumber) {
        $sku = $skus | Where-Object { $_.skuPartNumber -eq $SkuPartNumber } | Select-Object -First 1
        if (-not $sku) {
            Write-Error ("The tenant has no SKU called '$SkuPartNumber'. Available: " +
                (($skus.skuPartNumber | Sort-Object) -join ', ')) -ErrorAction Stop
            return
        }
        $free = $sku.prepaidUnits.enabled - $sku.consumedUnits
        if ($free -le 0) {
            Write-Error ("SKU '$SkuPartNumber' has no free units ($($sku.consumedUnits) of " +
                "$($sku.prepaidUnits.enabled) consumed). Assigning from it would fail.") -ErrorAction Stop
            return
        }
    }
    else {
        if (-not $available) {
            Write-Warning ("No subscribed SKU has a free unit, so no licence can be assigned. The licensing " +
                "shapes will be absent from this environment; everything else still seeds.")
            return
        }
        # Prefer the free SKUs. They are the ones a tenant is most likely to have thousands
        # spare of, and consuming a paid seat for a lab account is a real cost.
        $preferred = 'FLOW_FREE', 'POWER_BI_STANDARD', 'TEAMS_EXPLORATORY', 'RMSBASIC'
        $sku = $available | Sort-Object @{ Expression = {
                $rank = $preferred.IndexOf($_.skuPartNumber)
                if ($rank -lt 0) { [int]::MaxValue } else { $rank }
            }
        } | Select-Object -First 1
    }

    Write-Verbose "Licensing with $($sku.skuPartNumber) ($($sku.skuId))"
    $results = [System.Collections.Generic.List[object]]::new()

    # Group-based first. Every member inherits it, which is the path the direct assignment
    # below then duplicates for one of them.
    $groupId = Resolve-EntraSeededId -Key 'lic-powerbi' -Kind Group -Cache $cache -Connection $connection
    if (-not $groupId) {
        Write-Warning "The seeded licence group does not exist, so no group-based assignment was made."
    }
    elseif ($PSCmdlet.ShouldProcess("Group lic-powerbi", "Assign $($sku.skuPartNumber)")) {
        Write-TestProgress -Activity 'Assigning licences' -Status 'group-based' -PercentComplete 33 -ShowProgress:$ShowProgress
        try {
            Invoke-EntraRequest -Method POST -Path "/groups/$groupId/assignLicense" -RetryOnNotFound -Body @{
                addLicenses    = @(@{ skuId = $sku.skuId; disabledPlans = @() })
                removeLicenses = @()
            } | Out-Null

            $results.Add([PSCustomObject]@{
                    PSTypeName    = 'EntraLicenseAssignment'
                    Target        = 'lic-powerbi'
                    TargetType    = 'Group'
                    SkuPartNumber = $sku.skuPartNumber
                    Path          = 'Group-based'
                    Succeeded     = $true
                    Detail        = 'Members inherit this licence'
                })
            Write-Verbose "Assigned $($sku.skuPartNumber) to the seeded licence group"
        }
        catch {
            Write-Warning "Group licence assignment failed: $($_.Exception.Message)"
        }
    }

    # Direct assignments. Priya duplicates what she already inherits; Owen fails because he
    # has no usageLocation, which is the point of him.
    $directTargets = @(
        [PSCustomObject]@{ Key = 'praghunathan'; Note = 'Also inherits this SKU from the group, so her assignment path is ambiguous' }
        [PSCustomObject]@{ Key = 'ofitzgerald'; Note = 'Has no usageLocation, so Entra refuses the assignment' }
    )

    $index = 0
    foreach ($target in $directTargets) {
        $index++
        Write-TestProgress -Activity 'Assigning licences' -Status $target.Key `
            -PercentComplete (33 + [int](67 * $index / $directTargets.Count)) -ShowProgress:$ShowProgress

        $userId = Resolve-EntraSeededId -Key $target.Key -Kind User -Cache $cache -Connection $connection
        if (-not $userId) {
            Write-Warning "Seeded user '$($target.Key)' does not exist; skipping its direct licence."
            continue
        }

        if (-not $PSCmdlet.ShouldProcess($target.Key, "Assign $($sku.skuPartNumber) directly")) { continue }

        try {
            Invoke-EntraRequest -Method POST -Path "/users/$userId/assignLicense" -RetryOnNotFound -Body @{
                addLicenses    = @(@{ skuId = $sku.skuId; disabledPlans = @() })
                removeLicenses = @()
            } | Out-Null

            $results.Add([PSCustomObject]@{
                    PSTypeName    = 'EntraLicenseAssignment'
                    Target        = $target.Key
                    TargetType    = 'User'
                    SkuPartNumber = $sku.skuPartNumber
                    Path          = 'Direct'
                    Succeeded     = $true
                    Detail        = $target.Note
                })
            Write-Verbose "Assigned $($sku.skuPartNumber) directly to '$($target.Key)'"
        }
        catch {
            # Owen is expected to land here. Reported rather than thrown, because a seeding
            # step that stops the run over a failure it was designed to produce is worse than
            # useless.
            Write-Warning "Direct licence assignment to '$($target.Key)' failed: $($_.Exception.Message)"
            $results.Add([PSCustomObject]@{
                    PSTypeName    = 'EntraLicenseAssignment'
                    Target        = $target.Key
                    TargetType    = 'User'
                    SkuPartNumber = $sku.skuPartNumber
                    Path          = 'Direct'
                    Succeeded     = $false
                    Detail        = $_.Exception.Message
                })
        }
    }

    Write-TestProgress -Activity 'Assigning licences' -Completed -ShowProgress:$ShowProgress

    if ($PassThru) { return $results.ToArray() }
}