Sending attachments with Mailgun using Powershell - 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

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))

Coinspot API with PowerShell

I'm struggling to access the Coinspot API from PowerShell. No matter what I do I always get the "no nonce" error back from the API:
$VerbosePreference = 'Continue'
$key = ''
$secret = ''
$epoc_start_date = ("01/01/1970" -as [DateTime])
[int]$nonce = ((New-TimeSpan -Start $epoc_start_date -End ([DateTime]::UtcNow)).TotalSeconds -as [string])
$baseUrl = 'www.coinspot.com.au/api'
$resourcePath = '/my/orders'
$url = 'https://{0}{1}&nonce={2}' -f $baseUrl, $resourcePath, $nonce
$encoded = New-Object System.Text.UTF8Encoding
$url_bytes = $encoded.GetBytes($url)
# create hash
$hmac = New-Object System.Security.Cryptography.HMACSHA512
$hmac.key = [Text.Encoding]::ASCII.GetBytes($secret)
$sha_result = $hmac.ComputeHash($url_bytes)
#remove dashes
$hmac_signed = [System.BitConverter]::ToString($sha_result) -replace "-";
$headers = #{
sign = $hmac_signed
key = $key
'content-type' = 'application/json'
}
$result = Invoke-RestMethod -Uri $url -Method Post -Headers $headers
$result
Alternatively I have already tested this:
$VerbosePreference = 'Continue'
$key = ''
$secret = ''
$epoc_start_date = ("01/01/1970" -as [DateTime])
[int]$nonce = ((New-TimeSpan -Start $epoc_start_date -End ([DateTime]::UtcNow)).TotalSeconds -as [string])
$baseUrl = 'www.coinspot.com.au/api'
$resourcePath = '/my/orders'
$url = 'https://{0}{1}' -f $baseUrl, $resourcePath
$body = #{
nonce = $nonce
}
$encoded = New-Object System.Text.UTF8Encoding
$body_bytes = $encoded.GetBytes($body)
# create hash
$hmac = New-Object System.Security.Cryptography.HMACSHA512
$hmac.key = [Text.Encoding]::ASCII.GetBytes($secret)
$sha_result = $hmac.ComputeHash($body_bytes)
#remove dashes
$hmac_signed = [System.BitConverter]::ToString($sha_result) -replace "-";
Invoke-RestMethod -Uri $url -Method Post -Headers #{sign = $hmac_signed ; key = $key ; 'content-type' = 'application/json' } -Body $($body | ConvertTo-Json)
The second gives me an invalid status error.
I have a feeling there's something wrong with my header.
Coinspot support responded:
Apologies for this.
Our current API system is way out of date and needs to be updated.
We know that we need to support the developers as best as we can but our current dev team are very busy with other things at the
moment.
They are aware of this and plan to update it as soon as possible, but right now there is no ETA for this.
Very sorry the inconvenience.

Trouble with Powershell script to call HTTP POST with multipart/form-data

I am developing a powershell script that should invoke a REST API using the HTTP POST method. The REST API is used to restore an application specific backup resource from external backup file. the KeyName for backup file in the form data must be "backupFile".
The content type is multipart/form-data. Here is what i am doing:
function invoke-rest {
param([string]$uri)
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
#$enc = [system.Text.Encoding]::UTF8
$request = [System.Net.HttpWebRequest]::Create($uri)
$request.Credentials = New-Object system.net.networkcredential("user","password")
$request.CookieContainer = New-Object System.Net.CookieContainer
$request.AllowWriteStreamBuffering = $true;
$boundary = "--------------"+(get-date -format yyyymmddhhmmss).ToString()
$header = "--"+$boundary
$body = $header + "`r`n" +"Content-Disposition: form-data; name='backupFile'; filename='somefile.sql.gz'"+"`r`n" + "Content-Type: multipart/form-data"+"`r`n`r`n"
$body = $body + [System.Text.Encoding]::UTF8.GetString($(Get-Content 'somefile.sql.gz' -Encoding byte)) + "`r`n"
$footer = $header+"--"
$body = $body + $footer
$bytes = [System.Text.Encoding]::UTF8.GetBytes($body)
$request.ContentType = "multipart/form-data; boundary="+$boundary
$request.Method = "Post"
$request.keepAlive = $true
$request.ContentLength = $bytes.Length
$requestStream = $request.GetRequestStream()
$requestStream.Write($bytes,0,$bytes.length);
$requestStream.Flush();
$requestStream.Close();
$response = $request.GetResponse()
$responseStream = $response.GetResponseStream()
$stream = new-object System.IO.StreamReader $responseStream
$xmlDump = $stream.ReadToEnd()
$output = [xml]$xmlDump
$response.close()
return $output
}
$uri = "http://localhost/rest/backups"
invoke-rest $uri
The error being thrown: REST request failed, A data form must exist with the name backupFile, returning: Bad Request (400)
What am i doing wrong here?
In this scenario, the 400 probably means something was incorrect in generating the
Is the file the only parameter you need to submit on the request? If so, you may be able to use the WebClient.UploadFile API and let it handle generating the bulk of the request.
$client = New-Object System.Net.WebClient
$client.Credentials = $creds
$client.UploadFile('http://localhost/rest/backups', 'c:\temp\somefile.sql.gz')
If you do need to submit multiple parameters in a mime multipart request, then you're looking at a world of pain. I've had to do this through powershell myself and it's not fun at all, particularly when you start involving binary data. After much frustration & headbanging, I ended up with the following to convert a hashtable of values and outputs a multipart. Sorry I can't spot exactly what's wrong with your code, but perhaps this can will either work outright for you, or lead you to identify what your issue is.
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'
#ISO-8859-1 is only encoding where byte value == code point value
$value = [System.Text.Encoding]::GetEncoding("ISO-8859-1").GetString($value)
}
$body += "`r`n`r`n"
$body += $value
$body += "`r`n"
}
$body += "--$boundary--"
return $body
}

How to POST .json file in Powershell without Invoke-WebRequest?

What I am currently doing:
Invoke-WebRequest -Uri https://coolWebsite.com/ext/ext -ContentType application/json -Method POST -Body $someJSONFile
I am looking for a way to POST this same .json file in Powershell without using Invoke-WebRequest, if it is possible. This new method would preferably allow me to get the server output content and parse through it in powershell.
Maybe by calling an outside cURL method? I really am not sure and all my internet research has proved fruitless.
How can I achieve this above result without Invoke-WebRequest?
You can try this :
# RestRequest.ps1
Add-Type -AssemblyName System.ServiceModel.Web, System.Runtime.Serialization, System.Web.Extensions
$utf8 = [System.Text.Encoding]::UTF8
function Request-Rest
{
[CmdletBinding()]
PARAM (
[Parameter(Mandatory=$true)]
[String] $URL,
[Parameter(Mandatory=$false)]
[System.Net.NetworkCredential] $credentials,
[Parameter(Mandatory=$true)]
[String] $JSON)
# Remove NewLine from json
$JSON = $JSON -replace "$([Environment]::NewLine) *",""
# Create a URL instance since the HttpWebRequest.Create Method will escape the URL by default.
# $URL = Fix-Url $Url
$URI = New-Object System.Uri($URL,$true)
try
{
# Create a request object using the URI
$request = [System.Net.HttpWebRequest]::Create($URI)
# Build up a nice User Agent
$UserAgent = "My user Agent"
$request.UserAgent = $("{0} (PowerShell {1}; .NET CLR {2}; {3})" -f $UserAgent, $(if($Host.Version){$Host.Version}else{"1.0"}),
[Environment]::Version,
[Environment]::OSVersion.ToString().Replace("Microsoft Windows ", "Win"))
$request.Credentials = $credentials
$request.KeepAlive = $true
$request.Pipelined = $true
$request.AllowAutoRedirect = $false
$request.Method = "POST"
$request.ContentType = "application/json"
$request.Accept = "application/json"
$utf8Bytes = [System.Text.Encoding]::UTF8.GetBytes($JSON)
$request.ContentLength = $utf8Bytes.Length
$postStream = $request.GetRequestStream()
$postStream.Write($utf8Bytes, 0, $utf8Bytes.Length)
#Write-String -stream $postStream -string $JSON
$postStream.Dispose()
try
{
#[System.Net.HttpWebResponse] $response = [System.Net.HttpWebResponse] $request.GetResponse()
$response = $request.GetResponse()
}
catch
{
$response = $Error[0].Exception.InnerException.Response;
Throw "Exception occurred in $($MyInvocation.MyCommand): `n$($_.Exception.Message)"
}
$reader = [IO.StreamReader] $response.GetResponseStream()
$output = $reader.ReadToEnd()
$reader.Close()
$response.Close()
Write-Output $output
}
catch
{
$output = #"
{
"error":1,
"error_desc":"Error : Problème d'accès au serveur $($_.Exception.Message)"
}
"#
Write-Output $output
}
}
Edited 19-10-2015
Here is an example usage :
#$urlBase = "http://192.168.1.1:8080/"
#######################################################################
# Login #
#######################################################################
$wsLogin = "production/login"
Function login
{
[CmdletBinding()]
PARAM
(
[ValidateNotNullOrEmpty()]
[String] $login,
[String] $passwd
)
Write-Verbose $wsLogin
#$jsonIn = [PSCustomObject]#{"login"=$login;"passwd"=$passwd} | ConvertTo-Json
$jsonIn = #"
{
"login":"$login",
"passwd":"$passwd"
}
"#
Write-Verbose $jsonIn
$jsonOut = Request-Rest -URL "$urlBase$wsLogin" -JSON $jsonIn -credentials $null
Write-Verbose $jsonOut
#return $jsonOut | ConvertFrom-Json
return $jsonOut
}
It is easy to convert that code to cURL
curl -v --insecure -X POST -H "Content-Type: application/json" --data-binary someJSONFile.js https://coolWebsite.com/ext/ext/