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.
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
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
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
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.
- $MailboxName = "sender@domain.com"
- $Mailboxes = @("user1@domain.com","user2@domain.com")
- $cred = New-Object System.Net.NetworkCredential("user1@domain.com","password@#")
- ## Choose to ignore any SSL Warning issues caused by Self Signed Certificates
- ## Code From http://poshcode.org/624
- ## Create a compilation environment
- $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider
- $Compiler=$Provider.CreateCompiler()
- $Params=New-Object System.CodeDom.Compiler.CompilerParameters
- $Params.GenerateExecutable=$False
- $Params.GenerateInMemory=$True
- $Params.IncludeDebugInformation=$False
- $Params.ReferencedAssemblies.Add("System.DLL") | Out-Null
- $TASource=@'
- namespace Local.ToolkitExtensions.Net.CertificatePolicy{
- public class TrustAll : System.Net.ICertificatePolicy {
- public TrustAll() {
- }
- public bool CheckValidationResult(System.Net.ServicePoint sp,
- System.Security.Cryptography.X509Certificates.X509Certificate cert,
- System.Net.WebRequest req, int problem) {
- return true;
- }
- }
- }
- '@
- $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)
- $TAAssembly=$TAResults.CompiledAssembly
- ## We now create an instance of the TrustAll and attach it to the ServicePointManager
- $TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")
- [System.Net.ServicePointManager]::CertificatePolicy=$TrustAll
- ## end code from http://poshcode.org/624
- $dllpath = "C:\Program Files\Microsoft\Exchange\Web Services\1.2\Microsoft.Exchange.WebServices.dll"
- [void][Reflection.Assembly]::LoadFile($dllpath)
- $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1)
- $service.TraceEnabled = $false
- $service.Credentials = $cred
- $service.autodiscoverurl($MailboxName,{$true})
- "using CAS : " + $service.Url
- $expRequest = @"
- <?xml version="1.0" encoding="utf-8"?>
- <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">
- <soap:Header><RequestServerVersion Version="Exchange2010_SP1" xmlns="http://schemas.microsoft.com/exchange/services/2006/types" />
- </soap:Header>
- <soap:Body>
- <GetMailTips xmlns="http://schemas.microsoft.com/exchange/services/2006/messages">
- <SendingAs>
- <EmailAddress xmlns="http://schemas.microsoft.com/exchange/services/2006/types">$MailboxName</EmailAddress>
- </SendingAs>
- <Recipients>
- "@
- foreach($mbMailbox in $Mailboxes){
- $expRequest = $expRequest + "<Mailbox xmlns=`"http://schemas.microsoft.com/exchange/services/2006/types`"><EmailAddress>$mbMailbox</EmailAddress></Mailbox>"
- }
- $expRequest = $expRequest + "</Recipients><MailTipsRequested>ModerationStatus</MailTipsRequested></GetMailTips></soap:Body></soap:Envelope>"
- $mbMailboxFolderURI = New-Object System.Uri($service.url)
- $wrWebRequest = [System.Net.WebRequest]::Create($mbMailboxFolderURI)
- $wrWebRequest.CookieContainer = New-Object System.Net.CookieContainer
- $wrWebRequest.KeepAlive = $false;
- $wrWebRequest.Headers.Set("Pragma", "no-cache");
- $wrWebRequest.Headers.Set("Translate", "f");
- $wrWebRequest.Headers.Set("Depth", "0");
- $wrWebRequest.ContentType = "text/xml";
- $wrWebRequest.ContentLength = $expRequest.Length;
- $wrWebRequest.Timeout = 60000;
- $wrWebRequest.Method = "POST";
- $wrWebRequest.Credentials = $cred
- $bqByteQuery = [System.Text.Encoding]::ASCII.GetBytes($expRequest);
- $wrWebRequest.ContentLength = $bqByteQuery.Length;
- $rsRequestStream = $wrWebRequest.GetRequestStream();
- $rsRequestStream.Write($bqByteQuery, 0, $bqByteQuery.Length);
- $rsRequestStream.Close();
- $wrWebResponse = $wrWebRequest.GetResponse();
- $rsResponseStream = $wrWebResponse.GetResponseStream()
- $sr = new-object System.IO.StreamReader($rsResponseStream);
- $rdResponseDocument = New-Object System.Xml.XmlDocument
- $rdResponseDocument.LoadXml($sr.ReadToEnd());
- $RecipientNodes = @($rdResponseDocument.getElementsByTagName("t:RecipientAddress"))
- $Datanodes = @($rdResponseDocument.getElementsByTagName("t:IsModerated"))
- for($ic=0;$ic -lt $RecipientNodes.length;$ic++){
- if($Datanodes[$ic]."#text" -eq $true){
- $RecipientNodes[$ic].EmailAddress + " is Moderated"
- }
- else{
- $RecipientNodes[$ic].EmailAddress + " isn't 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
- $folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)
- $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($Service,$folderid)
- $ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)
- $SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.ItemSchema]::ItemClass,"IPM.Note.Microsoft.Approval.Request")
- $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);
- $Propset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
- $Propset.add($PidNameApprovalRequestor)
- $ivItemView.PropertySet = $Propset
- do{
- $fiResults = $Inbox.findItems($SfSearchFilter,$ivItemView)
- foreach($Item in $fiResults.Items){
- $propval = $null
- if($Item.TryGetProperty($PidNameApprovalRequestor,[ref]$propval)){
- "From : " + $propval
- }
- "Recieved : " + $Item.DateTimeReceived
- "Subject : " + $Item.Subject
- }
- $ivItemView.Offset += $fiResults.Items.Count
- }while($fiResults.MoreAvailable -eq $true)
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
- $folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)
- $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($Service,$folderid)
- $ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)
- $SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.ItemSchema]::ItemClass,"IPM.Note.Microsoft.Approval.Request")
- $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);
- $VerbResponse = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition([Microsoft.Exchange.WebServices.Data.DefaultExtendedPropertySet]::Common,0x8524,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);
- $PR_NORMALIZED_SUBJECT = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x0E1D,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);
- $PR_REPORT_TAG = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x0031,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary);
- $Propset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
- $Propset.add($PidNameApprovalRequestor)
- $Propset.add($PR_NORMALIZED_SUBJECT)
- $Propset.add($PR_REPORT_TAG)
- $ivItemView.PropertySet = $Propset
- do{
- $fiResults = $Inbox.findItems($SfSearchFilter,$ivItemView)
- foreach($Item in $fiResults.Items){
- $propval = $null
- if($Item.TryGetProperty($PidNameApprovalRequestor,[ref]$propval)){
- "From : " + $propval
- }
- "Recieved : " + $Item.DateTimeReceived
- "Subject : " + $Item.Subject
- $NormalSubject = $null
- [Void]$Item.TryGetProperty($PR_NORMALIZED_SUBJECT,[ref]$NormalSubject)
- $ReportID = $null
- [Void]$Item.TryGetProperty($PR_REPORT_TAG,[ref]$ReportID)
- $Item.Load()
- if($NormalSubject -eq "Egg"){
- # Create Email Object
- $EmailMessage = New-Object Microsoft.Exchange.WebServices.Data.EmailMessage($service)
- #Set the Subject
- $EmailMessage.Subject = "Approve: " + $NormalSubject
- #Add Recipients
- $EmailMessage.ToRecipients.Add($Item.Sender.Address)
- #Send Message SendandSave will save the Message to the Sender Sent Item Folder
- $EmailMessage.ItemClass = "IPM.Note.Microsoft.Approval.Reply.Approve"
- $EmailMessage.SetExtendedProperty($VerbResponse,"Approve")
- $EmailMessage.SetExtendedProperty($PR_REPORT_TAG,$ReportID)
- $EmailMessage.SendAndSaveCopy() }
- }
- $ivItemView.Offset += $fiResults.Items.Count
- }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
- $folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)
- $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($Service,$folderid)
- $ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)
- $SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.ItemSchema]::ItemClass,"IPM.Note.Microsoft.Approval.Request")
- $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);
- $VerbResponse = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition([Microsoft.Exchange.WebServices.Data.DefaultExtendedPropertySet]::Common,0x8524,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);
- $PR_NORMALIZED_SUBJECT = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x0E1D,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);
- $PR_REPORT_TAG = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x0031,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary);
- $Propset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
- $Propset.add($PidNameApprovalRequestor)
- $Propset.add($PR_NORMALIZED_SUBJECT)
- $Propset.add($PR_REPORT_TAG)
- $ivItemView.PropertySet = $Propset
- do{
- $fiResults = $Inbox.findItems($SfSearchFilter,$ivItemView)
- foreach($Item in $fiResults.Items){
- $propval = $null
- if($Item.TryGetProperty($PidNameApprovalRequestor,[ref]$propval)){
- "From : " + $propval
- }
- "Recieved : " + $Item.DateTimeReceived
- "Subject : " + $Item.Subject
- $NormalSubject = $null
- [Void]$Item.TryGetProperty($PR_NORMALIZED_SUBJECT,[ref]$NormalSubject)
- $ReportID = $null
- [Void]$Item.TryGetProperty($PR_REPORT_TAG,[ref]$ReportID)
- $Item.Load()
- if($NormalSubject -eq "Chicken"){
- # Create Email Object
- $EmailMessage = New-Object Microsoft.Exchange.WebServices.Data.EmailMessage($service)
- #Set the Subject
- $EmailMessage.Subject = "Reject: " + $NormalSubject
- #Add Recipients
- $EmailMessage.ToRecipients.Add($Item.Sender.Address)
- #Send Message SendandSave will save the Message to the Sender Sent Item Folder
- $EmailMessage.ItemClass = "IPM.Note.Microsoft.Approval.Reply.Reject"
- $EmailMessage.SetExtendedProperty($VerbResponse,"Reject")
- $EmailMessage.SetExtendedProperty($PR_REPORT_TAG,$ReportID)
- $EmailMessage.Body = "Sorry no Chicken"
- $EmailMessage.SendAndSaveCopy() }
- }
- $ivItemView.Offset += $fiResults.Items.Count
- }while($fiResults.MoreAvailable -eq $true)