How to create JSON payload using power shell Foreach loop - json

Requirement: To send E-Mail via send grid account (Send Grid API) by attaching multiple attachments.
Description: I am able to create json payload and able to send with single attachment by hard coding attachment values. I am opening window forms dialog and able to select single/multiple files that needs to be attached.
Code:
$FileBrowser = New-Object System.Windows.Forms.OpenFileDialog -Property #{
InitialDirectory = [Environment]::GetFolderPath('Desktop')
#Filter = 'Documents (*.docx)|*.docx|SpreadSheet (*.xlsx)|*.xlsx'
Filter = 'All files (*.*)| *.*'
Title = 'Select File(s) for Attachments'
Multiselect = $true
}
$FileBrowser.ShowDialog() | Out-Null
$FilesEncodedContents = New-Object System.Collections.ArrayList
$AttachmentsjsonRequest = #()
if ($FileBrowser.FileNames.Count -gt 0) {
foreach ($file in $FileBrowser.FileNames) {
[string] $filerawContent = $null
$filedetails = Get-Item $file
$filerawContent = ConvertToBase64Encode $file
if (![string]::IsNullOrWhitespace($filerawContent)) {
$FilesEncodedContents.Add($filerawContent)
$obj = New-Object -TypeName PSObject
$obj | Add-Member -MemberType NoteProperty -Name filename -Value (Get-Item $file).Name
$obj | Add-Member -MemberType NoteProperty -Name content_id -Value (Get-Item $file).Name
$obj | Add-Member -MemberType NoteProperty -Name content -Value $filerawContent
$obj | Add-Member -MemberType NoteProperty -Name disposition -Value 'attachment'
$AttachmentsjsonRequest += $obj
}
}
}
Write-Host "$AttachmentsjsonRequest"
$headers = #{ }
$headers.Add("Authorization", "Bearer $ApiKey")
$headers.Add("Content-Type", "application/json")
$jsonRequest = [ordered]#{
personalizations = #(#{to = #(#{email = "$MailTo" })
subject = "$Subject"
})
from = #{email = "no-reply#xxx.com" }
attachments = "$AttachmentsjsonRequest"
content = #( #{ type = "text/plain"
value = "Sample Mail Body"
}
)
} | ConvertTo-Json -Depth 100
Write-Host $jsonRequest | ConvertTo-Json -Depth 100
Invoke-RestMethod -Uri "https://api.sendgrid.com/v3/mail/send" -Method Post -Headers
$headers -Body $jsonRequest
Write-Host "Mail Sent"
#region ConvertToBase64Encode
Function ConvertToBase64Encode([string] $AttachementFile) {
[string] $fileContentEncoded = $null
if (Test-Path $AttachementFile -PathType leaf) {
$fileContent = get-content $AttachementFile
$fileContentBytes = [System.Text.Encoding]::UTF8.GetBytes($fileContent)
$fileContentEncoded = [System.Convert]::ToBase64String($fileContentBytes)
$fileContentEncoded | set-content ((Get-Item -Path $AttachementFile).Name + ".b64")
}
else {
$fileContentEncoded = $null
Write-Host "File : $FileAttachment not exists,skipping and continue to add if any other
attachments uploaded"
}
return $fileContentEncoded
}
#endregion
Problem: [UPDATED]
Am getting below error after trying to upload single or multiple attachments
{"errors":[{"message":"Invalid type. Expected: array, given: string.","field":"attachments","help":"http://sendgrid.com/docs/API_Reference/Web_API_v3/Mail/errors.html#message.attachments"}]} .
Reference Links :
Send Grid API Documentation:
https://sendgrid.com/docs/API_Reference/api_v3.html

My advice is to consider not building the JSON as a text, first build a Powshell object that you convert to JSON with ConvertTo-Json.
Using this method, arrays will be correctly represented in JSON. don't forget to set the -DEPTH param.
attachments array[object] An array of objects in which you can specify any attachments you want to include.
So in your Powershell object attachments is going to be a #().
of objects with content, type, filename, disposition, conten_id properties.

I realized error details in depth in late . It was giving hint in JSON payload "attachments" field is showing as string rather than array object which it is fixed by adding #(#()) to attachmentjsonrequest in "$jsonrequest" variable .
In Short: "attachmentjsonrequest is an array object which needs to convert to JSON payload by using #()
Thanks for suggesting to use array object.
attachments = #(#($AttachmentsjsonRequest))

Related

Adding JSON content to PSCustomObject - The property cannot be found on this object

Quite new to objects in PS.
I'm trying to create pscustomobject, adding JSON contents to it via ConvertFrom-JSON and then I'm trying to get contents from another JSON to be set on one of the properties ( nested hierarchy)
$combinedObject=#()
$props = #{
ServiceDefinitions = #()
DataCenters = #()
}
$combinedObject = New-Object -TypeName PSCustomObject -Property $props
$servicedefinitions = Get-ChildItem -Path .\ServiceDefinitions\ | Select Name
$datacenters = Get-ChildItem -Path .\DataCenters\ | Select Name
$environments = #("Production")
$env="TEST"
Foreach ($datacenter in $datacenters)
{
$datacenterdata = $null
write-host "new run"
write-host $datacenter.Name
$datacentername = $datacenter.Name
$datacenterdata = Get-Content -Path .\DataCenters\$datacentername\config.json -Raw
$datacenterformatteddata = $datacenterdata | ConvertFrom-Json -Depth 5
$combinedObject.DataCenters += $datacenterformatteddata
$combinedObject.DataCenters.$datacentername
}
Foreach ($datacenter in $datacenters)
{
$pods = $null
$datacetnername = $null
$datacentername = $datacenter.Name
$pods = Get-ChildItem -Path .\DataCenters\$datacentername\$env\Pod\ | Select Name
Foreach ($pod in $pods)
{
$podname = $pod.Name
$poddata = Get-Content -Path .\DataCenters\$datacentername\$env\Pod\$podname\config.json -Raw
#echo $combinedObject.DataCenters
write-host $datacentername
$podformatteddata = $poddata | ConvertFrom-Json -Depth 5
$combinedObject.DataCenters.$datacentername.pods += $podformatteddata
}
}
For each loop iterations I receive
The property 'pods' cannot be found on this object. Verify that the property exists and can be set.
I can query the pods but cannot set it, it looks to be of a system type System.Object[] do I need to convert it somehow to PSCustomObject for the contents of the next JSON file to be added to it?
Resolved by changing JSON from
pods:[] to
podlist:{ pods:[]}
and referencing
$combinedObject.DataCenters.$datacentername.podlist.pods
to set the value.

Powershell Parse Swagger Json

The following works to parse a Swagger json into resource, method, httptype but probably... the $path.Definition part is weirdly, how can i get $path.Definition to be an array not a string that i need to parse for the array symbol.
$json = Get-Content -Path "$PSScriptRoot/Test/example_swagger.json" | ConvertFrom-Json
$paths = Get-Member -InputObject $json.paths -MemberType NoteProperty
$result = ""
foreach($path in $paths) {
$elements = $path.Name.Substring(5).split("/") -join ","
$httpmethods = $path.Definition.Substring($path.Definition.IndexOf("#{"))
if ($httpmethods.Contains("get")) {
$result += $elements + ", GET" + "`n"
}
if ($httpmethods.Contains("post")) {
$result += $elements + ", POST" + "`n" #same methodnames different http methods
}
}
$result
As detailed in my answer to Iterating through a JSON file PowerShell, the output of ConvertFrom-Json is hard to iterate over. This makes "for each key in object" and "keys of object not known ahead of time" kinds of situations more difficult to handle, but not impossible.
You need a helper function:
# helper to turn PSCustomObject into a list of key/value pairs
function Get-ObjectMember {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True, ValueFromPipeline=$True)]
[PSCustomObject]$obj
)
$obj | Get-Member -MemberType NoteProperty | ForEach-Object {
$key = $_.Name
[PSCustomObject]#{Key = $key; Value = $obj."$key"}
}
}
with this, the approach gets a whole lot simpler:
$swagger = Get-Content -Path "example_swagger.json" -Encoding UTF8 -Raw | ConvertFrom-Json
$swagger.paths | Get-ObjectMember | ForEach-Object {
[pscustomobject]#{
path = $_.Key
methods = $_.Value | Get-ObjectMember | ForEach-Object Key
}
}
Applied to the default Swagger file from https://editor.swagger.io/ as a sample, this is printed
path methods
---- -------
/pet {post, put}
/pet/findByStatus get
/pet/findByTags get
/pet/{petId} {delete, get, post}
/pet/{petId}/uploadImage post
/store/inventory get
/store/order post
/store/order/{orderId} {delete, get}
/user post
/user/createWithArray post
/user/createWithList post
/user/login get
/user/logout get
/user/{username} {delete, get, put}

ConvertFrom-JSON to object

It looks like the way I am expecting this to work doesn't. I want multiple objects returned, but it seems to be returning just one. It is beyond me how I do it.
A very simple JSON file:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"storageAccountName": {
"value": "sa01"
},
"virtualNetworkName": {
"value": "nvn01"
}
}
}
I want to dynamically add the parameters and their values into a nice pscustomobject (that would look like the following with the above data):
ParameterName | Value
===========================
storageAccountName | sa01
virtualNetworkName | nvn01
What I don't understand is why the following returns one object:
$TemplateParametersFile = "C:\Temp\deploy-Project-Platform.parameters.json"
$content = Get-Content $TemplateParametersFile -Raw
$JsonParameters = ConvertFrom-Json -InputObject $content
$JsonParameters.parameters | Measure-Object
Whilst writing this, I eventually found a solution that get's what I want, which I'll post in the answer section. Feel free to school me and improve...
I would do things a little differently, skipping the hashtable, and using the hidden PSObject property. So, picking up after you have the JSON data stored in $content, I would do something like this:
#Convert JSON file to an object
$JsonParameters = ConvertFrom-Json -InputObject $content
#Create new PSObject with no properties
$oData = New-Object PSObject
#Loop through properties of the $JsonParameters.parameters object, and add them to the new blank object
$JsonParameters.parameters.psobject.Properties.Name |
ForEach{
Add-Member -InputObject $oData -NotePropertyName $_ -NotePropertyValue $JsonParameters.parameters.$_.Value
}
$oData
By the way, it had issues converting the JSON you posted, I had to add quotes around the two values, such as "value": "sa01".
Using the same JSON file as shown above:
<#
# Read in JSON from file on disk
$TemplateParametersFile = "C:\Temp\deploy-Project-Platform.parameters.json"
$content = Get-Content $TemplateParametersFile -Raw
#>
#Retrieve JSON file from Azure storage account.
$TemplateParametersFile = "https://{storageAccountName}.blob.core.windows.net/{SomeContainer}/deploy-Project-Platform.parameters.json"
$oWc = New-Object System.Net.WebClient
$webpage = $oWc.DownloadData($TemplateParametersFile)
$content = [System.Text.Encoding]::ASCII.GetString($webpage)
#Convert JSON file to an object (IMHO- Sort of!)
$JsonParameters = ConvertFrom-Json -InputObject $content
#Build hashtable - easier to add new items - the whole purpose of this script
$oDataHash = #{}
$JsonParameters.parameters | Get-Member -MemberType NoteProperty | ForEach-Object{
$oDataHash += #{
$_.name = $JsonParameters.parameters."$($_.name)" | Select -ExpandProperty Value
}
}
#Example: adding a single item to the hashtable
$oDataHash.Add("VirtualMachineName","aDemoAdd")
#Convert hashtable to pscustomobject
$oData = New-Object -TypeName PSCustomObject
$oData | Add-Member -MemberType ScriptMethod -Name AddNote -Value {
Add-Member -InputObject $this -MemberType NoteProperty -Name $args[0] -Value $args[1]
}
$oDataHash.Keys | Sort-Object | ForEach-Object{
$oData.AddNote($_,$oDataHash.$_)
}
$oData
And the result:
storageAccountName VirtualMachineName virtualNetworkName
------------------ ------------------ ------------------
sa01 aDemoAdd nvn01
Agreed, the question asked for a Parameter / Value pair, and this results in the parameter's name being assigned as the noteproperty, but I think it will be easier to use it this way. Of course, $oDataHash returns it as a Key/value pair.
This script also pulls the JSON file directly from an Azure storage account. No need to save to disk. If you want to save to disk, change $oWc.DownloadData() to $oWc.DownloadFile() . The commented bit at the top, reads from disk.
I am sure there are much more succinct ways to achieve the same result, and I'd love to here them. For me, at the moment this works.

CSV to Dynamic Tables to HTML Email

I am trying to write a script that
reads a CSV file that has 3 columns: email, server, date. There are duplicate email addresses, but no duplicate servers
makes a table for each distinct email address from CSV file
sends one email to each distinct email address from CSV file with a list of servers and their dates in an HTML table format.
$Email_Template = "C:\template.html"
[String] $Email_Template = Get-Content "Email_Template"
$csv = Import-Csv "C:\file.csv"
$emailarray = #()
foreach ($test in $csv) {
$emailobject = New-Object PSObject |
Add-Member -Name Email -Value $test.Email -MemberType NoteProperty -PassThru |
Add-Member -Name Email -Value $test.Server -MemberType NoteProperty -PassThru |
Add-Member -Name Email -Value $test.Date -MemberType NoteProperty -PassThru
$emailarray += $emailobject
}
foreach ($test in $EmailArray) {
try {
$Parameters = #{
#email variables (from cc, etc.)
bodyashtml = $true
}
$Parameters += #{to = $test.Email}
Send-MailMessage #Parameters -ErrorAction Stop
}
}
I'm having success creating the tables by email name, but am having trouble referencing those to be added to the email associated with them.
You are losing the value of the email property of the $emailobject because you are calling Add-Member multiple times with the same name.
A cleaner way to construct the object is to use a dictionary:
$emailobject = New-Object PSObject -Property #{"Email"=$test.Email; "Server" = $test.Server ; "Date" = $test.Date}
Will create an email object with 3 properties.

ConvertFrom-Json in Powershell v2

Code
I have the following script, largely based off information on this site:
cls
if ($PSVersionTable.PSVersion.Major -lt 3) {
function ConvertTo-Json([psobject] $item){
[System.Reflection.Assembly]::LoadWithPartialName("System.Web.Extensions") | out-null
$ser = New-Object System.Web.Script.Serialization.JavaScriptSerializer
$hashed = #{}
$item.psobject.properties | %{ $hashed.($_.Name) = $_.Value }
write-output $ser.Serialize($hashed)
}
function ConvertFrom-Json([string] $json){
[System.Reflection.Assembly]::LoadWithPartialName("System.Web.Extensions") | out-null
$ser = New-Object System.Web.Script.Serialization.JavaScriptSerializer
write-output (new-object -type PSObject -property $ser.DeserializeObject($json))
}
}
$jsonObj = (new-object -type psobject -property #{
MyDummyData = #{
Version = '1.0.4'
CreationDate = ("{0:yyyy-MM-ddTHH:mm:ss.fffZ}" -f ((Get-Date).ToUniversalTime()))
Demo = 'hello'
Recurse = #{
Hello = "hi"
Greetings = #{
what = "now"
}
}
}
})
"`n`nobj"
$jsonObj
"`n`nobj to json"
$jsonx = ConvertTo-Json($jsonObj)
$jsonx
"`n`njson to obj"
$x = ConvertFrom-Json($jsonx)
$x
"Compare"
$x -eq $jsonObj
Output
obj
MyDummyData
-----------
{Version, Demo, CreationDate, Recurse}
obj to json
{"MyDummyData":{"Version":"1.0.4","Demo":"hello","CreationDate":"2015-07-23T18:
59:04.265Z","Recurse":{"Greetings":{"what":"now"},"Hello":"hi"}}}
json to obj
{[Version, 1.0.4], [Demo, hello], [CreationDate, 2015-07-23T18:59:04.265Z], ...
Compare
False
Problem
When I test it ConvertTo-Json works perfectly.
However ConvertFrom-Json doesn't resolve to the object I started with, but rather gives me an object containing a number of name-value pairs.
Question
Can anyone spot what I've missed; or are the methods proposed on here only suitable in certain scenarios?
Related
Example of existing answer: PowerShell 2.0 ConvertFrom-Json and ConvertTo-Json implementation