Thursday, December 08, 2011

Using the new EWS password expiration operation in Exchange 2010 SP2 in Powershell

If it hadn't escaped your notice Exchange 2010 SP2 was released this week although from an EWS perspective there isn't that much to shout about one of the interesting new operations is the GetPasswordExpiration operation. This allows you to get the DateTime when the password for an account will expire. Currently version 1.1 of the Managed API doesn't support this operation but from the information that has been released the next version should. In the meantime you can take advantage of this new operation using some raw SOAP.

The following is a quick sample script that makes a GetPasswordExpiration request there is some SSL ignore code so it should run okay in a test environment without a valid SSL cert. To use this script you need to configure the following variables.

$MailboxName = "user@domain.com" 
      
$cred = New-Object System.Net.NetworkCredential("user@domamin.com","password")

If you don't have autodiscover configured if your trying in a dev/test enviroment you can also hardcode the CASURL in

$mbMailboxFolderURI = New-Object System.Uri("https://192.168.42.132/ews/exchange.asmx") 

I've put a download of this script here the script itself looks like


  1. $MailboxName = "user@domain.com"    
  2.          
  3. $cred = New-Object System.Net.NetworkCredential("user@domamin.com","password")     
  4.     
  5. $dllpath = "C:\Program Files\Microsoft\Exchange\Web Services\1.1\Microsoft.Exchange.WebServices.dll"    
  6. [void][Reflection.Assembly]::LoadFile($dllpath)     
  7. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1)     
  8. $service.TraceEnabled = $false    
  9.     
  10. $service.Credentials = $cred    
  11. $service.autodiscoverurl($MailboxName,{$true})     
  12.   
  13. ## Code From http://poshcode.org/624  
  14. ## Create a compilation environment  
  15. $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider  
  16. $Compiler=$Provider.CreateCompiler()  
  17. $Params=New-Object System.CodeDom.Compiler.CompilerParameters  
  18. $Params.GenerateExecutable=$False  
  19. $Params.GenerateInMemory=$True  
  20. $Params.IncludeDebugInformation=$False  
  21. $Params.ReferencedAssemblies.Add("System.DLL") | Out-Null  
  22.   
  23. $TASource=@' 
  24.   namespace Local.ToolkitExtensions.Net.CertificatePolicy{ 
  25.     public class TrustAll : System.Net.ICertificatePolicy { 
  26.       public TrustAll() {  
  27.       } 
  28.       public bool CheckValidationResult(System.Net.ServicePoint sp, 
  29.         System.Security.Cryptography.X509Certificates.X509Certificate cert,  
  30.         System.Net.WebRequest req, int problem) { 
  31.         return true; 
  32.       } 
  33.     } 
  34.   } 
  35. '@   
  36. $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)  
  37. $TAAssembly=$TAResults.CompiledAssembly  
  38.   
  39. ## We now create an instance of the TrustAll and attach it to the ServicePointManager  
  40. $TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")  
  41. [System.Net.ServicePointManager]::CertificatePolicy=$TrustAll  
  42.   
  43. ## end code from http://poshcode.org/624  
  44.        
  45.       
  46.     $expRequest = @" 
  47. <?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"> 
  48. <soap:Header><RequestServerVersion Version="Exchange2010_SP2" xmlns="http://schemas.microsoft.com/exchange/services/2006/types" /> 
  49. </soap:Header> 
  50. <soap:Body> 
  51. <GetPasswordExpirationDate xmlns="http://schemas.microsoft.com/exchange/services/2006/messages"><MailboxSmtpAddress>$MailboxName</MailboxSmtpAddress> 
  52. </GetPasswordExpirationDate></soap:Body></soap:Envelope> 
  53. "@  
  54.         
  55. $mbMailboxFolderURI = New-Object System.Uri($service.url)    
  56.   
  57. $wrWebRequest = [System.Net.WebRequest]::Create($mbMailboxFolderURI)     
  58. $wrWebRequest.KeepAlive = $false;     
  59. $wrWebRequest.Headers.Set("Pragma""no-cache");     
  60. $wrWebRequest.Headers.Set("Translate""f");     
  61. $wrWebRequest.Headers.Set("Depth""0");     
  62. $wrWebRequest.ContentType = "text/xml";     
  63. $wrWebRequest.ContentLength = $expRequest.Length;     
  64. $wrWebRequest.Timeout = 60000;     
  65. $wrWebRequest.Method = "POST";     
  66. $wrWebRequest.Credentials = $cred    
  67. $bqByteQuery = [System.Text.Encoding]::ASCII.GetBytes($expRequest);     
  68. $wrWebRequest.ContentLength = $bqByteQuery.Length;     
  69. $rsRequestStream = $wrWebRequest.GetRequestStream();     
  70. $rsRequestStream.Write($bqByteQuery, 0, $bqByteQuery.Length);     
  71. $rsRequestStream.Close();     
  72. $wrWebResponse = $wrWebRequest.GetResponse();     
  73. $rsResponseStream = $wrWebResponse.GetResponseStream()     
  74. $sr = new-object System.IO.StreamReader($rsResponseStream);     
  75. $rdResponseDocument = New-Object System.Xml.XmlDocument     
  76. $rdResponseDocument.LoadXml($sr.ReadToEnd());     
  77. $ExpNodes = @($rdResponseDocument.getElementsByTagName("PasswordExpirationDate"))     
  78. $ExpNodes[0].'#text'  

Monday, November 28, 2011

Creating an Out of Office Board using Remote Powershell, Mailtips and the EWS Managed API

Following on from my previous post on using MailTips and one of my other popular posts the FreeBusy board the following script creates an Out of office board that shows in one page peoples out of office status and if the out of office message is turned on it displays the OOF message for that user.Here's a quick picture of what it produces


So how does it work it takes advantage of MailTips within EWS to get the OOF status of a user and what the OOF message is if its set. If you have a large number of users it batches the MailTips request in batches of 100 to ensure the request is successful. To get the list of users to query it takes advantage of using Remote powershell and uses the Get-Mailbox cmdlet.

The following script is setup to work with Office365 but will work OnPremise as well if change around the remote powershell URL.

The following variables are those that need to be configured to run this script

$UserName = "user@domain.com"
$Password = "password"

This is the username and password to use for both the remote powershell connection and it will also be used in the EWS Connection.

$MailboxName = $UserName

This line is setting the Sender email Address for MailTips to the above username if your using UPN's that are different from your SMTP addresses then you will need to change this variable.

$rpRemotePowershell = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.outlook.com/powershell -credential $adminCredential  -Authentication Basic -AllowRedirection

If your using this in a OnPrem environment then you need to change the URL and auth to suit your environment currently this is setup to connect to Office365.

I've put a download of this script here the script itself looks like

  1. $UserName = "user@domain.com"  
  2. $Password = "pasdfawrd."  
  3. $mbHash = @{ }  
  4. $MailboxName = $UserName  
  5.   
  6. $batchSize = 100  
  7. $global:oaBoard = ""  
  8.   
  9. $Mailboxes = @()  
  10.   
  11. $cred = New-Object System.Net.NetworkCredential($UserName,$Password)     
  12. $secpassword = ConvertTo-SecureString $Password -AsPlainText -Force  
  13. $adminCredential = New-Object -TypeName System.Management.Automation.PSCredential -argumentlist $UserName,$secpassword     
  14.   
  15. If(Get-PSSession | where-object {$_.ConfigurationName -eq "Microsoft.Exchange"}){  
  16.     write-host "Session Exists"  
  17. }  
  18. else{  
  19.     $rpRemotePowershell = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.outlook.com/powershell -credential $adminCredential  -Authentication Basic -AllowRedirection   
  20.     $importresults = Import-PSSession $rpRemotePowershell   
  21. }   
  22.   
  23. get-mailbox -ResultSize unlimited | where-object {$_.HiddenFromAddressListsEnabled -eq $false}  | foreach-object{  
  24.     if ($mbHash.ContainsKey($_.WindowsEmailAddress.ToString()) -eq $false){  
  25.         $mbHash.Add($_.WindowsEmailAddress.ToString(),$_.DisplayName)  
  26.           
  27.     }  
  28.     $emailAddress = $_.WindowsEmailAddress.ToString()  
  29.     $Mailboxes += $emailAddress  
  30. }  
  31.   
  32. $dllpath = "C:\Program Files\Microsoft\Exchange\Web Services\1.1\Microsoft.Exchange.WebServices.dll"  
  33. [void][Reflection.Assembly]::LoadFile($dllpath)  
  34. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1)  
  35. $service.TraceEnabled = $false  
  36.   
  37. $service.Credentials = $cred  
  38. $service.autodiscoverurl($MailboxName,{$true})  
  39.   
  40. function GetMailTips{  
  41. $mbMailboxFolderURI = New-Object System.Uri($service.url)  
  42. $wrWebRequest = [System.Net.WebRequest]::Create($mbMailboxFolderURI)  
  43. $wrWebRequest.CookieContainer =  New-Object System.Net.CookieContainer
  44. $wrWebRequest.KeepAlive = $false;  
  45. $wrWebRequest.Headers.Set("Pragma""no-cache");  
  46. $wrWebRequest.Headers.Set("Translate""f");  
  47. $wrWebRequest.Headers.Set("Depth""0");  
  48. $wrWebRequest.ContentType = "text/xml";  
  49. $wrWebRequest.ContentLength = $expRequest.Length;  
  50. $wrWebRequest.Timeout = 60000;  
  51. $wrWebRequest.Method = "POST";  
  52. $wrWebRequest.Credentials = $cred  
  53. $bqByteQuery = [System.Text.Encoding]::ASCII.GetBytes($expRequest);  
  54. $wrWebRequest.ContentLength = $bqByteQuery.Length;  
  55. $rsRequestStream = $wrWebRequest.GetRequestStream();  
  56. $rsRequestStream.Write($bqByteQuery, 0, $bqByteQuery.Length);  
  57. $rsRequestStream.Close();  
  58. $wrWebResponse = $wrWebRequest.GetResponse();  
  59. $rsResponseStream = $wrWebResponse.GetResponseStream()  
  60. $sr = new-object System.IO.StreamReader($rsResponseStream);  
  61. $rdResponseDocument = New-Object System.Xml.XmlDocument  
  62. $rdResponseDocument.LoadXml($sr.ReadToEnd());  
  63. $RecipientNodes = @($rdResponseDocument.getElementsByTagName("t:RecipientAddress"))  
  64. $Datanodes = @($rdResponseDocument.getElementsByTagName("t:OutOfOffice"))  
  65. for($ic=0;$ic -lt $RecipientNodes.length;$ic++){  
  66.     if($Datanodes[$ic].ReplyBody.Message -eq ""){  
  67.           
  68.         $global:oaBoard = $global:oaBoard + "<tr>"  + "`r`n"  
  69.         $global:oaBoard = $global:oaBoard + "<td>" + $mbHash[$RecipientNodes[$ic].EmailAddress] + "</td>"  + "`r`n"  
  70.         $global:oaBoard = $global:oaBoard + "<td bgcolor=`"#41A317`">In the Office</td>"  + "`r`n"  
  71.         $global:oaBoard = $global:oaBoard + "<td></td>"  + "`r`n"  
  72.         $global:oaBoard = $global:oaBoard + "</tr>"  + "`r`n"  
  73.     }  
  74.     else{  
  75.         $global:oaBoard = $global:oaBoard + "<tr>"  + "`r`n"  
  76.         $global:oaBoard = $global:oaBoard + "<td>" + $mbHash[$RecipientNodes[$ic].EmailAddress] + "</td>"  + "`r`n"  
  77.         $global:oaBoard = $global:oaBoard + "<td bgcolor=`"#153E7E`">Out of the Office</td>"  + "`r`n"  
  78.         $global:oaBoard = $global:oaBoard + "<td>" + $Datanodes[$ic].ReplyBody.Message  + "</td>" + "`r`n"  
  79.         $global:oaBoard = $global:oaBoard + "</tr>"  + "`r`n"  
  80.     }  
  81. }  
  82. }  
  83.   
  84.   
  85. $expHeader = @" 
  86. <?xml version="1.0" encoding="utf-8"?> 
  87. <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">  
  88. <soap:Header><RequestServerVersion Version="Exchange2010_SP1" xmlns="http://schemas.microsoft.com/exchange/services/2006/types" />  
  89. </soap:Header>  
  90. <soap:Body>  
  91. <GetMailTips xmlns="http://schemas.microsoft.com/exchange/services/2006/messages">  
  92. <SendingAs>  
  93. <EmailAddress xmlns="http://schemas.microsoft.com/exchange/services/2006/types">$MailboxName</EmailAddress>  
  94. </SendingAs>  
  95. <Recipients>  
  96. "@  
  97.   
  98. $global:oaBoard = $global:oaBoard + "<table><tr bgcolor=`"#95aedc`">" +"`r`n"  
  99. $global:oaBoard = $global:oaBoard + "<td align=`"center`" style=`"width=200;`" ><b>User</b></td>" +"`r`n"  
  100. $global:oaBoard = $global:oaBoard + "<td align=`"center`" style=`"width=200;`" ><b>Status</b></td>" +"`r`n"  
  101. $global:oaBoard = $global:oaBoard + "<td align=`"center`" style=`"width=200;`" ><b>Message</b></td>" +"`r`n"  
  102. $global:oaBoard = $global:oaBoard + "</tr>" + "`r`n"  
  103.   
  104. $expRequest = $expHeader  
  105.   
  106. $bCount =0  
  107. foreach($mbMailbox in $Mailboxes){  
  108.     $bCount++  
  109.     $expRequest = $expRequest + "<Mailbox xmlns=`"http://schemas.microsoft.com/exchange/services/2006/types`"><EmailAddress>$mbMailbox</EmailAddress></Mailbox>"   
  110.     if($bCount -eq $batchSize){  
  111.         $expRequest = $expRequest + "</Recipients><MailTipsRequested>OutOfOfficeMessage</MailTipsRequested></GetMailTips></soap:Body></soap:Envelope>"  
  112.         GetMailTips  
  113.         $expRequest = $expHeader  
  114.         $bCount = 0  
  115.     }  
  116. }  
  117. if($bCount -ne 0){  
  118.     $expRequest = $expRequest + "</Recipients><MailTipsRequested>OutOfOfficeMessage</MailTipsRequested></GetMailTips></soap:Body></soap:Envelope>"  
  119.     GetMailTips  
  120. }  
  121. $global:oaBoard = $global:oaBoard + "</table>"  + "  "   
  122. $global:oaBoard | out-file "c:\oofboard.htm"  

Tuesday, November 08, 2011

Using MailTips in EWS to get the OOF (Out of Office) Status of users with C# and Powershell

MailTips is one of the really useful new features of Exchange 2010 that can be exceedingly useful to anybody using Exchange Web Services. It provides access to Information such as the OOF status of a user or users within one WebService call and without the need for Full Access or elevated rights you would normally need with a GetUserOofSettings operations or using the Remote powershell cmdlets such as Get-MailboxAutoReplyConfiguration.

The other thing MailTips are useful for apart from the other standard Mail-tips listed http://msdn.microsoft.com/en-us/library/dd877060%28v=exchg.140%29.aspx (things like the Max message size is useful for anybody sending email via EWS etc)  is the ability to create Custom MailTips. This gives you the ability to now retrieve a custom property that is stored within Active Directory via EWS. Which provides a workaround to not being able to access the information in the AD Custom Attributes http://technet.microsoft.com/en-us/library/ee423541.aspx currently in EWS.

To use the EWS Mailtips operations you need to use WSDL Proxy objects or Custom SOAP code as currently these operations aren't available for use within the EWS Managed API. If you want to script it I would still combine it with EWS Managed API code because of ease of access to things like Autodiscover this gives you.

I've created a powershell and C# sample of using the GetMailtips operation the C# looks like


  1. ExchangeServiceBinding esb = new ExchangeServiceBinding();   
  2. esb.Credentials = new NetworkCredential("user@domain.com""password""");   
  3. esb.Url = "https://ch1prd0302.outlook.com/EWS/Exchange.asmx";   
  4. esb.RequestServerVersionValue = new RequestServerVersion();   
  5. esb.RequestServerVersionValue.Version = ExchangeVersionType.Exchange2010_SP1;   
  6. GetMessageTrackingReportRequestType gmt = new GetMessageTrackingReportRequestType();   
  7. GetMailTipsType gmType = new GetMailTipsType();   
  8. gmType.MailTipsRequested = new MailTipTypes();   
  9. gmType.MailTipsRequested = MailTipTypes.OutOfOfficeMessage;   
  10. gmType.Recipients = new EmailAddressType[1];   
  11. EmailAddressType rcip = new EmailAddressType();   
  12. rcip.EmailAddress = "user@domain.com";   
  13. gmType.Recipients[0] = rcip;   
  14. EmailAddressType sendAs = new EmailAddressType();   
  15. sendAs.EmailAddress = "sender@domain.com";   
  16. gmType.SendingAs = sendAs;   
  17.   
  18. GetMailTipsResponseMessageType gmResponse = esb.GetMailTips(gmType);   
  19. if (gmResponse.ResponseClass == ResponseClassType.Success) {   
  20.     if (gmResponse.ResponseMessages[0].MailTips.OutOfOffice.ReplyBody.Message != "")   
  21.     {   
  22.         //User Out   
  23.         Console.WriteLine(gmResponse.ResponseMessages[0].MailTips.OutOfOffice.ReplyBody.Message);   
  24.     }   
  25.     else {    
  26.         //user In   
  27.     }   
  28.                      
  29. }  
With the Powershell example the following information needs to be configured

$MailboxName = "sender@domain.com"

Mailbox which will do the check from
 
$Mailboxes = @("user1@domain.com","user2@domain.com")

Mailbox you want to check the OOF of (this is just an array or Strings)  
 
$cred = New-Object System.Net.NetworkCredential("user1@domain.com","password@#")

Credentials to use

I've put a download of the script here the code itself looks like


  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. $dllpath = "C:\Program Files\Microsoft\Exchange\Web Services\1.1\Microsoft.Exchange.WebServices.dll"  
  8. [void][Reflection.Assembly]::LoadFile($dllpath)   
  9. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1)   
  10. $service.TraceEnabled = $false  
  11.   
  12. $service.Credentials = $cred  
  13. $service.autodiscoverurl($MailboxName,{$true})   
  14.   
  15.   
  16. $expRequest = @"  
  17. <?xml version="1.0" encoding="utf-8"?>  
  18. <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">  
  19. <soap:Header><RequestServerVersion Version="Exchange2010_SP1" xmlns="http://schemas.microsoft.com/exchange/services/2006/types" />  
  20. </soap:Header>  
  21. <soap:Body>  
  22. <GetMailTips xmlns="http://schemas.microsoft.com/exchange/services/2006/messages">  
  23. <SendingAs>  
  24. <EmailAddress xmlns="http://schemas.microsoft.com/exchange/services/2006/types">$MailboxName</EmailAddress>  
  25. </SendingAs>  
  26. <Recipients>  
  27. "@   
  28.   
  29. foreach($mbMailbox in $Mailboxes){   
  30.     $expRequest = $expRequest + "<Mailbox xmlns=`"http://schemas.microsoft.com/exchange/services/2006/types`"><EmailAddress>$mbMailbox</EmailAddress></Mailbox>"    
  31. }   
  32.   
  33. $expRequest = $expRequest + "</Recipients><MailTipsRequested>OutOfOfficeMessage</MailTipsRequested></GetMailTips></soap:Body></soap:Envelope>"  
  34. $mbMailboxFolderURI = New-Object System.Uri($service.url)   
  35. $wrWebRequest = [System.Net.WebRequest]::Create($mbMailboxFolderURI)   
  36. $wrWebRequest.KeepAlive = $false;   
  37. $wrWebRequest.Headers.Set("Pragma""no-cache");   
  38. $wrWebRequest.Headers.Set("Translate""f");   
  39. $wrWebRequest.Headers.Set("Depth""0");   
  40. $wrWebRequest.ContentType = "text/xml";   
  41. $wrWebRequest.ContentLength = $expRequest.Length;   
  42. $wrWebRequest.Timeout = 60000;   
  43. $wrWebRequest.Method = "POST";   
  44. $wrWebRequest.Credentials = $cred  
  45. $bqByteQuery = [System.Text.Encoding]::ASCII.GetBytes($expRequest);   
  46. $wrWebRequest.ContentLength = $bqByteQuery.Length;   
  47. $rsRequestStream = $wrWebRequest.GetRequestStream();   
  48. $rsRequestStream.Write($bqByteQuery, 0, $bqByteQuery.Length);   
  49. $rsRequestStream.Close();   
  50. $wrWebResponse = $wrWebRequest.GetResponse();   
  51. $rsResponseStream = $wrWebResponse.GetResponseStream()   
  52. $sr = new-object System.IO.StreamReader($rsResponseStream);   
  53. $rdResponseDocument = New-Object System.Xml.XmlDocument   
  54. $rdResponseDocument.LoadXml($sr.ReadToEnd());   
  55. $RecipientNodes = @($rdResponseDocument.getElementsByTagName("t:RecipientAddress"))   
  56. $Datanodes = @($rdResponseDocument.getElementsByTagName("t:OutOfOffice"))   
  57. for($ic=0;$ic -lt $RecipientNodes.length;$ic++){   
  58.     if($Datanodes[$ic].ReplyBody.Message -eq ""){   
  59.         $RecipientNodes[$ic].EmailAddress + " : In the Office"  
  60.     }   
  61.     else{   
  62.         $RecipientNodes[$ic].EmailAddress + " : Out of Office"  
  63.     }   
  64. }   

Saturday, October 22, 2011

Changing Folder and Calendar Permissions in Exchange using the EWS Managed API and powershell

While Exchange 2010 provides some really good cmdlets now for setting and changing folder permissions such as Set-MailboxFolderPermission http://technet.microsoft.com/en-us/library/ff522363.aspx . Using EWS to do this can still be useful in circumstances where the cmdlets can't be used or your having problems with localized folder names or you want to take advantage of EWS Impersonation to get around any issues your having with acquiring the correct rights to make the changes you might need.

I'll look at the example of changing the default calendar permissions of the default ACL on the calendar as this is one of the harder things to do and there are a few points that can catch you out.

First point is that when you modify permissions with EWS you basically grab the DACL from the server make your changes locally to the DACL and the post it back in a Update Folder operation. This means the logic you use on the client side needs to be up to dealing with this. The permissions object in the Managed API has a Add and Remove methods and you have the ability to modify am ACE within the permissions collection. The one problem you can have is if the Folder permissions is currently set to custom and you try to use one the default FolderPermissionLevel. You will also have problem if you don't check the current collection and try to add a duplicate ACE into the collection.

A simple way to solve this is to first search the current collection for any existing ACE entries and remove the existing ACE then add the new rights you want set using the Add method. The other issue you need to deal with when setting the calendar folder permission is to also make sure you set the corresponding rights on the FreeBusy Data folder or you may have problems such as explained in http://support.microsoft.com/kb/184151

I've put together two examples both of these use EWS Impersonation which you can configure in 2010 via RBAC http://msdn.microsoft.com/en-us/library/bb204095%28v=EXCHG.140%29.aspx

There are 3 varibles in the script you need to modify

$MailboxName = "user@domain.com"




This is the mailbox you want to make the changes and what will be impersonated

$Permission = [Microsoft.Exchange.WebServices.Data.FolderPermissionLevel]::Editor






This is the rights you want to set

$credentials = New-Object System.Net.NetworkCredential("user@domain.com,"password")



These are the user credentials to use this user must have impersonation rights

I've posted both examples up here

In the first example this shows how to set the Default permissions for the default user on the Calendar to Editor


The second Example shows how to set calendar permission for a particular user to Editor this example looks like



  1. $MailboxName = "mailbox@domain.com"  
  2.   
  3.  
  1. $credentials = New-Object System.Net.NetworkCredential("user@domain.com","Password#")  
  2.   
  3. $NewACLUser = "newacl@domain.com"  
  4.   
  5. $dllpath = "C:\Program Files\Microsoft\Exchange\Web Services\1.1\Microsoft.Exchange.WebServices.dll"  
  6.   
  7. [void][Reflection.Assembly]::LoadFile($dllpath)  
  8.  
  9. $Permission = [Microsoft.Exchange.WebServices.Data.FolderPermissionLevel]::Editor  
  10.  
  11. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010)  
  12. $service.Credentials = $credentials  
  13. $service.AutodiscoverUrl($MailboxName,{$true})  
  14. $service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName);  
  15.   
  16.   
  17. function addFolderPerm($folder){  
  18.     $existingperm = $null  
  19.     foreach($fperm in $folder.Permissions){  
  20.         if($fperm.UserId.StandardUser -eq $null){  
  21.             if ($fperm.UserId.PrimarySmtpAddress.ToLower() -eq $NewACLUser.ToLower()){  
  22.                 $existingperm = $fperm  
  23.             }  
  24.         }  
  25.     }  
  26.     if($existingperm -ne $null){  
  27.         $folder.Permissions.Remove($existingperm)  
  28.     }   
  29.     $newfp = new-object Microsoft.Exchange.WebServices.Data.FolderPermission($NewACLUser,$Permission)  
  30.     $folder.Permissions.Add($newfp)  
  31.     $folder.Update()  
  32. }  
  33.   
  34. "Checking : " + $MailboxName   
  35. $folderidcnt = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Calendar,$MailboxName)  
  36. $Calendar = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderidcnt)  
  37. "Set Calendar Rights"  
  38. addFolderPerm($Calendar)  
  39. $sf1 = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,"Freebusy Data")  
  40.   
  41. $fvFolderView = New-Object Microsoft.Exchange.WebServices.Data.FolderView(1000)  
  42. $fvFolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Shallow;  
  43.   
  44. $folderidRoot = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Root,$MailboxName)  
  45. $fiResult = $Service.FindFolders($folderidRoot,$sf1,$fvFolderView)  
  46. if($fiResult.Folders.Count -eq 1){  
  47.     "Set FreeBusy Rights"  
  48.     $Freebusyfld = $fiResult.Folders[0]  
  49.     $Freebusyfld.Load()  
  50.     addFolderPerm($Freebusyfld)  
  51. }  

Friday, September 30, 2011

Using EWS Streaming notifications in Powershell to trigger a script when new mail arrives in Exchange 2010 SP1

EWS Streaming notifications where a great addition to Exchange 2010 SP1 that went some way to filling that large emotional gap that all Exchange developers felt with the demise of Store Event sinks in Exchange 2010. There is some good information and samples of using Streaming notifications in .NET which are a good read at http://msdn.microsoft.com/en-us/library/hh312849%28v=exchg.140%29.aspx and http://blogs.msdn.com/b/exchangedev/archive/2010/12/22/working-with-streaming-notifications-by-using-the-ews-managed-api.aspx

Notifications in EWS are the method you can use to get real-time notifications of email arriving or a calendar appointments being created. To distill what happens with streaming notification in a few lines is that when you create a subscription request for a particular Exchange mailbox folder with a CAS server that connection remains open for a maximum time of 30 minutes (this bit is important). During the next 30 minutes if something happens in the folder your subscribed to the server can use the connection your app has opened to notify you that something has happened. If you understand how ActiveSync works the process is very similar using a hanging http request.  After 30 minutes you then need to re-establish the connection which re-establishes the notifications channel to your application. (this 30 minute period can be changed but 30 is the MAX)

To use this within Powershell requires using some of the event features added in version 2.0 in particular the Register-ObjectEvent cmdlet. The cmldet allows you to subscribe to the events that are generated by a Microsoft .NET Framework object the object in question in this context is the StreamingSubscriptionConnection. This object has a few events the main one that fires when a NewMail arrives or an Item is created is the "OnNotificationEvent". The other import event is the "OnDisconnect" event which will fire when the subscription period ends or something else happens on the CAS side. The third event is the OnSubscriptionError which you need to work with for error checking and reliability.

The following script uses Register-ObjectEvent to create a subscription that will start a process that will log the Subject of the Message that caused the notification to fire. What you receive from the server when a notification is fired is the ItemId of the object that fired it. To get more information about what caused the notification you need to bind to this Item Id. To achieve this the ExchangeService object is passed in via the MessageData and a normal EWS bind is done.

The OnDisconnect Event is also registered so when this event fires the StreamingSubscriptionConnection open method is called which re-establishes the registration for another 30 minutes.

Reliability as this is pretty simple example it just listens to one folder in a mailbox (you can change the subscription type to listen to all folder in a mailbox) and only works withing a currently opened powershell session. To create a reliable process that could be relied on in a business context requires that you put some form of maintenance routine around the Notification subscription which in fully blown .NET is a lot easier to handle so using powershell and notifications outside of prototyping isn't something I'd recommend. The other things to consider when using EWS notification is the default throttling limits in 2010 SP1. For EWS Subscriptions the maximum number subscription you can have by default is 20 Subs per account so if your looking a subscribing to say a large number of calendar folders your going to have problems with throttling once you go past 20 users (remembering throttling is enabled by default) this is particular acute if your trying to use ExchangeOnline where you can't change since (ouch).

I've put a download of this script here the code itself looks like

  1. $MailboxName = "user@domain.com"  
  2. $dllpath = "C:\Program Files\Microsoft\Exchange\Web Services\1.1\Microsoft.Exchange.WebServices.dll"  
  3. [void][Reflection.Assembly]::LoadFile($dllpath)  
  4. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1)  
  5. $service.TraceEnabled = $false  
  6.   
  7. $service.Credentials = New-Object System.Net.NetworkCredential("user@domain.com","password")  
  8. $service.AutodiscoverUrl($MailboxName ,{$true})  
  9.   
  10. $fldArray = new-object Microsoft.Exchange.WebServices.Data.FolderId[] 1  
  11. $Inboxid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)  
  12. $fldArray[0] = $Inboxid  
  13. $stmsubscription = $service.SubscribeToStreamingNotifications($fldArray, [Microsoft.Exchange.WebServices.Data.EventType]::NewMail)  
  14. $stmConnection = new-object Microsoft.Exchange.WebServices.Data.StreamingSubscriptionConnection($service, 30);  
  15. $stmConnection.AddSubscription($stmsubscription)  
  16. Register-ObjectEvent -inputObject $stmConnection -eventName "OnNotificationEvent" -Action {  
  17.     foreach($notEvent in $event.SourceEventArgs.Events){      
  18.         [String]$itmId = $notEvent.ItemId.UniqueId.ToString()  
  19.         $message = [Microsoft.Exchange.WebServices.Data.EmailMessage]::Bind($event.MessageData,$itmId)  
  20.         "Subject : " + $message.Subject + " " + (Get-Date) | Out-File c:\log2.txt -Append   
  21.     }   
  22. } -MessageData $service  
  23. Register-ObjectEvent -inputObject $stmConnection -eventName "OnDisconnect" -Action {$event.MessageData.Open()} -MessageData $stmConnection  
  24. $stmConnection.Open()  

Friday, September 16, 2011

Getting Size and Item details for single folders in Exchange 2003 via Powershell

While this is now something you can do in one line in Exchange 2007 and 2010 if your migrating or managing 2000/3 you may want to get the Size and Item Count details of one particular mailbox folder for all users on a server eg how big the Inbox,SentItems or Calendar folder is. In Exchange 2003 you could get the Mailbox Size via WMI but to get an individual folder size you need to access the Mailbox itself (eg you can use pfdavadmin etc). To do this via Power shell for all users on the server you first need an ADSI query to get all the mailboxes on a particular server then you can use with WebDAV via the virtual admin root or MAPI via RDO\Redemption  http://www.dimastr.com/redemption/ which works well in Powershell. I have two scripts that use show how do to this using each of these API's which I've posted a downloadable copy here

To run these scripts you need to use the Netbios name of the server as a commandline parameter the RDO version of the script looks like


$snServerName = $args[0]

$Global:rptCollection = @()

function QueryMailbox($mb){
$mb
$rdoSession = new-object -com Redemption.RDOSession
$rdoSession.LogonExchangeMailbox($SmtpAddress,$snServerName)
$calendar = $rdoSession.Stores.DefaultStore.GetDefaultFolder(9)




$Itmcnt = "" | select DisplayName,SMTPAddress,ItemCount,Size
$Itmcnt.DisplayName = $displayName
$Itmcnt.SMTPAddress = $SmtpAddress
$Itmcnt.ItemCount = $calendar.Items.Count
$Itmcnt.Size = [System.Math]::Round(($calendar.Fields(235405315) /1024),2)
$Global:rptCollection += $Itmcnt
$Itmcnt
$rdoSession.logoff()
}

function GetUsers(){
$root = [ADSI]'LDAP://RootDSE'
$cfConfigRootpath = "LDAP://" + $root.ConfigurationNamingContext.tostring()
$dfDefaultRootPath = "LDAP://" + $root.DefaultNamingContext.tostring()
$configRoot = [ADSI]$cfConfigRootpath
$dfRoot = [ADSI]$dfDefaultRootPath
$searcher = new-object System.DirectoryServices.DirectorySearcher($configRoot)
$searcher.Filter = '(&(objectCategory=msExchExchangeServer)(cn=' + $snServerName  + '))'
$searcher.PropertiesToLoad.Add("cn")
$searcher.PropertiesToLoad.Add("gatewayProxy")
$searcher.PropertiesToLoad.Add("legacyExchangeDN")
$searcher1 = $searcher.FindAll()
foreach ($server in $searcher1){
    $snServerEntry = New-Object System.DirectoryServices.directoryentry
        $snServerEntry = $server.GetDirectoryEntry()
    $snServerName = $snServerEntry.cn
    $snExchangeDN = $snServerEntry.legacyExchangeDN
}
$searcher.Filter = '(&(objectCategory=msExchRecipientPolicy)(cn=Default Policy))'
$searcher1 = $searcher.FindAll()
foreach ($recppolicies in $searcher1){
         $gwaddrrs = New-Object System.DirectoryServices.directoryentry
        $gwaddrrs = $recppolicies.GetDirectoryEntry()
    foreach ($address in $gwaddrrs.gatewayProxy){
        if($address.Substring(0,5) -ceq "SMTP:"){$dfAddress = $address.Replace("SMTP:@","")}
    }   
   
}
$arMbRoot = "https://" + $snServerName + "/exadmin/admin/" + $dfAddress + "/mbx/"
$gfGALQueryFilter =  "(&(&(&(& (mailnickname=*)(!msExchHideFromAddressLists=TRUE)(| (&(objectCategory=person)" `
+ "(objectClass=user)(msExchHomeServerName=" + $snExchangeDN + ")) )))))"
$dfsearcher = new-object System.DirectoryServices.DirectorySearcher($dfRoot)
$dfsearcher.Filter = $gfGALQueryFilter
$searcher2 = $dfsearcher.FindAll()
foreach ($uaUsers in $searcher2){
         $uaUserAccount = New-Object System.DirectoryServices.directoryentry
        $uaUserAccount = $uaUsers.GetDirectoryEntry()
    foreach ($address in $uaUserAccount.proxyaddresses){
        if($address.Substring(0,5) -ceq "SMTP:"){$uaAddress = $address.Replace("SMTP:","")}
    }
    $SmtpAddress = $uaAddress
    $displayName = $uaUserAccount.DisplayName[0]
    QueryMailbox($uaAddress)
}
}

GetUsers
$Global:rptCollection | Export-Csv -NoTypeInformation c:\mailbox.csv

Wednesday, September 07, 2011

Create Search Folders using the EWS Managed API in a Mailbox or Archive Store using Powershell

Search folders are one of the suite of Exchange search options you can use programatically or to provide users with different views of their mailbox data in Outlook or OWA. Essentially a search folder is like a regular mailbox folder, except that it contains only links to messages in other folders that meet the criteria set in the search filter restriction which means that search folders are great for nonchanging, nondynamic queries. Search folders like Search Filters make use of restrictions in Exchange rather then using the Content Index which means that this comes along with some compromises ,if you are going to use search folders (or a lot of search filters) defiantly have a read of  http://technet.microsoft.com/en-us/library/cc535025%28EXCHG.80%29.aspx .

To create Search Folders using the EWS Managed API its pretty simple for search folders to be visible in Outlook you need to use the SearchFolders WellKnownFolderName enumeration which in a Mailbox relates to the Finder folder in the Non_IPM_Subtree.

Eg the following will create a Search folder to find Items greater than 5 MB in a Mailbox

$MailboxName = "user@domain.com"

$dllpath = "C:\Program Files\Microsoft\Exchange\Web Services\1.1\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 = New-Object System.Net.NetworkCredential("user@domain.com","password")
$service.AutodiscoverUrl($MailboxName ,{$true})

$svFldid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::SearchFolders,$MailboxName)
$SearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsGreaterThan([Microsoft.Exchange.WebServices.Data.ItemSchema]::Size, (5*1024*1024));
$SearchFolder = new-object Microsoft.Exchange.WebServices.Data.SearchFolder($service);
$searchFolder.SearchParameters.RootFolderIds.Add([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot);
$searchFolder.SearchParameters.Traversal = [Microsoft.Exchange.WebServices.Data.SearchFolderTraversal]::Deep;
$searchFolder.SearchParameters.SearchFilter = $SearchFilter
$searchFolder.DisplayName = "Bigger then 5 MB";
$searchFolder.Save($svFldid);

If you want to create a Search folder in the Archive Store there's no enumeration for the SearchFolders root folder in the Archive Store so you first need to find the Finder folder in the Archive Store then you can use this when you create the Search Folder for example the following will create a Search folder to find Items greater than 5 MB in the Archive Store.

$MailboxName = "user@domain.com"

$dllpath = "C:\Program Files\Microsoft\Exchange\Web Services\1.1\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 = New-Object System.Net.NetworkCredential("user@domain.com","password")
$service.AutodiscoverUrl($MailboxName ,{$true})

$FolderView = New-Object Microsoft.Exchange.WebServices.Data.FolderView(1);
$fsf = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName, "Finder");
$ffResult = $service.FindFolders([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::ArchiveRoot,$fsf, $FolderView)

$SearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsGreaterThan([Microsoft.Exchange.WebServices.Data.ItemSchema]::Size, (5*1024*1024));
$SearchFolder = new-object Microsoft.Exchange.WebServices.Data.SearchFolder($service);
$searchFolder.SearchParameters.RootFolderIds.Add([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::ArchiveMsgFolderRoot);
$searchFolder.SearchParameters.Traversal = [Microsoft.Exchange.WebServices.Data.SearchFolderTraversal]::Deep;
$searchFolder.SearchParameters.SearchFilter = $SearchFilter
$searchFolder.DisplayName = "Bigger then 5 MB";
$searchFolder.Save($ffResult.folders[0].Id);





Wednesday, August 31, 2011

Reading Extended Rights on an Exchange database using ADSI and C#

Although there are a few better methods of doing this these days this is still handy to have especially if your running older versions of Exchange or you want to audit the raw ACE's that are being added by RBAC in Exchange 2010. Extended rights get used for a number of things EWS Impersonation is one, SendAs is another anyway here's an old C# sample I tripped over today.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Security.AccessControl;
using System.DirectoryServices;

namespace showImpRights
{
class Program
{
static void Main(string[] args)
{
DirectoryEntry rootdse = new DirectoryEntry("LDAP://RootDSE");
DirectoryEntry cfg = new DirectoryEntry("LDAP://" + rootdse.Properties["configurationnamingcontext"].Value);
DirectoryEntry exRights = new DirectoryEntry("LDAP://cn=Extended-rights," + rootdse.Properties["configurationnamingcontext"].Value);
Hashtable exRighthash = new Hashtable();
foreach (DirectoryEntry chent in exRights.Children) {
if( exRighthash.ContainsKey(chent.Properties["rightsGuid"].Value) == false){
exRighthash.Add(chent.Properties["rightsGuid"].Value,chent.Properties["DisplayName"].Value);}
}

DirectorySearcher cfgsearch = new DirectorySearcher(cfg);
cfgsearch.Filter = "(objectCategory=msExchPrivateMDB)";
cfgsearch.PropertiesToLoad.Add("distinguishedName");
cfgsearch.SearchScope = SearchScope.Subtree;
SearchResultCollection res = cfgsearch.FindAll();
foreach (SearchResult se in res)
{
DirectoryEntry ssStoreObj = se.GetDirectoryEntry();
ActiveDirectorySecurity StoreobjSec = ssStoreObj.ObjectSecurity;
AuthorizationRuleCollection Storeacls = StoreobjSec.GetAccessRules(true, true, typeof(System.Security.Principal.SecurityIdentifier));
foreach (ActiveDirectoryAccessRule ace in Storeacls)
{
if (ace.IdentityReference.Value != "S-1-5-7" & ace.IdentityReference.Value != "S-1-1-0" & ace.IsInherited != true)
{
DirectoryEntry sidUser = new DirectoryEntry("LDAP://");
Console.WriteLine(sidUser.Properties["DisplayName"].Value.ToString());
Console.WriteLine(exRighthash[ace.ObjectType.ToString()].ToString());
}


}
}


}
}
}

Friday, August 05, 2011

Using the AllItems Search folder from Outlook 2010 on Exchange 2010 with Powershell and the EWS Managed API

Search folders in Exchange make searching for items in multiple folders a little more easier and/or allows you to group and find email with certain properties easier eg things like Unread email, Flagged for followup etc. Outlook 2010 makes use of these features for To-Do list etc. One useful search folder that gets created by Outlook 2010 when its used against a Exchange 2010 server is the AllItems search folder. eg this one




This is a search folder that gets created in the NON_IPM_Subtree folder and essentially allows you to do a generic search of all the folders within a Mailbox. This can be useful in a powershell script if you do need to enumerate every item within a mailbox to use the Allitems search folder you first need to do a search for this search folder in the Mailbox Root and then you can use some normal findItems enumeration code.

To filter a findfolders operation to only return search folders you can use the PR_Folder_Type extended property if this property is set to 2 then you know the folder is a search folder

$PR_FOLDER_TYPE = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(13825,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer);


The following sample script shows how to return items that where received in January 2011 in all folders within a mailbox using the AllItems search folder with an AQS query. I've put a download of the code here the script itself looks like

$AqsString = "System.Message.DateReceived:01/01/2011..01/31/2011"
$MailboxName = "domain.com"

$dllpath = "C:\Program Files\Microsoft\Exchange\Web Services\1.1\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.Credentials = New-Object System.Net.NetworkCredential("user@domain.com","passwod")

$service.AutodiscoverUrl($MailboxName,{$true})
$PR_FOLDER_TYPE = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(13825,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer);

"Checking : " + $MailboxName
$folderidcnt = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Root,$MailboxName)
$fvFolderView = New-Object Microsoft.Exchange.WebServices.Data.FolderView(1000)
$fvFolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Shallow;
$sf1 = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo($PR_FOLDER_TYPE,"2")
$sf2 = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,"allitems")
$sfSearchFilterCol = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+SearchFilterCollection([Microsoft.Exchange.WebServices.Data.LogicalOperator]::And)
$sfSearchFilterCol.Add($sf1)
$sfSearchFilterCol.Add($sf2)
$fiResult = $Service.FindFolders($folderidcnt,$sfSearchFilterCol,$fvFolderView)
$fiItems = $nulll
$ItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)
if($fiResult.Folders.Count -gt 0){
$fiResult.Folders[0].DisplayName
do{
$fiItems = $fiResult.Folders[0].findItems($AqsString,$ItemView)
$ItemView.offset += $fiItems .Items.Count
foreach($Item in $fiItems.Items){
$Item.Subject
}
}while($fiItems .MoreAvailable -eq $true)
}

Wednesday, July 20, 2011

Using Autodiscover in powershell with the EWS Managed API and Exchange 2010 and hosted Orgs

When it comes to scripting Exchange autodiscover can be a great source for all types of configuration information about an Exchange mailbox and in 2010 it now returns a lot more information about a users mailbox. The EWS Managed API 1.1 provides some new objects that allows a more granular use of Autodiscover which can be helpful in different situations for example during migrations where DNS records for autodiscover may not exist or point to different server etc.

To look at one example lets take Office365 if you want to connect to mailboxes in the Exchange Online cloud using EWS but DNS isn't setup your hosted domain for autodiscover you could use the following script to discover the EWS URL to use.

$mailbox = "glen@mydomain.inthecloudblah.com"
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true }
$dllpath = "C:\Program Files\Microsoft\Exchange\Web Services\1.1\Microsoft.Exchange.WebServices.dll"
[void][Reflection.Assembly]::LoadFile($dllpath)
$adAutoDiscoverService = New-Object Microsoft.Exchange.WebServices.Autodiscover.AutodiscoverService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010);
$uri=[system.URI] "https://autodiscover-s.outlook.com/autodiscover/autodiscover.svc"
$adAutoDiscoverService.Url = $uri
$adAutoDiscoverService.Credentials = New-Object System.Net.NetworkCredential("username","password")
$adAutoDiscoverService.EnableScpLookup = $false;
$adAutoDiscoverService.RedirectionUrlValidationCallback = {$true};
$adAutoDiscoverService.PreAuthenticate = $true;
$UserSettings = new-object Microsoft.Exchange.WebServices.Autodiscover.UserSettingName[] 1
$UserSettings[0] = [Microsoft.Exchange.WebServices.Autodiscover.UserSettingName]::ExternalEwsUrl
$adResponse = $adAutoDiscoverService.GetUserSettings($mailbox , $UserSettings);
$adResponse.Settings[[Microsoft.Exchange.WebServices.Autodiscover.UserSettingName]::ExternalEwsUrl]

A few things to note about this script is first the hardcoded URL to autodiscover this means no autodiscover DNS records are needed for the email your looking up.

$uri=[system.URI] "https://autodiscover-s.outlook.com/autodiscover/autodiscover.svc"
$adAutoDiscoverService.Url = $uri

But as I've said the Autodiscover service isn't just not limited to retrieving URL's have a look at the usersettingname enumeration http://msdn.microsoft.com/en-us/library/microsoft.exchange.webservices.autodiscover.usersettingname%28v=exchg.80%29.aspx . With this you can do things such as use Alternate mailboxes to see if an online archive is configured.

$UserSettings[0] = [Microsoft.Exchange.WebServices.Autodiscover.UserSettingName]::AlternateMailboxes
$adResponse = $adAutoDiscoverService.GetUserSettings($mailbox , $UserSettings);
$adResponse.Settings[[Microsoft.Exchange.WebServices.Autodiscover.UserSettingName]::AlternateMailboxes]


$adAutoDiscoverService.EnableScpLookup = $false;

Which means that the Managed API won't try an Active Directory look up of SCP records for autodiscover which can be very useful to speed up on the discovery process and help during migrations or other AD config issues.

Sunday, July 10, 2011

Running Exchange Online and Office365 Powershell cmdlets in C# and managed code

When you’re looking at automating Office365 and Exchange Online from managed code you need to be aware of the 2 sets of cmdlets that you may need to use depending on the tasks that your trying to perform. Most of the administration of ExchangeOnline is done using remote powershell where Exchange Online provides a subset of the normal on-premise Exchange 2010 SP1 cmdlets. The other cmdlet set to be aware of is the MSOnline powershell module which you need download and install http://onlinehelp.microsoft.com/en-us/office365-enterprises/hh124998.aspx. The MSOnline module contains cmdlets to allow administration of the wider Office365 service and perform more of the directory/service and service provider functions more akin to Active directory management in a on premise environment (eg adding users to groups etc).

So when using this from Managed code to use Remote powershell against Exchange Online you use the standard code you would use against an on-premise Exchange 2010 deployment against the endpoint https://ps.outlook.com. With the Office365 MSOnline module you need to load this into a runspace and then firstly use the Connect-MsolService cmdlet to connect to and authenticate against Office365. Then you execute as per normal the desired cmdlets.

Here's some sample code the first sample uses remote powershell to connect to ExchangeOnline.

System.Security.SecureString secureString = new System.Security.SecureString();
string myPassword = "password";
foreach (char c in myPassword)
secureString.AppendChar(c);
PSCredential credential = new PSCredential("glen@domain.com", secureString);
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(new Uri("https://ps.outlook.com/PowerShell-LiveID?PSVersion=2.0"), "http://schemas.microsoft.com/powershell/Microsoft.Exchange", credential);
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Basic;
connectionInfo.SkipCACheck = true;
connectionInfo.SkipCNCheck = true;

connectionInfo.MaximumConnectionRedirectionCount = 4;
Runspace runspace = System.Management.Automation.Runspaces.RunspaceFactory.CreateRunspace(connectionInfo);
runspace.Open();
// Make a Get-Mailbox requst using the Server Argument
Command gmGetMailbox = new Command("get-mailbox");
gmGetMailbox.Parameters.Add("ResultSize", "Unlimited");
Pipeline plPileLine = runspace.CreatePipeline();
plPileLine.Commands.Add(gmGetMailbox);
Collection RsResultsresults = plPileLine.Invoke();
Dictionary gmResults = new Dictionary();
foreach (PSObject obj in RsResultsresults)
{
gmResults.Add(obj.Members["WindowsEmailAddress"].Value.ToString(), obj);
}
plPileLine.Stop();
plPileLine.Dispose();

This second example loads the MSOnline powershell module into a runspace

InitialSessionState iss = InitialSessionState.CreateDefault();
iss.ImportPSModule(new[] { "MSOnline" });
using (Runspace psRunSpace = RunspaceFactory.CreateRunspace(iss))
{
psRunSpace.Open();
using (System.Management.Automation.PowerShell powershell = System.Management.Automation.PowerShell.Create())
{
powershell.Runspace = psRunSpace;
Command connect = new Command("Connect-MsolService");
System.Security.SecureString secureString = new System.Security.SecureString();
string myPassword = "password";
foreach (char c in myPassword)
secureString.AppendChar(c);

connect.Parameters.Add("Credential", new PSCredential("glen@domain.com", secureString));
powershell.Commands.AddCommand(connect);
Collection results = null;
Collection errors = null;
results = powershell.Invoke();
errors = powershell.Streams.Error.ReadAll();
powershell.Commands.Clear();
Command getuser = new Command("Get-MsolUser");
getuser.Parameters.Add("MaxResults", 100);
powershell.Commands.AddCommand(getuser);
results = null;
errors = null;
results = powershell.Invoke();
}
}
}





Wednesday, June 29, 2011

Processing BCC's in Exchange Transport Agents

One of the most important learning points when thinking about programing or designing Transport agents is to understand the different between a P1 (Delivery Envelope) and P2 (Display Envelope)

The P1 (Delivery Envelope) contains the recipient To and From information that is transmitted to the MTA server be it Exchange or any other SMTP server in the SMTP conversation and is the actual information that will be used for delivery of the message. Eg the Evaluating MTA will only examine the P1 recipients to determine what recipients a message would be delivered to.

The P2(Display Envelope) contains all the information that that the user will see when they receive the message this includes the From, To ,CC and Subject of a message. The important point to understand is that the MTA that is processing the message doesn’t look at these headers to determine whom to deliver the message to.

BCC’s

One of the things that confuses a lot of people when they look at messages via an API or within an Transport Agent is that most API’s include the ability to set and get BCC address’s but they will always appear blank when you attempt to read them for example when a message is passing through the transport pipeline. The simple explanation for this is if you have understood about P1 and P2 recipients is that BCC’s when a message is submitted become P1 recipients of the message for the first MTA to process but they are never added to the P2 envelope. From the MTA’s perspective because delivery of TO, CC or BCC are treated exactly the same these elements are merely display information and the MTA just treats these recipients as another RCPT TO entry. To detect BCC in a Transport Agent you can do two things the first is reconcile the P1 Recipients against the P2 Recipients if you find a P1 recipient that doesn’t have a matching P2 entry then you can assume that this is either a BCC or potentially an alternate or journal recipient. The other way is to look at the Microsoft.Exchange.Transport.RecipientP2Type http://msdn.microsoft.com/en-us/library/aa579279%28EXCHG.140%29.aspx EnvelopeRecipient.Properties Property this returns an Integer that tells you what type of recipient each P1 envelope entry is.

For example

foreach (EnvelopeRecipient envelopeRecp in QueuedMessage.MailItem.Recipients) {
object RecpType = null;
if (envelopeRecp.Properties.TryGetValue("Microsoft.Exchange.Transport.RecipientP2Type",out RecpType))
{
switch((Int32)RecpType){
case 1 : // Process To
break;
case 2 : // Process CC
break;
case 3 : // Process BCC
break;
}
}
}

Saturday, May 14, 2011

Sending a message with a classification in EWS in C# and Powershell

Message classifications have been around since Exchange 2007 and are a feature of Exchange that allows you to setup your email system to add classification to messages to comply with e-mail policies and regulatory responsibilities that Governments and other bodies may require of an organization. If you want to set a message classification on a Message your sending pro-grammatically via EWS you need to first find out what the GUID of the classification you want to set is. There are a few ways of doing this the easiest is using the Get-MessageClassification cmdlet http://technet.microsoft.com/en-us/library/aa996911.aspx the value you will need for your app or script is the ClassificationID which represents the PidLidClassificationGuid Mapi property which if you’re playing around with Transport Agents this gets converted into the x-ms-exchange-organization-classification x-header. You can also grab it out of your classification xml file if you have deployed it to Outlook.

All the properties that relate to classification are documented in http://msdn.microsoft.com/en-us/library/ee217686%28v=EXCHG.80%29.aspx. At the most basic level if you want to set a classification on a message your sending you need to set the following two properties on that message.

PidLidClassificationGuid which you set to the GUID of the classification you want applied to the message

PidLidClassified which is a Boolean that tells the Exchange this message is classified.

With the EWS Managed API in C# this look like

EmailMessage emEmailMessage = new EmailMessage(service);
emEmailMessage.ToRecipients.Add("user@domaim.com");
emEmailMessage.Subject = "test Classify";
emEmailMessage.Body = new MessageBody("test");
ExtendedPropertyDefinition PidLidClassificationGuid = new ExtendedPropertyDefinition(DefaultExtendedPropertySet.Common, 34232, MapiPropertyType.String);
emEmailMessage.SetExtendedProperty(PidLidClassificationGuid, "e67e794b-f6d1-4c8f-9f63-1118d21dafa6");
ExtendedPropertyDefinition PidLidClassified = new ExtendedPropertyDefinition(DefaultExtendedPropertySet.Common,34229, MapiPropertyType.Boolean);
emEmailMessage.SetExtendedProperty(PidLidClassified, true);
emEmailMessage.SendAndSaveCopy();

In Powershell it looks like

$MailboxName = "user@domain.com"
$dllpath = "C:\Program Files\Microsoft\Exchange\Web Services\1.1\Microsoft.Exchange.WebServices.dll"
[void][Reflection.Assembly]::LoadFile($dllpath)
$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010)
#$service.Credentials = New-Object System.Net.NetworkCredential("glen@domain.com","password")
$service.AutodiscoverUrl($MailboxName,{$true})
$message = New-Object Microsoft.Exchange.WebServices.Data.EmailMessage($service)
$message.Subject = "Test"
$message.Body = "Test 123"
$message.ToRecipients.Add("user@domain.com")
$PidLidClassificationGuid = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition([Microsoft.Exchange.WebServices.Data.DefaultExtendedPropertySet]::Common, 34232,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);
$message.SetExtendedProperty($PidLidClassificationGuid, "e67e794b-f6d1-4c8f-9f63-1118d21dafa6");
$PidLidClassified = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition([Microsoft.Exchange.WebServices.Data.DefaultExtendedPropertySet]::Common,34229,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Boolean);
$message.SetExtendedProperty($PidLidClassified, $true);
$message.Send.Invoke()