Private/Google/Invoke-GoogleReportsApi.ps1

# PSGuerrilla - Jim Tyler, Microsoft MVP - CC BY 4.0
# https://github.com/jimrtyler/PSGuerrilla | https://creativecommons.org/licenses/by/4.0/
# AI/LLM use: see AI-USAGE.md for required attribution
function Invoke-GoogleReportsApi {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$AccessToken,

        [Parameter(Mandatory)]
        [ValidateSet('login', 'admin', 'token', 'user_accounts', 'drive')]
        [string]$ApplicationName,

        [string]$UserKey = 'all',
        [datetime]$StartTime,
        [string]$EventName,
        [int]$MaxRetries = 3,
        [switch]$Quiet
    )

    $baseUri = "https://admin.googleapis.com/admin/reports/v1/activity/users/$UserKey/applications/$ApplicationName"
    $headers = @{ Authorization = "Bearer $AccessToken" }

    $allEvents = [System.Collections.Generic.List[hashtable]]::new()
    $pageToken = $null
    $pageCount = 0

    do {
        # Build query parameters
        $queryParams = @{}
        if ($StartTime) {
            $queryParams['startTime'] = $StartTime.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.000Z')
        }
        if ($EventName) {
            $queryParams['eventName'] = $EventName
        }
        if ($pageToken) {
            $queryParams['pageToken'] = $pageToken
        }
        $queryParams['maxResults'] = 1000

        $queryString = ($queryParams.GetEnumerator() | ForEach-Object { "$($_.Key)=$([System.Uri]::EscapeDataString($_.Value))" }) -join '&'
        $uri = if ($queryString) { "$baseUri`?$queryString" } else { $baseUri }

        $response = $null
        for ($attempt = 0; $attempt -lt $MaxRetries; $attempt++) {
            try {
                $response = Invoke-RestMethod -Uri $uri -Headers $headers -Method Get -ErrorAction Stop
                break
            } catch {
                $statusCode = $_.Exception.Response.StatusCode.value__
                if ($statusCode -in @(429, 503) -and $attempt -lt ($MaxRetries - 1)) {
                    $wait = [Math]::Pow(2, $attempt)
                    Write-Verbose "Rate limited ($statusCode), waiting ${wait}s..."
                    Start-Sleep -Seconds $wait
                } elseif ($statusCode -eq 400) {
                    Write-Warning "Application '$ApplicationName' returned 400: $($_.ErrorDetails.Message ?? $_.Exception.Message)"
                    return @($allEvents)
                } elseif ($statusCode -in @(401, 403)) {
                    throw "Authentication failed ($statusCode) for $ApplicationName. Check service account permissions and domain-wide delegation. $($_.ErrorDetails.Message ?? $_.Exception.Message)"
                } else {
                    if ($attempt -eq ($MaxRetries - 1)) {
                        Write-Warning "API call failed after $MaxRetries retries for $ApplicationName`: $($_.ErrorDetails.Message ?? $_.Exception.Message)"
                        return @($allEvents)
                    }
                    $wait = [Math]::Pow(2, $attempt)
                    Start-Sleep -Seconds $wait
                }
            }
        }

        if (-not $response) {
            Write-Warning "No response received for $ApplicationName after $MaxRetries retries"
            break
        }

        # Parse activities into flat event list
        foreach ($activity in @($response.items)) {
            if (-not $activity) { continue }
            $ipAddress = $activity.ipAddress
            $actorEmail = $activity.actor.email
            $eventTime = $activity.id.time

            foreach ($event in @($activity.events)) {
                if (-not $event) { continue }
                $evtName = $event.name
                $params = @{}
                foreach ($p in @($event.parameters)) {
                    if (-not $p -or -not $p.name) { continue }
                    $val = if ($p.value) { $p.value }
                          elseif ($p.multiValue) { $p.multiValue }
                          elseif ($null -ne $p.boolValue) { $p.boolValue }
                          elseif ($p.intValue) { $p.intValue }
                          else { '' }
                    $params[$p.name] = $val
                }

                $allEvents.Add(@{
                    Timestamp   = $eventTime
                    User        = $actorEmail
                    EventName   = $evtName
                    IpAddress   = $ipAddress
                    Source      = $ApplicationName
                    Params      = $params
                })
            }
        }

        $pageCount++
        if (-not $Quiet -and $pageCount % 10 -eq 0) {
            Write-Progress -Activity "Fetching $ApplicationName events" `
                -Status "Page $pageCount, $($allEvents.Count) events" `
                -PercentComplete -1
        }

        $pageToken = $response.nextPageToken
    } while ($pageToken)

    if (-not $Quiet) {
        Write-Progress -Activity "Fetching $ApplicationName events" -Completed
    }

    return @($allEvents)
}