I have an Azure Function based on PowerShell running, amongst other functions, an orchestrator. I have been calling the function several times with a json body.
When testing today, the function complained about the body not containing the required parameters. Looking into the issue it turned out, that it could be solved by converting the body from json twice!
Code prior to fix:
$request = $Context.Input | ConvertFrom-json -AsHashtable
Write-Information "ServicePrincipal: $($request.ServicePrincipalName)"
Writes "ServicePrincipal: " as output
After fix:
$request = $Context.Input | ConvertFrom-json -AsHashtable
if ($($request.gettype().name) -eq 'String') {
$request = $request | ConvertFrom-json -AsHashtable
Write-Host $($request.gettype().name)
}
Write-Information "ServicePrincipal: $($request.ServicePrincipalName)"
Writes "HashTable" and "ServicePrincipal: myname#myorg.com" as output.
Why has the behavior of the body content suddenly changed?
Related
I am badly struck by this problem. I request you to answer or give a hint. I am running out of options.
I am calling an azure runbook upon high CPU utilization via a WebHook. My problem is inside runbook data is not getting decoded properly. For example, the below line is not printing anything.
Write-Output $WebHookData.RequestHeader
Wheras IF i try to explictly convert the data to JSON, like this
*$WebhookData = ConvertFrom-Json $WebhookData*
then it is a throwing error.
ConvertFrom-Json : Invalid JSON primitive: . At line:6 char:31 +
$WebhookData = $WebhookData | ConvertFrom-Json
By the way, I am trying to use the runbook available on Azure gallery {Vertically scale up an Azure Resource Manager VM with Azure Automation}
My Webhook is called from alert created on VM.
A very strange observation:
Working WebHood Example (found in an example) {"WebhookName":"test1","RequestBody":" [\r\n {\r\n \"Message\": \"Test Message\"\r\n }\r\n****]****"
Not Working(the data sent upon calling runbook from VM):
{"WebhookName":"test2","RequestBody":" {\"schemaId\":\"AzureMonitorMetricAlert\"}}
Thanks
I was getting the same error. From my testing, it appears that when performing a "Test" of the runbook, the Webhook data is received as plain text, but when invoked remotely it comes through already formatted as JSON. Here was my solution to cover both scenarios and so far has been working well...
Param (
[object] $WebhookData
)
# Structure Webhook Input Data
If ($WebhookData.WebhookName) {
$WebhookName = $WebhookData.WebhookName
$WebhookHeaders = $WebhookData.RequestHeader
$WebhookBody = $WebhookData.RequestBody
} ElseIf ($WebhookData) {
$WebhookJSON = ConvertFrom-Json -InputObject $WebhookData
$WebhookName = $WebhookJSON.WebhookName
$WebhookHeaders = $WebhookJSON.RequestHeader
$WebhookBody = $WebhookJSON.RequestBody
} Else {
Write-Error -Message 'Runbook was not started from Webhook' -ErrorAction stop
}
I tried with a webhook, the script Write-Output $WebHookData.RequestHeader should work fine.
And if I use ConvertFrom-Json $WebhookData, I can reproduce your issue, not sure why it occurred, according to the doc, the $WebhookData is also in a JSON format, if it is accepted, you could use ConvertFrom-Json -InputObject $WebhookData.RequestBody, it will work fine.
My runbook:
param
(
[Parameter (Mandatory = $false)]
[object] $WebhookData
)
if ($WebhookData) {
Write-Output $WebhookData.RequestHeader
$Body = ConvertFrom-Json -InputObject $WebhookData.RequestBody
Write-Output $Body
} else
{
Write-Output "Missing information";
exit;
}
The powershell script I used to send a webhook:
$uri = "https://s5events.azure-automation.net/webhooks?token=xxxxxxxxxxxx"
$vms = #(
#{ Name="vm01";ResourceGroup="vm01"},
#{ Name="vm02";ResourceGroup="vm02"}
)
$body = ConvertTo-Json -InputObject $vms
$header = #{ message="StartedbyContoso"}
$response = Invoke-WebRequest -Method Post -Uri $uri -Body $body -Headers $header
$jobid = (ConvertFrom-Json ($response.Content)).jobids[0]
Output:
I had the same problem use following to get webhookdata if using test pane with Alert json as input
if(-Not $WebhookData.RequestBody){
$WebhookData = (ConvertFrom-Json -InputObject $WebhookData)
}
$RequestBody = ConvertFrom-JSON -InputObject $WebhookData.RequestBody
Thought this would be pretty simple, but alas, I can't figure it out. It appears that PowerShell will prettify JSON with a single cmdlet.
Goal: Prettify JSON using a PowerShell Azure Function app
Using Microsoft Flow, send an HTTP request (POST) to an Azure Function w/ "ugly", serialized JSON file in body
Azure Function reads file in (then uses ConvertToJson cmdlet to prettify?) and outputs the file back to Flow
Questions:
What do I put in the run.ps1 area of the Azure Function to make this happen?
What do I put in the functions.json area of the Azure Function to make this happen?
I have taken below serialize string
'{ "baz": "quuz", "cow": [ "moo", "cud" ], "foo": "bar" }'
which was mentioned in Prettify json in powershell 3
Here is my function which i used with HttpPost and send the request:
using namespace System.Net
# Input bindings are passed in via param block.
param($Request, $TriggerMetadata)
# Write to the Azure Functions log stream.
Write-Host "PowerShell HTTP trigger function processed a request."
# Interact with query parameters or the body of the request.
$name = $Request.Query.baz
if (-not $name) {
$name = $Request.Body.baz
}
if ($name) {
$status = [HttpStatusCode]::OK
$body = "Hello $name"
}
else {
$status = [HttpStatusCode]::BadRequest
$body = "Please pass a name on the query string or in the request body."
}
# Associate values to output bindings by calling 'Push-OutputBinding'.
Push-OutputBinding -Name Response -Value ([HttpResponseContext]#{
StatusCode = $status
Body = $body
})
and below you can see , i am able to read it from the string which i posted.
You can use ConvertFrom-Json to convert it but i wondering if you even need it as you can access it by doing below:
$name = $Request.Query.baz
my binding is same as yours. Hope it helps.
Let me know if you still need any help.
Are you looking for something like this?
using namespace System.Net
param($Request, $TriggerMetadata)
Push-OutputBinding -Name Response -Value ([HttpResponseContext]#{
StatusCode = [HttpStatusCode]::OK
Body = $Request.RawBody | ConvertFrom-Json | ConvertTo-Json
})
Earlier this week, I asked this question to find the best way to go through a directory of text files with log information in JSON format and count how many of each unique messages there are.
I was able to do so with the answers provided. However, the problem I'm having now is that one of the files is formatted in a way that ConvertFrom-JSON doesn't like. It throws the error:
ConvertFrom-Json : Invalid object passed in, ':' or '}' expected.
Initially, I thought I could use 'erroraction -silentlycontinue' to skip that file and move on (there's just one line with nothing meaningful in it). However, it appears to be a known issue that this doesn't work with ConvertFrom-JSON and the alternative is to use a Try / Catch.
How would I use a try / catch to bypass the one bad file? Or is there another way to cleanly skip this file without having to remove it from the directory?
Here is what I have started out with. It's not much, but some guidance on this would be great. I have also seen some info online that I would use gc -Raw or Out-string before the ConvertFrom-JSON, but that gave me the same result.
try {
gci -Path "path" | gc | ConvertFrom-Json | Group-Object message -NoElement
}
catch {
write-host "can't convert file to JSON"
}
finally {
}
You'll have to insert your error handling into the pipeline. This is where ForEach-Object will come in handy:
Get-ChildItem -Path "path" |
ForEach-Object -Process {
try {
$_ | Get-Content | ConvertFrom-Json
} catch {
write-host "can't convert file '$_' to JSON"
}
} | Group-Object message -NoElement
The successful conversions will be passed through.
Additionally, consider using Write-Warning instead of Write-Host for your error condition. It seems to best fit this situation, and can be redirected or opted out by an invoker.
If you think this condition is even less serious than a warning, consider Write-Verbose so invokers can opt in instead.
The answer worked for me
ConvertFrom-Json cannot read my JSON
Use the -Raw parameter of the Get-Content cmdlet, otherwise Get-Content reads each line separately and it will be stored in the variable as an array.
$json = Get-Content c:\temp\net\cars.json -Raw
ConvertFrom-Json $json
Try this and you will end up with a nicely usable object:
$cars = Get-Content c:\temp\net\cars.json -Raw | ConvertFrom-Json | Select -ExpandProperty cars
Following on from a question yesterday (JSON and references to other JSON objects).
Is it possible to merge JSON objects at runtime in a similar fashion?
In my test.json I wish to insert the $Defaults.wimos object into WIMS.wimos at runtime similar to what I did for the $Paths.drive value in $Defaults.
{
Paths: {drive: "W:"},
Defaults: {wimos: {dstdrive: "$($Paths.drive)"}
},
WIMS: {
winos: "$($Defaults.wimos)",
wimre: {dstdrive: "$($Paths.drive)"}
}
}
In the following code I cannot work out the syntax to have the object replaced at runtime.
$JSONConfig="test.json"
$rawJSON = (Get-Content $JSONConfig -Raw)
$pathJSON = $rawJSON | ConvertFrom-Json
#
# Load Paths from JSON into $Paths
#
$Paths=$pathJSON.Paths
#
# Merge JSON objects to have the defaults replaced
#
$DefaultsJSON=($ExecutionContext.InvokeCommand.ExpandString($rawJSON)) | ConvertFrom-Json
#
# Load Defaults into $Defaults
#
$Defaults=$DefaultsJSON.Defaults
#
# Merge JSON objects to have the System replaced
#
$JSON = ($ExecutionContext.InvokeCommand.ExpandString($rawJSON)) | ConvertFrom-Json
$JSON.WIMS
write-host ("JSON.WIMS.wimre.dstdrive =" + $JSON.WIMS.wimre.dstdrive)
write-host ("JSON.WIMS.wimos.dstdrive =" + $JSON.WIMS.wimos.dstdrive) #This fails to access and print "W:"
I would like to be able to replace $($Defaults.wimos) and be able to query the dstdrive member.
Is this possible to achieve this in powershell? Any suggestions on how?
Thanks
Stuart
There's a couple of issues in your code. First, the value that would be inserted into your template would be interpolated as a string and not a json-object.
To do this, you could use the ConvertTo-json within the template. I'll show you how in a minute.
If you're gonna be recreating and using different "templates" within the same file (like path, defaults etc), I would create a helper function for recreating / filling out the template on your session variables all the time (in order to avoid repeating code).
Create the following method:
function Get-ParsedJsonTemplate(){
return ($ExecutionContext.InvokeCommand.ExpandString((Get-Content $JSONConfig -Raw))) | ConvertFrom-Json
}
Change your template json to the following (showing you that you can use powershell code within your template file:
{
Paths: {drive: "W:"},
Defaults: {wimos: {dstdrive: "$($Paths.drive)"}},
WIMS: {
winos: $(
if($Defaults -and $Defaults.wimos){
$Defaults.wimos|ConvertTo-Json -Compress
} else {
#{"dstdrive"=""}|ConvertTo-Json -compress
}
),
wimre: {dstdrive: "$($Paths.drive)"}
}
}
You also had a typo in either your template or your printout (I see that you used wimos in your "printout" in powershell and "winos" in your template. I modified the code and the following should work for you (given the above test.json):
$JSONConfig="c:\tmp\test.json"
function Get-ParsedJsonTemplate(){
return ($ExecutionContext.InvokeCommand.ExpandString((Get-Content $JSONConfig -Raw))) | ConvertFrom-Json
}
$pathJSON = Get-ParsedJsonTemplate;
$Paths=$pathJSON.Paths
$DefaultsJSON=Get-ParsedJsonTemplate;
$Defaults=$DefaultsJSON.Defaults
$JSON = Get-ParsedJsonTemplate;
$JSON.WIMS
write-host ("JSON.WIMS.wimre.dstdrive =" + $JSON.WIMS.wimre.dstdrive)
write-host ("JSON.WIMS.wimos.dstdrive =" + $JSON.WIMS.winos.dstdrive)
The reason your script was failing was due to the fact that $Defaults.wimos was not the string { dstdrive: "W:" } it was instead a PSCustomObject which would be interpolated to a the value #{drive=W:}. To obtain the correct behavior, you need to convert the $Defaults.wimos value back to a json string.
$Defaults = $DefaultsJSON.Defaults
$Defaults.wimos = $Defaults.wimos | ConvertTo-Json -Compress
I am trying to write a powershell script that reads a file and prints "true" if it is a valid JSON file. I am using Powershell v3.0 and this is what I have right now :
$text = Get-Content .\filename.txt -Raw
$powershellRepresentation = $text | ConvertFrom-Json
How do I check the return code? I mean I want something like this :
if(file not a JSON file){
Write-Host "not JSON"
}
else{
Write-Host "True"
}
UPDATE 2021: PowerShell 6 and newer versions
PowerShell 6 brings a brand new Test-Json cmdlet. Here is the reference.
You can simply pass the raw file content directly to the Test-Json cmdlet.
$text = Get-Content .\filename.txt -Raw
if ($text | Test-Json) {
$powershellRepresentation = ConvertFrom-Json $text -ErrorAction Stop;
Write-Host "Provided text has been correctly parsed to JSON";
} else {
Write-Host "Provided text is not a valid JSON string";
}
PowerShell 5 and earlier versions
There is no Test-Json cmdlet in these versions, so the best way is to put your ConvertFrom-Json cmdlet inside a try ... catch block
try {
$powershellRepresentation = ConvertFrom-Json $text -ErrorAction Stop;
$validJson = $true;
} catch {
$validJson = $false;
}
if ($validJson) {
Write-Host "Provided text has been correctly parsed to JSON";
} else {
Write-Host "Provided text is not a valid JSON string";
}
If you encounter this question and can use PowerShell 6 or later, there is now a Test-Json cmdlet. It can also not just validate that it's valid JSON, but that it conforms to a particular JSON schema using the -Schema param.
Example
$isValid = Get-Content .\filename.txt -Raw | Test-Json
if($isValid){
Write-Host "not JSON"
}
else{
Write-Host "True"
}
ARM Template Warning
A note for users looking to validate an ARM template via -Schema (I can't imagine a more perfect use case). At the time of writing, there are one or more bugs in the underlying library Test-Json uses for validation, NJsonSchema, and it is not possible to validate an ARM template.
GitHub Issues
PowerShell Issue #9560
NJsonSchema Issue #588
I don't think that it exists an other solution than catching the exception using ConvertFrom-Json.
ConvertFrom-JSON would work but only for a JSON object < 2MB in size.
For higher you can use JavaScriptSerializer class
try
{
$jsser = New-Object System.Web.Script.Serialization.JavaScriptSerializer
$jsser.MaxJsonLength = $jsser.MaxJsonLength * 10
$jsser.RecursionLimit = 99
$outObject = $jsser.DeserializeObject($json)
}
catch
{
Write-Host "Error converting $text to JSON"
}
ConvertFrom-Json shd be the appropriate way to go.
Test-Json unfortunately has alot of known unsupported JSON Types.
I.E. it cannot parse Json-Arrays or Primitives properly leading to falsely assuming it has wrong JSON-Syntax.