Monday, July 31, 2017

PowerShell and Exchange Web Services (EWS) script to parse bounced emails

In my previous post, I had shared how to run PowerShell scripts on a locally configured Outlook profile, with Windows 10 powershell.

In this post, we will be making use of EWS to connect to an exchange server, impersonate a system maibox, download the emails and parsing the email body to extract the bounced email recipient.

References:
https://goodworkaround.com/2015/01/29/powershell-and-ews-managed-api/
https://msdn.microsoft.com/en-us/library/office/jj900168(v=exchg.150).aspx
https://www.linkedin.com/pulse/how-use-date-range-search-query-ews-managed-api-using-sunil-chauhan

What you need:
Microsoft Exchange Web Services API (download from nuget)

If you do not know which version of Exchange server your outlook profile is hosted on, refer to this article:

The script will attempt to detect a pre-embedded header. I find that this is the easiest way of detection since I have control over the email sending mechanism as well.
Example: in my email sending script (vbs sample here), I embed a header field
With Flds
' Set custom header so that the email can be easily read from bounce back addresses
.Item("urn:schemas:mailheader:x-CustomHdr") = strRecipientEmail
.Update
End With

Full source code:


 # Load the EWS Managed API  
 Add-Type -Path "C:\Microsoft.Exchange.WebServices.2.2\lib\40\Microsoft.Exchange.WebServices.dll"  
 #---------------------------------------------------------------------------------  
 # Functions 
 #---------------------------------------------------------------------------------  
 function Clear-DeletedItems  
 {  
   param(   
     $exchangeService  
   )  
   $deletedItems = [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::DeletedItems  
   $deletedItemsFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($exchangeService,$deletedItems)  
   # 1000 items at a time  
   $itemView = New-Object -TypeName Microsoft.Exchange.WebServices.Data.ItemView -ArgumentList 10  
   $dateTimeItem = [Microsoft.Exchange.WebServices.Data.ItemSchema]::DateTimeReceived  
   $timeLapse = (Get-Date).AddDays(-30)   
   $searchFilter = New-Object -TypeName Microsoft.Exchange.WebServices.Data.SearchFilter+IsLessThanOrEqualTo -ArgumentList $dateTimeItem,$timeLapse   
   $foundItems = $exchangeService.FindItems($deletedItemsFolder.Id, $searchFilter, $itemView)  
   Foreach($e in $foundItems){  
     write-host "deleting " $e.Subject  
     $e.Delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete)  
   }  
 }  
 function Parse-Emails  
 {  
   param($mailboxName,  
   $smtpServerName,  
   $username,  
   $domain,  
   $password,  
   $batchSize = 1000,  
   $clearDeletedItems = $false,   
   $emailFrom = "test@test.com",   
   $emailTo = "test@test.com"  
   )  
   try   
   {  
     $Exchange2007SP1 = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2007_SP1  
     $Exchange2010  = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010  
     $Exchange2010SP1 = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1  
     $Exchange2010SP2 = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2  
     $Exchange2013  = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013  
     $Exchange2013SP1 = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013_SP1  
     # create EWS Service object for the target mailbox name  
     $cred = New-Object System.Net.NetworkCredential($username, $password, $domain);  
     $exchangeService = New-Object -TypeName Microsoft.Exchange.WebServices.Data.ExchangeService -ArgumentList $Exchange2010SP2  
     # Use this line if running as logged in user  
     # $exchangeService.UseDefaultCredentials = $true  
     $exchangeService.Credentials = $cred  
     $exchangeService.AutodiscoverUrl($mailboxName)  
     # Optional. remove deleted items > 30 days  
     if ($clearDeletedItems) {  
       Clear-DeletedItems -exchangeService $exchangeService  
     }  
     # bind to the Inbox folder of the target mailbox  
     $inboxFolderName = [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox  
     $inboxFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($exchangeService,$inboxFolderName)  
     # Optional: reduce the query overhead by viewing the inbox 1000 items at a time  
     $itemView = New-Object -TypeName Microsoft.Exchange.WebServices.Data.ItemView -ArgumentList $batchSize  
     # search the mailbox for messages based on time  
     #$dateTimeItem = [Microsoft.Exchange.WebServices.Data.ItemSchema]::DateTimeReceived  
     #$timeLapse = (Get-Date).AddDays(-30) #(Get-Date).AddMinutes(-15)  
     #$searchFilter = New-Object -TypeName Microsoft.Exchange.WebServices.Data.SearchFilter+IsLessThanOrEqualTo -ArgumentList $dateTimeItem,$timeLapse   
     # search the mailbox for the subject containing "Undeliverable:"  
     #$subjectItem = [Microsoft.Exchange.WebServices.Data.ItemSchema]::Subject  
     #$filterString = "Undeliverable:"  
     #$searchFilter = New-Object -TypeName Microsoft.Exchange.WebServices.Data.SearchFilter+ContainsSubstring -ArgumentList $subjectItem, $filterString  
     # with filter  
     #$foundItems = $exchangeService.FindItems($inboxFolder.Id, $searchFilter, $itemView)  
     # without filter  
     $foundItems = $exchangeService.FindItems($inboxFolder.Id, $itemView)  
     Foreach($e in $foundItems)  
     {  
       write-host $e.Subject -foregroundcolor Yellow  
       # First you have to load the mail item. Only entries are loaded at this point  
       $e.Load()  
       $body = $e.Body.Text  
       if ($body -like "*Delivery has failed to these recipients*") {  
         write-host "failed delivery detected" -ForegroundColor Cyan  
         # look for lines like this: x-CustomHdr: test@email.com  
         # x-CustomHdr is a custom header only found in FACTS  
         $regex = '\sx-CustomHdr:\s*([\w_.+-]+@[\w-]+\.[\w-.]+)\s'  
         $found = $body -match $regex  
         if ($found) {  
           $emailAddr = $matches[1]  
           write-host $emailAddr  
           # do something here 
         }  
       }  
       $e.Delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::MoveToDeletedItems)  
       # Or delete permanently  
       # $e.Delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete)   
     }  
   }  
   catch  
   {  
    $entryType = "Error"  
    $subject = "Error in mailbox monitor script"  
    $messageBody = "{0}`r`n{1}" -f $_.Exception.Message,$_.InvocationInfo.PositionMessage  
    Write-EventLog -LogName "Application" -Source "Application" -EventId 1 -Category 4 -EntryType $entryType -Message $messageBody  
    $smtpClient = New-Object -TypeName Net.Mail.SmtpClient -ArgumentList $smtpServerName  
    $smtpClient.Send($emailFrom, $emailTo, $subject, $messageBody)  
   }  
 }  
 $mailbox = "sender@test.com"  
 $smtpServerName = "smtp.testserver.com"  
 $username = "username"  
 $domain = "DOMAIN"  
 $password = ("password" | ConvertTo-SecureString -asPlainText -Force)  
 Parse-Emails -smtpServerName $smtp -mailboxName $mailbox -username $username -domain $domain -password $password -clearDeletedItems $true -batchSize 1000  
 

No comments:

Related Posts Plugin for WordPress, Blogger...