Error Handling for Invoke-RestMethod - Powershell - rest

I have a powershell script using the Skytap API (REST). I would like to catch the error, if there is one, and try to display it.
For example, we are changing the IP:
Invoke-RestMethod -Uri https://cloud.skytap.com/configurations/XXXXXX/vms/YYYYYY/interfaces/ZZZZZZ?ip=10.0.0.1 -Method PUT -Headers $headers
If the IP is used somewhere else, I will get the 409 Conflict Error (Request is well-formed but conflicts with another resource or permission).
I would like to check if the error is 409 and then tell it to do something else about it.

This is somewhat awkward but the only way to do it as far as I know without doing something more complicated like using .NET's WebRequest and ConvertFrom-Json (or whatever data format you are expecting).
try {
Invoke-RestMethod ... your parameters here ...
} catch {
# Dig into the exception to get the Response details.
# Note that value__ is not a typo.
Write-Host "StatusCode:" $_.Exception.Response.StatusCode.value__
Write-Host "StatusDescription:" $_.Exception.Response.StatusDescription
}

I know you asked for Powershellv4, but since v6/v7 :
Try {
$WebRequestResult = Invoke-RestMethod -Uri $URL -Headers $Headers -Body $BodyJSON -Method $Method -ContentType $ContentType -SkipCertificateCheck
} Catch {
if($_.ErrorDetails.Message) {
Write-Host $_.ErrorDetails.Message
} else {
Write-Host $_
}
}

The Special Variable $? will solve this. It stands for $LASTEXITCODE = 0 (everything ok). It will ask for the Result Code from the previous Command.
Invoke-RestMethod -Uri https://cloud.skytap.com/configurations/XXXXXX/vms/YYYYYY/interfaces/ZZZZZZ?ip=10.0.0.1 -Method PUT -Headers $headers
if (!$?) {
throw $_.ErrorDetails.Message
}

Powershell 7 introduces the -SkipHttpErrorCheck parameter. This instructs the cmdlet to behave in a similar way to web requests in programming frameworks (i.e. where 404, 409, etc. are valid responses - the web request is successful but the server returned an error code).
This can be combined with the -StatusCodeVariable parameter. This instructs the cmdlet to insert the response code into a variable. However, the variable name is passed as a string (not as a reference). For example:
$scv = $null
Invoke-RestMethod ... -SkipHttpErrorCheck -StatusCodeVariable "scv"

Related

Raise powershell error and stop execution of script

I am working on making a ReST api call to the token server and grab the token. Then make another ReST api call by passing in the token and save the query response. I'm able to achieve when everything works as expected. But, if I were to run into errors I need to raise the powershell error and want the execution of the powershell script to stop at the point of failure. I need to save the api response on to a network share as the next steps. I noticed the execution to continue even if the ReST api call fails for some reason. I'm using a generic try{} catch{} block with some generic error message. I've been trying to raise the actual error that the powershell is throwing but does not work.
PoSh:
try {
$clientSecret = ''
$clientId = ''
$tenantId = ''
# Construct URI
$uri = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"
# Construct Body
$body = #{
client_id = $clientId
client_secret = $clientSecret
scope = 'https://graph.microsoft.com/.default'
grant_type = 'client_credentials'
}
$Uri = 'https://apiserver.com/v1/data'
# Get OAuth 2.0 Token
$tokenRequest = Invoke-WebRequest -Method Post -Uri $uri -ContentType 'application/x-www-form-urlencoded' -Body $body -UseBasicParsing
# Access Token
$token = ($tokenRequest.Content | ConvertFrom-Json).access_token
$api = Invoke-RestMethod -Method Get -Uri $Uri -ContentType 'application/json' -Headers #{Authorization = "Bearer $token"} -ErrorAction Stop
}
catch {
"Error"
Write-Host "StatusCode:" $_.Exception.Response.StatusCode.value__
Write-Host "StatusDescription:" $_.Exception.Response.StatusDescription
Write-Host "ErrorMessage:" $_.ErrorDetails.Message
}
I need to raise the powershell error and want the execution of the powershell script to stop at the point of failure.
When a terminating error triggers the catch block of a try / catch/ finally statement, execution continues by default.
To rethrow such an error as a script-terminating (fatal) error, simply use throw inside your catch block.
Alternatively, if you don't need to process the error before rethrowing it, place $ErrorActionPreference = 'Stop' at the top your scope, which will cause any error, including non-terminating ones, to become script-terminating (fatal) errors.

How to check if output from command contains "error"?

I have 2 powershell scripts
script1.ps1 contains REST calls to Vault, which returns a json response containing some keys/values.
Function getValues($token) {
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("X-Vault-Token", $token)
$response = Invoke-RestMethod -method GET -uri "https://vault.com:8243/v1/secret/vault/development" -ContentType 'application/json' -headers $headers
$response.data| ConvertTo-Json -depth 100
}
getValues $token
As you can see, the method requires a $token.
I have that token saved in a system variable.
Now i call script1 in script2 to get the keys/values like this:
$resp = & ".\script1.ps1" | convertfrom-json
that works great and i get values as i need.
however, i am testing when if there is errors in the future, for example by removing the token from the system variable.
This $resp returned:
errors
------
{missing client token}
great. now i want to exit the script should that happen.
I tried a check like this:
if($resp.tostring().contains("error"))
{
write-output "error!"
exit 1
}
but it never exited nor did it return the output error!
how am i suppose to look for a match in this case?

Using powershell's Invoke-RestMethod cmdlet is only returning the word "success" as a result

I have written 2 scripts. One in python and one in powershell. Python is able to call a rest api and is returned the string "JSESSIONID=8kfv0fi1bc84gtw2xvnqsrt4;Path=/;Secure;HttpOnly ". When I use the following code in powershell, it returns "success". What am I doing wrong?
$getEncCode = "Er6TmdhXn09Y9C1I"
$dataPart1 = #{EncCode=$getEncCode}
$dataPart = $dataPart1 | ConvertTo-Json
$uri = "https://10.164.42.77:8092/getEnc/2252953/login"
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
$result = Invoke-RestMethod -Method Post -Body $dataPart -uri $uri -ContentType "application/json"
Write-Host $result
Write-Host $result uses the $result.ToString() method to display that object as a string.
Likely there is more data there to show. Simply remove Write-Host to see it. Or do something like Get-Member -InputObject $result to see all immediate properties, methods etc.
Have a look at Printing object properties in Powershell to see other ways to deal with this.

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.

Assign a REST method response to a value

I have a powershell script and I am trying to assign the response of the GET method to value $a. Bellow I have my code but it does not work. How can I assign the response to this value?
Thank you
$a = Invoke-RestMethod -Uri "https://cloud.skytap.com/templates/555949" -Method GET -ContentType "application/json" -Headers $headers | out-null
Write-Host "$a"
You're sending your code to out-null, which removes the output!
Remove | Out-Null and you'll find that your assignment to $a is working. Or perhaps you'll find an error message instead. If so, let me know and I'll do my best to help you.
To clarify, you should be running this instead.
$a = Invoke-RestMethod -Uri "https://cloud.skytap.com/templates/555949" -Method GET -ContentType "application/json" -Headers $headers