Private/Authentication/Microsoft/Get-PartnerAccessToken.ps1

function Get-PartnerAccessToken {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$TenantId,
        
        [Parameter(Mandatory)]
        [string]$Scopes,
        
        [Parameter()]
        [ValidateSet('Application', 'Delegated')]
        [string]$FlowType = 'Delegated',

        [Parameter()]
        [switch]$Force
    )

    begin {
        try {
            if (!$script:PartnerCredentials) {
                $script:PartnerCredentials = Get-PartnerCredentials
            }
        }
        catch {
            Write-ModuleLog -Message "Failed to get partner credentials" -Level Error -Component 'PartnerAccessToken' -ErrorRecord $_
            throw [TokenOperationException]::new(
                'Initialization',
                'Failed to get partner credentials',
                $_
            )
        }
    }

    process {
        try {
            $cacheKey = "$TenantId|$Scopes|$FlowType"
            $now = Get-Date
            $refreshThreshold = $now.AddMinutes($script:TokenCacheConfig.RefreshBuffer)

            # Validate inputs
            if (-not [Guid]::TryParse($TenantId, [ref][Guid]::Empty)) {
                Write-ModuleLog -Message "Invalid token format detected" -Level Error -Component 'PartnerAccessToken' `
                    -ThrowError -ErrorOperation 'ValidateTenantId' -ErrorMessage 'Invalid TenantId format'
            }

            # Check cache for valid token
            if (!$Force -and $script:TokenCache.ContainsKey($cacheKey)) {
                $cachedToken = $script:TokenCache[$cacheKey]
                
                # Token is still valid
                if ($cachedToken.ExpirationDateTime -gt $refreshThreshold) {
                    Write-ModuleLog -Message "Using cached token for $cacheKey" -Level Verbose -Component 'PartnerAccessToken'
                    return $cachedToken
                }
                # Token needs refresh
                elseif ($cachedToken.ExpirationDateTime -gt $now -and $FlowType -eq 'Delegated') {
                    Write-ModuleLog -Message "Token for $cacheKey is expired, attempting refresh" -Level Verbose -Component 'PartnerAccessToken'
                    try {
                        $newToken = Get-RefreshedToken -ExistingToken $cachedToken -TenantId $TenantId -Scopes $Scopes
                        $script:TokenCache[$cacheKey] = $newToken
                        Save-TokenCache
                        return $newToken
                    }
                    catch {
                        Write-ModuleLog -Message "Token refresh failed for $cacheKey" -Level Warning -Component 'PartnerAccessToken' -ErrorRecord $_
                        # Fall through to get new token
                    }
                }
            }

            Write-ModuleLog -Message "Getting new token for $cacheKey" -Level Verbose -Component 'PartnerAccessToken'

            # Prepare token request
            $body = @{
                client_id     = $script:PartnerCredentials.ApplicationId
                client_secret = $script:PartnerCredentials.ApplicationSecret
                scope         = $Scopes
            }

            if ($FlowType -eq 'Application') {
                $body.grant_type = "client_credentials"
            }
            else {
                if ([string]::IsNullOrEmpty($script:PartnerCredentials.RefreshToken)) {
                    Write-ModuleLog -Message "No refresh token available for delegated flow" -Level Error -Component 'PartnerAccessToken' `
                        -ThrowError -ErrorOperation 'TokenAcquisition' -ErrorMessage 'No refresh token available for delegated flow'
                }
                $body.grant_type = "refresh_token"
                $body.refresh_token = $script:PartnerCredentials.RefreshToken
            }

            # Get new token
            try {
                $response = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token" `
                    -Method POST `
                    -Body $body `
                    -ContentType 'application/x-www-form-urlencoded'
            }
            catch {
                $detailedError = switch -Regex ($_.Exception.Message) {
                    'AADSTS700016' { 'Application not found or not authorized for tenant' }
                    'AADSTS7000215' { 'Invalid client secret provided' }
                    'AADSTS9000410' { 'Invalid scope requested' }
                    'AADSTS50034' { 'Invalid tenant ID' }
                    default { $_.Exception.Message }
                }

                Write-ModuleLog -Message "Failed to acquire token: $detailedError" -Level Error -Component 'PartnerAccessToken' -ErrorRecord $_
            }

            try {
                $token = ConvertFrom-JwtToken -Token $response.access_token

                # Store refresh token if available (for delegated flow)
                if ($FlowType -eq 'Delegated' -and $response.refresh_token) {
                    $token | Add-Member -NotePropertyName 'refresh_token' -NotePropertyValue $response.refresh_token
                }
            }
            catch {
                Write-ModuleLog -Message "Failed to process acquired token" -Level Error -Component 'PartnerAccessToken' -ErrorRecord $_
            }
            
            # Manage cache
            try {
                if ($script:TokenCache.Count -ge $script:TokenCacheConfig.MaxSize) {
                    $oldestTokens = $script:TokenCache.GetEnumerator() | 
                    Sort-Object { $_.Value.ExpirationDateTime } |
                    Select-Object -First ($script:TokenCache.Count - $script:TokenCacheConfig.MaxSize + 1)
                    
                    foreach ($oldToken in $oldestTokens) {
                        $script:TokenCache.Remove($oldToken.Key)
                    }
                }

                $script:TokenCache[$cacheKey] = $token
                Save-TokenCache
            }
            catch {
                Write-ModuleLog -Message "Failed to manage token cache" -Level Error -Component 'PartnerAccessToken' -ErrorRecord $_
            }
            
            return $token
        }
        catch [TokenOperationException] {
            throw
        }
        catch {
            Write-ModuleLog -Message "An unexpected error occurred during token operation" -Level Error -Component 'PartnerAccessToken' -ErrorRecord $_
        }
    }
}