Invoke-RestMethod: POST Assoc-Array using JSON to WebService (System.Collections.Hashtable) - powershell

I want to POST an associative array to a web-service.
Therefore I create the array in PowerShell this way:
$Data = #{
"MyProperty" = "bla bla";
"MyFolder" = #{
"MySubFolder1" = #{
"MySubProperty" = "bla bla";
}
"MySubFolder2" = #{
"MySubProperty" = "bla bla";
}
}
}
I use the following line to POST the data to the web-service:
$Response = Invoke-RestMethod -Method Post -Uri $URL -Body $Data
The point is, that not the whole array is reaching the web-service.
Only the top-level data is received correctly. The nested arrays are not accessible. (System.Collections.Hashtable)
It looks like this:
Array
(
[MyProperty] => bla bla
[MyFolder] => System.Collections.Hashtable
)
I did also try Invoke-WebRequest instead of Invoke-RestMethod and did play with ConvertTo-JSON, but I was not able to find a clean solution to transfer the data nicely to the web-service.
How can I solve this problem?
Thank you!

$Data has to be converted to JSON using ConvertTo-JSON.
Using the following code-line did the trick:
$Response = Invoke-RestMethod -Method Post -Uri $URL -Body ( $Data | ConvertTo-JSON -Compress ) -ContentType "application/json; charset=utf-8"

Related

Unable to ingest JSON data into Azure Event Hub

I wrote the below Powershell script to get the JSON data from an API endpoint (https://data.melbourne.vic.gov.au/resource/vh2v-4nfs) and then write this data in JSON format to Azure Event hub. I am able to successfully get the data from the endpoint however the data is not getting ingested into Azure Event Hub.
Can anyone please let me know what's wrong with the below code:
$url = "https://data.melbourne.vic.gov.au/resource/vh2v-4nfs"
$apptoken = "k7lQcUCVFoROv7rQh9fSSXMkZ"
# Set header to accept JSON
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("Accept","application/json")
$headers.Add("X-App-Token",$apptoken)
$results = Invoke-RestMethod -Uri $url -Method get -Headers $headers
$results
$method = "POST"
$URI = "https://YOURNS.servicebus.windows.net/eh-streetparking/messages"
$signature = "SharedAccessSignature sr=YOURNS.servicebus.windows.net%2feh-streetparking&sig=K6bfL1VjW9FUcL0B5xaI%3d&se=16722&skn=eh-sap-streetparking"
#$authInfo = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("$signature"))
# API headers
$headers = #{
"Authorization"=$signature;
# "Content-Type"="application/json;type=entry;charset=utf-8";
"Content-Type"="application/json";
}
# execute the Azure REST API
foreach ( $result in $results)
{
Invoke-RestMethod -Uri $URI -Method $method -Headers $headers -Body $result
}
The value you have presented as the return result from your Invoke-RestMethod is actually a deserialized PowerShell object, not JSON. It appears to be having its quotes removed at some point too.
PSObject ($results) looks like this: $x = #{account_id="12345"; username="12345"; is_locked="False"; employee_id="12345"; first_name="John"; middle_initial="Roger"; last_name="Doe"; full_name="John Roger Doe"}
You can do this to access individual values:
$x.full_name
Finally, follow this syntax to send POST request:
$Cred = Get-Credential
$Url = "https://server.contoso.com:8089/services/search/jobs/export"
$Body = #{
search = "search index=_internal | reverse | table index,host,source,sourcetype,_raw"
output_mode = "csv"
earliest_time = "-2d#d"
latest_time = "-1d#d"
}
Invoke-RestMethod -Method 'Post' -Uri $url -Credential $Cred -Body $body -OutFile output.csv

Get-content not producing an array that Invoke-restmethod can process

As a follow-up to this question, instead of using a long array in the script I wanted to draw from a text file. So I replaced this:
$URLs = 'http://websiteone.com','http://websitetwo.com','http://websitethree.com'
with this
$URLs = Get-Content ./urlfile.txt
or (functionally the same as far I know) this
$URLs = #(Get-Content ./urlfile.txt)
But I end up with Invoke-RestMethod : The remote server returned an error: (400) Bad Request.
Incorporating the great response form my last question, my foreach loop looks like this:
foreach($URL in $URLs) {
$BODY = #([pscustomobject]#{"client" = #{"clientId" = "company"; "clientVersion" = "1.0"}; "threatInfo" = #{"threatTypes" = "MALWARE","SOCIAL_ENGINEERING","THREAT_TYPE_UNSPECIFIED","UNWANTED_SOFTWARE","POTENTIALLY_HARMFUL_APPLICATION"; "platformTypes" = "ANY_PLATFORM"; "threatEntryTypes" = "URL","EXECUTABLE","THREAT_ENTRY_TYPE_UNSPECIFIED"; "threatEntries" = #{"url" = $URL}}})
$JSONBODY = $BODY | ConvertTo-Json
$Result = Invoke-RestMethod -Method 'POST' -Uri $Uri -Body $JSONBODY -Headers $HEADERS
if ( ([string]::IsNullOrEmpty($Result)) ) {} else {write-host $URL "ALERT: Safe browsing match!"}
}
... but this doesn't work if I create the array with the Get-Content cmdlet. If I run the script either way, then type $URLs, I get the exact same data returned. What am I doing wrong with get-content?
The Invoke-RestMethod cmdlet is there to make one Rest request at a time and can't take an array.
You will need to add a forEach loop to step through your $urls one at a time, something like this:
foreach($url in $urls){
$result = Invoke-RestMethod -Uri $url
#do something with $result
}
So to integrate into your sample from the previous question, you should have a urls.txt file which looks like this:
http://google.com
http://small.com
https://fast.com/
And then your code would look like this:
$URLs = get-content .\urls.txt
$HEADERS = #{ 'Content-Type' = "application/json" }
$GOOGLE_API_KEY='[API Key]'
$Uri = 'https://safebrowsing.googleapis.com/v4/threatMatches:find?key='+ $GOOGLE_API_KEY
foreach($URL in $URLs) {
$BODY = #([pscustomobject]#{"client" = #{"clientId" = "company"; "clientVersion" = "1.0"}; "threatInfo" = #{"threatTypes" = "MALWARE","SOCIAL_ENGINEERING","THREAT_TYPE_UNSPECIFIED","UNWANTED_SOFTWARE","POTENTIALLY_HARMFUL_APPLICATION"; "platformTypes" = "ANY_PLATFORM"; "threatEntryTypes" = "URL"; "threatEntries" = #{"url" = $URL}}})
$JSONBODY = $BODY | ConvertTo-Json
$result = Invoke-RestMethod -Method 'POST' -Uri $Uri -Body $JSONBODY -Headers $HEADERS
[pscustomObject]#{SiteName=$url;ThreatInfo=$result.Matches}
}
This would load up the list of $urls from your text file, then run a Rest Request on each, storing the result in $result. Finally, it will make a new PowerShell Object with the site name and show you if there are any matches from the Google SafeBrowsing API.
You'll need to run the command interactively and see which properties from $result are meaningful to you, but you can see all of the expected properties in the Google API Docs.
Edit
Found the bug. It turns out when we use Get-Content the object returned back retains some of the document formatting information from the original file! We can see this by inspecting $JSONBODY. We also see that the conversion to Json from [PSCustomObject is leaving a lot of cruft behind too.
To fix this, we should cast $URL into a string using the ToString() method and also ditch casting to [psCustomObject] too as shown below.
$BODY = #{
"client" = #{
"clientId" = "company"; "clientVersion" = "1.0"
};
"threatInfo" = #{
"threatTypes" = "MALWARE",
"SOCIAL_ENGINEERING",
"THREAT_TYPE_UNSPECIFIED",
"UNWANTED_SOFTWARE",
"POTENTIALLY_HARMFUL_APPLICATION"; "platformTypes" = "ANY_PLATFORM"; "threatEntryTypes" = "URL"; "threatEntries" = #{
"url" = $URL.ToString()
}
}
}
$JSONBODY = $BODY | ConvertTo-Json

How to call Invoke-RestMethod in powershell for each JSON entry

$person = #{
"Username"="Test3";
"Password"="Test3";
}
$json = $person | ConvertTo-Json
Invoke-RestMethod -Method POST -Header $Header -ContentType "application/json" -uri "http://testa.katest.com/CreateUsers/api/v1.0/Users" -Body $json
Above powershell script works perfect and also creates the username Test3 in the system:
But If I would like to call this Invoke-RestMethod for creating multiple users at the same time - What command should I use?
Since I can only assume information about your API, I can offer two potential ways to do this.
If the POST supports a JSON array, you can do the following:
$persons = #{
"Username"="Test3";
"Password"="Test3";
},#{
"Username"="Test4";
"Password"="Test4";
}
$json = $persons | ConvertTo-Json
Invoke-RestMethod -Method POST -Header $Header -ContentType "application/json" -uri "http://testa.katest.com/CreateUsers/api/v1.0/Users" -Body $json
If POST only supports one json with one user, you can do the following:
$persons = #{
"Username"="Test3";
"Password"="Test3";
},#{
"Username"="Test4";
"Password"="Test4";
}
foreach ($person in $persons) {
$json = $person | ConvertTo-Json
Invoke-RestMethod -Method POST -Header $Header -ContentType "application/json" -uri "http://testa.katest.com/CreateUsers/api/v1.0/Users" -Body $json
}
Each method centers around creating an array of hash tables. Each hash table has a key-value pair for Username and Password. Then you can choose to either iterate or not before converting to JSON.

Error 400 bad request when creating a group calendar event in powershell with Microsoft Graph API

I'm trying to create an event in a calendar in an Office 365 group via powershell.
This is my first experience to do this type of programming so sorry if my question will be very basic :-)
First, I created a simple json file (calendar.json)
{
"start":
{
"dateTime":"2017-03-12T17:00:00.0000000",
"timeZone":"UTC"
},
"end":
{
"dateTime":"2017-03-12T17:30:00.0000000",
"timeZone":"UTC"
},
"responseStatus": {
"response": "None"
},
"iCalUId": "null",
"isReminderOn": false,
"subject": "Test Event created from API"
}
Then I create the event with these steps:
Use a tested powershell function that give me the token
Add header with this code:
$headers = #{}
$headers.Add('Authorization','Bearer ' + $token.AccessToken)
$headers.Add('Content-Type',"application/json")
Because I'm starting now, I convert the json file in an object and then the object in json (I know, it's quite stupid, but I've done so beacuse I have no knowledge of json and how convert without error in powershell code)
$json = ConvertFrom-Json -InputObject (Gc 'C:\Users\mmangiante\OneDrive - Interactive Media S.p.A\Office 365\calendar.json'-Raw)
$body = ConvertTo-Json $json
Call the Invoke-RestMethod
response = Invoke-RestMethod 'https://graph.microsoft.com/v1.0/groups/768afb0c-bafd-4272-b855-6b317a3a9953/calendar/events' -Method Post -Headers $headers -Body $json
What is returned is a 400 bad request.
It's the same error of Sending Microsoft Graph request events returns 400
Given the answer given to that question I modified my code to return the error:
try{$restp=Invoke-RestMethod 'https://graph.microsoft.com/v1.0/groups/768afb0c-bafd-4272-b855-6b317a3a9953/calendar/events' -Method Post -Headers $headers -Body $json
} catch {$err=$_}
$err
like suggested in How do I get the body of a web request that returned 400 Bad Request from Invoke-RestMethod but I found nothing of interest.
The only thing that I found is that, at the time of writing, the Invoke-RestMethod doesn't return the full response as in this https://github.com/PowerShell/PowerShell/issues/2193
I suppose my json is not "well formed", but I don't know why.
Does anyone have a suggestion?
This formatting has worked for me in the past. Let me know if this resolves your issues:
$headers = #{
"Authorization" = ("Bearer {0}" -f $token);
"Content-Type" = "application/json";
}
$body = #{
Name1 = 'Value1';
Name2 = 'Value2';
}
$bodyJSON = $body | ConvertTo-Json
Invoke-RestMethod -Method Post -Uri <API HERE> -Headers $headers -Body $bodyJSON -OutFile $output
Thanks Shawn,
I tried your suggestion but without luck; I have done other test with 2 other api and found discordant results.
The first test is to use the graph api related to contact, creating a contact as in https://graph.microsoft.io/en-us/docs/api-reference/v1.0/api/user_post_contacts
I created the powershell representation of the sample json:
$contactbody = #{
givenName = 'Ciccio';
surname = 'Patacca';
emailAddresses = #(
#{
address = 'cicciopatacca#cicciociccio.com'
name = 'Ciccio Patacca'
}
)
businessPhones = (
'+39 555 123 4567'
)
}
and then converted in json
$contactbody = $contactbody | ConvertTo-Json
I retrieved from Azure Portal the object ID related to my user in my company and so I call the rest method:
$response = Invoke-RestMethod 'https://graph.microsoft.com/v1.0/users/e37d2dbe-bdaf-4098-9bb0-03be8c653f7d/contact' -Method Post -Headers $headers -Body $contactbody
The final result is a 400 bad request error.
So, I tried another example and reused some code retrieved from a google search.
This time I copied the powershell json representation as in the sample and converted:
$body = #{"displayName"="ps-blog"; "mailEnabled"=$false; "groupTypes"=#("Unified"); "securityEnabled"=$false; "mailNickname"="ps1" } | ConvertTo-Json
and, as stated in https://graph.microsoft.io/en-us/docs/api-reference/v1.0/api/group_post_groups I called the rest method
$response = Invoke-RestMethod 'https://graph.microsoft.com/v1.0/groups' -Method Post -Headers $headers -Body $body
and this worked.
So I thought that the previously powershell representations of the sample with the error were not correctly formed (even if when I printed it they are equal to the samples on the graph api); for test, I rewritten the last powershell of the body as this:
$body = #{
displayName = 'TestGraphGroup';
mailEnabled = $true;
groupTypes = #('Unified')
securityEnabled = $false;
mailNickname = 'TestGraphGroup'
}
$body = $body | ConvertTo-Json
and invoked last method: it worked again.
So, where I'm doing wrong?
A new SDK was released that makes this easier.
Checkout instructions on how to use it here

How to do a webrequest with JSON Body containing arrays or lists?

The Situation
I need to interact with a REST API which requires JSON post data.
So I started working with something like this:
Simple working example
$ReqURI = 'http://httpbin.org/post'
Invoke-WebRequest -Method Post -Uri $ReqURI -Body #{
'api.token' = "api.token"
'action' = 'create item'
} -Verbose| fl *
So I tested it with httpbin.org:
Issue
But If you need to use a list or array in the body part like this example:
$ReqURI = 'http://httpbin.org/post'
$Response = Invoke-RestMethod -Method Post -Uri $ReqURI -Body #{
'api.token' = "api.token"
'names' = #('rJENK', 'rFOOBAR')
} -Verbose| fl *
$Response
... you get something like a converting error:
So I thought I could convert the body myself into a JSON string and using the -Depth parameter from ConvertTo-JSON. In addition to that I tried how it looks like if I convert the hashtable into an object first.
But both tries return the same and even worse result:
So finally I switched to Invoke-WebRequest. But the results here are the same.
My reference is a working api call with the JSON string:
"api.token" : "fooobar",
"names": [
"rJENK",
"rFOOBAR"
]
Personal Solution
I figured out a workaround. It seems like the api I'm working with can't handle requests containing nested elements or array created by PowerShell.
Non working example:
$ReqURI = 'http://httpbin.org/post'
$Response = Invoke-RestMethod -Method Post -Uri $ReqURI -Body #{
'api.token' = "api.token"
'names' = #('rJENK', 'rFOOBAR')
} -Verbose| fl *
$Response
Instead I had to fake an array with indexes. That's the workaround:
$ReqURI = 'http://httpbin.org/post'
$Response = Invoke-RestMethod -Method Post -Uri $ReqURI -Body #{
'api.token' = "api.token"
'names[0]' = 'rJENK'
'names[1]' = 'rFOOBAR'
} -Verbose| fl *
$Response
So now you have to use the content type as Application/Json like:
$ReqURI = 'http://httpbin.org/post'
$Jsonbody= #{
'api.token' = "api.token"
'names' = #('rJENK', 'rFOOBAR')
} | ConvertTo-Json
$Response = Invoke-RestMethod -Method Post -ContentType 'application/json' -Uri $ReqURI -Body $body -Verbose| fl *
This should do your work. Hope it helps.