Private/Invoke-AzTableRestMethod.ps1
|
function Invoke-AzTableRestMethod { <# .SYNOPSIS Sends an authenticated HTTP request to the Azure Table Storage REST API. .DESCRIPTION Builds the full request URL (appending the SAS token when applicable), obtains the correct authorization headers, and dispatches the request. Returns a PSCustomObject with Content (parsed JSON) and Headers (response headers) so that callers can inspect continuation tokens. On HTTP errors the native exception from Invoke-WebRequest propagates unchanged, so callers can inspect $_.Exception.StatusCode directly. When the response body contains an OData error, ErrorDetails.Message is rewritten as "<code>: <message>" for readability; otherwise the raw error body is kept in $_.ErrorDetails.Message. .OUTPUTS [PSCustomObject] with properties: - Content : Parsed JSON body, or $null for empty responses - Headers : Response header collection - StatusCode : HTTP status code (int) #> [CmdletBinding()] [OutputType([PSCustomObject])] param ( [Parameter(Mandatory)] [PSCustomObject]$Context, [Parameter(Mandatory)] [ValidateSet('GET', 'POST', 'PUT', 'DELETE', 'PATCH')] [string]$Method, # Path after the base endpoint, e.g. "Tables", "mytable()", "Tables('mytable')" [Parameter(Mandatory)] [string]$Resource, # Optional OData query string (without leading '?'), e.g. "$filter=PartitionKey eq 'pk'" [string]$QueryString = '', # Optional request body (will be serialized to JSON) [object]$Body = $null, # Optional ETag for conditional operations (If-Match header) [string]$ETag = $null ) $contentType = '' $bodyBytes = $null if ($null -ne $Body) { $contentType = 'application/json' $bodyJson = $Body | ConvertTo-Json -Depth 10 -Compress $bodyBytes = [System.Text.Encoding]::UTF8.GetBytes($bodyJson) } # Build authorization headers $authHeaders = Get-AzTableAuthorizationHeader ` -Context $Context ` -Method $Method ` -Resource $Resource ` -ContentType $contentType $requestHeaders = @{ 'DataServiceVersion' = '3.0;NetFx' 'MaxDataServiceVersion' = '3.0;NetFx' 'Accept' = 'application/json;odata=nometadata' } foreach ($key in $authHeaders.Keys) { $requestHeaders[$key] = $authHeaders[$key] } if ($ETag) { $requestHeaders['If-Match'] = $ETag } # Build URL - SAS token always goes first so OData params are appended afterward $url = "$($Context.Endpoint)/$Resource" if ($Context.AuthType -eq 'SasToken') { $url += $Context.SasToken # already starts with '?' if ($QueryString) { $url += "&$QueryString" } } elseif ($QueryString) { $url += "?$QueryString" } $requestParams = @{ Uri = $url Method = $Method Headers = $requestHeaders UseBasicParsing = $true ErrorAction = 'Stop' } if ($null -ne $bodyBytes) { $requestParams['Body'] = $bodyBytes $requestParams['ContentType'] = 'application/json' } try { $response = Invoke-WebRequest @requestParams } catch [Microsoft.PowerShell.Commands.HttpResponseException] { $errorRecord = $_ $rawBody = $errorRecord.ErrorDetails.Message if (-not [string]::IsNullOrWhiteSpace($rawBody)) { $parsedError = try { $rawBody | ConvertFrom-Json -ErrorAction Stop } catch { $null } $odataError = $parsedError.'odata.error' if ($odataError.code) { # Azure puts RequestId/Time on extra lines; keep only the human-readable first line $messageValue = ([string]$odataError.message.value -split "`n")[0].Trim() $friendly = if ($messageValue) { "$($odataError.code): $messageValue" } else { [string]$odataError.code } $errorRecord.ErrorDetails = [System.Management.Automation.ErrorDetails]::new($friendly) } } throw $errorRecord } $parsedContent = $null $rawContent = $response.Content if ($rawContent -is [byte[]]) { $rawContent = [System.Text.Encoding]::UTF8.GetString($rawContent) } if (-not [string]::IsNullOrWhiteSpace($rawContent)) { $parsedContent = $rawContent | ConvertFrom-Json } return [PSCustomObject]@{ Content = $parsedContent Headers = $response.Headers StatusCode = [int]$response.StatusCode } } |