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?
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
.
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
$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;
}
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.
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