Private/M365License.ps1

# M365 License - Private helpers

function New-HTMLReport {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Organization,

        [Parameter(Mandatory = $true)]
        [array]$Report,

        [Parameter(Mandatory = $true)]
        [array]$SubscriptionOverview,

        [Parameter(Mandatory = $false)]
        [string]$ExportPath
    )

    # Default ExportPath to current folder if not provided
    if (-not $ExportPath) {
        $safeOrganization = $Organization -replace '[\\/:*?"<>|]', '_'
        $ExportPath = Join-Path (Get-Location).Path "$safeOrganization-M365LicensingReport.html"
    }

    $exportDir = Split-Path -Path $ExportPath -Parent
    if ($exportDir -and -not (Test-Path $exportDir)) {
        New-Item -Path $exportDir -ItemType Directory -Force | Out-Null
    }

    # Calculate license counts for dashboard statistics
    $directLicenses = ($Report | Where-Object { $_.AssignmentType -eq "Direct" }).Count
    $inheritedLicenses = ($Report | Where-Object { $_.AssignmentType -eq "Inherited" }).Count
    $bothLicenses = ($Report | Where-Object { $_.AssignmentType -eq "Both" }).Count
    $DisabledUsersWithLicenses = ($Report | Where-Object { $_.AccountEnabled -eq "No" }).Count

    # Generate table rows for user licenses
    $tableRows = ""
    foreach ($item in $Report) {
        $accountStatusClass = if ($item.AccountEnabled -eq "No") { 'class="table-danger"' } else { '' }

        $assignmentTypeText = [System.Net.WebUtility]::HtmlEncode($item.AssignmentType)

        $accountStatus = if ($item.AccountEnabled -eq "Yes") {
            '<span class="rk-badge rk-badge-ok">Enabled</span>'
        }
        else {
            '<span class="rk-badge rk-badge-error">Disabled</span>'
        }

        $tableRows += @"
    <tr $accountStatusClass>
        <td>$([System.Net.WebUtility]::HtmlEncode($item.DisplayName))</td>
        <td>$([System.Net.WebUtility]::HtmlEncode($item.UserPrincipalName))</td>
        <td>$accountStatus</td>
        <td>$([System.Net.WebUtility]::HtmlEncode($item.LastSuccessfulSignIn))</td>
        <td>$([System.Net.WebUtility]::HtmlEncode($item.AssignedLicensesFriendlyName))</td>
        <td>$assignmentTypeText</td>
        <td>$([System.Net.WebUtility]::HtmlEncode($item.Inheritance))</td>
    </tr>
"@

    }

    # Generate table rows for subscription overview
    $subscriptionRows = ""
    foreach ($item in $SubscriptionOverview) {
        $availabilityPercentage = if ($item.TotalLicenses -ne 0) {
            [Math]::Round(($item.AvailableLicenses / $item.TotalLicenses) * 100)
        }
        else {
            0
        }

        $availabilityBadge = if ($availabilityPercentage -lt 10) {
            '<span class="rk-badge rk-badge-error">' + $item.AvailableLicenses + ' (' + $availabilityPercentage + '%)</span>'
        }
        elseif ($availabilityPercentage -lt 20) {
            '<span class="rk-badge rk-badge-warn">' + $item.AvailableLicenses + ' (' + $availabilityPercentage + '%)</span>'
        }
        else {
            '<span class="rk-badge rk-badge-ok">' + $item.AvailableLicenses + ' (' + $availabilityPercentage + '%)</span>'
        }

        $licenseStatusBadge = if ($item.LicenseStatus -eq "Enabled") {
            '<span class="rk-badge rk-badge-ok">Enabled</span>'
        }
        else {
            '<span class="rk-badge rk-badge-error">Disabled</span>'
        }

        $subscriptionRows += @"
    <tr>
        <td>$([System.Net.WebUtility]::HtmlEncode($item.FriendlyName))</td>
        <td>$([System.Net.WebUtility]::HtmlEncode($item.CreatedDate))</td>
        <td>$([System.Net.WebUtility]::HtmlEncode($item.EndDate))</td>
        <td>$licenseStatusBadge</td>
        <td>$($item.ConsumedUnits)</td>
        <td>$($item.TotalLicenses)</td>
        <td>$availabilityBadge</td>
    </tr>
"@

    }

    # Generate table rows for disabled users (users with AccountEnabled = No that have licenses)
    $disabledUsersRows = ""
    $disabledUsers = $Report | Where-Object { $_.AccountEnabled -eq "No" }
    foreach ($item in $disabledUsers) {
        $assignmentTypeText = [System.Net.WebUtility]::HtmlEncode($item.AssignmentType)

        $disabledUsersRows += @"
    <tr>
        <td>$([System.Net.WebUtility]::HtmlEncode($item.DisplayName))</td>
        <td>$([System.Net.WebUtility]::HtmlEncode($item.UserPrincipalName))</td>
        <td><span class="rk-badge rk-badge-error">Disabled</span></td>
        <td>$([System.Net.WebUtility]::HtmlEncode($item.LastSuccessfulSignIn))</td>
        <td>$([System.Net.WebUtility]::HtmlEncode($item.AssignedLicensesFriendlyName))</td>
        <td>$assignmentTypeText</td>
        <td>$([System.Net.WebUtility]::HtmlEncode($item.Inheritance))</td>
    </tr>
"@

    }

    # Get current date for report
    $CurrentDate = (Get-Date).ToString('yyyy-MM-dd HH:mm:ss')

    # Build stat tiles HTML
    $statsCardsHtml = @"
            <div class="rk-stat-tile t-rust">
                <div class="rk-stat-eyebrow">DIRECT</div>
                <div class="rk-stat-number">$directLicenses</div>
                <div class="rk-stat-caption">Direct license assignments</div>
            </div>
            <div class="rk-stat-tile t-olive">
                <div class="rk-stat-eyebrow">INHERITED</div>
                <div class="rk-stat-number">$inheritedLicenses</div>
                <div class="rk-stat-caption">Group-based assignments</div>
            </div>
            <div class="rk-stat-tile t-steel">
                <div class="rk-stat-eyebrow">BOTH</div>
                <div class="rk-stat-number">$bothLicenses</div>
                <div class="rk-stat-caption">Direct + Inherited</div>
            </div>
            <div class="rk-stat-tile t-rose">
                <div class="rk-stat-eyebrow">DISABLED</div>
                <div class="rk-stat-number">$DisabledUsersWithLicenses</div>
                <div class="rk-stat-caption">Disabled users with licenses</div>
            </div>
"@


    # Build body content HTML (tabs + panels + filter containers + tables + script)
    $bodyContentHtml = @"
    <!-- Tab Navigation -->
    <div class="rk-tabs">
        <button class="rk-tab active" data-target="panel-license-assignment">License Assignment</button>
        <button class="rk-tab" data-target="panel-subscription-overview">Subscription Overview</button>
        <button class="rk-tab" data-target="panel-disabled-users">Disabled Users</button>
    </div>

    <!-- License Assignment Panel -->
    <div id="panel-license-assignment" class="rk-panel active">
        <div class="rk-filter-bar">
            <span>Filters:</span>
            <select id="accountStatusFilter" class="form-select" style="max-width:180px;">
                <option value="">All Accounts</option>
                <option value="Enabled">Enabled</option>
                <option value="Disabled">Disabled</option>
            </select>
            <select id="assignmentTypeFilter" class="form-select" style="max-width:180px;">
                <option value="">All Types</option>
                <option value="Direct">Direct</option>
                <option value="Inherited">Inherited</option>
                <option value="Both">Both</option>
            </select>
            <select id="licenseNameFilter" class="form-select" style="max-width:220px;">
                <option value="">All Licenses</option>
            </select>
            <button class="rk-filter-chip" onclick="clearLicenseFilters()">Clear</button>
        </div>
        <div class="rk-card">
            <div class="rk-card-header">
                <span>License Assignment</span>
                <div class="rk-show-all">
                    <label class="rk-toggle-switch">
                        <input type="checkbox" id="licensesShowAllToggle">
                        <span class="rk-toggle-slider"></span>
                    </label>
                    <span>Show all</span>
                </div>
            </div>
            <div class="rk-card-body">
                <table id="licensesTable" class="table table-bordered" style="width:100%">
                    <thead>
                        <tr>
                            <th>Display Name</th>
                            <th>User Principal Name</th>
                            <th>Account Status</th>
                            <th>Last Successful Sign In</th>
                            <th>License</th>
                            <th>Assignment Type</th>
                            <th>Inheritance Details</th>
                        </tr>
                    </thead>
                    <tbody>
                        $tableRows
                    </tbody>
                </table>
            </div>
        </div>
    </div>

    <!-- Subscription Overview Panel -->
    <div id="panel-subscription-overview" class="rk-panel">
        <div class="rk-card">
            <div class="rk-card-header">
                <span>Subscription Overview</span>
                <div class="rk-show-all">
                    <label class="rk-toggle-switch">
                        <input type="checkbox" id="subscriptionShowAllToggle">
                        <span class="rk-toggle-slider"></span>
                    </label>
                    <span>Show all</span>
                </div>
            </div>
            <div class="rk-card-body">
                <table id="subscriptionTable" class="table table-bordered" style="width:100%">
                    <thead>
                        <tr>
                            <th>Subscription</th>
                            <th>Created Date</th>
                            <th>End Date</th>
                            <th>License Status</th>
                            <th>Consumed Units</th>
                            <th>Total Licenses</th>
                            <th>Available Licenses</th>
                        </tr>
                    </thead>
                    <tbody>
                        $subscriptionRows
                    </tbody>
                </table>
            </div>
        </div>
    </div>

    <!-- Disabled Users Panel -->
    <div id="panel-disabled-users" class="rk-panel">
        <div class="rk-card">
            <div class="rk-card-header">
                <span>Disabled Users with Licenses</span>
                <div class="rk-show-all">
                    <label class="rk-toggle-switch">
                        <input type="checkbox" id="disabledUsersShowAllToggle">
                        <span class="rk-toggle-slider"></span>
                    </label>
                    <span>Show all</span>
                </div>
            </div>
            <div class="rk-card-body">
                <table id="disabledUsersTable" class="table table-bordered" style="width:100%">
                    <thead>
                        <tr>
                            <th>Display Name</th>
                            <th>User Principal Name</th>
                            <th>Account Status</th>
                            <th>Last Successful Sign In</th>
                            <th>License</th>
                            <th>Assignment Type</th>
                            <th>Inheritance Details</th>
                        </tr>
                    </thead>
                    <tbody>
                        $disabledUsersRows
                    </tbody>
                </table>
            </div>
        </div>
    </div>

    <script>
    `$(document).ready(function() {
        // Initialize all tables using the shared helper
        var licensesTable = initRKTable('#licensesTable');
        var subscriptionTable = initRKTable('#subscriptionTable');
        var disabledUsersTable = initRKTable('#disabledUsersTable');

        // Populate license name filter dropdown
        function populateLicenseFilter() {
            var values = [...new Set(licensesTable.column(4).data().toArray())].sort();
            var select = `$('#licenseNameFilter');
            values.forEach(function(value) {
                if (value && value.toString().trim() !== '') {
                    // Strip HTML tags for display
                    var text = value.replace(/<[^>]*>/g, '').trim();
                    if (text) {
                        select.append($('<option>').val(text).text(text));
                    }
                }
            });
        }

        // Custom filtering for the licenses table
        `$.fn.dataTable.ext.search.push(function(settings, data, dataIndex) {
            if (settings.nTable.id !== 'licensesTable') return true;

            var accountStatus = `$('#accountStatusFilter').val();
            var assignmentType = `$('#assignmentTypeFilter').val();
            var licenseName = `$('#licenseNameFilter').val();

            var rowAccountStatus = data[2];
            var rowAssignmentType = data[5];
            var rowLicenseName = data[4];

            if (accountStatus && rowAccountStatus.indexOf(accountStatus) === -1) return false;
            if (assignmentType && rowAssignmentType.indexOf(assignmentType) === -1) return false;
            if (licenseName && rowLicenseName.indexOf(licenseName) === -1) return false;

            return true;
        });

        // Apply filters on change
        `$('#accountStatusFilter, #assignmentTypeFilter, #licenseNameFilter').on('change', function() {
            licensesTable.draw();
        });

        // Clear filters
        window.clearLicenseFilters = function() {
            `$('#accountStatusFilter, #assignmentTypeFilter, #licenseNameFilter').val('');
            licensesTable.search('').columns().search('').draw();
        };

        // Show all toggle for licenses table
        `$('#licensesShowAllToggle').on('change', function() {
            licensesTable.page.len(`$(this).is(':checked') ? -1 : 10).draw();
        });

        // Show all toggle for subscription table
        `$('#subscriptionShowAllToggle').on('change', function() {
            subscriptionTable.page.len(`$(this).is(':checked') ? -1 : 10).draw();
        });

        // Show all toggle for disabled users table
        `$('#disabledUsersShowAllToggle').on('change', function() {
            disabledUsersTable.page.len(`$(this).is(':checked') ? -1 : 10).draw();
        });

        // Populate filters after tables are initialized
        setTimeout(function() {
            populateLicenseFilter();
        }, 100);
    });
    </script>
"@


    # Report-specific CSS
    $customCss = @"
    .rk-filter-bar .form-select {
        font-family: 'Geist Mono', ui-monospace, monospace;
        font-size: 0.75rem;
        padding: 4px 8px;
        border-radius: 6px;
    }
    .table-danger td {
        background-color: rgba(192, 57, 43, 0.08) !important;
    }
    [data-theme="dark"] .table-danger td {
        background-color: rgba(224, 96, 80, 0.1) !important;
    }
"@


    # Generate the full HTML report using the shared template
    $htmlContent = Get-RKSolutionsReportTemplate `
        -TenantName $Organization `
        -ReportTitle 'License' `
        -ReportSlug 'm365-licenses' `
        -Eyebrow 'M365 LICENSE ASSIGNMENT' `
        -Lede 'License assignment overview including direct, inherited, and disabled user assignments.' `
        -StatsCardsHtml $statsCardsHtml `
        -BodyContentHtml $bodyContentHtml `
        -CustomCss $customCss `
        -ReportDate $CurrentDate `
        -Tags @('M365', 'Licensing', 'Entra ID')

    # Export to HTML file
    $htmlContent | Out-File -FilePath $ExportPath -Encoding utf8

    # Set script-scoped variable for email attachment
    $script:ExportPath = $ExportPath

    Write-Host "All actions completed successfully." -ForegroundColor Cyan
    Write-Host "Report saved to: $ExportPath" -ForegroundColor Cyan

    # Open the HTML file (cross-platform: Invoke-Item uses default handler; fallback so script does not fail in headless env)
    if (-not $SendEmail) {
        try {
            Invoke-Item $ExportPath -ErrorAction Stop
        } catch {
            Write-Host "Report saved to: $ExportPath (could not open automatically)." -ForegroundColor Yellow
        }
    }
}

function Get-LicenseIdentifiers {
    $header = 'Product_Display_Name', 'String_Id', 'GUID', 'Service_Plan_Name', 'Service_Plan_Id', 'Service_Plans_Included_Friendly_Names'
    $params = @{
        Method = 'Get'
        Uri    = "https://download.microsoft.com/download/e/3/e/e3e9faf2-f28b-490a-9ada-c6089a1fc5b0/Product%20names%20and%20service%20plan%20identifiers%20for%20licensing.csv"
    }
    $Identifiers = Invoke-RestMethod @params | ConvertFrom-Csv -Header $header |
    ForEach-Object {
        [PSCustomObject]@{
            GUID                 = $_.GUID
            String_Id            = $_.String_Id
            Product_Display_Name = $_.Product_Display_Name
        }
    }
    return $Identifiers | Select-Object -Skip 1
}


function Invoke-M365LicenseReportCore {
    param(
        [Parameter(Mandatory=$false)] [switch] $SendEmail,
        [Parameter(Mandatory=$false)] [string[]] $Recipient,
        [Parameter(Mandatory=$false)] [string] $From,
        [Parameter(Mandatory=$false)] [string] $ExportPath
    )
        # CODE

        # Get Organization Name
        $Organization = Invoke-MgGraphRequest -Uri "beta/organization" -OutputType PSObject | Select-Object -Expand Value | Select-Object -ExpandProperty DisplayName

        # Get product identifiers
        $Identifiers = Get-LicenseIdentifiers

        # Select all SKUs with friendly display name
        [array]$SKU_friendly = $Identifiers | Select-Object GUID, String_Id, Product_Display_Name -Unique

        # NEW CLOUD LICENSING API: Get allotments with subscription details in one call (Beta API)
        # This replaces the previous two separate calls to subscribedSkus and directory/subscriptions
        Write-Host "INFO: Retrieving allotment and subscription data using Cloud Licensing API..." -ForegroundColor Cyan

        try {
            # Try new Cloud Licensing API first
            # Note: subscriptions is included by default, no need to expand
            [Array]$allotments = Invoke-GraphRequestWithPaging -Uri "beta/admin/cloudLicensing/allotments?`$select=id,allottedUnits,consumedUnits,skuId,skuPartNumber,assignableTo,subscriptions"

            if (-not $allotments -or $allotments.Count -eq 0) {
                throw "Allotments API returned empty results"
            }

            $useCloudLicensingAPI = $true
            Write-Host "INFO: Successfully retrieved data from Cloud Licensing API" -ForegroundColor Green

            # Diagnostic: Show what properties are available in first subscription (if verbose)
            if ($VerbosePreference -eq 'Continue' -and $allotments.Count -gt 0) {
                $firstAllotment = $allotments[0]
                if ($firstAllotment.subscriptions -and $firstAllotment.subscriptions.Count -gt 0) {
                    $firstSub = $firstAllotment.subscriptions[0]
                    Write-Verbose "Subscription properties available: $($firstSub.PSObject.Properties.Name -join ', ')"
                }
            }

            # Always show diagnostic info about subscription structure (helps with troubleshooting)
            Write-Host "INFO: Found $($allotments.Count) allotments" -ForegroundColor Cyan
            $totalSubscriptions = ($allotments | ForEach-Object { if ($_.subscriptions) { $_.subscriptions.Count } else { 0 } } | Measure-Object -Sum).Sum
            Write-Host "INFO: Total subscriptions across all allotments: $totalSubscriptions" -ForegroundColor Cyan

            # Supplementary call to get dates from legacy API if needed
            # The allotments API includes startDate and nextLifecycleDate, but we fetch
            # from legacy API as a fallback in case subscription IDs don't match perfectly
            Write-Host "INFO: Retrieving subscription dates as fallback..." -ForegroundColor Cyan
            [Array]$LegacySubscriptions = Invoke-MgGraphRequest -Uri "beta/directory/subscriptions?`$select=id,createdDateTime,nextLifecycleDateTime,skuId" -OutputType PSObject |
                Select-Object -ExpandProperty Value

            # Create lookup table for quick access to both created and end dates
            $subscriptionDateLookup = @{}
            foreach ($legacySub in $LegacySubscriptions) {
                if ($legacySub.id) {
                    $subscriptionDateLookup[$legacySub.id] = @{
                        CreatedDate = $legacySub.createdDateTime
                        EndDate = $legacySub.nextLifecycleDateTime
                    }
                }
            }

            Write-Host "INFO: Created lookup table with $($subscriptionDateLookup.Count) subscription dates" -ForegroundColor Cyan
            if ($VerbosePreference -eq 'Continue' -and $subscriptionDateLookup.Count -gt 0) {
                Write-Verbose "Sample lookup IDs: $(($subscriptionDateLookup.Keys | Select-Object -First 3) -join ', ')"
                $firstId = $subscriptionDateLookup.Keys | Select-Object -First 1
                if ($firstId) {
                    Write-Verbose "Sample data for ID $firstId - CreatedDate: $($subscriptionDateLookup[$firstId].CreatedDate), EndDate: $($subscriptionDateLookup[$firstId].EndDate)"
                }
            }

            # Show how many subscriptions have end dates
            $subsWithEndDates = ($LegacySubscriptions | Where-Object { $_.nextLifecycleDateTime }).Count
            $subsWithoutEndDates = $LegacySubscriptions.Count - $subsWithEndDates
            Write-Host "INFO: Subscriptions with end dates: $subsWithEndDates, without end dates: $subsWithoutEndDates" -ForegroundColor Cyan
        }
        catch {
            # Fallback to legacy API if Cloud Licensing API fails
            Write-Host "WARNING: Cloud Licensing API failed, falling back to legacy API. Error: $($_.Exception.Message)" -ForegroundColor Yellow
            $useCloudLicensingAPI = $false

            # Legacy API calls
            [Array]$Skus = Invoke-MgGraphRequest -Uri "Beta/subscribedSkus" -OutputType PSObject |
            Select-Object -ExpandProperty Value
            [Array]$Subscriptions = Invoke-MgGraphRequest -Uri "beta/directory/subscriptions" -OutputType PSObject |
            Select-Object -ExpandProperty Value
        }

        # Create an overview of subscriptions with their end date
        $SubscriptionOverview = [System.Collections.Generic.List[PSObject]]::new()

        if ($useCloudLicensingAPI) {
            # NEW: Process allotments from Cloud Licensing API
            $datesFoundCount = 0
            $datesNotFoundCount = 0

            # Group allotments by SKU to combine duplicate licenses
            $allotmentsBySkuId = @{}

            foreach ($allotment in $allotments) {
                # Get friendly name
                $friendlyName = $SKU_friendly | Where-Object { $_.GUID -eq $allotment.skuId } |
                    Select-Object -ExpandProperty Product_Display_Name -ErrorAction SilentlyContinue

                if (-not $friendlyName) {
                    $friendlyName = if ($allotment.skuPartNumber) { $allotment.skuPartNumber } else { "Unknown License ($($allotment.skuId))" }
                }

                # Initialize SKU group if not exists
                if (-not $allotmentsBySkuId.ContainsKey($allotment.skuId)) {
                    $allotmentsBySkuId[$allotment.skuId] = @{
                        FriendlyName = $friendlyName
                        SKUPartNumber = $allotment.skuPartNumber
                        AssignableTo = $allotment.assignableTo
                        TotalLicenses = 0
                        ConsumedUnits = 0
                        CreatedDates = @()
                        EndDates = @()
                        SubscriptionIds = @()
                    }
                }

                # Aggregate license counts
                $allotmentsBySkuId[$allotment.skuId].TotalLicenses += if ($allotment.allottedUnits) { $allotment.allottedUnits } else { 0 }
                $allotmentsBySkuId[$allotment.skuId].ConsumedUnits += if ($allotment.consumedUnits) { $allotment.consumedUnits } else { 0 }

                # Process subscriptions to collect dates
                if ($allotment.subscriptions -and $allotment.subscriptions.Count -gt 0) {
                    foreach ($subscription in $allotment.subscriptions) {
                        if ($subscription.id) {
                            $allotmentsBySkuId[$allotment.skuId].SubscriptionIds += $subscription.id
                        }

                        # Resolve created/start date
                        $subCreated = $null
                        if ($subscription.startDate) { $subCreated = $subscription.startDate }
                        elseif ($subscription.createdDateTime) { $subCreated = $subscription.createdDateTime }
                        elseif ($subscription.createdDate) { $subCreated = $subscription.createdDate }
                        elseif ($subscriptionDateLookup -and $subscription.id -and $subscriptionDateLookup.ContainsKey($subscription.id)) {
                            $subCreated = $subscriptionDateLookup[$subscription.id].CreatedDate
                        }

                        if ($subCreated) {
                            $datesFoundCount++
                            $d = try { [DateTime]$subCreated } catch { $null }
                            if ($d) {
                                $allotmentsBySkuId[$allotment.skuId].CreatedDates += $d
                            }
                        } else {
                            $datesNotFoundCount++
                        }

                        # Resolve end/lifecycle date
                        $subEnd = $null
                        if ($subscription.nextLifecycleDate) { $subEnd = $subscription.nextLifecycleDate }
                        elseif ($subscription.nextLifecycleDateTime) { $subEnd = $subscription.nextLifecycleDateTime }
                        elseif ($subscription.endDate) { $subEnd = $subscription.endDate }
                        elseif ($subscription.expiryDate) { $subEnd = $subscription.expiryDate }
                        elseif ($subscriptionDateLookup -and $subscription.id -and $subscriptionDateLookup.ContainsKey($subscription.id)) {
                            $subEnd = $subscriptionDateLookup[$subscription.id].EndDate
                        }

                        if ($subEnd -and $subEnd -ne "No end date found") {
                            $e = try { [DateTime]$subEnd } catch { $null }
                            if ($e) {
                                $allotmentsBySkuId[$allotment.skuId].EndDates += $e
                            }
                        }
                    }
                }
            }

            # Now create subscription overview with one row per SKU
            foreach ($skuId in $allotmentsBySkuId.Keys) {
                $skuData = $allotmentsBySkuId[$skuId]

                # Get earliest created date
                $createdDate = if ($skuData.CreatedDates.Count -gt 0) {
                    ($skuData.CreatedDates | Measure-Object -Minimum).Minimum
                } else { $null }

                $formattedCreatedDate = if ($createdDate) {
                    try { Get-Date $createdDate -Format "dd-MM-yyyy HH:mm" }
                    catch { $createdDate.ToString() }
                } else { "Unknown" }

                # Get latest end date
                $endDate = if ($skuData.EndDates.Count -gt 0) {
                    ($skuData.EndDates | Measure-Object -Maximum).Maximum
                } else { $null }

                if (-not $endDate) {
                    $endDate = "No end date found"
                }

                $formattedEndDate = if ($endDate -ne "No end date found") {
                    try { Get-Date $endDate -Format "dd-MM-yyyy HH:mm" }
                    catch { $endDate }
                } else { $endDate }

                # Determine license status
                $licenseStatus = "Enabled"
                if ($endDate -ne "No end date found") {
                    try {
                        $dateObj = [DateTime]$endDate
                        $licenseStatus = if ($dateObj -gt (Get-Date)) { "Enabled" } else { "Disabled" }
                    } catch {
                        $licenseStatus = "Unknown"
                    }
                }

                $availableLicenses = $skuData.TotalLicenses - $skuData.ConsumedUnits

                $SubscriptionOverview.Add([PSCustomObject]@{
                    SubscriptionId    = ($skuData.SubscriptionIds | Select-Object -First 1)
                    FriendlyName      = $skuData.FriendlyName
                    SKUPartNumber     = $skuData.SKUPartNumber
                    CreatedDate       = $formattedCreatedDate
                    EndDate           = $formattedEndDate
                    LicenseStatus     = $licenseStatus
                    ConsumedUnits     = $skuData.ConsumedUnits
                    TotalLicenses     = $skuData.TotalLicenses
                    AvailableLicenses = $availableLicenses
                    AssignableTo      = $skuData.AssignableTo
                })
            }

            # Show summary of date matching
            Write-Host "INFO: Created dates - Found: $datesFoundCount, Not Found: $datesNotFoundCount" -ForegroundColor Cyan
        }
        else {
            # LEGACY: Process subscriptions from old API
            foreach ($subscription in $Subscriptions) {
                $sku = $Skus | Where-Object { $_.SkuId -eq $subscription.SkuId }
                $friendlyName = $SKU_friendly | Where-Object { $_.GUID -eq $sku.SkuId } |
                Select-Object -ExpandProperty Product_Display_Name -ErrorAction SilentlyContinue

                if (-not $friendlyName) {
                    $friendlyName = "Unknown License ($($sku.SkuId))"
                }

                $endDate = if ($null -eq $subscription.NextLifecycleDateTime) {
                    "No end date found"
                } else {
                    $subscription.NextLifecycleDateTime
                }

                # Format dates
                $formattedCreatedDate = if ($subscription.CreatedDateTime -is [DateTime]) {
                    Get-Date $subscription.CreatedDateTime -Format "dd-MM-yyyy HH:mm"
                } elseif ($subscription.CreatedDateTime) {
                    try {
                        Get-Date $subscription.CreatedDateTime -Format "dd-MM-yyyy HH:mm"
                    } catch {
                        $subscription.CreatedDateTime
                    }
                } else {
                    "Unknown"
                }

                $formattedEndDate = if ($endDate -is [DateTime]) {
                    Get-Date $endDate -Format "dd-MM-yyyy HH:mm"
                } elseif ($endDate -and $endDate -ne "No end date found") {
                    try {
                        Get-Date $endDate -Format "dd-MM-yyyy HH:mm"
                    } catch {
                        $endDate
                    }
                } else {
                    $endDate
                }

                # Determine license status
                $licenseStatus = if ($endDate -eq "No end date found") {
                    "Enabled"
                } elseif ($endDate -is [DateTime] -and $endDate -gt (Get-Date)) {
                    "Enabled"
                } elseif ($endDate -ne "No end date found") {
                    try {
                        $dateObj = [DateTime]$endDate
                        if ($dateObj -gt (Get-Date)) {
                            "Enabled"
                        } else {
                            "Disabled"
                        }
                    } catch {
                        "Unknown"
                    }
                } else {
                    "Unknown"
                }

                # Calculate available licenses
                $totalLicenses = if ($subscription.TotalLicenses) { $subscription.TotalLicenses } else { 0 }
                $consumedUnits = if ($sku.ConsumedUnits) { $sku.ConsumedUnits } else { 0 }
                $availableLicenses = $totalLicenses - $consumedUnits

                $SubscriptionOverview.Add([PSCustomObject]@{
                    SubscriptionId    = $subscription.Id
                    FriendlyName      = $friendlyName
                    CreatedDate       = $formattedCreatedDate
                    EndDate           = $formattedEndDate
                    LicenseStatus     = $licenseStatus
                    ConsumedUnits     = $consumedUnits
                    TotalLicenses     = $totalLicenses
                    AvailableLicenses = $availableLicenses
                })
            }
        }

        # Output the overview
        Write-Host "INFO: Generating subscription overview..." -ForegroundColor Cyan

        # Get all users with licenses - using paging to ensure all results are retrieved
        Write-Host "INFO: Retrieving user license data..." -ForegroundColor Cyan
        $users = Invoke-GraphRequestWithPaging -Uri "beta/users?`$select=UserPrincipalName,LicenseAssignmentStates,DisplayName,AccountEnabled,AssignedLicenses,signInActivity&`$top=999"

        # Get all groups with their licenses
        Write-Host "INFO: Retrieving group license data..." -ForegroundColor Cyan
        $Groups = Invoke-GraphRequestWithPaging -Uri "beta/groups?`$select=id,displayName,assignedLicenses&`$top=999"
        $groupsWithLicenses = @()

        # Loop through each group and check if it has any licenses assigned
        Write-Host "INFO: Checking groups for licenses..." -ForegroundColor Cyan
        foreach ($group in $Groups) {
            if ($group.assignedLicenses -and $group.assignedLicenses.Count -gt 0) {
                $groupData = [PSCustomObject]@{
                    ObjectId    = $group.id
                    DisplayName = $group.displayName
                    Licenses    = $group.assignedLicenses
                }
                $groupsWithLicenses += $groupData
            }
        }

        # Initialize the report collection
        $Report = [System.Collections.Generic.List[PSObject]]::new()

        # Process user license data
        $totalUsers = $users.Count
        $currentIndex = 0

        foreach ($user in $users) {
            $currentIndex++
            Write-Progress -Activity "Processing users" -Status "Processing $currentIndex of $totalUsers" -PercentComplete (($currentIndex / $totalUsers) * 100)

            # Skip users with no license assignment states
            if (-not $user.LicenseAssignmentStates) {
                continue
            }

            # Group licenses by SkuId to detect both direct and inherited assignments
            $licensesBySkuId = @{}

            foreach ($license in $user.LicenseAssignmentStates) {
                $SkuId = $license.SkuId
                $AssignedByGroup = $license.AssignedByGroup

                if (-not $licensesBySkuId.ContainsKey($SkuId)) {
                    $licensesBySkuId[$SkuId] = @{
                        DirectAssignment = $false
                        GroupAssignments = @()
                    }
                }

                if ($null -eq $AssignedByGroup) {
                    $licensesBySkuId[$SkuId].DirectAssignment = $true
                }
                else {
                    $licensesBySkuId[$SkuId].GroupAssignments += $AssignedByGroup
                }
            }

            # Process each unique license
            foreach ($SkuId in $licensesBySkuId.Keys) {
                $licenseInfo = $licensesBySkuId[$SkuId]
                $isDirect = $licenseInfo.DirectAssignment
                $isInherited = ($licenseInfo.GroupAssignments.Count -gt 0)

                # Determine assignment type
                $assignmentType = if ($isDirect -and $isInherited) {
                    "Both"
                }
                elseif ($isDirect) {
                    "Direct"
                }
                elseif ($isInherited) {
                    "Inherited"
                }
                else {
                    "Unknown"
                }

                # Get friendly name for the license
                $friendlyName = $SKU_friendly | Where-Object { $_.GUID -eq $SkuId } |
                Select-Object -ExpandProperty Product_Display_Name -ErrorAction SilentlyContinue

                if (-not $friendlyName) {
                    $friendlyName = "Unknown License ($SkuId)"
                }

                # Get group names if inherited
                $groupNames = ""
                if ($isInherited) {
                    $groupNamesList = @()
                    foreach ($groupId in $licenseInfo.GroupAssignments) {
                        $group = $groupsWithLicenses | Where-Object { $_.ObjectId -eq $groupId }
                        if ($group) {
                            $groupNamesList += $group.DisplayName
                        }
                        else {
                            $groupNamesList += "Unknown Group ($groupId)"
                        }
                    }
                    $groupNames = $groupNamesList -join ", "
                }

                # Determine inheritance description
                if ($isDirect -and -not $groupNames) {
                    $inheritance = "Direct"
                }
                elseif (-not $isDirect -and $groupNames) {
                    $inheritance = $groupNames
                }
                elseif ($isDirect -and $groupNames) {
                    $inheritance = "Direct, $groupNames"
                }
                else {
                    $inheritance = "Unknown"
                }

                # Last Login Activity (robust handling of null/invalid values)
                $lastSignIn = ConvertTo-DateString -Value $user.signInActivity.lastSignInDateTime
                if ($lastSignIn -eq "No sign-in activity" -or $lastSignIn -eq "Invalid date value") {
                    $lastSignIn = ConvertTo-DateString -Value $user.signInActivity.lastSuccessfulSignInDateTime
                }

                # Create the license data object
                $licenseData = [PSCustomObject]@{
                    UserPrincipalName            = $user.UserPrincipalName
                    DisplayName                  = $user.DisplayName
                    AccountEnabled               = if ($user.AccountEnabled) { "Yes" } else { "No" }
                    LastSuccessfulSignIn = $lastSignIn
                    AssignedLicenses             = $SkuId
                    AssignedLicensesFriendlyName = $friendlyName
                    Inheritance                  = $inheritance
                    AssignmentType               = $assignmentType
                    IsDirect                     = $isDirect
                    IsInherited                  = $isInherited
                }

                # Add to the report
                $Report.Add($licenseData)
            }
        }


        # Calculate metrics for summary boxes
        $script:directLicenses = ($Report | Where-Object { $_.IsDirect -eq $true -and $_.IsInherited -eq $false }).Count
        $script:inheritedLicenses = ($Report | Where-Object { $_.IsInherited -eq $true -and $_.IsDirect -eq $false }).Count
        $script:bothLicenses = ($Report | Where-Object { $_.IsDirect -eq $true -and $_.IsInherited -eq $true }).Count
        $script:DisabledUsersWithLicenses = ($Report | Where-Object { $_.AccountEnabled -eq "No" } | Select-Object -Unique UserPrincipalName).Count

        # Output summary information
        Write-Host "INFO: License Summary:" -ForegroundColor Cyan
        Write-Host "Total users processed: $totalUsers" -ForegroundColor White
        Write-Host "Users with licenses: $($Report | Select-Object -Unique UserPrincipalName | Measure-Object | Select-Object -ExpandProperty Count)" -ForegroundColor White
        Write-Host "Direct license assignments: $script:directLicenses" -ForegroundColor White
        Write-Host "Inherited license assignments: $script:inheritedLicenses" -ForegroundColor White
        Write-Host "Both direct and inherited: $script:bothLicenses" -ForegroundColor White
        Write-Host "Disabled users with licenses: $script:DisabledUsersWithLicenses" -ForegroundColor White

        # Export to HTML
        New-HTMLReport -Organization $Organization -Report $Report -SubscriptionOverview $SubscriptionOverview -ExportPath $ExportPath

        # Send email with the report
        $emailSent = $false
        if ($SendEmail) {
            $subject = "$Organization - Microsoft 365 License Assignment Report"
            $bodyHtml = "<html><body style='font-family: Segoe UI, Arial, sans-serif;'><h2>Microsoft 365 License Assignment Report</h2><p>Attached is the latest Microsoft 365 license assignment report for $Organization.</p><p>Open the attached HTML in a browser for the full report.</p><p style='color:#666;'>Generated by RKSolutions - please do not reply.</p></body></html>"
            $emailSent = Send-EmailWithAttachment -Recipient $Recipient -AttachmentPath $script:ExportPath -From $From -Subject $subject -BodyHtml $bodyHtml

            if ($emailSent) {
                Write-Host "INFO: Email sent successfully." -ForegroundColor Green
            }
            else {
                Write-Host "ERROR: Failed to send email." -ForegroundColor Red
            }
        }
        else {
            Write-Host "INFO: Email sending is disabled. Set -SendEmail to `$true to enable." -ForegroundColor Yellow
        }

        # Clean up the report file
        if ($SendEmail -and $emailSent) {
            if (Test-Path -Path $script:ExportPath) {
                Remove-Item -Path $script:ExportPath -Force
                Write-Host "INFO: Temporary report file deleted." -ForegroundColor Green
            } else {
                Write-Host "INFO: No temporary report file found to delete." -ForegroundColor Yellow
            }
        }
}