Powershell: Invoke-RestMethod should return the full error response from the remote endpoint

Created on 6 Sep 2016  路  12Comments  路  Source: PowerShell/PowerShell

Steps to reproduce

Today, if you use Invoke-RestMethod and have any sort of malformed data in your request, Invoke-RestMethod eats the server response and instead returns a generic PowerShell error message, greatly increasing pain when troubleshooting.

As an example of desired behavior, it would be good if PowerShell surfaced the remote server response instead of just writing an error and dumping the response.

Expected behavior

It would be wonderful if Invoke-RestMethod wrote to an alternate stream or provided rich error data when encountering an error with an endpoint.

For example. note the error data I'm about to get when I used the very useful Failure function provided in this blog post, I'm able to see the full remote server response, which makes troubleshooting this an absolute breeze.

function Failure {
$global:helpme = $body
$global:helpmoref = $moref
$global:result = $_.Exception.Response.GetResponseStream()
$global:reader = New-Object System.IO.StreamReader($global:result)
$global:responseBody = $global:reader.ReadToEnd();
Write-Host -BackgroundColor:Black -ForegroundColor:Red "Status: A system exception was caught."
Write-Host -BackgroundColor:Black -ForegroundColor:Red $global:responsebody
Write-Host -BackgroundColor:Black -ForegroundColor:Red "The request body has been saved to `$global:helpme"
break
}

With this function loaded in memory, I'll run the below code with try/catch and call Failure on error.

$Splat = @{
    Method      = 'PUT'
    Uri         = 'https://api.us.onelogin.com/api/1/users/27697924/add_roles'
    ContentType = "application/json"
    Headers     = @{authorization = "bearer:$token" }
    Body        = @{role_id_array = (143175)}
}

PS C:\git> Invoke-RestMethod @Splat

Status: A system exception was caught.
{"status":{"error":true,"code":400,"type":"bad request","message":"role_id_array should be -\u003e array of positive integers"}}
The request body has been saved to $global:helpme

Actual behavior

In today's PowerShell, all server response data from Invoke-RestMethod is eaten and a generic PowerShell error is presented. In scouring the results from $error[0] I was unable to find the actual server response.

$Splat = @{
    Method      = 'PUT'
    Uri         = 'https://api.us.onelogin.com/api/1/users/27697924/add_roles'
    ContentType = "application/json"
    Headers     = @{authorization = "bearer:$token" }
    Body        = @{role_id_array = (143175)}
}

PS C:\git> Invoke-RestMethod @Splat
Invoke-RestMethod : The remote server returned an error: (400) Bad Request.
At line:1 char:1
+ Invoke-RestMethod @Splat
+ ~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebException
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand

Environment data

> $PSVersionTable


Name                           Value                                                                                                                                                                                                                             
----                           -----                                                                                                                                                                                                                             
PSVersion                      5.1.14393.103                                                                                                                                                                                                                     
PSEdition                      Desktop                                                                                                                                                                                                                           
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}                                                                                                                                                                                                           
BuildVersion                   10.0.14393.103                                                                                                                                                                                                                    
CLRVersion                     4.0.30319.42000                                                                                                                                                                                                                   
WSManStackVersion              3.0                                                                                                                                                                                                                               
PSRemotingProtocolVersion      2.3                                                                                                                                                                                                                               
SerializationVersion           1.1.0.1                                                                                                                                                                                                                           



Area-Cmdlets Committee-Reviewed Issue-Enhancement Resolution-Fixed

Most helpful comment

The experience won't be exactly the same as the coreClr types are different, but it's improved and similar:

PS /home/steve/repos/PowerShell> invoke-webrequest http://httpstat.us/418                                     
invoke-webrequest : Response status code does not indicate success: 418 (I'm a teapot).
At line:1 char:1
+ invoke-webrequest http://httpstat.us/418
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (Method: GET, Re...rShell/6.0.0
}:HttpRequestMessage) [Invoke-WebRequest], I'm a teapot
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCo 
   mmand
PS /home/steve/repos/PowerShell> $error[0].exception.response                                                 


Version             : 1.1
Content             : System.Net.Http.StreamContent
StatusCode          : 418
ReasonPhrase        : I'm a teapot
Headers             : {[Cache-Control, System.String[]], [Server, System.String[]], [X-AspNetMvc-Version, 
                      System.String[]], [X-AspNet-Version, System.String[]]...}
RequestMessage      : Method: GET, RequestUri: 'http://httpstat.us/418', Version: 1.1, Content: <null>, 
                      Headers:
                      {
                        User-Agent: Mozilla/5.0
                        User-Agent: (Windows NT; Linux 4.4.0-64-generic #85-Ubuntu SMP Mon Feb 20 11:50:30 
                      UTC 2017; en-US)
                        User-Agent: WindowsPowerShell/6.0.0
                      }
IsSuccessStatusCode : False

All 12 comments

Excellent write-up Stephen! I can second Stephen's frustration with this issue!

I've definitely been bitten by this.

Same as the above, also using a helper function to get the error response stream. Hope the fix doesn't break those modules though, but even if it does it would be worth it not having to deal with it everytime.

Great write-up!

Essentially, if we just remove one line

response.EnsureSuccessStatusCode();

https://github.com/PowerShell/PowerShell/blob/aa764dd54edd5b214eb1fcb67aba5370014e7663/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebRequestPSCmdlet.CoreClr.cs#L350

we will get a pretty decent output.
For example:

     Reading web response                                                                                                                                          StatusCode        : 400e stream... (Number of bytes read: 229)                                                                                                     StatusDescription : BadRequest                                                                                                                                     Content           : Error in call to API function "team/get_info": Bad HTTP "Content-Type" header: "application/x-www-form-urlencoded".  Expecting one of 
                    "application/json", "application/json; charset=utf-8", "text/plain...
RawContent        : HTTP/1.1 400 BadRequest
                    Server: nginx
                    Date: Sat, 11 Feb 2017 06:39:47 GMT
                    Connection: keep-alive
                    User-Agent: 

                    Error in call to API function "tea...
Forms             : 
Headers           : {[Server, System.String[]], [Date, System.String[]], [Connection, System.String[]], [User-Agent, System.String[]]...}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        : 
RawContentLength  : 229

Btw, in the FullCLR version there is no call to it.

Here's the experience I have coded up, feedback welcome:

PS /home/steve/repos/PowerShell> invoke-webrequest http://httpstat.us/418       
invoke-webrequest : 418 I'm a teapot
At line:1 char:1
+ invoke-webrequest http://httpstat.us/418
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (Method: GET, Re...rShell/6.0. 
   0
}:HttpRequestMessage) [Invoke-WebRequest], I'm a teapot
    + FullyQualifiedErrorId : 418,Microsoft.PowerShell.Commands.InvokeWebReque 
   stCommand
PS /home/steve/repos/PowerShell> $e.FullyQualifiedErrorId                       
418,Microsoft.PowerShell.Commands.InvokeWebRequestCommand

The content of the response is stuffed into the Message property of the ErrorDetail and the HTTP Status Code is in the front of the FullyQualifiedErrorId. Response headers are thrown away (not sure if there's any useful case when you receive an error from the server).

HTTP Status Code is in the front of the FullyQualifiedErrorId

Not sure that's a good idea.

@vors, open to suggestions. Could prefix with HttpStatus, but then it's more parsing for scripts that want the actual code

@PowerShell/powershell-committee (subset of @HemantMahawar @lzybkr @khansen00 and myself) think that the most important piece is that you're able to get at the Response property of the exception as you can in FullCLR:

When running Invoke-WebRequest http://httpstat.us/418, the behavior currently differs between Core and Full:

FullCLR:

PS C:\Users\jaiello> $error[1].exception.response


IsMutuallyAuthenticated : False
Cookies                 : {ARRAffinity=ce89bc5d89e3eae7a38e90a2582b973f890225b3bddc47e95c77a1d7831a8a71}
Headers                 : {X-AspNetMvc-Version, Access-Control-Allow-Origin, Content-Length, Cache-Control...}
SupportsHeaders         : True
ContentLength           : 16
ContentEncoding         :
ContentType             : text/plain; charset=utf-8
CharacterSet            : utf-8
Server                  : Microsoft-IIS/8.0
LastModified            : 2/15/2017 4:16:36 PM
StatusCode              : 418
StatusDescription       : I'm a teapot
ProtocolVersion         : 1.1
ResponseUri             : http://httpstat.us/418
Method                  : GET
IsFromCache             : False

CoreCLR:

PS C:\Program Files\PowerShell\6.0.0.15> $error[0].exception | fl -force *


Message        : Response status code does not indicate success: 418 (I'm a teapot).
Data           : {}
InnerException :
TargetSite     : System.Net.Http.HttpResponseMessage EnsureSuccessStatusCode()
StackTrace     :    at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
                    at Microsoft.PowerShell.Commands.WebRequestPSCmdlet.ProcessRecord()
HelpLink       :
Source         : System.Net.Http
HResult        : -2146233088

The experience won't be exactly the same as the coreClr types are different, but it's improved and similar:

PS /home/steve/repos/PowerShell> invoke-webrequest http://httpstat.us/418                                     
invoke-webrequest : Response status code does not indicate success: 418 (I'm a teapot).
At line:1 char:1
+ invoke-webrequest http://httpstat.us/418
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (Method: GET, Re...rShell/6.0.0
}:HttpRequestMessage) [Invoke-WebRequest], I'm a teapot
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCo 
   mmand
PS /home/steve/repos/PowerShell> $error[0].exception.response                                                 


Version             : 1.1
Content             : System.Net.Http.StreamContent
StatusCode          : 418
ReasonPhrase        : I'm a teapot
Headers             : {[Cache-Control, System.String[]], [Server, System.String[]], [X-AspNetMvc-Version, 
                      System.String[]], [X-AspNet-Version, System.String[]]...}
RequestMessage      : Method: GET, RequestUri: 'http://httpstat.us/418', Version: 1.1, Content: <null>, 
                      Headers:
                      {
                        User-Agent: Mozilla/5.0
                        User-Agent: (Windows NT; Linux 4.4.0-64-generic #85-Ubuntu SMP Mon Feb 20 11:50:30 
                      UTC 2017; en-US)
                        User-Agent: WindowsPowerShell/6.0.0
                      }
IsSuccessStatusCode : False

Tried another work around to capture error from a REST API is anyone is trying to capture that:

function Failure {
               $Message =  $_.ErrorDetails.Message;
        $json = $Message | ConvertFrom-Json  #if error you are trying to capture is returned in json
               Write-Host ("Message: "+ $Message)
}

With this function loaded in memory, put your try / catch around Invoke-RestMethod and call Failure on error. Hope this helps.

Was this page helpful?
0 / 5 - 0 ratings