Powershell: SendGrid - Send to multiple recipients using specific structure - powershell

I had a previous issue where sending an excel file wouldn't work because it got corrupted but got that working thanks to some help. Now a new issue I have is that I don't know how to send to multiple recipients. The main issue is that my sendgrid code is within a loop that reads from a file, and the number of recipients is dynamic. And I get the structure of the code from an existing github source.
MAINLY: Need to know how to send to multiple recipients via variable using the structure below.
function Send-SendGridEmail {
param(
[Parameter(Mandatory = $true)]
[String] $destEmailAddress,
[Parameter(Mandatory = $true)]
[String] $fromEmailAddress,
[Parameter(Mandatory = $true)]
[String] $subject,
[Parameter(Mandatory = $false)]
[string]$contentType = 'text/plain',
[Parameter(Mandatory = $true)]
[String] $contentBody,
[parameter(Mandatory = $false)]
[string]$FileName,
[parameter(Mandatory = $false)]
[string]$FileNameWithFilePath,
[parameter(Mandatory = $false)]
[string]$AttachementType
)
<#
.Synopsis
Function to send email with SendGrid
.Description
A function to send a text or HTML based email
See https://sendgrid.com/docs/API_Reference/api_v3.html for API details
This script provided as-is with no warranty. Test it before you trust it.
www.ciraltos.com
.Parameter apiKey
The SendGrid API key associated with your account
.Parameter destEmailAddress
The destination email address
.Parameter fromEmailAddress
The from email address
.Parameter subject
Email subject
.Parameter type
The content type, values are “text/plain” or “text/html”. "text/plain" set by default
.Parameter content
The content that you'd like to send
.Example
Send-SendGridEmail
#>
############ Update with your SendGrid API Key ####################
$apiKey = "key"
$headers = #{
'Authorization' = 'Bearer ' + $apiKey
'Content-Type' = 'application/json'
}
$base64string = [Convert]::ToBase64String([IO.File]::ReadAllBytes($FileNameWithFilePath))
$body = #{
personalizations = #(
#{
to = #(
#{
email = $destEmailAddress
}
)
}
)
from = #{
email = $fromEmailAddress
}
subject = $subject
content = #(
#{
type = $contentType
value = $contentBody
}
)
attachments = #(
#{
content=$base64string
filename=$FileName
type= $AttachementType
disposition="attachment"
}
)
}
try {
$bodyJson = $body | ConvertTo-Json -Depth 4
}
catch {
$ErrorMessage = $_.Exception.message
write-error ('Error converting body to json ' + $ErrorMessage)
Break
}
try {
Invoke-RestMethod -Uri https://api.sendgrid.com/v3/mail/send -Method
Post -Headers $headers -Body $bodyJson
}
catch {
$ErrorMessage = $_.Exception.message
write-error ('Error with Invoke-RestMethod ' + $ErrorMessage)
Break
}
}
# Call the function
# Splat the input
#Send-SendGridEmail #splat
# Sample code with HTML body
$htmlBody = #"
//stuff
"#
$splat2 = #{
destEmailAddress = '""
fromEmailAddress = 'noreply#domaint.com'
subject = 'Test Email'
contentType = 'text/html'
contentBody = $htmlBody
FileName = "filename"
FileNameWithFilePath = "path\excel.xlsx"
AttachementType = "application/vnd.openxmlformats-
officedocument.spreadsheetml.sheet"
}
Send-SendGridEmail #splat2

Related

Sendgrid in Powershell : Adds a "?" before the variable in subject and body

I am trying to send emails using Sendgrid in Powershell.
$Parameters = #{
ToAddress = "blabla#centoso.com"
FromAddress = "blabla#centoso.co"
Subject = "Your files for the job $file are ready for download"
Body = $downloadurl
APIKey = "blabla"
}
SendGridMail #Parameters
Looking at the variables in Powershell they are:
$file = test.zip
$downloadurl = "https://centoso.com/centoso-download/$file"
$downloadurl - Outputs "https://centoso.com/centoso-download/test.zip"
but when I receive the Sendgrid email :
$file = ?test.zip
$downloadurl = "https://centoso.com/centoso-download/?test.zip"
This is the SendGridMail function which I used on several occasions:
Function SendGridMail {
param (
[cmdletbinding()]
[parameter()]
[string]$ToAddress,
[parameter()]
[string]$FromAddress,
[parameter()]
[string]$Subject,
[parameter()]
[string]$Body,
[parameter()]
[string]$APIKey
)
$SendGridBody = #{
"personalizations" = #(
#{
"to"= #(
#{
"email" = $ToAddress
}
)
"subject" = $Subject
}
)
"content"= #(
#{
"type" = "text/plain"
"value" = $Body
}
)
"from" = #{
"email" = $FromAddress
}
}
$BodyJson = $SendGridBody | ConvertTo-Json -Depth 10
#Header for SendGrid API
$Header = #{
"authorization" = "Bearer $APIKey"
}
#Send the email through SendGrid API
$Parameters = #{
Method = "POST"
Uri = "https://api.sendgrid.com/v3/mail/send"
Headers = $Header
ContentType = "application/json"
Body = $BodyJson
}
Invoke-RestMethod #Parameters
}
I have used this function on different other scripts, but I do not understand where the question mark is coming from.

Send message to azure service bus with properties

I am using the following powershell code to send a message to an Azure Service Bus Topic with a property set:
function Send-AzServiceBusMessage {
param(
[Parameter(Mandatory = $true)]
[string] $ResourceGroupName,
[Parameter(Mandatory = $true)]
[string] $NamespaceName,
[Parameter(Mandatory = $true)]
[string] $TopicName,
[Parameter(Mandatory = $false)]
[string] $PolicyName = 'RootManageSharedAccessKey',
[Parameter(Mandatory = $false)]
[string] $Property1
)
$message = [PSCustomObject] #{ "Body" = "Test message"; "Property1" = $Property1 }
$namespace = (Get-AzServiceBusNamespace -ResourceGroupName $ResourceGroupName -Name $namespacename).Name
$key = (Get-AzServiceBusKey -ResourceGroupName $ResourceGroupName -Namespace $namespacename -Name $PolicyName).PrimaryKey
$message.psobject.properties.Remove("Body")
$token = New-AzServiceBusSasToken -Namespace $namespace -Policy $PolicyName -Key $Key
#set up the parameters for the Invoke-WebRequest
$headers = #{ "Authorization" = "$token"; "Content-Type" = "application/atom+xml;type=entry;charset=utf-8" }
$uri = "https://$namespace.servicebus.windows.net/$TopicName/messages"
$headers.Add("BrokerProperties", $(ConvertTo-Json -InputObject $Message -Compress))
$result = Invoke-WebRequest -Uri $uri -Headers $headers -Method Post -Body $body
if ($result.StatusCode -ne 201) {
$result
}
I have a rule set up on a topic subscription such that:
Property1 in ('somevalue')
However, if I set up a catch all subscription with no rules, I can see the message is being received by that and not by the subscription with the rule. So my question is how do I send messages with properties set using powershell. Similar to this in C#:
var message = new BrokeredMessage { MessageId = Guid.NewGuid().ToString()};
message.Properties["Property1"] = "somevalue";
Based on the documentation it looks like you have to nest your custom properties in a Properties member:
$headers.Add("BrokerProperties", $(ConvertTo-Json -InputObject #{ Properties = $message } -Compress))
The solution was as above with the following modifications:
$message = [PSCustomObject] #{ "Body" = "Test message" }
$headers = #{ "Authorization" = "$token"; "Content-Type" = "application/atom+xml;type=entry;charset=utf-8"; "Property1" = $Property1 }
$headers.Add("BrokerProperties", $(ConvertTo-Json -InputObject $Message -Compress))

Sending attachments with Mailgun using Powershell

I cannot find any answer on Google or either Mailgun support on how to send attachments using Powershell. Sending mails without attachments works fine with Mailgun and Powershell. This is my code.
$apikey = "key-1342323f7d35352fa4dda3af3ca10e"
$idpass = "api:$($apikey)"
$basicauth = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($idpass))
$headers = #{
Authorization = "Basic $basicauth";
}
$url = "https://api.mailgun.net/v3/sandboxac77741768d442323b96495501ac24b.mailgun.org/messages"
$mailbody = #{
from = "Mailgun Sandbox <postmaster#sandboxac77741768d442323b96495501ac24b.mailgun.org>";
to = "myadress#email.com";
subject = "Testing mail";
text = "Email body here";
attachment = "D:\temp\logfile.txt"
}
Invoke-RestMethod -Uri $url -Method Post -Headers $headers -Body $mailbody -ContentType "multipart/form-data"
This results in:
Invoke-RestMethod : The remote server returned an error: (400) Bad Request
Special thanks to https://stackoverflow.com/users/213135/alex from whom I stole code.
function ConvertTo-MimeMultiPartBody {
param([Parameter(Mandatory=$true)][string]$Boundary, [Parameter(Mandatory=$true)][hashtable]$Data)
$body = "";
$Data.GetEnumerator() |% {
$name = $_.Key
$value = $_.Value
$body += "--$Boundary`r`n"
$body += "Content-Disposition: form-data; name=`"$name`""
if ($value -is [byte[]]) {
$fileName = $Data['FileName']
if(!$fileName) { $fileName = $name }
$body += "; filename=`"$fileName`"`r`n"
$body += 'Content-Type: application/octet-stream'
$value = [System.Text.Encoding]::GetEncoding("ISO-8859-1").GetString($value)
}
$body += "`r`n`r`n" + $value + "`r`n"
}
return $body + "--$boundary--"
}
$emaildomain = "example.com"
$apikey = "key-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
$url = "https://api.mailgun.net/v2/$($emaildomain)/messages"
$headers = #{
Authorization = "Basic " + ([System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes("api:$($apikey)")))
}
$email_parms = #{
from = "me#example.com";
to = "you#example.com";
subject = "My Test Email";
text = 'Your email does not support HTML.';
html = "Hello World!";
filename = "example.pdf"
attachment = ([IO.File]::ReadAllBytes("c:\example.pdf"));
}
$boundary = [guid]::NewGuid().ToString()
$body = ConvertTo-MimeMultiPartBody $boundary $email_parms
Invoke-RestMethod -Uri $url -Method Post -Headers $headers -Body $body -ContentType "multipart/form-data; boundary=$boundary"
a slight addition to have multiple attachments, pushed it in a module:
mailgun.psm1
<#
.SYNOPSIS
Interface to the Mailgun API Module
.DESCRIPTION
Sends an e-mail through the Mailgun API see https://documentation.mailgun.com/en/latest/user_manual.html#sending-via-api
Define your maildomain and API key in MailGun.psd1 and place inside the module folder
.EXAMPLE
$from = "from#stackoverflow.com"
$to = "to1#stackoverflow.com", "another#stackoverflow.com"
$bcc = "bcc1#stackoveflow.com", "bcc2#stackoverflow.com"
$subject = "a mail with a subject"
$html = "this <b>test with attachments</b> text<br/> And another interesting line <hr />"
$attachments = #{
"ExampleAttachment1.png" = 'c:\temp\whatapps.png'
"AnotherExample.txt" = 'c:\temp\Msinfo.txt'
}
Send-MailgunEmail -from $from -to -bcc $bcc $to -subject $subject -htmlText $html -attachments $attachments
#>
$ModuleData = Import-PowerShellDataFile "$PSScriptRoot\MailGun.psd1"
function ConvertTo-MimeMultiPartBody {
<#
.SYNOPSIS
Convert email content with multiple attachments to correct format
with help from https://stackoverflow.com/questions/45463391/sending-attachments-with-mailgun-using-powershell
#>
param(
[Parameter(Mandatory = $true)]
[string]
$Boundary,
[Parameter(Mandatory = $true)]
[hashtable]
$Data
)
$lb = "`r`n"
$body = ""
$Data.GetEnumerator() | ForEach-Object {
$body += "--{0}{1}Content-Disposition: form-data; name=`"" -f $Boundary, $lb
if ($_.Value -is [byte[]]) {
$body += "attachment`"; filename=`"{0}`"{1}Content-Type: application/octet-stream{2}{3}{4}" -f
$_.Key, $lb, $lb, $lb, [System.Text.Encoding]::GetEncoding("ISO-8859-1").GetString($_.Value)
}
else {
$body += "{0}`"{1}{2}{3}" -f $_.Key, $lb, $lb, $_.Value
}
$body += $lb
}
return "{0}{1}--{2}--" -f $lb, $body, $boundary
}
function Send-MailgunEmail() {
<#
.SYNOPSIS
Sends an email through mailguns API
#>
param(
# The From e-mail address
[Parameter(Mandatory = $True)]
[string] $from,
# The To e-mail address (comma divided string)
[Parameter(Mandatory = $True)]
[array] $to,
# The bcc e-mail adresses
[Parameter(Mandatory = $False)]
[array] $bcc,
# the subject
[Parameter(Mandatory = $False)]
[string] $subject = 'Mail without subject',
# the text in the Text part
[Parameter(Mandatory = $False)]
[string] $text = 'Mail does not support Non Html readers',
# the text in the HTML part
[Parameter(Mandatory = $False)]
[string] $htmlText = '<html>no body content</html>',
# Either pass as parameter or set in MailGun.psd1
[Parameter(Mandatory = $False)]
[string] $emaildomain = $ModuleData.PrivateData.mailgun_emaildomain,
# the api key as found in the mailgun profile either pass or set in MailGun.psd1
[Parameter(Mandatory = $False)]
[string] $apikey = $ModuleData.PrivateData.mailgun_apikey,
# a list of attachments
[Parameter(Mandatory = $False)]
[hashtable] $attachments
)
$data = #{
from = $from
to = $to -join ','
subject = $subject
text = $text
html = "<html>$htmlText</html>"
}
if ($bcc) { $data.Add('bcc', [string]($bcc -join ',')) }
foreach ($attachment in $attachments.GetEnumerator()) {
$attachmentbytes = [IO.File]::ReadAllBytes($attachment.Value)
$data.Add($attachment.Name, $attachmentbytes)
}
$url = "https://api.mailgun.net/v3/$($emaildomain)/messages"
$headers = #{Authorization = "Basic " + [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes("api:$($apikey)"))}
$boundary = [guid]::NewGuid().ToString()
$contenttype = "multipart/form-data; boundary=$boundary"
$body = ConvertTo-MimeMultiPartBody -Boundary $boundary -Data $data
return Invoke-RestMethod -Method Post -Uri $url -Headers $headers -Body $body -ContentType $contenttype
}
Export-ModuleMember -Function Send-MailgunEmail
mailgun.psd1
#{
RootModule = 'MailGun.psd1'
ModuleVersion = '1.0.0.0'
GUID = 'e0383493-22b2-4838-8095-67c865ae46de'
Author = 'edelwater'
PrivateData = #{
mailgun_emaildomain = 'EXAMPLE.COM'
mailgun_apikey = 'key-123fdsdafgd9gf043dfgd03sdd'
}
}
testmailgunmodule.ps1
Import-Module "$PSScriptRoot\.\Modules\MailGun.psm1" -Force
$from = "from#stackoverflow.com"
$to = "to1#stackoverflow.com", "another#stackoverflow.com"
$bcc = "bcc1#stackoveflow.com", "bcc2#stackoverflow.com"
$subject = "a mail with a subject"
$html = "this <b>test with attachments</b> text<br/> And another interesting line <hr />"
$attachments = #{
"ExampleAttachment1.png" = 'c:\temp\whatapps.png'
"AnotherExample.txt" = 'c:\temp\Msinfo.txt'
}
Send-MailgunEmail -from $from -to $to -subject $subject -htmlText $html -attachments $attachments

Create Team City Build configurations using restapi and powershell

I am trying to create build configurations via the restapi and powershell and keep getting the following error:
Exception calling "GetResponse" with "0" argument(s): "The remote server returned an error: (405) Method Not Allowed."
It seems that I can use GET fine, the issue appears to be with the PUT command
Code Snippet
$url = http://%teamcityServer%:8111/app/rest/buildTypes/id:%projectname%"
$req = [System.Net.WebRequest]::Create($url)
$req.ContentType = "text/plain"
$req.UseDefaultCredentials = $true
$req.Credentials = Get-Credential("username")
$req.Method ="PUT"
$req.ContentLength = 0
$req.Accept = "*/*"
$resp = $req.GetResponse()
$results = [xml]$resp.ReadToEnd()
Output from the Team City log
2015-09-10 09:14:30,582] WARN [io-8111-exec-70] - est.jersey.ExceptionMapperUtil - Error has occurred during request processing (405). Error: javax.ws.rs.WebApplicationException. Not supported request. Please check URL, HTTP method and transfered data are correct. metadata: [Allow:[HEAD,DELETE,GET,OPTIONS],] Request: PUT '/app/rest/buildTypes/id:%project%'
Team City Version is 9.1.1 so I believe this is possible.
I'm fairly new to the restapi so any input is appreciated.
Its possible but you need to post xml data to create builds/projects. For example if you need to create a project you can POST XML something like
<newProjectDescription name='New Project Name' id='newProjectId' copyAllAssociatedSettings='true'><parentProject locator='id:project1'/><sourceProject locator='id:project2'/></newProjectDescription>
to http://teamcity:8111/httpAuth/app/rest/projects.
Check more info on Teamcity REST documentation.
You didn't mention your powershell version, but if you are using 3.0 or later you can call Invoke-WebRequest cmdlet and your code should look something like:
Invoke-WebRequest -Uri $uri -Credential $cred -Method Post -Body $body -ContentType "Application/xml"
For PowerShell 2.0 You can write your own Web-Request method like:
function Web-Request{
param(
[Parameter(Mandatory=$true)]
[string]
$Uri,
[Parameter(Mandatory=$false)]
[string]
$Username = $null,
[Parameter(Mandatory=$false)]
[string]
$Password = $null,
[Parameter(Mandatory=$true)]
[string]
$ContentType,
[Parameter(Mandatory=$true)]
[string]
$Method,
[Parameter(Mandatory=$false)]
[string]
$PostString
)
$webRequest = [System.Net.WebRequest]::Create($Uri)
$webRequest.ContentType = $ContentType
$webRequest.Method = $Method
$webRequest.Accept = "*/*"
if ($Username)
{
$webRequest.Credentials = new-object system.net.networkcredential($Username, $Password)
}
try
{
switch -regex ($Method)
{
"PUT|POST" # PUT and POST behaves similar ways except that POST is only used for creation while PUT for creation/modification
{
if ($PostString -ne "")
{
$PostStringBytes = [System.Text.Encoding]::UTF8.GetBytes($PostString)
$webrequest.ContentLength = $PostStringBytes.Length
$requestStream = $webRequest.GetRequestStream()
$requestStream.Write($PostStringBytes, 0,$PostStringBytes.length)
}
else
{
$requestStream = $webRequest.GetRequestStream()
}
}
"DELETE"
{
$requestStream = $webRequest.GetRequestStream()
}
default
{
# GET requests usually don't have bodies, default will behave like a GET
}
}
if ($requestStream -ne $null)
{
$requestStream.Close()
}
[System.Net.WebResponse] $resp = $webRequest.GetResponse();
$rs = $resp.GetResponseStream();
[System.IO.StreamReader] $sr = New-Object System.IO.StreamReader -argumentList $rs;
[string] $results = $sr.ReadToEnd();
}
catch
{
$results = "Error : $_.Exception.Message"
}
finally
{
if ($sr -ne $null) { $sr.Close(); }
if ($resp -ne $null) { $resp.Close(); }
$resp = $null;
$webRequest = $null;
$sr = $null;
$requestStream = $null;
}
return $results
}

JIRA REST API gives HTTP error 400 with PowerShell v3

I'm attempting to write some PowerShell functions to interface with our Atlassian JIRA system (JIRA 5.2, download version). Unfortunately, I've found through trial and error that the Invoke-RestMethod doesn't seem to work (doesn't support authentication headers), so I've written up a simple function called Invoke-JiraMethod. I can confirm that this method works for GET requests; I've been able to use it to get Jira objects to my heart's desire. As soon as I tried to create an issue, though, I started getting a HTTP 400 / Bad request error.
I've followed the steps here to get my issue metadata, and I'm filling out all the required fields in my input object. Could anyone help me figure out how to solve the 400 error? I can provide more information if needed - I just didn't want to overflow the description of the question. :)
Function Invoke-JiraMethod
{
<#
.Synopsis
Low-level function that directly invokes a REST method on JIRA
.Description
Low-level function that directly invokes a REST method on JIRA. This is designed for
internal use.
#>
[CmdletBinding()]
param
(
[ValidateSet("Get","Post")] [String] $Method,
[String] $URI,
[String] $Username,
[String] $Password
)
process
{
$token = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes("${Username}:${Password}"))
$webRequest = [System.Net.WebRequest]::Create($URI)
$webRequest.Method = $Method.ToUpper()
$webRequest.AuthenticationLevel = "None"
$webRequest.Headers.Add('Authorization', "Basic $token")
#$webRequest.Headers.Add('Authorization', $token)
$webRequest.PreAuthenticate = $true
$webRequest.ContentType = "application/json"
Write-Verbose "Invoking JIRA method $Method with URI $URI"
$response = $webRequest.GetResponse()
$requestStream = $response.GetResponseStream()
$readStream = New-Object -TypeName System.IO.StreamReader -ArgumentList $requestStream
$json = $readStream.ReadToEnd()
$readStream.Close()
$response.Close()
$result = $json | ConvertFrom-Json
Write-Output $result
}
}
Function New-JiraIssue
{
param
(
[Parameter(Mandatory = $true,
Position = 0)]
[string] $ProjectKey,
[Parameter(Mandatory = $true,
Position = 1)]
[string] $IssueType,
[Parameter(Mandatory = $false)]
[string] $Priority = 3,
[Parameter(Mandatory = $true,
Position = 2)]
[string] $Summary,
[Parameter(Mandatory = $true,
Position = 3)]
[string] $Description,
[Parameter(Mandatory = $true,
Position = 4)]
[string] $Location,
[Parameter(Mandatory = $true,
Position = 5)]
[string] $Phone,
[Parameter(Mandatory = $false)]
[string] $Reporter,
[Parameter(Mandatory = $false)]
[PSCredential] $Credential
)
process
{
$ProjectObject = New-Object -TypeName PSObject -Property #{"key"=$ProjectKey}
$IssueTypeObject = New-Object -TypeName PSObject -Property #{"id"=$IssueType}
if ( -not ($Reporter))
{
Write-Verbose "Reporter not specified; defaulting to $JiraDefaultUser"
$Reporter = $JiraDefaultUser
}
$ReporterObject = New-Object -TypeName PSObject -Property #{"name"=$Reporter}
$fields = New-Object -TypeName PSObject -Property ([ordered]#{
"project"=$ProjectObject;
"summary"=$Summary;
"description"=$Description;
"issuetype"=$IssueTypeObject;
"priority"=$Priority;
"reporter"=$ReporterObject;
"labels"="";
$CustomFields["Location"]=$Location;
$CustomFields["Phone"]=$Phone;
})
$json = New-Object -TypeName PSObject -Property (#{"fields"=$fields}) | ConvertTo-Json
Write-Verbose "Created JSON object:`n$json"
# https://muwebapps.millikin.edu/jira/rest/api/latest/issue/IT-2806
# $result = Invoke-RestMethod -Uri $JiraURLIssue -Method Post -ContentType "application/json" -Body $json -Credential $Credential
if ($Username -or $Password)
{
$result = (Invoke-JiraMethod -Method Post -URI "${JiraURLIssue}" -Username $Username -Password $Password)
} else {
$result = (Invoke-JiraMethod -Method Post -URI "${JiraURLIssue}" -Username $JiraDefaultUser -Password $JiraDefaultPassword)
}
Write-Output $result
}
}
Thanks in advance!
I was receiving the error 400 because the issue type id number I had put into the json data wasn't mapped to anything. fiddler helped me diagnose that.
I used this bit of code to figure out authenticating to jira via invoke-restmethod's -Headers option: http://poshcode.org/3461
then I put the json data into a here string, and ran invoke-restmethod.
code bits below. replace "any valid... type id number" with actual project and issue type ids you get from your jira admin.
$uri = "$BaseURI/rest/api/2/issue"
$jsonString = #'
{
"fields": {
"project":
{
"id": "any valid project id number"
},
"summary": "No REST for the Wicked.",
"description": "Creating of an issue using ids for projects and issue types using the REST API",
"issuetype": {
"id": "any valid issue type id number"
}
}
}
'#
$headers = Get-HttpBasicHeader $Credentials
Invoke-RestMethod -uri $uri -Headers $headers -Method Post -Body $jsonString -ContentType "application/json"