Providers/Okta/Private/Get-OktaErrorDetail.ps1
|
function Get-OktaErrorDetail { <# .SYNOPSIS Extracts the useful part of a failed Okta API response .DESCRIPTION Okta puts the actionable text in the response body, under errorSummary and errorCauses, and PowerShell throws that body away in favour of a generic "The remote server returned an error" message. Invoke-TestWebRequest reads the body once, whichever way the running PowerShell hands it over, and puts it in $_.ErrorDetails.Message on both editions. This reads it from there and flattens errorCauses, which is the field that actually says which attribute was rejected and why. .PARAMETER ErrorRecord The ErrorRecord caught from Invoke-WebRequest .OUTPUTS String suitable for putting straight into an exception message .EXAMPLE catch { throw (Get-OktaErrorDetail -ErrorRecord $_) } .NOTES Author: Jeffrey Stuhr Version: 1.0.0 Last Updated: 2026-08-07 #> [CmdletBinding()] [OutputType([string])] param( [Parameter(Mandatory = $true)] [System.Management.Automation.ErrorRecord]$ErrorRecord ) # The body is in ErrorDetails on both editions: Invoke-TestWebRequest reads a failed response # once, with -SkipHttpErrorCheck on PowerShell 7 and from the response stream on Windows # PowerShell, and puts it there. Nothing here reads a stream. $rawBody = $null if ($ErrorRecord.ErrorDetails -and -not [string]::IsNullOrWhiteSpace($ErrorRecord.ErrorDetails.Message)) { $rawBody = $ErrorRecord.ErrorDetails.Message } # Whatever is left when the body is absent or is not an Okta error document. A transport # failure is the common case: PowerShell 7 puts the inner exception's entire ToString, # stack trace included, into both ErrorDetails.Message and Exception.Message, so a refused # connection yields twenty lines of System.Net internals wrapped around one useful # sentence. Keep the sentence. The full record is still on $Error[0] for anyone who wants # it, and Okta always answers an API error with JSON, so nothing structured is lost here. $fallbackSource = if ([string]::IsNullOrWhiteSpace($rawBody)) { $ErrorRecord.Exception.Message } else { $rawBody } $fallback = (@($fallbackSource -split "`r?`n") | Where-Object { $_.Trim() } | Select-Object -First 1) $fallback = if ($fallback) { $fallback.Trim() } else { 'no further detail was returned' } if ([string]::IsNullOrWhiteSpace($rawBody)) { return $fallback } $parsed = $null try { $parsed = $rawBody | ConvertFrom-Json } catch { return $fallback } if (-not $parsed) { return $fallback } # The token endpoint uses the OAuth error shape, everything else uses Okta's own. if ($parsed.PSObject.Properties['error'] -and -not $parsed.PSObject.Properties['errorSummary']) { $oauthParts = @($parsed.error) if ($parsed.PSObject.Properties['error_description']) { $oauthParts += $parsed.error_description } return ($oauthParts -join ': ') } $parts = @() if ($parsed.PSObject.Properties['errorSummary']) { $parts += $parsed.errorSummary } if ($parsed.PSObject.Properties['errorCauses'] -and $parsed.errorCauses) { $parts += @($parsed.errorCauses | ForEach-Object { $_.errorSummary }) } if ($parsed.PSObject.Properties['errorCode'] -and $parsed.errorCode) { $parts += "[$($parsed.errorCode)]" } $message = ($parts | Where-Object { $_ }) -join ' ' if ([string]::IsNullOrWhiteSpace($message)) { return $fallback } return $message } |