Wednesday, July 11, 2012

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)