Summary: Guest blogger Mike Pfeiffer shows how to send email messages using Windows PowerShell and Exchange online.
Microsoft Scripting Guy Ed Wilson here. Guest Blogger Mike Pfeiffer recently published a book called Microsoft Exchange 2010 PowerShell Cookbook. Mike has been in the IT field for over 13 years, spending most of his time as an enterprise consultant focused on Active Directory and Exchange implementation and migration projects. He is a Microsoft Certified Master on Exchange 2010, and a Microsoft Exchange MVP. You can find his writings online at mikepfeiffer.net, where he blogs regularly about Exchange Server and Windows PowerShell-related topics.
Sending the output of a script in an email message is simple with Windows PowerShell 2.0, thanks to the Send-MailMessage cmdlet. I have seen some great solutions people have created with this cmdlet to generate and deliver automated reports, notifications, and monitoring alerts. Even before Windows PowerShell 2.0, it was easy to use the classes in the System.Net.Mail namespace to accomplish the same goal. In either case, as long as you have access to an SMTP server, you can easily automate the transmission of email messages with Windows PowerShell.
Messaging is getting a little more complicated these days, though. Now we have hosted solutions such as Exchange Online offered through Office 365. Imagine that your organization has decided to go with a fully hosted deployment of Exchange Online, meaning that all of your organization’s mailboxes are hosted exclusively in the cloud. What do you do with your scripts that need to send email messages?
Out of the box, SMTP Relay with Exchange Online requires a Transport Layer Security (TLS) connection, and you must connect on SMTP port 587. The good news is that there is an easier way to send email messages via Exchange Online using Windows PowerShell. The answer is the Exchange Web Services (EWS) Managed API, which is a fully object-oriented .NET Framework wrapper for the EWS XML protocol.
Here are a few of the benefits to using EWS to send email messages:
- Messages are sent through the web services endpoint on port 443, which is firewall friendly.
- The API has a built-in autodiscover client that will determine the web service endpoint for you automatically. You don’t need to provide a server name.
- You don’t need to worry about connector settings, mail relay, and TLS.
- Messages sent through the API can be saved in the senders Sent Items folder, which can provide tracking and reporting information.
In order to use the EWS Managed API and the code provided in this article, you’ll need a machine running Windows Server 2008 R2, Windows Server 2008, Windows 7, or Windows Vista. You’ll also need Windows PowerShell 2.0 with.NET Framework 3.5 installed. As long as these requirements are met, head over to the Microsoft Download Center and grab EWS Managed API 1.1. You will want to download the appropriate MSI package for your machine, either x86 or x64, and run through the installation. The installer simply extracts the EWS assembly to a folder on your hard drive, which by default will be under C:\Program Files\Microsoft\Exchange\Web Services\1.1
After this is complete, we are ready to write some code. Before we can start working with the classes in the EWS Managed API, the assembly must be loaded so that the .NET Framework types are available:
Add-Type -Path ‘C:\Program Files\Microsoft\Exchange\Web Services\1.1\Microsoft.Exchange.WebServices.dll’
Next, we need to create an instance of the ExchangeService class that can be used to send SOAP messages to an Exchange server using the API. This class basically defines the connection information for the web service. It provides several properties and methods, some of which can be used to specify our credentials and set the web services endpoint URL using the built-in autodiscover client:
$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService -ArgumentList Exchange2010_SP1
Notice that we are passing the Exchange version to the ExchangeService class constructor. This is actually optional in this case because the 1.1 version of the API will automatically set this to Exchange2010_SP1, which is the same version running in the cloud. However, it’s good to know if you ever want to write code that targets an Exchange server running on premise. The ExchangeVersion Enumeration contains all of the supported versions that can be used.
Since our goal is to send a message from Exchange Online, we will need to authenticate to the web service. This can be set on the existing $service objects Credentials property:
$service.Credentials = New-Object Microsoft.Exchange.WebServices.Data.WebCredentials -ArgumentList user@yourdomain.onmicrosoft.com, “P@ssw0rd”
The credentials used here should be the mailbox from which you want to send the email message. This will be a valid user name and password for an existing Exchange Online user account.
At this point, we can use the AutoDiscoverUrl method of the $service object to automatically set the EWS end-point:
$service.AutodiscoverUrl(‘user@yourdomain.onmicrosoft.com’, {$true})
The first argument provided for the AutoDiscoverUrl method is the e-mail address for your Exchange Online user account. The API will take the domain portion of the address and query DNS for an autodiscover record. Once it is resolved, the API will hit the autodiscover endpoint and determine the Exchange web services URL in the cloud. The second argument passed to the method is a scriptblock that returns $true. This allows the server to redirect API to the appropriate Exchange Online server during the autodiscover process.
If you take a look at the $service object after invoking the AutodiscoverUrl method, you’ll notice that the EWS URL is automatically set to an Exchange Online server running in the cloud.
Now that we have an authenticated connection established to Exchange Online, we can create an instance of the EmailMessage class and send a message:
$message = New-Object Microsoft.Exchange.WebServices.Data.EmailMessage -ArgumentList $service
$message.Subject = ‘Test is a test’
$message.Body = ‘This message is being sent through EWS with PowerShell’
$message.ToRecipients.Add(‘sysadmin@contoso.com’)
$message.SendAndSaveCopy()
Setting the Subject, Body, and From properties of an EmailMessage object is pretty straightforward. Adding recipients requires that we use the Add method of the ToRecipients property. When adding multiple recipients, you can call this method for each one. There are also properties for CcRecipients and BccRecipients.
When it comes to actually sending the message, there are two methods that can be used. The SendAndSaveCopy method sends the message and retains a copy in the senders Sent Items folder. Alternatively, the Send method will send the message without saving a copy. Because we passed the $service object to the EmailMessage class constructor when creating the message, our Exchange Online credentials and web services endpoint located through autodiscover will be used to transmit the message.
While the code we have looked at so far is useful, we can automate things even further. Let us wrap the code up into a reusable function to make this a little easier.
function Send-O365MailMessage {
[CmdletBinding()]
param(
[Parameter(Position=1, Mandatory=$true)]
[String[]]
$To,
[Parameter(Position=2, Mandatory=$false)]
[String[]]
$CcRecipients,
[Parameter(Position=3, Mandatory=$false)]
[String[]]
$BccRecipients,
[Parameter(Position=4, Mandatory=$true)]
[String]
$Subject,
[Parameter(Position=5, Mandatory=$true)]
[String]
$Body,
[Parameter(Position=6, Mandatory=$false)]
[Switch]
$BodyAsHtml,
[Parameter(Position=7, Mandatory=$true)]
[System.Management.Automation.PSCredential]
$Credential
)
begin {
#Load the EWS Managed API Assembly
Add-Type -Path ‘C:\Program Files\Microsoft\Exchange\Web Services\1.1\Microsoft.Exchange.WebServices.dll’
}
process {
#Insatiate the EWS service object
$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService -ArgumentList Exchange2010_SP1
#Set the credentials for Exchange Online
$service.Credentials = New-Object Microsoft.Exchange.WebServices.Data.WebCredentials -ArgumentList `
$Credential.UserName, $Credential.GetNetworkCredential().Password
#Determine the EWS endpoint using autodiscover
$service.AutodiscoverUrl($Credential.UserName, {$true})
#Create the email message and set the Subject and Body
$message = New-Object Microsoft.Exchange.WebServices.Data.EmailMessage -ArgumentList $service
$message.Subject = $Subject
$message.Body = $Body
#If the -BodyAsHtml parameter is not used, send the message as plain text
if(!$BodyAsHtml) {
$message.Body.BodyType = ‘Text’
}
#Add each specified recipient
$To | ForEach-Object{
$null = $message.ToRecipients.Add($_)
}
#Add each specified carbon copy recipient
if($CcRecipients) {
$CcRecipients | ForEach-Object{
$null = $message.CcRecipients.Add($_)
}
}
#Add each specified blind copy recipient
if($BccRecipients) {
$BccRecipients | ForEach-Object{
$null = $message.BccRecipients.Add($_)
}
}
#Send the message and save a copy in the Sent Items folder
$message.SendAndSaveCopy()
}
}
This function provides several parameters used to set the subject, body, and recipients for the message. The To, CcRecipients, and BccRecipients parameters accept multiple addresses and you can specify one or more addresses for each recipient type when sending a message. I’ve also included a BodyAsHtml switch parameter, which can be used to send the message in HTML format—or in plain text, when the parameter is not used.
After you have added this function to your shell session, you can call it just like a cmdlet and send a message through the web service:
$creds = Get-Credential
Send-O365MailMessage -To user@domain.com -Subject ‘test’ -Body ‘this is a test’ -BodyAsHtml -Credential $creds
This code first uses the Get-Credential cmdlet to store your Exchange Online credentials. We then call the Send-O365MailMessage cmdlet to send an email message through Exchange Online.
As a side note, EWS works the same on premise as it does in the cloud. If you have an existing Exchange 2010 SP1 deployment onsite, you can use the code samples in this article to send messages through the web service on your on-premise servers.
Thank you, Mike, for sharing your knowledge and experience with us today.
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy
0 comments