PSSendGrid.psm1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
Function Remove-Diacritics {
    # remove al special characters
    [CmdletBinding()]
    param(
        [parameter(Position = 1)]
        [string]$Value
    )
    $Value = $Value.Replace("ẞ", "ss")
    $TextWithoutDiacritics = [Text.Encoding]::ASCII.GetString([Text.Encoding]::GetEncoding("Cyrillic").GetBytes($Value))
    if ($Value -ne $TextWithoutDiacritics) {
        Write-Verbose "Replaced $Value with $TextWithoutDiacritics"
    }
    $TextWithoutDiacritics
}

function New-AddressArray {
    # Create an array with all the address data
    [outputtype([array])]
    [CmdletBinding()]
    param(
        [parameter(Position = 0)]
        [string[]]$Addresses,
        [parameter(Position = 1)]
        [string[]]$Names
    )

    $EmailAddressRegex = '^([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$'
    $AllAddresses = @()
    $i = 0
    if ( $Addresses.Count -eq 0) {
        Write-Verbose "No addresses found, moving on"
    }
    else {
        Write-Verbose "Found $($Addresses.Count) addresses"
        foreach ($AddressValue in $Addresses) {
            Write-Verbose "Processing Address $($i) Value: $($AddressValue)"
            if ($AddressValue -notmatch $EmailAddressRegex) {
                Throw "Invalid email address: $AddressValue"
            }
            else {
                $AddressObject = [pscustomObject]@{
                    "email" = (Remove-Diacritics $AddressValue)
                }
                if ($Names.Count -gt $i) {
                    $AddressObject | Add-Member -MemberType NoteProperty -Name "Name" -Value (Remove-Diacritics $Names[$i])
                }
                Write-Verbose "Added Name $($AddressObject.Name) to AddressObject"
                $AllAddresses += $AddressObject
            }
            $i++
        }
        $AllAddresses
    }
}

Function Send-PSSendGridMail {
    <#
    .SYNOPSIS
    Send an email through the SendGrid API
    .DESCRIPTION
    This function is a wrapper around the SendGrid API.
    It is possible to send attachments as well.
    .PARAMETER ToAddress
    Emailaddress of the receiving end, multiple addresses can be entered in an array
    .PARAMETER ToName
    Name of the receiving end, multiple names can be entered in an array.
    When using multiple addresses, the ToName array must be in the same order as the ToAddress array.
    .PARAMETER FromAddress
    Source email address
    .PARAMETER FromName
    Source name
    .PARAMETER CcAddress
    Emailaddress of the CC recipient, multiple addresses can be entered in an array.
    .PARAMETER CcName
    Name of the CC recipient, multiple names can be entered in an array.
    When using multiple addresses, the CcName array must be in the same order as the CcAddress array.
    .PARAMETER BccAddress
    Emailaddress of the BCC recipient, multiple addresses can be entered in an array.
    .PARAMETER BccName
    Name of the BCC recipient, multiple names can be entered in an array.
    When using multiple addresses, the BccName array must be in the same order as the BccAddress array.
    .PARAMETER Subject
    Subject of the email
    .PARAMETER Body
    Body of the email when using plain text
    .PARAMETER BodyAsHTML
    Body of the email when using HTML
    .PARAMETER Token
    SendGrid token for the API
    .PARAMETER AttachmentPath
    Path to file(s) that needs to be attached.
    This can be a single string or an array of strings
    .PARAMETER AttachmentDisposition
    Attachment or Inline. Use inline to add image to HTML body
    .PARAMETER AttachmentID
    AttachmentID(s) for inline attachment, to refer to from the HTML.
    This can be a single string or an array of strings
    For multiple Attachments, the IDs should be in the same order as the attachmentPaths
    .EXAMPLE
    $Parameters = @{
        FromAddress = "example@example.com"
        ToAddress = "example2@example.com"
        Subject = "SendGrid example"
        Body = "This is a plain text email"
        Token = "adfdasfaihghaweoigneawfaewfawefadfdsfsd4tg45h54hfawfawfawef"
        FromName = "Jane Doe"
        ToName = "John Doe"
    }
    Send-PSSendGridMail @Parameters
 
    =======
    Sends an email from example@example.com to example2@example.com
 
    .EXAMPLE
    $Parameters = @{
        Token = "API TOKEN"
        ToAddress = "example@example.com"
        BodyAsHTML = "<h1>MetHTML</h1><img src='cid:exampleID'>"
        ToName = "Example2"
        FromName = "Example1"
        FromAddress = "example2@example.com"
        AttachmentID = "exampleID"
        AttachmentPath = "C:\temp\exampleimage.jpg"
        AttachmentDisposition = "inline"
        Subject = "Test"
    }
    Send-PSSendGridMail @Parameters
 
    =======
    Sends an email with an inline attachment in the HTML
    .EXAMPLE
    $Parameters = @{
        FromAddress = "example@example.com"
        ToAddress = @("example2@example.com", "example3@example.com")
        cCAddress = "Example4@example.com"
        Subject = "SendGrid example"
        Body = "This is a plain text email"
        Token = "adfdasfaihghaweoigneawfaewfawefadfdsfsd4tg45h54hfawfawfawef"
        FromName = "Jane Doe"
        ToName = @("John Doe", "Bert Doe")
    }
    Send-PSSendGridMail @Parameters
 
    =======
    Sends an email from example@example.com to example2@example.com and example3@example.com, with CC to Example4@example.com
    .LINK
    https://github.com/Ba4bes/PSSendGrid
    .LINK
    https://4bes.nl/2020/07/26/pssendgrid-send-email-from-powershell-with-sendgrid/
    .NOTES
    Created by Barbara Forbes
    Script has been tested for Windows PowerShell and PowerShell 7.
    Only has been tested on Windows.
    @Ba4bes
    https://4bes.nl
 
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')]
    param (
        [parameter(Mandatory = $True)]
        [string[]]$ToAddress,
        [parameter(Mandatory = $False)]
        [string[]]$ToName,
        [parameter(Mandatory = $True)]
        [string]$FromAddress,
        [parameter(Mandatory = $False)]
        [string]$FromName,
        [parameter(Mandatory = $False)]
        [string[]]$CCAddress,
        [parameter(Mandatory = $False)]
        [string[]]$CCName,
        [parameter(Mandatory = $False)]
        [string[]]$BCCAddress,
        [parameter(Mandatory = $False)]
        [string[]]$BCCName,
        [parameter(Mandatory = $True)]
        [string]$Subject,
        [parameter()]
        [string]$Body,
        [parameter()]
        [string]$BodyAsHTML,
        [parameter(Mandatory = $True)]
        [string]$Token,
        [parameter()]
        [ValidateScript( { Test-Path $_ })]
        [string[]]$AttachmentPath,
        [parameter()]
        [ValidateSet('attachment', 'inline')]
        [string]$AttachmentDisposition = "attachment",
        [parameter()]
        [string[]]$AttachmentID

    )
    Write-Verbose "Starting Function Send-PSSendGridMail"
    # Set body as HTML or as text
    if (-not[string]::IsNullOrEmpty($BodyAsHTML)) {
        $MailbodyType = 'text/HTML'
        $MailbodyValue = (Remove-Diacritics $BodyAsHTML)
        Write-Verbose "Found BodyAsHTML parameter, Type is set to text/HTML"
    }
    else {
        $MailbodyType = 'text/plain'
        $MailBodyValue = (Remove-Diacritics $Body)
        Write-Verbose "Found no BodyAsHTML parameter, Type is set to text/plain"
    }
    # Check for attachments. If they are present, convert to Base64
    if ($AttachmentPath) {
        Write-Verbose "AttachmentPath parameter was used"
        $AllAttachments = @()
        foreach ($Attachment in $AttachmentPath) {
            try {
                if ($PSVersionTable.PSVersion.Major -lt 6) {
                    $EncodedAttachment = [convert]::ToBase64String((Get-Content $Attachment -encoding byte))
                }
                else {
                    $AttachmentContent = Get-Content -Path $Attachment -AsByteStream -ErrorAction Stop

                    $EncodedAttachment = [convert]::ToBase64String($AttachmentContent)
                }
                Write-Verbose "Attachment found at $Attachment has been encoded"
            }
            Catch {

                Throw "Could not convert file $Attachment"
            }


            # Get the extension for the attachment type
            $AttachmentExtension = $Attachment.Split('.')[-1]
            Switch ($IsWindows) {
                $true { $AttachmentName = $Attachment.Split('\')[-1] }
                $false { $AttachmentName = $Attachment.Split('/')[-1] }
                $Null { $AttachmentName = $Attachment.Split('\')[-1] }
                default {
                    Throw "Could not work with attachmentpath"
                }
            }
            Write-Verbose "AttachmentExtension: $AttachmentExtension"
            $ImageExtensions = @("jpg", "png", "gif")
            if ($ImageExtensions -contains $AttachmentExtension) {
                $Type = "image/$AttachmentExtension"
                Write-Verbose "Attachment is an image, type set to $Type"
            }
            else {
                $Type = "Application/$AttachmentExtension"
                Write-Verbose "Attachment is not an image, type set to $Type"
            }
            $Attachmentobject = [pscustomObject]@{
                AttachmentName    = $AttachmentName
                EncodedAttachment = $EncodedAttachment
                AttachmentType    = $Type
            }
            $AllAttachments += $Attachmentobject
        }
        Write-Verbose "$($AllAttachments.Count) attachments have been processed"
    }
    # Create to, cc, bcc objects if needed
    Write-Verbose "Adding toAddress array if needed"
    $ToAddresses = New-AddressArray -Addresses $ToAddress -Names $ToName
    Write-Verbose "Adding ccAddress array if needed"
    $CCAddresses = New-AddressArray -Addresses $CCAddress -Names $CCName
    Write-Verbose "Adding bccAddress array if needed"
    $BCCAddresses = New-AddressArray -Addresses $BCCAddress -Names $BCCName

    # Create a body to send to the API

    $Personalization = @{
        'subject' = (Remove-Diacritics $Subject)
    }
    Write-Verbose "adding subject: $($Personalization.subject)"
    if ($ToAddresses) {
        $Personalization.Add('to', @($ToAddresses))
    }
    if ($CCAddresses) {
        $Personalization.Add("cc", @($CCAddresses))
    }
    if ($BCCAddresses) {
        $Personalization.Add("bcc" , @($BCCAddresses))
    }
    $SendGridBody = [pscustomObject]@{
        "personalizations" = @(
            $Personalization
        )
        "content"          = @(
            @{
                "type"  = $mailbodyType
                "value" = $MailBodyValue
            }
        )
        "from"             = @{
            "email" = (Remove-Diacritics $FromAddress)
            "name"  = (Remove-Diacritics $FromName)
        }
    }

    # Add attachments to body if they are present
    if ($AttachmentPath) {
        $Attachments = @()
        $i = 0
        foreach ($Attachmentobject in $AllAttachments) {
            Write-Verbose "Attachment is added to body"
            $Object = @{
                content     = $Attachmentobject.EncodedAttachment
                filename    = $Attachmentobject.AttachmentName
                type        = $Attachmentobject.AttachmentType
                disposition = $AttachmentDisposition
            }
            if ($AttachmentID) {
                # AttachmentIDs are added in the same order as the attachments
                $Object.add("content_id", $AttachmentID[$i])
            }
            $Attachments += $Object
            $i++
            Write-Verbose "Attachment Filename: $($Object.filename)"
            Write-Verbose "Attachment type: $($Object.type)"
            Write-Verbose "Attachment disposition: $($object.disposition)"
            Write-Verbose "Attachment ObjectID: $($object.content_id)"
        }
        $SendGridBody | Add-Member -MemberType NoteProperty -Name "Attachments" -Value $Attachments
    }
    $BodyJson = $SendGridBody | ConvertTo-Json -Depth 4

    #Create the header
    $Header = @{
        "authorization" = "Bearer $token"
    }
    #send the mail through Sendgrid
    $Parameters = @{
        Method      = "POST"
        Uri         = "https://api.sendgrid.com/v3/mail/send"
        Headers     = $Header
        ContentType = "application/json"
        Body        = $BodyJson
    }
    if ($PSCmdlet.ShouldProcess(
            ("An email will be send from {0} to {1} with subject: {2}" -f $SendGridBody.from.email, $SendGridBody.personalizations.to.email, $SendGridBody.personalizations.subject),
            ("Do you want to send an email from {0} to {1} with subject: {2}?" -f $SendGridBody.from.email, $SendGridBody.personalizations.to.email, $SendGridBody.personalizations.subject),
            "Send email")) {
        Invoke-RestMethod @Parameters
    }
    Write-Verbose "API has been called, function is done"
}