I am trying to use powershell and the API to update a cell, but I get an error: 1004 You are not authorized to perform this action. I've elided the IDs . . .
I have a 30 day trial. Do I need to buy in to some level of support before I can use the API to do updates or what? The sheet is on my account and I am the owner. Is Write Sheets role not included in Owner?
I am using powershell 5.1 to make the http request – here is my script. It first gets the correct sheet ID, then puts the rows and columns I am interested in into hashes, and then tries to update the sheet, but although I am the owner, I am unauthorized.
# access account of jjj.mmm#ddd.com
$apiKey = "3ccfgk..."
$url = "https://api.smartsheet.com/2.0/sheets/"
$get_headers = #{"Authorization" = "Bearer " + $apiKey}
$put_headers = #{"Authorization" = "Bearer " + $apiKey, "Content-Type: application/json" }
# get all the sheets
$response = Invoke-RestMethod -Uri $url -Headers $get_headers
$rd = $response.data
$json = ConvertTo-Json $rd
# find the Forecast sheet
$sheet_id = $rd | Where {$_.name -eq "Forecast Intermediary Sheet"} | Select -ExpandProperty id
"Sheet ID: $sheet_id"
#get the Forecast sheet
$surl = "$url$sheet_id"
$surl
$response = Invoke-RestMethod -Uri $surl -Headers $get_headers
$response | Format-List
# iterate over the rows and get the active ones
$active_rows = #{}
foreach ($row in $response.rows) {
if ($row.rowNumber -ge 14) {
if ($row.cells[2].value -Match "Active") {
$active_rows.Add($row.cells[2].value, $row.id)
}
}
}
# get the col_ids by date
$date_cols = #{}
foreach ($c in $response.columns) {
if ($c.title -as [datetime]) {
$d = $c.title -replace "/20", "/"
$ds = [datetime]::parseexact($d, 'mm/dd/yy', $null)
$date_cols.Add($ds.Tostring("yyyy-mm-dd"), $c.id)
}
}
"UPDATE:"
$rowid = $active_rows["SSL PS Active"]
$colid = $date_cols["2017-11-01"]
"row: $rowid, col: $colid"
$json = '[{ "id": "'+$rowid+'", "cells": [{"columnId": "'+$colid+'","value": "23"} }]'
"JSON: $json"
$purl = "$surl/rows"
"PUT URL: $purl"
$r = Invoke-RestMethod -Method "PUT" -uri $purl -Headers $put_headers -Body $json
$r
Below is the output of the script. The error seems to indicate it is syntactically correct, but OWNER is insufficient permissions.
Sheet ID: 5724...
https://api.smartsheet.com/2.0/sheets/5724....
UPDATE:
row: 5668..., col: 5118...
JSON: [{ "id": "5668...", "cells": [{"columnId": "5118...","value": "23"} }]
PUT URL: https://api.smartsheet.com/2.0/sheets/5724.../rows
Invoke-RestMethod : {
"errorCode" : 1004,
"message" : "You are not authorized to perform this action.",
"refId" : "14a7o8lu9sfyj"
}
At \\winfiles\jmoore\powershell\ss_api.ps1:58 char:6
+ $r = Invoke-RestMethod -Method "PUT" -uri $purl -Headers $put_headers ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod],
WebException
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodComand
Here is the meta data of my sheet
Sheet ID: 5724...
https://api.smartsheet.com/2.0/sheets/5724....
id : 5724...
name : Forecast Intermediary Sheet
version : 2
totalRowCount : 37
accessLevel : OWNER
effectiveAttachmentOptions : {GOOGLE_DRIVE, DROPBOX, ONEDRIVE, EGNYTE...}
ganttEnabled : False
dependenciesEnabled : False
resourceManagementEnabled : False
cellImageUploadEnabled : True
userSettings : #{criticalPathEnabled=False; displaySummaryTasks=True}
permalink : https://app.smartsheet.com/b/home?lx=LCTEj6F0xWNKWWxFUuLH0w
createdAt : 2017-11-22T22:34:51Z
modifiedAt : 2017-11-22T22:34:51Z
columns : {#{id=6985...; index=0; title=Column1; type=TEXT_NUMBER; primary=True;
validation=False; width=64}, #{id=1356...; index=1; title=Column2;
type=PICKLIST; options=System.Object[]; validation=False; width=64},
#{id=5859...; index=2; title=Column3; type=TEXT_NUMBER; validation=False;
....
You should be able to make API calls using an API Access Token that's owned by a trial Smartsheet account, and you can most certainly update a sheet that you own using an API Access Token you own.
I'd suspect that the 1004 error response is being caused by the fact that the contents of $put_headers isn't formatted properly. i.e., Smartsheet isn't able to accurately parse the headers you're attempting to specify in $put_headers in order to identify the Authorization header and read its value. When Smartsheet doesn't see the Authorization header in an inbound API request, it'll respond with the 1004 You are not authorized to perform this action. error response.
To troubleshoot this issue, I'd suggest that you use a tool like Fiddler to examine the outbound "Update Rows" request, paying special attention to what headers are present in the request. Then, if you discover that the Authorization header is absent from the request, you'll need to figure out how to specify multiple request headers in PowerShell, and update your code accordingly re how you're setting the value of $put_headers.
Update (adding PowerShell code):
I'm no PowerShell expert, but you might try replacing this line:
$put_headers = #{"Authorization" = "Bearer " + $apiKey, "Content-Type: application/json"}
With these lines instead:
$put_headers = #{}
$put_headers.Add("Authorization", "Bearer " + $apiKey)
$put_headers.Add("Content-Type", "application/json")
Thanks #kim-brandl
Or if you want to build the headers in a single command, you need to use syntax like this:
$put_headers = #{"Authorization" = "Bearer " + $apiKey; "Content-Type" = "application/json" }
Note also that your json payload has a few problems including:
Unmatched brackets
Extra square brackets on the outside (the body is an object, not an array)
Column and row ids must be numbers, not strings
I suggest you test the payload in a tool like postman first.
Related
I have the following PowerShell API script:
$VMname = "abcd"
$IP_address = '2.2.2.2'
$url = "https://ansibletower.xyz.com/api/v2/job_templates/12321/launch/"
$token = "9998980fsfdfsdfdf"
$headers = #{Authorization = "Bearer $token"}
$contentType = "application/json"
$method = "POST"
#### $body = '{"extra_vars": {"vm_name": "abc", "vm_ip_address": "2.2.2.2"}}'
$body = '{"extra_vars":{"vm_name":'$VMname', "vm_ip_address":'$IP_address'}}'
Invoke-RestMethod -ContentType "$contentType" -Uri $url -Method $method -Headers $headers -Body $body
When I try it with manually predefined values in the body (see the commented body line above) - it works. But when I try it with variables $VMname and $IP_address, I get the error:
Unexpected token '$VMname', "vm_ip_address":'$IP_address'}}''
expression or statement.
And if I remove single quotes before and after variables VMname and IP_address I get:
{"detail":"JSON parse error - Expecting value: line 1
column...Possible cause: trailing comma."}
It is obviously a problem with syntax and / or formatting but I cannot find the solution. Does anyone know?
Use ConvertTo-Json for this:
$VMname = "abcd"
$IP_address = '2.2.2.2'
$body = ConvertTo-Json -Compress -Depth 9 #{
extra_vars = #{
vm_name = $VMname
vm_ip_address = $IP_address
}
}
$Body
{"extra_vars":{"vm_name":"abcd","vm_ip_address":"2.2.2.2"}}
Btw, it is probably not even required to serialize your body to Json as Invoke-Restmethod is supposed to take care of that:
-body
When the input is a POST request and the body is a String, the value to the left of the first equals sign (=) is set as a key in the form data and the remaining text is set as the value. To specify multiple keys, use an IDictionary object, such as a hash table, for the Body.
I'm trying to create a PS script that is supposed to post MS Teams alerts via webhooks, regarding some metrics. The current solution that I have almost made work is via a PSCustomObject, which is afterwards converted to JSON and used as the body of the alert. The below is the current code that I am using:
$JSONBody = [PSCustomObject][Ordered] #{
"#type" = "MessageCard"
"title" = "Alert Title"
"text" = "Alert 1: $alert1CountVariable
Alert 2: $alert2CountVariable
Alert 3: $alert3CountVariable"
}
$TeamsMessageBody = ConvertTo-Json $JSONBody -Depth 100
$parameters = #{
"URI" = '<Teams Webhook URI>'
"Method" = 'POST'
"Body" = $TeamsMessageBody
"ContentType" = 'application/json'
}
Invoke-RestMethod #parameters
Everything works as needed, but as you can see the text parameter within the PSCustomObject is supposed to parse the 3 alerts on 3 separate lines, which does not seem to happen whatever I try. I tried inserting the \n and \r operators (also tried \n and \r) and nothing works this far.
Another working method that I have is the following:
$Body = '{"text": "Alert 1: ' + $alert1CountVariable +' Alert 2: ' + $alert2CountVariable +' Alert 3: ' + $alert3CountVariable +'"}'
$TeamsUrl = '<Teams Webhook URI>'
Invoke-WebRequest -Uri $TeamsUrl -Method Post -Body $Body -ContentType "application/json"
However, this also does not fully satisfy the criteria, as it still does not display the alets on separate lines and there is no title here.
Any advise on how I can make any of these two options work?
The teams interface seems to accept HTML:
Please see your script adjusted below:
$JSONBody = [PSCustomObject][Ordered] #{
"#type" = "MessageCard"
"title" = "Alert Title"
"text" = "Alert 1: $alert1CountVariable <br>
Alert 2: $alert2CountVariable <br>
Alert 3: $alert3CountVariable"
}
$TeamsMessageBody = ConvertTo-Json $JSONBody -Depth 100
$parameters = #{
"URI" = '<Teams Webhook URI>'
"Method" = 'POST'
"Body" = $TeamsMessageBody
"ContentType" = 'application/json'
}
Invoke-RestMethod #parameters
I have inserted <br> at the end of each line which is a line break in HTML.
We're using ADO Server 2019 and as part of a larger project I need to upload some emails to the ADO environment to attach to work items. I've already figured out that you first need to upload the attachment, pass over the attachment ID to the work item patch request and add the relationship from the work item end however what I can't for the life of me figure out is where to pick the actual email or any item up to upload.
The example that's given within the MS docs shows you how to post an upload but it uploads nothing, it just creates a blank page.
POST https://{instance}/fabrikam/_apis/wit/attachments?fileName=textAsFileAttachment.txt&api-version=5.0
A body for the request needs to be built to define where the attachment is from what I can gather however there isn't any kind of documentation around this for simply posting it via HTTP.
We're using Blue Prism so using C# is an option but not ideal.
Thanks in advance.
Please follow the steps below:
1.Upload a text file
POST https://{instance}/fabrikam/_apis/wit/attachments?fileName=textAsFileAttachment.txt&api-version=5.0
Request Body: "User text content to upload"
2.You will get a response like:
{
"id": "6b2266bf-a155-4582-a475-ca4da68193ef",
"url": "https://fabrikam:8080/tfs/_apis/wit/attachments/6b2266bf-a155-4582-a475-ca4da68193ef?fileName=textAsFileAttachment.txt"
}
Copy this url from the response.
3.Add an attachment to work items:
POST https://{instance}/fabrikam/_apis/wit/attachments?fileName=textAsFileAttachment.txt&api-version=5.0
Request body:
[
{
"op": "add",
"path": "/relations/-",
"value": {
"rel": "AttachedFile",
"url": "https://fabrikam:8080/tfs/_apis/wit/attachments/6b2266bf-a155-4582-a475-ca4da68193ef?fileName=textAsFileAttachment.txt",
"attributes": {
"comment": "Spec for the work"
}
}
}
]
Replace the url of the request body.
You have to pass a file content if you create a new attachment. Upload a binary file
POST
https://dev.azure.com/fabrikam/_apis/wit/attachments?fileName=imageAsFileAttachment.png&api-version=6.1-preview.3
"[BINARY FILE CONTENT]"
Here is an example through Powershell:
$user = ""
$token = "<personal access token>"
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$token)))
$org = "org_name"
$teamProject = "teamproject"
$wiId = "work item Id"
$folderPath = "c:/temp"
$fileName = "some_file.zip"
$createAttachmetUrlTemplate = "https://dev.azure.com/$org/$teamProject/_apis/wit/attachments?fileName={fileName}&api-version=5.0"
$updateWIUrlTemplate = "https://dev.azure.com/$org/_apis/wit/workitems/{id}?api-version=5.0"
$wiBodyTemplate = "[{`"op`": `"add`",`"path`": `"/relations/-`",`"value`": {`"rel`": `"AttachedFile`",`"url`": `"{attUrl}`", `"attributes`": {`"comment`": `"Spec for the work`"}}}]"
function InvokePostRequest ($PostUrl, $body)
{
return Invoke-RestMethod -Uri $PostUrl -Method Post -ContentType "application/json" -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)} -Body $body
}
function InvokePatchRequest ($PatchUrl, $body)
{
return Invoke-RestMethod -Uri $PatchUrl -Method Patch -ContentType "application/json-patch+json" -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)} -Body $body
}
$bytes = [System.IO.File]::ReadAllBytes("$folderPath/$fileName")
$createAttachmetUrl = $createAttachmetUrlTemplate -replace "{filename}", $fileName
$resAtt = InvokePostRequest $createAttachmetUrl $bytes
$updateWIUrl = $updateWIUrlTemplate -replace "{id}", $wiId
$wiBody = $wiBodyTemplate -replace "{attUrl}", $resAtt.url
InvokePatchRequest $updateWIUrl $wiBody
I am using Powershell to dynamically create a payload of data to be packaged up and sent on in a REST API Post Request.
My problem is that when it is recived by the API, it is listed as System.Collections.Hashtable. I am clearly doing something wrong here in how the data is being formatted, but nothing seems to work for me.
Here's how it is received by the API
{
"properties": {
"recip_test": [
"System.Collections.Hashtable",
"System.Collections.Hashtable"
],
"offending_shifts": "MAX, OnCall-Default Shift",
"group_name": "Alarmpoint Administrators"
}
}
I've tried ConvertTo-Json as well as += () / .Add() but none of those seem to work.
I am looping through an array of data which represent ID's in that array. For each item in that array (in the loop) I need to make a hash table which looks like this,
$recipient = #{
'id' = $y
'recipientType' = 'PERSON'
}
And then take that hash and shovel it into the payload field for recipients which then needs to be passed in the POST request. Below is the full code.
foreach($x in $collated_group_data) {
if ($x.group_name -ne 'Alarmpoint Administrators') {
next
}
$uuid = "***********/triggers?apiKey=**************"
$webhook_path = "$base/api/integration/1/functions/$uuid"
$payload = #{
'properties' = #{
'group_name' = $x.group_name
'offending_shifts' = $x.offending_shifts.Substring(0, $x.offending_shifts.Length - 2)
'recipients' = #()
}
}
foreach($y in $x.supervisor_ids) {
$payload.properties.recipients += #{'id' = $y; 'recipientType' = 'PERSON'}
}
$payload = $payload | ConvertTo-Json
Invoke-WebRequest -Uri $webhook_path -Method POST -Body $payload -ContentType 'application/json'
}
You must use the -Depth parameter with a value of 3 or greater in the ConvertTo-Json command in this case.
$payload = $payload | ConvertTo-Json -Depth 3
By default, the -Depth parameter is set to 2. The parameter specifies how many levels of contained objects are included in the JSON representation. You have three levels in your example.
I need to perform an Invoke-Webrequest with a specifically formatted body to add devices to a product. Here is what it looks like in json (example straight from the vendor's documentation):
$body_json = '{"datasource": [{
"parentId": "123456789000",
"name": "(name)",
"id": "(value)",
"typeId": 0,
"childEnabled": false,
"childCount": 0,
"childType": 0,
"ipAddress": "(ipAddress)",
"zoneId": 0,
"url": "(url)",
"enabled": false,
"idmId": 123456789000,
"parameters": [{
"key": "(key)",
"value": "(value)"
}]
}]}'
When I try to submit this in its json representation though, I get the following error:
Invoke-WebRequest : Can not deserialize instance of
com.vendor.etc.DataSourceDetail out of START_ARRAY token at [Source:
java.io.StringReader#22c614; line: 1, column: 1] At
C:\powershell_script_location\ps.ps1:114 char 9
+ $request = Invoke-WebRequest $url -Method Post -Headers $headers -Body $body_json - ...
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation:
(System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest],
WebException + FullyQualifiedErrorId :
WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
The issue is with the format of the "parameters", parameter because the request submits fine when omitting the "parameters", but then the
devices that I'm adding are missing important parameter details.
Is there something wrong with Invoke-WebRequest, JavaScriptSerializer,
the vendor's code, or is this a user error? Let me know if any clarification is needed.
Unfortunately I don't know what a com.vendor.etc.DataSourceDetail instance looks like, as I am using an API and I don't have access to it directly.
Use Invoke-RestMethod instead of Invoke-WebRequest.
If you have the body as a string use:
Invoke-RestMethod -Uri http://your-url.com -Method POST -Body $body_json -ContentType "application/json"
If the body must be constructed from data/parameters, it might be easier to build a hashtable and convert it to json via ConvertTo-Json:
$body_json = #{
datasource = #(
#{
parentId = 123456789000
name = "name"
id = "value"
typeId = 0
childEnabled = $false
childCount = 0
childType = 0
ipAddress = "ipAddress"
zoneId = 0
url = "url"
enabled = $false
idmId = 123456789000
parameters = #( #{
key = "key"
value = "value"
})
})} | ConvertTo-Json -Depth 4
Invoke-RestMethod -Method 'Post' -Uri http://your-url.com -Body $body_json -ContentType "application/json"
Body Undefined
I couldn't understand why the req.body on the server was undefined (NodeJS Azure Function). It turns out I had a header that was an empty string.
It's not clear whether is was invoke-restmethod or azure-functions-core-tools that has a bug.
FWIW.