Today, I'm introducing a new PowerShell module called Mailozaurr. It's a module that aims to deliver functionality around Email for multiple use cases. I've started it since native SMTP cmdlet Send-MailMessage is obsolete, and I thought it would be good to write a replacement that adds more features over it as things around us are changing rapidly.
Send-MailMessage
cmdlet is obsolete. This cmdlet does not guarantee secure connections to SMTP servers. While there is no immediate replacement available in PowerShell, we recommend you do not use Send-MailMessage
. For more information, see Platform Compatibility note DE0005.While initially, it started as a way to send emails, it now has grown to support a bit more than that. Since I've started playing with MailKit library (on which Mailozaurr is based), I've noticed its potential for other use cases. MailKit supports a lot of features (not all are implemented in Mailozaurr yet), so if anything touches SMTP, IMAP, or POP3, it's most likely already implemented by it. All I have to do is write PowerShell implementation of it.
What you get today (or if you follow me on GitHub ages ago) is new PowerShell module to
Each of the subjects described above comes usually with a limited number of steps you need to do. Let's list all the functions that are available
(get-command -Module mailozaurr ) | Where-Object { $_.CommandType -eq 'function' } | Select Name
And that's what you get
Connect-IMAP Connect-oAuthGoogle Connect-oAuthO365 Connect-POP ConvertTo-GraphCredential ConvertTo-OAuth2Credential Disconnect-IMAP Disconnect-POP Find-DKIMRecord Find-DMARCRecord Find-MxRecord Find-SPFRecord Get-IMAPFolder Get-IMAPMessage Get-POPMessage Resolve-DnsQuery Save-POPMessage Send-EmailMessage Test-EmailAddress
Interested? Read on!
Some months ago, I had to help a Client with analyzing how and why something happened on his email account. It was accessed via POP3 Downloader, which downloaded emails and further put it in another mailbox. Long story short, something went wrong, and we wanted what POP3 saw when it comes to dates of emails and how it accessed those emails. At first, I thought – that should be easy – I can use telnet (not very secure) and mimic few commands (5 or so) to request data, and I'll get excellent output that I can assess. It may surprise you, but if you ask anything from POP3, it downloads everything. Not just a list of emails, but a list of emails and their content going from the first email to last email. As you may imagine having 10gb mailbox outputting content right into the telnet window didn't go well.
The LIST command output was quite surprising and not helpful at all. Then I remembered – I have a MailKit solution with basic SMTP support working and – maybe I can get POP3 up and running. At that point, the proof-of-concept Mailozaurr solution saved the day. So how would you access POP3 with Mailozaurr now?
For now, there are four steps one needs to do. Connect to POP3, List emails (not necessary if you know Index value), Save email, and finally disconnect POP3.
$UserName = '[email protected]' $Password = 'TextPassword' $Client = Connect-POP3 -Server 'pop.gmail.com' -Password $Password -UserName $UserName -Port 995 -Options Auto Get-POP3Message -Client $Client -Index 0 -Count 5 Save-POP3Message -Client $Client -Index 6 -Path "$Env:UserProfile\Desktop\mail.eml" Disconnect-POP3 -Client $Client
Or in a more secure way (still, you will need to enable Less secure app access if you intend to use Gmail)
$Credentials = Get-Credential $Client = Connect-POP3 -Server 'pop.gmail.com' -Credential $Credentials -Port 995 -Options Auto Get-POP3Message -Client $Client -Index 0 -Count 5 Save-POP3Message -Client $Client -Index 6 -Path "$Env:UserProfile\Desktop\mail.eml" Disconnect-POP3 -Client $Client
You can of course play with it a bit more. You can save the output of Get-Pop3Message
to variable and review the properties of the email.
$Emails = Get-POP3Message -Client $Client -Index 0 -Count 5 $Emails[0]
Finally, you can also use oAuth 2.0 if you're into a more secure approach
$ClientID = '93933307418' $ClientSecret = 'gk2ztAG' $oAuth2 = Connect-oAuthGoogle -ClientID $ClientID -ClientSecret $ClientSecret -GmailAccount '[email protected]' -Scope https://mail.google.com/ $Client = Connect-POP3 -Server 'pop.gmail.com' -Credential $oAuth2 -Port 995 -Options Auto -oAuth2 Get-POP3Message -Client $Client -Index 0 -Count 5 | Format-Table Save-POP3Message -Client $Client -Index 7 -Path "$Env:UserProfile\Desktop\mail7.eml" Disconnect-POP3 -Client $Client -Verbose
If it's the first time you're requesting a new token, you will be taken to approve it in your browser.
Like you can use Mailozaurr to access POP, you can also use it to access IMAP protocol. I've not spent much time on it, so it's not ready for production use, but feel free to provide feedback and let me know what you think should be added. The same principle applies. You use Connect-IMAP to connect to the server and then Get-IMAPFolder to list some data. Finally, use Disconnect-IMAP to close the connection.
$UserName = '[email protected]' $Password = '' $Client = Connect-IMAP -Server 'imap.gmail.com' -Password $Password -UserName $UserName -Port 993 -Options Auto Get-IMAPFolder -Client $Client -Verbose ## Not yet sure how to best process messages #Get-IMAPMessage -Client $Client -Verbose #foreach ($folder in $client.Data.Inbox.GetSubfolders($false)) { # "[folder] {0}", $folder.Name #} Disconnect-IMAP -Client $Client
More secure option maybe?
$Credential = Get-Credential $Client = Connect-IMAP -Server 'imap.gmail.com' -Credential $Credential -Port 993 -Options Auto Get-IMAPFolder -Client $Client -Verbose ## Not yet sure how to best process messages #Get-IMAPMessage -Client $Client -Verbose #foreach ($folder in $client.Data.Inbox.GetSubfolders($false)) { # "[folder] {0}", $folder.Name #} Disconnect-IMAP -Client $Client
Finally, connecting to IMAP using oAuth 2.0.
$ClientID = '9393330741' $ClientSecret = 'gk2ztAGU' $oAuth2 = Connect-oAuthGoogle -ClientID $ClientID -ClientSecret $ClientSecret -GmailAccount '[email protected]' -Scope https://mail.google.com/ $Client = Connect-IMAP -Server 'imap.gmail.com' -Port 993 -Options Auto -Credential $oAuth2 -oAuth2 Get-IMAPFolder -Client $Client -Verbose ## Not yet sure how to best process messages #Get-IMAPMessage -Client $Client -Verbose #foreach ($folder in $client.Data.Inbox.GetSubfolders($false)) { # "[folder] {0}", $folder.Name #} Disconnect-IMAP -Client $Client -Verbose
As I mentioned at the beginning of this blog post, Send-MailMessage is obsolete. While it doesn't mean it will stop working anytime soon, I've decided to create my command and enhance it with features. Send-EmailMessage should be able to be drag and drop replacement for your standard scenarios and then used in all other situations you may have.
if (-not $MailCredentials) { $MailCredentials = Get-Credential } # this is simple replacement (drag & drop to Send-MailMessage) Send-EmailMessage -To '[email protected]' -Subject 'Test' -Body 'test me' -SmtpServer 'smtp.office365.com' -From '[email protected]' ` -Attachments "$PSScriptRoot\..\README.MD", "$PSScriptRoot\..\Mailozaurr.psm1" -Encoding UTF8 -Cc '[email protected]' -Priority High -Credential $MailCredentials ` -UseSsl -Port 587 -Verbose $Body = EmailBody { EmailText -Text 'This is my text' EmailTable -DataTable (Get-Process | Select-Object -First 5 -Property Name, Id, PriorityClass, CPU, Product) } $Text = 'This is my text' Send-EmailMessage -From @{ Name = 'Przemysław Kłys'; Email = '[email protected]' } -To '[email protected]' ` -Server 'smtp.office365.com' -Credential $MailCredentials -HTML $Body -Text $Text -DeliveryNotificationOption OnSuccess -Priority High ` -Subject 'This is another test email' -SecureSocketOptions Auto
As you can see above, I'm using PSWriteHTML to create a simple HTML $Body for demonstration purposes. I'm also defining the same text as a string. This is because Send-EmailMessage can take both at the same time, but it's not necessary. Finally, I'm sending emails two times using similar, but not the same parameter sets with attached files.
Since we requested confirmation from server that our message was sent, we got that as well.
We can do the same thing with a bit more complicated HTML (built with PSWriteHTML).
$Body = EmailBody -FontFamily 'Calibri' -Size 15 { EmailText -Text 'Hello ', $UserNotify, ',' -Color None, Blue, None -Verbose -LineBreak EmailText -Text 'Your password is due to expire in ', $PasswordExpiryDays, 'days.' -Color None, Green, None EmailText -LineBreak EmailText -Text 'To change your password: ' EmailText -Text '- press ', 'CTRL+ALT+DEL', ' -> ', 'Change a password...' -Color None, BlueViolet, None, Red EmailText -LineBreak EmailTextBox { 'If you have forgotten your password and need to reset it, you can do this by clicking here. ' 'In case of problems please contact the HelpDesk by visiting [Evotec Website](https://evotec.xyz) or by sending an email to Help Desk.' } EmailText -LineBreak EmailText -Text 'Alternatively you can always call ', 'Help Desk', ' at ', '+48 22 00 00 00' ` -Color None, LightSkyBlue, None, LightSkyBlue -TextDecoration none, underline, none, underline -FontWeight normal, bold, normal, bold EmailText -LineBreak EmailTextBox { 'Kind regards,' 'Evotec IT' } } if (-not $MailCredentials) { $MailCredentials = Get-Credential } Send-EmailMessage -From @{ Name = 'Przemysław Kłys'; Email = '[email protected]' } -To '[email protected]' ` -Server 'smtp.office365.com' -SecureSocketOptions Auto -Credential $MailCredentials -HTML $Body -DeliveryNotificationOption OnSuccess -Priority High ` -Subject 'This is another test email' Send-MailMessage -To '[email protected]' -Subject 'Test' -Body 'test me' -SmtpServer 'smtp.office365.com' -From '[email protected]' ` -Attachments "$PSScriptRoot\..\Mailozaurr.psd1" -Encoding UTF8 -Cc '[email protected]' -DeliveryNotificationOption OnSuccess -Priority High -Credential $MailCredentials -UseSsl -Port 587 -Verbose
As you can see, two emails arrived, actually four because I also received two emails with confirmation about delivery. There was PSD1 attached to one of the emails which was blocked by my Outlook. You can also use UserName and Password using clear text but I don't recommend it. I've added it because you never know, but I would suggest using the PSCredential parameter.
Mailozaurr as in the case of POP3 and IMAP4 protocols also support oAuth 2.0 for SMTP. I've spent some time implementing it for both Office 365 and Google and it somewhat works. For Google, as shown for other protocols you just need to use Connect-oAuthGoogle to generate Credentials object.
$ClientID = '939333074185' $ClientSecret = 'gk2ztAGU' $CredentialOAuth2 = Connect-oAuthGoogle -ClientID $ClientID -ClientSecret $ClientSecret -GmailAccount '[email protected]' Send-EmailMessage -From @{ Name = 'Przemysław Kłys'; Email = '[email protected]' } -To '[email protected]' ` -Server 'smtp.gmail.com' -HTML $Body -Text $Text -DeliveryNotificationOption OnSuccess -Priority High ` -Subject 'This is another test email' -SecureSocketOptions Auto -Credential $CredentialOAuth2 -oAuth
As you can see above to send an email using oAuth 2.0 is just a matter of adding oAuth switch parameter to Send-EmailMessage and passing $CredentialOAuth2 object to the Credential parameter. That's all there is to Send-EmailMessage. Of course, you first need to get ClientID and ClientSecret – but that's something I have planned for another article (or you can google on your own).
Similarly sending an email with Office 365 using oAuth 2.0 is essentially the same from the Send-EmailMessage perspective.
$ClientID = '4c1197dd-53' $TenantID = 'ceb371f6-87' $CredentialOAuth2 = Connect-oAuthO365 -ClientID $ClientID -TenantID $TenantID Send-EmailMessage -From @{ Name = 'Przemysław Kłys'; Email = '[email protected]' } -To '[email protected]' ` -Server 'smtp.office365.com' -HTML $Body -Text $Text -DeliveryNotificationOption OnSuccess -Priority High ` -Subject 'This is another test email' -SecureSocketOptions Auto -Credential $CredentialOAuth2 -oAuth2
The difference is, after spending some time on this, I am not able to force Connect-oAuth365 command to cache or refresh token without prompting for confirmation every single time. While it works without any interaction for Gmail after the first prompt, for Office 365, it requires authorization from the user. Since Send-EmailMessage doesn't „care” about how the OAuth token is built, you can use any other modules to generate OAuth token or help me out and throw some ideas at me, as I gave up for now. This is also why I've exposed ConvertTo-OAuth2Credential cmdlet so you can simply pass your own UserName and Token. I do hope I can solve this issue in the future so you won't have to be bothered with it.
ConvertTo-OAuth2Credential -UserName 'UserName' -Token 'Test'
I believe the MSAL.PS module in Get-MsalToken has the answer I need for caching and refreshing tokens, but since I didn't have much time in the last days to work on it – I'm leaving it for you to try and play with it, or just use it as the way to generate and refresh tokens.
While I failed my battle with oAuth 2.0 for Office 365, Microsoft also offers a way to send emails with Graph API. I've decided to implement it as part of Send-EmailMessage as well.
# Credentials for Graph $ClientID = '0fb383f1' $DirectoryID = 'ceb371f6' $ClientSecret = 'VKDM_' $Credential = ConvertTo-GraphCredential -ClientID $ClientID -ClientSecret $ClientSecret -DirectoryID $DirectoryID # Sending email Send-EmailMessage -From @{ Name = 'Przemysław Kłys'; Email = '[email protected]' } -To '[email protected]' ` -Credential $Credential -HTML $Body -Subject 'This is another test email 1' -Graph -Verbose -Priority High # sending email with From as string (it won't matter for Exchange ) Send-EmailMessage -From '[email protected]' -To '[email protected]' ` -Credential $Credential -HTML $Body -Subject 'This is another test email 2' -Graph -Verbose -Priority Low
It uses the same pattern as the oAuth implementation. Simply use Graph switch parameter to tell Send-EmailMessage that it's dealing with Graph Credentials and then pass $ClientID, $DirectoryID and $ClientSecret as $Credentials object using ConvertTo-GraphCredential cmdlet.
Keep in mind, for now, the implementation of the only works for Application Permissions. I've not yet added support for Delegated Permissions as I didn't have time. Maybe it will work without any changes, but perhaps not. Don't know. As with oAuth, I've not spent time preparing step by step instructions on how to request Graph API credentials, but I'll do so when time allows me to. For now, Google is your man. What I've noticed is that I couldn't send larger HTML/JS/CSS content. Maybe it's due to the size limits of JSON, or I'm not escaping characters properly. The example below shows an HTML body built with PSWriteHTML using an Online switch, meaning it doesn't inline minimized JavaScript content, which may be the sole reason for it failing.
# It seems larger HTML is not supported. Online makes sure it uses less libraries inline # it may be related to not escaping chars properly for JSON, may require investigation $Body = EmailBody { EmailText -Text 'This is my text' EmailTable -DataTable (Get-Process | Select-Object -First 5 -Property Name, Id, PriorityClass, CPU, Product) } -Online # Credentials for Graph $ClientID = '0fb383f1' $DirectoryID = 'ceb371f6' $ClientSecret = 'VKDM_' $Credential = ConvertTo-GraphCredential -ClientID $ClientID -ClientSecret $ClientSecret -DirectoryID $DirectoryID # Sending email Send-EmailMessage -From @{ Name = 'Przemysław Kłys'; Email = '[email protected]' } -To '[email protected]' ` -Credential $Credential -HTML $Body -Subject 'This is another test email 1' -Graph -Verbose -Priority High
Test-EmailAddress -EmailAddress 'evotec@test', '[email protected]', '[email protected]', '[email protected].', '[email protected]', 'olly@somewhere.', 'olly@somewhere', 'user@☎.com', '[email protected]'
Now that we have our connectivity part out of the way for Mailozaurr, I've also added a cmdlet called Test-EmailAddress. Given a set of email addresses, it will tell you if the email is correct or not. Nothing major but can be useful.
When you are part of a larger company, you get to manage not just one domain, but often 5, 50, or even 100. Checking if the settings are correct is not an easy task, and it's easy to overlook if you check for things manually. Therefore I've created a way for me to query DNS to test one or multiple domains at the same time.
Find-MxRecord -DomainName 'evotec.pl', 'evotec.xyz' | Format-Table * Find-DMARCRecord -DomainName 'evotec.pl', 'evotec.xyz' | Format-Table * Find-SPFRecord -DomainName 'evotec.pl', 'evotec.xyz' | Format-Table * Find-DKIMRecord -DomainName 'evotec.pl', 'evotec.xyz' | Format-Table * Find-DKIMRecord -DomainName 'evotec.pl', 'evotec.xyz' -Selector 'selector1' | Format-Table *
From all those cmdlets, DKIM one is unique. You need to know Selector to be able to check for its settings. By default, it uses Selector1, but you can specify your own. But if you're as lazy as I am and want to supply one long list of domains with proper Selector next to it, I've added a way that should meet your needs.
$Domains = @( @{ DomainName = 'evotec.pl'; Selector = 'selector1' } @{ DomainName = 'evotec.xyz'; Selector = 'selector1' } @{ DomainName = 'microsoft.com'; Selector = 'selector2' } ) Find-MxRecord -DomainName $Domains | Format-Table * Find-DMARCRecord -DomainName $Domains | Format-Table * Find-SPFRecord -DomainName $Domains | Format-Table * Find-DKIMRecord -DomainName $Domains | Format-Table *
While Selector is only needed for DKIM I've added the ability to pass it the same way to all cmdlets – and Find-MxRecord, Find-SPFRecord, Find-DMARCRecord simply skip Selector and use DomainName only.
We're almost done, sorry for that. While Find cmdlets are useful on their own, I thought it would be cool to have a report out of it. Below you can find a simple report that checks five domains for their MX, DKIM, DMARC, and SPF records and display it in both HTML and EXCEL.
$ExcelReport = "$PSScriptRoot\Output\MailSystemSummary.xlsx" $HTMLReport = "$PSScriptRoot\Output\MailSystemSummary.html" $Domains = @( @{ DomainName = 'evotec.pl'; Selector = 'selector1' } @{ DomainName = 'evotec.xyz'; Selector = 'selector1' } @{ DomainName = 'microsoft.com'; Selector = 'selector2' } @{ DomainName = 'gmail.com'; Selector = 'selector2' } @{ DomainName = 'google.com'; Selector = 'selector2' } ) $MXRecords = Find-MxRecord -DomainName $Domains $DMARCRecords = Find-DMARCRecord -DomainName $Domains $SPFRecords = Find-SPFRecord -DomainName $Domains $DKIMRecords = Find-DKIMRecord -DomainName $Domains # Export to Excel $MXRecords | ConvertTo-Excel -FilePath $ExcelReport -WorksheetName 'MX Records' -AutoFilter -AutoFit $DMARCRecords | ConvertTo-Excel -FilePath $ExcelReport -WorksheetName 'DMARC Records' -AutoFilter -AutoFit $SPFRecords | ConvertTo-Excel -FilePath $ExcelReport -WorksheetName 'SPF Records' -AutoFilter -AutoFit $DKIMRecords | ConvertTo-Excel -FilePath $ExcelReport -WorksheetName 'DKIM Records' -AutoFilter -AutoFit -OpenWorkBook # Export to HTML New-HTML { New-HTMLSection -Invisible { New-HTMLSection -HeaderText 'MX' { New-HTMLTable -DataTable $MXRecords -Filtering } New-HTMLSection -HeaderText 'SPF' { New-HTMLTable -DataTable $SPFRecords { New-TableCondition -Name 'SPF' -ComparisonType string -Operator like -Value '*-all*' -BackgroundColor Green -Color White New-TableCondition -Name 'SPF' -ComparisonType string -Operator like -Value '*~all*' -BackgroundColor Yellow -Color Black New-TableCondition -Name 'SPF' -ComparisonType string -Operator like -Value '*\+all*' -BackgroundColor Red -Color White New-TableCondition -Name 'Count' -ComparisonType number -Operator gt -Value 1 -BackgroundColor Red -Color White New-TableCondition -Name 'Count' -ComparisonType number -Operator lt -Value 1 -BackgroundColor Red -Color White New-TableCondition -Name 'Count' -ComparisonType number -Operator eq -Value 1 -BackgroundColor Green -Color White } -Filtering } } New-HTMLSection -Invisible { New-HTMLSection -HeaderText 'DKIM' { New-HTMLTable -DataTable $DKIMRecords -Filtering -WordBreak break-all { New-TableCondition -Name 'Count' -ComparisonType number -Operator gt -Value 1 -BackgroundColor Red -Color White New-TableCondition -Name 'Count' -ComparisonType number -Operator lt -Value 1 -BackgroundColor Red -Color White New-TableCondition -Name 'Count' -ComparisonType number -Operator eq -Value 1 -BackgroundColor Green -Color White } } New-HTMLSection -HeaderText 'DMARC' { New-HTMLTable -DataTable $DMARCRecords -Filtering { New-TableCondition -Name 'Count' -ComparisonType number -Operator gt -Value 1 -BackgroundColor Red -Color White New-TableCondition -Name 'Count' -ComparisonType number -Operator lt -Value 1 -BackgroundColor Red -Color White New-TableCondition -Name 'Count' -ComparisonType number -Operator eq -Value 1 -BackgroundColor Green -Color White } } } } -Open -FilePath $HTMLReport -Online
Which gets you four sections with expandable tables in HTML along with some conditional coloring
And four Excel worksheets
Of course, for HTML, you will need PSWriteHTML, and for Excel, you will need PSWriteExcel.
All those described features of Mailozaurr are one command away. You simply need to use Install-Module cmdlet and it will get installed from PowerShellGallery.
Install-Module Mailozaurr -Force
And if you need HTML support or Excel support from the examples above you need to do
Install-Module PSWriteHTML -Force Install-Module PSWriteExcel -Force
For sources, as always visit GitHub. All my projects are hosted on it.
Couple of ideas
I was also wondering if I should merge the PSBlackListChecker project to work on Windows/Linux and Mac. I've not touched this project for a while, but since I'm trying to create one nice Mailozaurr, it may be useful to merge that in with updates to cross-platform. What do you think?