Public/Get-M365UserLicenses.ps1

function Get-M365UserLicenses {
    <#
    .SYNOPSIS
        Retrieves user license assignments from Microsoft Graph.
    .OUTPUTS
        [PSCustomObject[]] One object per user with license, identity, and sign-in fields.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$AccessToken,

        [switch]$IncludeDisabledAccounts
    )

    # Build SKU cache for friendly names
    Write-M365Log "Building SKU reference cache..."
    $skuCache = @{}
    try {
        $skus = Invoke-M365GraphRequest -AccessToken $AccessToken -Uri "https://graph.microsoft.com/v1.0/subscribedSkus" -AllPages
        foreach ($sku in $skus) {
            $skuCache[$sku.skuId] = $sku.skuPartNumber
        }
        Write-M365Log "Cached $($skuCache.Count) SKUs"
    }
    catch {
        Write-M365Log "Warning: Could not retrieve SKUs: $_" -Level Warning
    }

    Write-M365Log "Retrieving users from Microsoft Graph..."
    # signInActivity requires Entra ID P1/P2 + AuditLog.Read.All (v1.0 supported, max $top=500)
    # See: https://learn.microsoft.com/graph/api/user-list?view=graph-rest-1.0#example-11
    $baseProperties = "id,userPrincipalName,displayName,mail,accountEnabled,userType,assignedLicenses,licenseAssignmentStates,department,jobTitle,usageLocation,createdDateTime"
    $urlWithSignIn = "https://graph.microsoft.com/v1.0/users?`$select=$baseProperties,signInActivity&`$top=500"

    $users = $null
    try {
        $users = Invoke-M365GraphRequest -AccessToken $AccessToken -Uri $urlWithSignIn -AllPages
        Write-M365Log "Retrieved $($users.Count) users from Graph (with signInActivity)"
    }
    catch {
        Write-M365Log "signInActivity failed ($_), retrying without it (tenant may lack Entra ID P1/P2)" -Level Warning
        $urlBasic = "https://graph.microsoft.com/v1.0/users?`$select=$baseProperties&`$top=999"
        $users = Invoke-M365GraphRequest -AccessToken $AccessToken -Uri $urlBasic -AllPages
        Write-M365Log "Retrieved $($users.Count) users from Graph (without signInActivity)"
    }

    $ingestionTime = (Get-Date).ToUniversalTime().ToString('o')

    foreach ($user in $users) {
        if (-not $IncludeDisabledAccounts -and -not $user.accountEnabled) {
            continue
        }

        [PSCustomObject]@{
            Id                      = $user.id
            UserPrincipalName       = $user.userPrincipalName
            DisplayName             = $user.displayName
            Mail                    = $user.mail
            AccountEnabled          = $user.accountEnabled
            UserType                = $user.userType
            AssignedLicenses        = if ($user.assignedLicenses) { ($user.assignedLicenses | ConvertTo-Json -Compress -Depth 5) } else { "[]" }
            LicenseAssignmentStates = if ($user.licenseAssignmentStates) { ($user.licenseAssignmentStates | ConvertTo-Json -Compress -Depth 5) } else { "[]" }
            Department              = $user.department
            JobTitle                = $user.jobTitle
            UsageLocation           = $user.usageLocation
            CreatedDateTime         = $user.createdDateTime
            SignInActivity          = if ($user.signInActivity) { ($user.signInActivity | ConvertTo-Json -Compress -Depth 5) } else { "{}" }
            IngestionTime           = $ingestionTime
        }
    }

    Write-M365Log "Emitted user license records to pipeline"
}