Raise powershell error and stop execution of script - powershell

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.

Related

Error resetting retainedByRelease property of a build in build pipeline through PowerShell

I am trying to set the value of retainedByRelease property of a build in one of my build pipelines by calling Azure Rest API through PowerShell. I am getting "page not found" error in the terminal. I suspect I am preparing the URL wrong but not sure where. Please help.
To figure out the URL, I got the area id from the below link for Build (which is 5d6898bb-45ec-463f-95f9-54d49c71752e).
https://learn.microsoft.com/en-us/azure/devops/extend/develop/work-with-urls?view=azure-devops&tabs=http#resource-area-ids-reference
Got the personalized token from VSO.
Below is what the script looks like (masked the token and the build id):
$orgURL = "https://dev.azure.com"
$personalToken = "*******"
$token = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$($personalToken)"))
$header = #{authorization="Bearer $token"}
$areaId = "5d6898bb-45ec-463f-95f9-54d49c71752e"
$orgResourceAreasUrl = [string]::Format("{0}/_apis/resourceAreas/{1}?api-version=5.0-preview.1",$orgURL,$areaId)
$results = Invoke-RestMethod -Uri $orgResourceAreasUrl -Headers $header
$basrUrl = $results.locationUrl
Invoke-RestMethod -Uri ($basrUrl + '_apis/build/builds/<someBuildId>?api-version=3.2') -Method Patch -Body (ConvertTo-Json #{"retainedByRelease"='false'}) -Headers $header -ContentType "application/json" | Out-Null
I am getting the error on the last line on Invoke-RestMethod call.
I am trying this because the build pipeline underneath is throwing some compliance error and I need to remove that pipeline and I am not able to delete it as this build is being retained by the release.
P.S, I have already checked the below links.
https://developercommunity.visualstudio.com/content/problem/442784/one-or-more-builds-associated-with-the-requested-p.html - looks like the permanent fix is not in place yet
https://almguide.net/2018/12/13/build-blocked-by-release/
-Uri ($basrUrl + 'https://tfsprodea1.visualstudio.com/')
The error caused by this incorrect url constructed. You can use Write-Host $orgResourceAreasUrl to print out the url you constructed previous, and you will see that the values of $results.locationUrl is https://tfsprodea1.visualstudio.com/ instead of https://dev.azure.com/{your org name}/. So, in fact, at the last line of script, the value of url you constructed is https://tfsprodea1.visualstudio.com/_apis/build/builds/<someBuildId>?api-version=3.2. This is not a correct url which can be identified by Azure Devops. The correct url should be https://dev.azure.com/{org name}/_apis/build/builds/{build id}?api-version=3.2
The root cause of this error is that the value you specified to orgURL is not correct. For correct the error you received, you should change the value of $orgURL and specify it as the below shown:
$orgURL = "https://dev.azure.com/{your org name}"
Only this, you can get the correct value of locationUrl.
In addition, at the line of script, the url you constructed is
https://dev.azure.com/{org name}/_apis/build/builds/{build id}?api-version=3.2
This is a definition message which is organization level, you could not UPDATE it with token. Or you will receive the message The project with id 'No project was specified.' does not exist, or you do not have permission to access it..
If you still want to update the value of retainedByRelease, please construct the url as
https://dev.azure.com/{org name}/{project name}/_apis/build/builds/{build id}?api-version=3.2
And this is a project level info which can be updated with token.
So, based on your script, I made some change:
[String]$project = "$env:SYSTEM_TEAMPROJECT"
$orgURL = "https://dev.azure.com/{your org name}"
$header = #{
Authorization = "Bearer $env:SYSTEM_ACCESSTOKEN"
}
$areaId = "5d6898bb-45ec-463f-95f9-54d49c71752e"
$orgResourceAreasUrl = [string]::Format("{0}/_apis/resourceAreas/{1}?api-version=5.0-preview.1",$orgURL ,$areaId)
$results = Invoke-RestMethod -Uri $orgResourceAreasUrl -Headers $header
$basrUrl = $results.locationUrl
Invoke-RestMethod -Uri ($basrUrl + $project + '/_apis/build/builds/{build id }?api-version=3.2') -Method Patch -Body (ConvertTo-Json #{"retainedByRelease"='false'}) -Headers $header -ContentType "application/json" | Out-Null
Note: Enable the below checkbox first so that you can use the System.AccessToken in your script.

Powershell is swallowing REST error response

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
}

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?

How can I correct Invalid CSRF token errors when calling a rest API?

I've got a script that will query a web rest service to find the ID for a specific object. I've gotten the query working fine under Powershell for a single query, but I need to run several hundred queries. It's really slow (And bad practice) to login for every single query.
I have the script logon to the server first and save the session. The I run the rest query using a post operation. First one works fine. Second one bombs out saying:
Invoke-RestMethod : Invalid CSRF Token
Invalid CSRF Token
An invalid cross-site request forgery token was detected in the request.
The code looks like this:
$secpasswd = ConvertTo-SecureString "password" -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential ("user", $secpasswd)
$headers = "Content-Type: text/plain","Accept: text/plain"
#Login it the server to store the session to WebSession.
$login = Invoke-WebRequest -Uri "https://devrhapapp01:8444" -Credential $cred -SessionVariable websesssion
#This one returns correctly.
$Results = Invoke-RestMethod -Uri "https://devrhapapp01:8444/api/components/find" -ContentType "text/plain" -Method Post -Body "Search1" -WebSession $websesssion
write-host $Results
#This one will give an error.
$Results = Invoke-RestMethod -Uri "https://devrhapapp01:8444/api/components/find" -ContentType "text/plain" -Method Post -Body "Search1" -WebSession $websesssion
write-host $Results
Not sure what is going on, but try submitting the correct anti-forgery token:
$forgeryToken = ($login.InputFields |
Where { $_.name -eq "__RequestVerificationToken" }).value
$forgeryTokenPostData = "__RequestVerificationToken=$forgeryToken"
Invoke-WebRequest .... -Body $forgeryTokenPostData

Error Handling for Invoke-RestMethod - Powershell

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"