Private/Users.ps1

function Get-M365SnapshotUsers {
    param(
        [Parameter(Mandatory=$true)]
        [hashtable]$GraphHeaders,

        [Parameter(Mandatory=$true)]
        [int]$EffectiveMaxUsers,

        [Parameter(Mandatory=$true)]
        [switch]$LoadAllUsers,

        [Parameter(Mandatory=$true)]
        [int]$MaxUsers,

        [Parameter(Mandatory=$true)]
        [switch]$ReturnObjects
    )

    $entraUsers = @()
    $userLastActivityByUpn = @{}
    $userActivityDataAvailable = $false
    try {
        $uri = "https://graph.microsoft.com/v1.0/users?`$select=displayName,userPrincipalName,accountEnabled,userType,assignedLicenses"
        $allUsers = @()

        do {
            $response = Invoke-RestMethod -Uri $uri `
                                          -Headers $GraphHeaders `
                                          -Method Get `
                                          -ErrorAction Stop

            if ($response.value) {
                $remaining = $EffectiveMaxUsers - $allUsers.Count
                if ($remaining -gt 0) {
                    $usersPage = @($response.value)
                    if ($usersPage.Count -gt $remaining) {
                        $allUsers += ($usersPage | Select-Object -First $remaining)
                    }
                    else {
                        $allUsers += $usersPage
                    }
                }
            }

            if ($allUsers.Count -ge $EffectiveMaxUsers) {
                $uri = $null
            }
            else {
                $uri = $response.'@odata.nextLink'
            }
        } while ($uri)

        try {
            $activeUsersUri = "https://graph.microsoft.com/v1.0/reports/getOffice365ActiveUserDetail(period='D30')"
            $activeUsersCsv = Invoke-RestMethod -Uri $activeUsersUri `
                                                -Headers $GraphHeaders `
                                                -Method Get `
                                                -ErrorAction Stop

            $userActivityDataAvailable = $true

            if (-not [string]::IsNullOrWhiteSpace([string]$activeUsersCsv)) {
                $activeUserRows = $activeUsersCsv | ConvertFrom-Csv
                foreach ($row in @($activeUserRows)) {
                    $upn = [string]$row.'User Principal Name'
                    if ([string]::IsNullOrWhiteSpace($upn)) {
                        continue
                    }

                    $lastActivityRaw = [string]$row.'Last Activity Date'
                    $lastActivityDisplay = '(unknown)'
                    if (-not [string]::IsNullOrWhiteSpace($lastActivityRaw)) {
                        $parsedLastActivity = [datetime]::MinValue
                        if ([datetime]::TryParse($lastActivityRaw, [ref]$parsedLastActivity)) {
                            $lastActivityDisplay = $parsedLastActivity.ToString('yyyy-MM-dd')
                        }
                        else {
                            $lastActivityDisplay = $lastActivityRaw
                        }
                    }

                    $userLastActivityByUpn[$upn.ToLower()] = $lastActivityDisplay
                }
            }
        }
        catch {
            if (-not $ReturnObjects) {
                Write-Host "[INFO] User activity report unavailable (Reports.Read.All may be missing): $($_.Exception.Message)" -ForegroundColor DarkGray
            }
        }

        $entraUsers = $allUsers | Select-Object @{N='DisplayName';E={$_.displayName}},
                                                @{N='UserPrincipalName';E={$_.userPrincipalName}},
                                                @{N='UserType';E={$_.userType}},
                                                @{N='AccountEnabled';E={$_.accountEnabled}},
                                                @{N='HasLicenses';E={$_.assignedLicenses.Count -gt 0}},
                                                @{N='LicenseCount';E={$_.assignedLicenses.Count}},
                                                @{N='LastActivityDate';E={
                                                    $upn = [string]$_.userPrincipalName
                                                    if ([string]::IsNullOrWhiteSpace($upn)) {
                                                        '(unknown)'
                                                    }
                                                    else {
                                                        $upnKey = $upn.ToLower()
                                                        if ($userLastActivityByUpn.ContainsKey($upnKey)) {
                                                            $userLastActivityByUpn[$upnKey]
                                                        }
                                                        else {
                                                            '(unknown)'
                                                        }
                                                    }
                                                }}

        if (-not $ReturnObjects) {
            Write-Host "[OK] Found $($entraUsers.Count) users`n" -ForegroundColor Green
            if (-not $LoadAllUsers -and $entraUsers.Count -ge $MaxUsers) {
                Write-Host "[INFO] User collection limited to $MaxUsers items. Use -LoadAllUsers to remove this limit.`n" -ForegroundColor DarkGray
            }
        }
    }
    catch {
        if (-not $ReturnObjects) {
            Write-Host "[WARNING] Could not collect users: $($_.Exception.Message)`n" -ForegroundColor Yellow
        }
    }

    return [PSCustomObject]@{
        Users = @($entraUsers)
        UserActivityDataAvailable = [bool]$userActivityDataAvailable
    }
}