3

I'm working against the OneLogin REST API and can't seem to get any calls with a PUT method working. When I test in Postman, I can pass a raw JSON body like this:

{
   "role_id_array":  [
                        115028
                     ]
}

to the endpoint:

https://api.us.onelogin.com/api/1/users//add_roles

This works just fine. However, when I attempt to do the same with PowerShell's Invoke-RestMethod command, I'm getting 400 Bad Request errors. My code looks like this:

$Splat = @{
    Method      = $Method
    Uri         = https://api.us.onelogin.com/api/1/users/12345678/add_roles
    ContentType = "application/json"
    Headers     = @{authorization = "bearer:$($Token.access_token)"}
    Body        = @{role_id_array = @(123456)}
}

Invoke-RestMethod @Splat

So far I've had no issues with any GET calls, only with PUT. Also, if I run the Body hashtable that I'm passing in through ConvertTo-Json, the output looks just like the above working example. Does anyone have any thoughts on why this doesn't work?

4
  • Try converting body to JSON ahead of time. I'm not certain that Invoke-RestMethod will automatically convert the Body payload to JSON. I've had to do this for other REST endpoints before. To be clear, you'd run $Body = @{role_id_array = @(123456)} | ConvertTo-JSON in it's own line, and then provide that as a separate param, or within your splat. Commented Sep 4, 2016 at 18:00
  • 2
    According to the docs on developers.onelogin.com/api-docs/1/users/assign-role-to-user, the response object should also have a message telling you why it's a bad request. Commented Sep 4, 2016 at 18:22
  • @FoxDeploy I believe Invoke-RestMethod does actually handle the JSON for you, but I have also tried converting before passing the body value. It doesn't appear to make a difference in the end result. Commented Sep 5, 2016 at 2:35
  • @Eris I can't seem to wrangle any of the more advanced response details out of the errors. It seems that Invoke-RestMethod and Invoke-WebRequest both handle any non-200 responses as errors and they seem to hide the entire repsonse body from you Commented Sep 5, 2016 at 2:49

2 Answers 2

9

I was able to get a PUT to work!

How to get rich error response data from Invoke-RestMethod

First, I used this blog post here which has an excellent method to retrieve the full error message of an Invoke-RestMethod or WebRequest cmdlet.

In his method, first define a Function called Failure.

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
}

Then, wrap all of your Invoke-RestMethod calls in a try Catch block like this.

try { 
    $e = Invoke-WebRequest 'https://api.us.onelogin.com/api/1/users/$id' `
        -Headers  @{ Authorization = "bearer:$token" } `
        -Body ( @{ phone = "7709746046" } | ConvertTo-Json ) `
        -Method Put `
        -ErrorAction:Stop `
        -ContentType 'application/json' 
} 
catch {Failure}

Now when you run into an error, you can see the actual message, like this

> Status: A system exception was caught.
{"status":{"error":true,"code":400,"type":"bad request","message":{"description":"notes is not a valid attribute for user model","attribute":"notes"}}}
The request body has been saved to $global:helpme

This was super helpful in helping me get rid of the errors I was encountering, and I was able to update a user entry using a PUT verb and get it to work.

Fixing your issue

I only had to make two changes to your code to get this to work.

First, put quotes around the URI, as your example code didn't have them and you must have quotes around strings in a hashtable.

Finally, pipe your body content to ConvertTo-JSON, otherwise the data is sent over as a string, as you found in Fiddler.

With those two changes, here's my request and the 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)} | ConvertTo-Json
}

try {Invoke-RestMethod @Splat -ErrorAction Stop }
catch {Failure}

Here's the response:

status                                                 
------                                                 
@{type=success; message=Success; error=False; code=200}

Update: we did it, this has now been fixed!

If you think PowerShell should present the actual server response for a non 200 status code, then help draw attention to this open issue on the PowerShell project page on Github.

Add your feedback or thumbs up it, and we might be able to get this changed in a future release of the language.

This issue is now fixed as of PowerShell v6.1

Sign up to request clarification or add additional context in comments.

Comments

-1

Ok, so I broke down and installed Fiddler and I think I'm onto something. It appears that the PowerShell cmdlets don't know how to handle arrays for the application/json contenttype. I'm not certain that this would be the case with XML as I haven't tested that.

I changed the body to use straight-up json strings and it worked:

$Body = "{ `"role_id_array`": [123456] }"

If I used a hashtable with the value set to an array like this:

$Body = @{role_id_array = 115028,128732}

Then I would get a 400 failure response. Fiddler showed me what was going on - here's the raw request:

PUT https://api.us.onelogin.com/api/1/users/12345678/add_roles HTTP/1.1 authorization: bearer:3c93ae2-redacted- User-Agent: Mozilla/5.0 (Windows NT; Windows NT 6.3; en-US) WindowsPowerShell/5.1.14394.1000 Content-Type: application/json Host: api.us.onelogin.com Content-Length: 33 role_id_array=System.Object%5b%5d

Notice that the array was not properly converted and instead Invoke-RestMethod simply folded it up into a typename definition, similar to the way Export-Csv will handle array property values.

So I was thinking that I had a problem with using the PUT method because all my GET calls were working, but that was a red herring. The real problem is dealing with a REST endpoint that expects array values for it's request parameter values.

EDIT: I found someone else who had posted this issue back in 2014, but his resolution was to set the -ContentType parameter to 'application/json':

Powershell v4 Invoke-RestMethod body sends System.Object

I've done this and am still having the same problem. Is this a bug?

3 Comments

Check out my updated answer, I think it should help you out.
Thanks @FoxDeploy! It does seem that this is working at the moment. I don't believe the string quotes were an issue as that would throw an error before I got to the Invoke-RestMethod call. That was probably just a bad copy-paste. I'm not certain why this wasn't working for me in the first place. It could be that one of the assumptions I was making was invalid and I couldn't tell because of the lack of a real response body.
It's funny that in the meantime I had found this article: stackoverflow.com/questions/18771424/… and came to a similar solution as you did for returning the data from the REST response. I'm planning to use this to return useful errors in the module I'm working on.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.