Skip to main content

EWS Managed API and Powershell How-To Series Part 11 Moderation

Moderation is an Exchange feature that was introduced in Exchange 2010 that allows the Human control of mail flow to a distribution group or mailbox. There is a good Blog article that describes the process at  http://blogs.technet.com/b/exchange/archive/2009/06/10/3407662.aspx. In this installment of the How-To series I'll go through all the different ways in which you can use and manipulate moderated email via EWS. 

Moderation Workflow

The moderation process flows through a workflow so I'll look at the full process and show where EWS can be used along that way. 

Before Send

The first thing you can do is even before a message is going to be sent is to work out if its going to be moderated by using MailTips. Eg the following code will access the MailTip for moderation and show you if the recipient will be moderated.

  1. $MailboxName = "sender@domain.com"    
  2.     
  3. $Mailboxes = @("user1@domain.com","user2@domain.com")     
  4.     
  5. $cred = New-Object System.Net.NetworkCredential("user1@domain.com","password@#")    
  6.   
  7. ## Choose to ignore any SSL Warning issues caused by Self Signed Certificates    
  8.     
  9. ## Code From http://poshcode.org/624  
  10. ## Create a compilation environment  
  11. $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider  
  12. $Compiler=$Provider.CreateCompiler()  
  13. $Params=New-Object System.CodeDom.Compiler.CompilerParameters  
  14. $Params.GenerateExecutable=$False  
  15. $Params.GenerateInMemory=$True  
  16. $Params.IncludeDebugInformation=$False  
  17. $Params.ReferencedAssemblies.Add("System.DLL") | Out-Null  
  18.   
  19. $TASource=@' 
  20.   namespace Local.ToolkitExtensions.Net.CertificatePolicy{ 
  21.     public class TrustAll : System.Net.ICertificatePolicy { 
  22.       public TrustAll() {  
  23.       } 
  24.       public bool CheckValidationResult(System.Net.ServicePoint sp, 
  25.         System.Security.Cryptography.X509Certificates.X509Certificate cert,  
  26.         System.Net.WebRequest req, int problem) { 
  27.         return true; 
  28.       } 
  29.     } 
  30.   } 
  31. '@   
  32. $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)  
  33. $TAAssembly=$TAResults.CompiledAssembly  
  34.   
  35. ## We now create an instance of the TrustAll and attach it to the ServicePointManager  
  36. $TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")  
  37. [System.Net.ServicePointManager]::CertificatePolicy=$TrustAll  
  38.   
  39. ## end code from http://poshcode.org/624  
  40.   
  41. $dllpath = "C:\Program Files\Microsoft\Exchange\Web Services\1.2\Microsoft.Exchange.WebServices.dll"  
  42. [void][Reflection.Assembly]::LoadFile($dllpath)  
  43. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1)  
  44. $service.TraceEnabled = $false  
  45.   
  46. $service.Credentials = $cred  
  47. $service.autodiscoverurl($MailboxName,{$true})  
  48. "using CAS : " + $service.Url  
  49.   
  50. $expRequest = @" 
  51. <?xml version="1.0" encoding="utf-8"?> 
  52. <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> 
  53. <soap:Header><RequestServerVersion Version="Exchange2010_SP1" xmlns="http://schemas.microsoft.com/exchange/services/2006/types" /> 
  54. </soap:Header> 
  55. <soap:Body> 
  56. <GetMailTips xmlns="http://schemas.microsoft.com/exchange/services/2006/messages"> 
  57. <SendingAs> 
  58. <EmailAddress xmlns="http://schemas.microsoft.com/exchange/services/2006/types">$MailboxName</EmailAddress> 
  59. </SendingAs> 
  60. <Recipients> 
  61. "@  
  62.   
  63. foreach($mbMailbox in $Mailboxes){  
  64.     $expRequest = $expRequest + "<Mailbox xmlns=`"http://schemas.microsoft.com/exchange/services/2006/types`"><EmailAddress>$mbMailbox</EmailAddress></Mailbox>"   
  65. }  
  66.   
  67. $expRequest = $expRequest + "</Recipients><MailTipsRequested>ModerationStatus</MailTipsRequested></GetMailTips></soap:Body></soap:Envelope>"  
  68. $mbMailboxFolderURI = New-Object System.Uri($service.url)  
  69. $wrWebRequest = [System.Net.WebRequest]::Create($mbMailboxFolderURI)  
  70. $wrWebRequest.CookieContainer =  New-Object System.Net.CookieContainer   
  71. $wrWebRequest.KeepAlive = $false;  
  72. $wrWebRequest.Headers.Set("Pragma""no-cache");  
  73. $wrWebRequest.Headers.Set("Translate""f");  
  74. $wrWebRequest.Headers.Set("Depth""0");  
  75. $wrWebRequest.ContentType = "text/xml";  
  76. $wrWebRequest.ContentLength = $expRequest.Length;  
  77. $wrWebRequest.Timeout = 60000;  
  78. $wrWebRequest.Method = "POST";  
  79. $wrWebRequest.Credentials = $cred  
  80. $bqByteQuery = [System.Text.Encoding]::ASCII.GetBytes($expRequest);  
  81. $wrWebRequest.ContentLength = $bqByteQuery.Length;  
  82. $rsRequestStream = $wrWebRequest.GetRequestStream();  
  83. $rsRequestStream.Write($bqByteQuery, 0, $bqByteQuery.Length);  
  84. $rsRequestStream.Close();  
  85. $wrWebResponse = $wrWebRequest.GetResponse();  
  86. $rsResponseStream = $wrWebResponse.GetResponseStream()  
  87. $sr = new-object System.IO.StreamReader($rsResponseStream);  
  88. $rdResponseDocument = New-Object System.Xml.XmlDocument  
  89. $rdResponseDocument.LoadXml($sr.ReadToEnd());  
  90. $RecipientNodes = @($rdResponseDocument.getElementsByTagName("t:RecipientAddress"))  
  91. $Datanodes = @($rdResponseDocument.getElementsByTagName("t:IsModerated"))  
  92. for($ic=0;$ic -lt $RecipientNodes.length;$ic++){  
  93.     if($Datanodes[$ic]."#text" -eq $true){  
  94.         $RecipientNodes[$ic].EmailAddress + " is Moderated"  
  95.     }  
  96.     else{  
  97.         $RecipientNodes[$ic].EmailAddress + " isn't Moderated"  
  98.     }  
  99. }  
 Once Moderated

Once a message has been moderated the moderator of that Mailbox/Group will be sent an Approval Email so they can either Accept or Reject the message. This Approval Email has a message class of IPM.Note.Microsoft.Approval.Request so if we want to find all the messages of this type with a Mailbox we need to construct a SearchFilter to find message of this type.

Eg to Find all the approval messages in the Inbox of a Mailbox

  1. $folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)     
  2. $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($Service,$folderid)  
  3. $ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)   
  4. $SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.ItemSchema]::ItemClass,"IPM.Note.Microsoft.Approval.Request")  
  5. $PidNameApprovalRequestor = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition([Microsoft.Exchange.WebServices.Data.DefaultExtendedPropertySet]::InternetHeaders,"x-ms-exchange-organization-approval-requestor",[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);    
  6. $Propset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)  
  7. $Propset.add($PidNameApprovalRequestor)  
  8. $ivItemView.PropertySet = $Propset  
  9.   
  10. do{    
  11.     $fiResults = $Inbox.findItems($SfSearchFilter,$ivItemView)  
  12.        
  13.     foreach($Item in $fiResults.Items){    
  14.         $propval = $null  
  15.         if($Item.TryGetProperty($PidNameApprovalRequestor,[ref]$propval)){  
  16.             "From : " + $propval  
  17.         }  
  18.         "Recieved : " + $Item.DateTimeReceived  
  19.         "Subject : " + $Item.Subject          
  20.     }    
  21.     $ivItemView.Offset += $fiResults.Items.Count    
  22. }while($fiResults.MoreAvailable -eq $true)  
One thing of note about this example is the use of the ms-exchange-organization-approval-requestor Header which contains the Original sender address of the Message that has been moderated.


Approving or Rejecting Emails

***********Disclaimer this bit is unsupported ************

There are no supported methods of Approving Moderated emails in EWS but using a little reverse engineering to work out what properties need to be set the following method seems to work okay.

Important Properties on Approval/Rejection Emails

ItemClass eg

$EmailMessage.ItemClass = "IPM.Note.Microsoft.Approval.Reply.Approve"
 

$EmailMessage.ItemClass = "IPM.Note.Microsoft.Approval.Reply.Reject" 


Subject - Use the Normalized subject from the approval Request email and then append Accept or Reject eg

 $NormalSubject = $null
 [Void]$Item.TryGetProperty($PR_NORMALIZED_SUBJECT,[ref]$NormalSubject)



$EmailMessage.Subject = "Approve: " + $NormalSubject
$EmailMessage.Subject = "Reject: " + $NormalSubject



RecipientTo - Needs to be set to the Microsoft Exchange Approval Assistant Address

$EmailMessage.ToRecipients.Add($Item.Sender.Address)


Extended Properties

VerbReponse - (not sure about this one)
PR_ReportTag - This is a critical property you get from the Approval Request which is used to correlate the Approval Response.

$PR_REPORT_TAG = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x0031,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary);
$VerbResponse = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition([Microsoft.Exchange.WebServices.Data.DefaultExtendedPropertySet]::Common,0x8524,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String); 

$ReportID = $null
[Void]$Item.TryGetProperty($PR_REPORT_TAG,[ref]$ReportID)
$EmailMessage.SetExtendedProperty($VerbResponse,"Reject")

$EmailMessage.SetExtendedProperty($PR_REPORT_TAG,$ReportID)

So to put this together as an Example if we had a Message in the Inbox with a Subject of  Egg and we wanted to approve this message we could use the following

  1. $folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)     
  2. $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($Service,$folderid)  
  3. $ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)   
  4. $SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.ItemSchema]::ItemClass,"IPM.Note.Microsoft.Approval.Request")  
  5. $PidNameApprovalRequestor = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition([Microsoft.Exchange.WebServices.Data.DefaultExtendedPropertySet]::InternetHeaders,"x-ms-exchange-organization-approval-requestor",[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);    
  6. $VerbResponse = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition([Microsoft.Exchange.WebServices.Data.DefaultExtendedPropertySet]::Common,0x8524,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);    
  7. $PR_NORMALIZED_SUBJECT = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x0E1D,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);       
  8. $PR_REPORT_TAG = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x0031,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary);  
  9.   
  10.   
  11.   
  12. $Propset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)  
  13. $Propset.add($PidNameApprovalRequestor)  
  14. $Propset.add($PR_NORMALIZED_SUBJECT)   
  15. $Propset.add($PR_REPORT_TAG)  
  16. $ivItemView.PropertySet = $Propset  
  17.   
  18. do{    
  19.     $fiResults = $Inbox.findItems($SfSearchFilter,$ivItemView)  
  20.     foreach($Item in $fiResults.Items){    
  21.         $propval = $null  
  22.         if($Item.TryGetProperty($PidNameApprovalRequestor,[ref]$propval)){  
  23.             "From : " + $propval  
  24.         }  
  25.     "Recieved : " + $Item.DateTimeReceived  
  26.     "Subject : " + $Item.Subject  
  27.     $NormalSubject = $null  
  28.     [Void]$Item.TryGetProperty($PR_NORMALIZED_SUBJECT,[ref]$NormalSubject)  
  29.     $ReportID = $null  
  30.     [Void]$Item.TryGetProperty($PR_REPORT_TAG,[ref]$ReportID)  
  31.     $Item.Load()  
  32.     if($NormalSubject -eq "Egg"){  
  33.     # Create Email Object    
  34.     $EmailMessage = New-Object Microsoft.Exchange.WebServices.Data.EmailMessage($service)   
  35.     #Set the Subject    
  36.     $EmailMessage.Subject = "Approve: " + $NormalSubject  
  37.     #Add Recipients    
  38.     $EmailMessage.ToRecipients.Add($Item.Sender.Address)    
  39.     #Send Message SendandSave will save the Message to the Sender Sent Item Folder    
  40.     $EmailMessage.ItemClass = "IPM.Note.Microsoft.Approval.Reply.Approve"  
  41.     $EmailMessage.SetExtendedProperty($VerbResponse,"Approve")  
  42.     $EmailMessage.SetExtendedProperty($PR_REPORT_TAG,$ReportID)  
  43.     $EmailMessage.SendAndSaveCopy() }   
  44.     }    
  45.     $ivItemView.Offset += $fiResults.Items.Count    
  46. }while($fiResults.MoreAvailable -eq $true)  

If we wanted to do the same thing this time we wanted to reject a message that was pending approval with the Subject of Chicken and this time say why we are rejecting it you could use

  1. $folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)     
  2. $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($Service,$folderid)  
  3. $ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)   
  4. $SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.ItemSchema]::ItemClass,"IPM.Note.Microsoft.Approval.Request")  
  5. $PidNameApprovalRequestor = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition([Microsoft.Exchange.WebServices.Data.DefaultExtendedPropertySet]::InternetHeaders,"x-ms-exchange-organization-approval-requestor",[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);    
  6. $VerbResponse = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition([Microsoft.Exchange.WebServices.Data.DefaultExtendedPropertySet]::Common,0x8524,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);    
  7. $PR_NORMALIZED_SUBJECT = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x0E1D,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);       
  8. $PR_REPORT_TAG = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x0031,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary);  
  9.   
  10.   
  11.   
  12. $Propset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)  
  13. $Propset.add($PidNameApprovalRequestor)  
  14. $Propset.add($PR_NORMALIZED_SUBJECT)   
  15. $Propset.add($PR_REPORT_TAG)  
  16. $ivItemView.PropertySet = $Propset  
  17.   
  18. do{    
  19.     $fiResults = $Inbox.findItems($SfSearchFilter,$ivItemView)  
  20.       
  21.     foreach($Item in $fiResults.Items){    
  22.         $propval = $null  
  23.         if($Item.TryGetProperty($PidNameApprovalRequestor,[ref]$propval)){  
  24.             "From : " + $propval  
  25.         }  
  26.     "Recieved : " + $Item.DateTimeReceived  
  27.     "Subject : " + $Item.Subject  
  28.     $NormalSubject = $null  
  29.     [Void]$Item.TryGetProperty($PR_NORMALIZED_SUBJECT,[ref]$NormalSubject)  
  30.     $ReportID = $null  
  31.     [Void]$Item.TryGetProperty($PR_REPORT_TAG,[ref]$ReportID)  
  32.     $Item.Load()  
  33.     if($NormalSubject -eq "Chicken"){  
  34.     # Create Email Object    
  35.     $EmailMessage = New-Object Microsoft.Exchange.WebServices.Data.EmailMessage($service)   
  36.     #Set the Subject    
  37.     $EmailMessage.Subject = "Reject: " + $NormalSubject  
  38.     #Add Recipients    
  39.     $EmailMessage.ToRecipients.Add($Item.Sender.Address)    
  40.     #Send Message SendandSave will save the Message to the Sender Sent Item Folder     
  41.     $EmailMessage.ItemClass = "IPM.Note.Microsoft.Approval.Reply.Reject"  
  42.     $EmailMessage.SetExtendedProperty($VerbResponse,"Reject")  
  43.     $EmailMessage.SetExtendedProperty($PR_REPORT_TAG,$ReportID)  
  44.     $EmailMessage.Body = "Sorry no Chicken"  
  45.     $EmailMessage.SendAndSaveCopy() }   
  46.     }    
  47.     $ivItemView.Offset += $fiResults.Items.Count    
  48. }while($fiResults.MoreAvailable -eq $true)  

Popular posts from this blog

Testing and Sending email via SMTP using Opportunistic TLS and oAuth in Office365 with PowerShell

As well as EWS and Remote PowerShell (RPS) other mail protocols POP3, IMAP and SMTP have had OAuth authentication enabled in Exchange Online (Official announcement here ). A while ago I created  this script that used Opportunistic TLS to perform a Telnet style test against a SMTP server using SMTP AUTH. Now that oAuth authentication has been enabled in office365 I've updated this script to be able to use oAuth instead of SMTP Auth to test against Office365. I've also included a function to actually send a Message. Token Acquisition  To Send a Mail using oAuth you first need to get an Access token from Azure AD there are plenty of ways of doing this in PowerShell. You could use a library like MSAL or ADAL (just google your favoured method) or use a library less approach which I've included with this script . Whatever way you do this you need to make sure that your application registration  https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-

The MailboxConcurrency limit and using Batching in the Microsoft Graph API

If your getting an error such as Application is over its MailboxConcurrency limit while using the Microsoft Graph API this post may help you understand why. Background   The Mailbox  concurrency limit when your using the Graph API is 4 as per https://docs.microsoft.com/en-us/graph/throttling#outlook-service-limits . This is evaluated for each app ID and mailbox combination so this means you can have different apps running under the same credentials and the poor behavior of one won't cause the other to be throttled. If you compared that to EWS you could have up to 27 concurrent connections but they are shared across all apps on a first come first served basis. Batching Batching in the Graph API is a way of combining multiple requests into a single HTTP request. Batching in the Exchange Mail API's EWS and MAPI has been around for a long time and its common, for email Apps to process large numbers of smaller items for a variety of reasons.  Batching in the Graph is limited to a m

How to test SMTP using Opportunistic TLS with Powershell and grab the public certificate a SMTP server is using

Most email services these day employ Opportunistic TLS when trying to send Messages which means that wherever possible the Messages will be encrypted rather then the plain text legacy of SMTP.  This method was defined in RFC 3207 "SMTP Service Extension for Secure SMTP over Transport Layer Security" and  there's a quite a good explanation of Opportunistic TLS on Wikipedia  https://en.wikipedia.org/wiki/Opportunistic_TLS .  This is used for both Server to Server (eg MTA to MTA) and Client to server (Eg a Message client like Outlook which acts as a MSA) the later being generally Authenticated. Basically it allows you to have a normal plain text SMTP conversation that is then upgraded to TLS using the STARTTLS verb. Not all servers will support this verb so if its not supported then a message is just sent as Plain text. TLS relies on PKI certificates and the administrative issue s that come around certificate management like expired certificates which is why I wrote th
All sample scripts and source code is provided by for illustrative purposes only. All examples are untested in different environments and therefore, I cannot guarantee or imply reliability, serviceability, or function of these programs.

All code contained herein is provided to you "AS IS" without any warranties of any kind. The implied warranties of non-infringement, merchantability and fitness for a particular purpose are expressly disclaimed.