Powershell is swallowing REST error response - rest

I am using Invoke-WebRequest in Powershell and whenever my request is determined to be invalid by the targeted API endpoint, it obviously denies the request and sends back an HTTP error code like (400) Bad Request but it also includes the reason for the error (provided by the API vendor) but that isn't included in the logs inside of PowerShell.
I confirmed the detailed error is sent back because I see it in PostMan and the vendor confirmed the same. Powershell just doesn't want to show it. Here is an example of my code and the response it is generating.
Invoke-WebRequest -Credential $cred -Uri $url -Method POST -Body $json -ContentType 'application/json'
Invoke-WebRequest : The remote server returned an error: (400) Bad Request.
At \\*****\******$\Appsense\Desktop\Untitled2.ps1:42 char:1
+ Invoke-WebRequest -Credential $cred -Uri $url -Method POST -Body $jso ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest)
[Invoke-WebRequest], WebException
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.
InvokeWebRequestCommand
How do I capture that more detailed error message?

Two parts to this. First you need to have it throw a terminating error with -ErrorAction Stop. This allows us to then use a try/catch block to catch the exception. With the Exception we can then get the detailed response stored in the Exception Status Description. This is fine for most requests.
To get the body of the message requires a few more steps. Since we get a WebResponse object, there is no "Nice" Message parameter for us. So we have to use a StreamReader to Stream the content ourselves:
try
{
$Response = Invoke-WebRequest -Credential $cred -Uri $url -Method POST -Body $json -ContentType 'application/json' -ErrorAction Stop
# This will only execute if the Invoke-WebRequest is successful.
$StatusCode = $Response.StatusCode
}
catch
{
#Excepion - Display error codes
Write-Host "StatusCode:" $_.Exception.Response.StatusCode.value__
Write-Host "StatusDescription:" $_.Exception.Response.StatusDescription
#Get body of me
$streamReader = [System.IO.StreamReader]::new($_.Exception.Response.GetResponseStream())
$ErrResp = $streamReader.ReadToEnd() | ConvertFrom-Json
$streamReader.Close()
Write-Host $ErrResp
}

Related

Invoke-RestMethod The underlying connection was closed: An unexpected error occurred on a receive

To Introduce myself : Working as a Power BI Developer with PBI Admin access.
My Powershell script stoped working suddenly and prompting me an error saying the underlying connection was closed. This was all working fine few days back.
Invoke-RestMethod : The underlying connection was closed: An unexpected error occurred on a receive.
At line:25 char:7
Invoke-RestMethod -Method Post -uri $url1 -Headers $authHeader ...
CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebExcepti on
FullyQualifiedErrorId:WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand
Login-PowerBI
$groupid = "Hidden"
$Reportid = "Hidden"
$Folder = "c:\temp\"
$Body = "{`”format`”:`”pdf`”}"
$filename = $Folder + "PowerBIMetrics.pdf"
$StatusCheck=0
$token = Get-PowerBIAccessToken -AsString
$authHeader = #{
"Authorization"= $token
"Content-Type" = "application/json"
}
$url1 = "https://api.powerbi.com/v1.0/myorg/groups/$groupid/reports/$Reportid/ExportTo"
Invoke-RestMethod -Method Post -uri $url1 -Headers $authHeader -body $Body'
I have also tried to look for solution and many of them (Power BI community/ DBA) is saying I need to add extra line of code below before I execute the Invoke-ResMethod command line,
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Unfortunately, I'm getting same error message if i add above line.
I passed in parameters groupId/reportid/body and click "run" but nothing happens, it doesn't give me any result, this was working fine before.
https://learn.microsoft.com/en-us/rest/api/power-bi/reports/export-to-file#code-try-0

Powershell WebRequest GET Succeeds but DELETE fails w/ 404

Very confused on this issue. Here is what I am dealing with currently.
I have an API endpoint which looks like this /subscriptions/:id. This endpoint serves as both a GET & a DELETE endpoint. When I run a GET, it returns the object as it normally does, but changing the action to a DELETE gives me back a 404 for the very same resource which was just returned in the GET, I don't know why.
Here is my Powershell code.
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$user = '*********'
$pass = ConvertTo-SecureString '*********' -AsPlainText -Force
$cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $user, $pass
$base = "https://*********.hosted.xmatters.com/api/xm/1"
$del_path = "$base/subscriptions/********uuid***********"
$path = "$base/subscriptions"
$payload = #{
id = '********uuid***********'
description = '**** NEW DESCRIPTION ****'
}
$params = $payload | ConvertTo-Json
// **** SUCCESS
$thing = Invoke-WebRequest -Credential $cred -Uri $del_path -Method GET
// **** FAILS
$thing = Invoke-WebRequest -Credential $cred -Uri $del_path -Method DELETE
// **** FAILS
$thing = Invoke-WebRequest -Credential $cred -Uri $path -Method POST -Body $params -ContentType 'application/json'
So, like mentioned previously, I am POSITIVE that the resource exists from the API. I am able to get it when running a GET but both POST (to update) & DELETE throw 404 errors. Here is the error I get.
Invoke-WebRequest : The remote server returned an error: (404) Not Found.
At \\mmfile\ct32373$\Appsense\Desktop\Line of Business Revisions.ps1:24 char:10
+ $thing = Invoke-WebRequest -Credential $cred -Uri $del_path -Method D ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebReque
st) [Invoke-WebRequest], WebException
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Comman
ds.InvokeWebRequestCommand
The strange thing is that I am able to run the DELETE request successfully using POSTMAN, however because of the volume of data I need to process, this is not a good option

Invoke-WebRequest / Invoke-RestMethod failed with error underlying connection closed

I have an existing REST API that accept x-www-form-urlencoded. The API need parameter apikey, and tested successfully in Postman as shown below.
However I need to invoke this API using Powershell.Below is my code :
$params = #{"apikey"="abcd1234"}
Invoke-WebRequest -Uri http://localhost:3030/api/v1/usergroupsync -Method POST -Body $params
#also tried below, no avail.
Invoke-RestMethod -Uri http://localhost:3030/api/v1/usergroupsync -Method POST -Body $params
However I encountered this error :
Invoke-WebRequest : The underlying connection was closed: An unexpected error occured on a receive At line:14 char:1
+ Invoke-WebRequest -Uri http://localhost:3030/api/v1/usergroupsync -Method POST -...
+==============================================================================
+ CategoryInfo : InvalidOperations: (System.Net.HttpWebRequest:HTTTpWebRequest) [Invoke-WebRequest], WebException
+ FullyQualifiedErrorId : WebcmdletWebResponseException,Microsoft.Powershell.Commands.InvokeWebRequest
If I remove -Body, there is no error, and Response was as expected "API Key is not valid" which means my REST API validate correctly.
So I suspect the reason if my issue is on the body? Any idea on how to solve this issue?
PS Version is 4.0.0
PS C:\> $PSVersionTable.PSVersion
Major Minor Build Revision
----- ----- ----- --------
4 0 -1 -1
You should use the -Header switch to pass your parameters. Though Invoke-WebRequest support header, I recommend using Invoke-RestMethod as it also return the Headers.
Try something like,
Invoke-RestMethod -Method Post -Uri http://localhost:3030/api/v1/usergroupsync -Body (ConvertTo-Json $body) -Header #{"apikey"=$apiKey}
Check this and this for more information

Can't find expected json body in System.Net.WebException

I'm working with an API via PowerShell that returns human-readable errors as a json object in the response body when an error occurs. However, when I attempt to find that json body in an exception, I can see the error, the underlying System.Net.WebException and the further underlying System.Net.HttpWebResponse, but nowhere can I find the actual body they're referring to. Is this something that is accessible?
For example, here is a valid API call that would work:
Invoke-RestMethod -Method Get -Headers #{Authorization="Token token=$YourTokenHere";"Content-type"="application/json"} -Uri "https://mydomain.pagerduty.com/api/v1/users/ABCDEF" -Body #{offset=0;limit=100}
If you then change the user ID at the end of the URI, it fails and you get this error:
Invoke-RestMethod : The remote server returned an error: (400) Bad Request.
At line:1 char:1
+ Invoke-RestMethod -Method Get -Headers #{Authorization="Token token=b ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebException
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand
I can dive down in to the error to see the underlying error and the response if I do the following and convert to json for easy viewing of subproperties:
$Error[0].Exception.Response | ConvertTo-Json
But no matter how I comb through these errors, I can't seem to find the json body. Where might it be, or how can I capture it? I seem to have the same result if I do a try/catch.
You could read the responsestream so you can get the body of the response. Ex:
try {
Invoke-RestMethod -Method Get -Headers #{Authorization="Token token=$YourTokenHere";"Content-type"="application/json"} -Uri "https://mydomain.pagerduty.com/api/v1/users/ABCDEF" -Body #{offset=0;limit=100}
} catch {
$stream = New-Object System.IO.StreamReader $_.Exception.Response.GetResponseStream()
$json = $stream.ReadToEnd()
$stream.Dispose()
$json
}
Output:
{"error":{"message":"Account Not Found","code":2007}}

How do I get the body of a web request that returned 400 Bad Request from Invoke-RestMethod

When I run the following statement
Invoke-RestMethod "https://api.mysite.com/the/endpoint" `
-Body (ConvertTo-Json $data) `
-ContentType "application/json" `
-Headers $DefaultHttpHeaders `
-Method Post
the endpoint returns 400 Bad Request, which causes PowerShell to show the following not-so-helpful message:
Invoke-WebRequest : The remote server returned an error: (400) Bad Request.
At line:1 char:1
+ Invoke-WebRequest "https://api.mysite.com/the/endpoint" -Body ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest], WebException
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
How do I get the body of the response, which might tell me what was wrong with the request I sent?
There is a known issue with PowerShell Invoke-WebRequest and Invoke-RestMethod where the shell eats the response body when the status code is an error (4xx or 5xx). Sounds like the JSON content you are looking for is evaporating in just this manner. You can fetch the response body in your catch block using $_.Exception.Response.GetResponseStream()
try {
Invoke-RestMethod "https://api.mysite.com/the/endpoint" `
-Body (ConvertTo-Json $data) `
-ContentType "application/json" `
-Headers $DefaultHttpHeaders `
-Method Post
}
catch {
$streamReader = [System.IO.StreamReader]::new($_.Exception.Response.GetResponseStream())
$ErrResp = $streamReader.ReadToEnd() | ConvertFrom-Json
$streamReader.Close()
}
$ErrResp
According to Invoke-RestMethod documentation, cmdlet can return different types depending on the content it receives. Assing cmdlet output to a variable ($resp = Invoke-RestMethod (...)) and then check if the type is HtmlWebResponseObject ($resp.gettype()). Then you'll have many properties at your disposal, like BaseResponse, Content and StatusCode.
If $resp is some other type (string, psobject and most probably null in this case), it seems that error message The remote server returned an error: (400) Bad Request is the response body, only stripped from html (I tested this on some of my methods), maybe even truncated . If you want to extract it, run the cmdlet using common parameter to store the error message: Invoke-RestMethod (...) -ErrorVariable RespErr and you'll have it in $RespErr variable.
EDIT:
Ok, I got it and it was pretty obvious :). Invoke-RestMethod throws an error, so lets just catch it:
try{$restp=Invoke-RestMethod (...)} catch {$err=$_.Exception}
$err | Get-Member -MemberType Property
TypeName: System.Net.WebException
Name MemberType Definition
---- ---------- ----------
Message Property string Message {get;}
Response Property System.Net.WebResponse Response {get;}
Status Property System.Net.WebExceptionStatus Status {get;}
Here's all you need, especially in WebResponse object.
I listed 3 properties that catch the eye, there's more. Also if you store $_ instead of $_.Exception there could be some properties PowerShell already extracted for you, but I don't expect nothing more meaningful than in .Exception.Response.
$RespErr will have the more details about the BadRequest in my case its
$responce = Invoke-RestMethod -Uri https://localhost:44377/explore/v2/Content -Method Post -Body $PostData -Headers $header -ErrorVariable RespErr;
$RespErr;
{ "error":{ "code":"","message":"The FavoriteName field is required." } }
It looks like it works only in localhost, i tried with my actual server it didn't work.
another way to try is this
try{
$response = ""
$response = Invoke-WebRequest -Uri https://contentserverint-mhdev.azurewebsites.net/apis/explore/v2/Content?overwrite=true -Method Post -Body $PostData -Headers $header -ErrorVariable RespErr
#$response = Invoke-RestMethod -Uri https://localhost:44377/explore/v2/Content?overwrite=true -Method Post -Body $PostData -Headers $header -ErrorVariable RespErr
Write-Host "Content created with url="$response.value[0]
}
catch [System.Net.WebException] {
$respStream = $_.Exception.Response.GetResponseStream()
$reader = New-Object System.IO.StreamReader($respStream)
$respBody = $reader.ReadToEnd() | ConvertFrom-Json
$respBody;
}
You can get HTTP response without exceptions regardless if it is 200 or 400:
Powershell 7 introduced -SkipHttpErrorCheck 👏
It works for both Invoke-WebRequest and Invoke-RestMethod:
PS> $res = Invoke-WebRequest -SkipHttpErrorCheck -Method POST https://login.microsoftonline.com/does-not-exist/oauth2/token
PS> $res
StatusCode : 400
StatusDescription : BadRequest
Content : {"error":"invalid_request","error_description":"AADSTS900144: The request body must contain the following parameter:
'grant_type'.\r\nTrace ID: f40877fd-ae34-4b95-a8d4-c7b8ba613801\r\nCorrelation ID: …
RawContent : HTTP/1.1 400 BadRequest
Cache-Control: no-store, no-cache
Pragma: no-cache
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
P3P: CP="DSP CUR OTPi IND OTRi…
Headers : {[Cache-Control, System.String[]], [Pragma, System.String[]], [Strict-Transport-Security, System.String[]], [X-Conte
nt-Type-Options, System.String[]]…}
Images : {}
InputFields : {}
Links : {}
RawContentLength : 503
RelationLink : {}
MS documentation says:
-SkipHttpErrorCheck
This parameter causes the cmdlet to ignore HTTP error statuses and
continue to process responses. The error responses are written to the
pipeline just as if they were successful.
For me it only worked in a Pester context, when setting the streams Position to 0 before reading it.
$statusCode = $null
$responseBody = $null
try {
$response = Invoke-RestMethod -Method GET -Uri "$($apiPrefix)$($operation)" -Headers $headers
}
catch [System.Net.WebException] {
$statusCode = $_.Exception.Response.StatusCode
$respStream = $_.Exception.Response.GetResponseStream()
$reader = New-Object System.IO.StreamReader($respStream)
$reader.BaseStream.Position = 0
$responseBody = $reader.ReadToEnd() | ConvertFrom-Json
}
$statusCode | Should Be $Expected
$responseBody | Should Not Be $null
If you are just after the response StatusCode and Content here is a novel way of solving this problem without lots of messy try/catch and manual reading of response streams:
# Place the trap within your chosen scope (e.g. function or script)
trap [Net.WebException] { continue; }
# Exceptions are no longer thrown here
$response = Invoke-WebRequest $endpoint
# Check if last command failed
if (!$?)
{
# $error[0] now contains the ErrorRecord of the last error (in this case from Invoke-WebRequest)
# Note: $response should be null at this point
# Due to the magic of Microsoft.PowerShell.Commands.InvokeWebRequestCommand.WebCmdletWebResponseException
# we can get the response body directly from the ErrorDetails field
$body = $error[0].ErrorDetails.Message
# For compatibility with $response.StatusCode lets cast to int
$statusCode = [int] $error[0].Exception.Response.StatusCode
}
As far as I can tell, the ErrorRecord.ErrorDetails.Message contains the exact equivalent to the Microsoft.PowerShell.Commands.WebResponseObject.Content property that would get returned to you on a successful invocation of Invoke-WebRequest, just without the hassle of having to do all that GetResponseStream() jazz.
The textual response sent from the server is contained in the error variable at the following place:
$_.ErrorDetails.Message
While not exactly as the OP intended; I had a similar case with wanting to see the body/headers of the SENT request, rather than the response.
The solution is -SessionVariable VarNameHere - You can then inspect this variable and see the exact content of the sent request.