As a follow-on post to the Send-MgUserMessage
article, I’d like to discuss Send-MgUserMail
.
- What is this cmdlet?
- How is
Send-MgUserMail
different fromSend-MgUserMessage
? - Did I waste a whole bunch of time with the other cmdlet and article only to find a better approach?
To answer the last question first: “The Time You Enjoy Wasting Is Not Wasted Time” 😊
At first glance, it’s silly that there are two brand new cmdlets that basically do the same thing. Though when you look at the underlying Graph namespaces they interact with, you’ll realize this is Graph’s fault for publishing two namespaces! The SDK cmdlet module is just trying to make Graph easier, so we can’t really blame PowerShell.
Send-MgUserMail | Send-MgUserMessage |
• Uses the Graph sendmail namespace• Can send an email in a single step • Requires Mail.Send permission | • Uses the Graph send namespace• Sends a previously created message • Could send emails created by an outside process or user • Can reply and forward messages in a mailbox • Requires Mail.Send permission, though you’ll possibly be creating the message itself with New-MgUserMessage , which requires Mail.ReadWrite (arguably too much power in most cases) |
As we begin to understand by the above table, Send-MgUserMail
requires fewer permissions and requires fewer moving parts*, so I expect to be using that instead of Send-MgUserMessage
in the future.
*You’re about to see, the “moving parts” may not actually be fewer, since its more complex to use Send-MgUserMail
, but I’ve made some functions below that you are welcome to include in your projects to simplify some of the more complex data structures.
To get started, be sure you have the correct modules on your computer and that the app you are using has the correct permissions. In my case, I am using application-based authentication as I mentioned in the previous post. You could also use delegated permissions for this task.
Install the modules:
Install-Module Microsoft.Graph.Authentication
Install-Module Microsoft.Graph.Users.Actions
Next, assign your custom application (or the built-in “Microsoft Graph PowerShell” application) the sendmail
permission. You can do this with the -Scopes
parameter of Connect-MgGraph
, or on the app directly, in the Azure AD Portal. I realize that this is a loaded statement, but Graph API permissions are a topic that is beyond the scope (heh) of this article.
If you need help, you can start with this article for guidance:
Determine required permission scopes
Once permissions are set, you can connect to Graph and start sending email. Unfortunately, Send-MgUserMail
‘s main parameter -BodyParameter
takes the following object type:
IPathsFh5OjtUsersUserIdMicrosoftGraphSendmailPostRequestbodyContentApplicationJsonSchema
No, I’m not joking. That’s what the documentation says! Needless to say, figuring out what this meant took some tinkering. In the end, it seems that these Mg* cmdlets love nested hash tables. I wrote a couple of functions to simplify the creation of two of them. the rest aren’t functions, but you can follow along to see the PowerShell interpretation of their structure.
This one takes a list ([array]
) of email addresses and converts it into the IMicrosoftGraphRecipient
object type:
Function ConvertTo-IMicrosoftGraphRecipient {
[cmdletbinding()]
Param(
[array]$smtpAddresses
)
foreach ($address in $smtpAddresses) {
@{
emailAddress = @{address = $address}
}
}
}
Note: Graph’s toRecipients
property requires an array of hash tables, even if you have only one recipient. I’ve addressed this in the script with [array]$toRecipients
, because I had trouble making the function output an array in all cases.
This next function uploads attachments, first by converting to Base64, and then nesting them in the IMicrosoftGraphAttachment
format.
Function ConvertTo-IMicrosoftGraphAttachment {
[cmdletbinding()]
Param(
[string]$UploadDirectory
)
$DirectoryContents = Get-ChildItem $UploadDirectory -Attributes !Directory -Recurse
foreach ($File in $DirectoryContents) {
$EncodedAttachment = [convert]::ToBase64String((Get-Content $File.FullName -Encoding byte))
@{
"@odata.type"= "#microsoft.graph.fileAttachment"
name = ($File.FullName -split '\\')[-1]
contentBytes = $EncodedAttachment
}
}
}
As I mentioned on the last post, if your attachment size is above 3MB, you should check out this function written by Glen Scales:
Sending a Message with a Large attachment using the Microsoft Graph and Powershell
Finally, here is the script itself. Hopefully, if you’ve followed along thus far, the rest doesn’t require much interpretation.
<#
Script for sending email with send-mgusermail
Ref:
https://mikecrowley.us/2021/10/27/sending-email-with-send-mgusermail-microsoft-graph-powershell
https://docs.microsoft.com/en-us/graph/api/user-sendmail
https://docs.microsoft.com/en-us/powershell/module/microsoft.graph.users.actions/send-mgusermail
#>
#region 1: Setup
$emailRecipients = @(
'user1@domain.com'
'user2@domain.biz'
)
$emailSender = 'me@domain.info'
$emailSubject = "Sample Email | " + (Get-Date -UFormat %e%b%Y)
$MgConnectParams = @{
ClientId = '<your app>'
TenantId = '<your tenant id>'
CertificateThumbprint = '<your thumbprint>'
}
Function ConvertTo-IMicrosoftGraphRecipient {
[cmdletbinding()]
Param(
[array]$SmtpAddresses
)
foreach ($address in $SmtpAddresses) {
@{
emailAddress = @{address = $address}
}
}
}
Function ConvertTo-IMicrosoftGraphAttachment {
[cmdletbinding()]
Param(
[string]$UploadDirectory
)
$directoryContents = Get-ChildItem $UploadDirectory -Attributes !Directory -Recurse
foreach ($file in $directoryContents) {
$encodedAttachment = [convert]::ToBase64String((Get-Content $file.FullName -Encoding byte))
@{
"@odata.type"= "#microsoft.graph.fileAttachment"
name = ($File.FullName -split '\\')[-1]
contentBytes = $encodedAttachment
}
}
}
#endregion 1
#region 2: Run
[array]$toRecipients = ConvertTo-IMicrosoftGraphRecipient -SmtpAddresses $emailRecipients
$attachments = ConvertTo-IMicrosoftGraphAttachment -UploadDirectory C:\tmp
$emailBody = @{
ContentType = 'html'
Content = Get-Content 'C:\tmp\HelloWorld.htm'
}
Connect-Graph @MgConnectParams
Select-MgProfile v1.0
$body += @{subject = $emailSubject}
$body += @{toRecipients = $toRecipients}
$body += @{attachments = $attachments}
$body += @{body = $emailBody}
$bodyParameter += @{'message' = $body}
$bodyParameter += @{'saveToSentItems' = $false}
Send-MgUserMail -UserId $emailSender -BodyParameter $bodyParameter
#endregion 2
Note: Future updates to this script will be on GitHub:
https://github.com/Mike-Crowley/Public-Scripts/blob/main/MgUserMail.ps1
In addition to the above, some may find it easier to work with the attachment object type included in the graph.mail module:
Import-Module Microsoft.Graph.Mail
[Microsoft.Graph.PowerShell.Models.IMicrosoftGraphAttachment]
e.g.
$MyFilePath = “C:\tmp\test.txt”
$ContentBytes = [convert]::ToBase64String((Get-Content $MyFilePath -Encoding byte))
[Microsoft.Graph.PowerShell.Models.IMicrosoftGraphAttachment]$MyAttachment = @{
“@odata.type” = “#microsoft.graph.fileAttachment”
Name = “test.txt”
ContentType = “text/plain”
ContentBytes = $ContentBytes
}
$Attachments = @($MyAttachment)