Wednesday, January 25, 2012

401 errors in Exchange Online using EWS WSDL proxy code and raw HTML


If your developing EWS code against Exchange Online or possibly another large load balanced Hosted Exchange system you may find that a lot of the sample code you see on MSDN or other places that uses EWS Proxy code will always return 401 errors when you try to use it against Exchange Online (The Managed API isn't affected) eg a standard example is something like http://msdn.microsoft.com/en-us/library/bb408524%28v=EXCHG.140%29.aspx. What can cause this problem is the failure to process the exchangecookie in an appropriate way. There is description of what the exchangecookie is used for on http://blogs.msdn.com/b/exchangedev/archive/2011/07/20/client-access-server-affinity-and-network-load-balancing-considerations-for-programmatic-access-to-exchange-online.aspx . Although this says nothing about Authentication if the request object your using be its a SOAP proxy or HttpwebRequest doesn't have the cookiecontainer initialized (which by default they don't) before you submit the first request you'll get a 401. So  basically if you have a normal EWS sample after you set the URL or Credentials make sure you initialize the cookie container.eg.
 
esb.Url = "https://CAS01.contoso.com/EWS/exchange.asmx";

esb..CookieContainer = new CookieContainer();


If your using Raw HTML and a HttpWebRequest make sure you do the same thing before submitting the first request.

A code sample is worth a 1000 words so here's a basic Exchange Online/Office365 Autodiscover and WDSL proxy sample that just does a simple GetFolder to the Inbox.

  1. String MbMailbox = "user@domain.onmicrosoft.com";  
  2. String AutoDiscoURL = "https://autodiscover-s.outlook.com/autodiscover/autodiscover.xml";  
  3. NetworkCredential ncCred = new NetworkCredential("user@domain.onmicrosoft.com","password");  
  4. String EWSURL = null;  
  5. String auDisXML = "<Autodiscover xmlns=\"http://schemas.microsoft.com/exchange/autodiscover/outlook/requestschema/2006\"><Request>" +  
  6.         "<EMailAddress>" + MbMailbox + "</EMailAddress>" +  
  7.         "<AcceptableResponseSchema>http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a</AcceptableResponseSchema>" +  
  8.         "</Request>" +  
  9.         "</Autodiscover>";  
  10. System.Net.HttpWebRequest adAutoDiscoRequest = (System.Net.HttpWebRequest)System.Net.HttpWebRequest.Create(AutoDiscoURL);  
  11.   
  12.               
  13. byte[] bytes = Encoding.UTF8.GetBytes(auDisXML);  
  14. adAutoDiscoRequest.ContentLength = bytes.Length;  
  15. adAutoDiscoRequest.ContentType = "text/xml";  
  16. adAutoDiscoRequest.Headers.Add("Translate""F");  
  17. adAutoDiscoRequest.Method = "POST";  
  18. adAutoDiscoRequest.Credentials = ncCred;  
  19.   
  20. Stream rsRequestStream = adAutoDiscoRequest.GetRequestStream();  
  21. rsRequestStream.Write(bytes, 0, bytes.Length);  
  22. rsRequestStream.Close();  
  23. adAutoDiscoRequest.AllowAutoRedirect = false;  
  24. WebResponse adResponse = adAutoDiscoRequest.GetResponse();  
  25. String Redirect = adResponse.Headers.Get("Location");  
  26. if (Redirect != null)  
  27. {  
  28.     adAutoDiscoRequest = (System.Net.HttpWebRequest)System.Net.HttpWebRequest.Create(Redirect);  
  29.     adAutoDiscoRequest.ContentLength = bytes.Length;  
  30.     adAutoDiscoRequest.ContentType = "text/xml";  
  31.     adAutoDiscoRequest.Headers.Add("Translate""F");  
  32.     adAutoDiscoRequest.Method = "POST";  
  33.     adAutoDiscoRequest.Credentials = ncCred;  
  34.     rsRequestStream = adAutoDiscoRequest.GetRequestStream();  
  35. }  
  36. rsRequestStream.Write(bytes, 0, bytes.Length);  
  37. rsRequestStream.Close();  
  38. adResponse = adAutoDiscoRequest.GetResponse();  
  39. Stream rsResponseStream = adResponse.GetResponseStream();  
  40. XmlDocument reResponseDoc = new XmlDocument();  
  41. reResponseDoc.Load(rsResponseStream);  
  42. XmlNodeList pfProtocolNodes = reResponseDoc.GetElementsByTagName("Protocol");  
  43. foreach (XmlNode xnNode in pfProtocolNodes)  
  44. {  
  45.     XmlNodeList adChildNodes = xnNode.ChildNodes;  
  46.     foreach (XmlNode xnSubChild in adChildNodes)  
  47.     {  
  48.         switch (xnSubChild.Name)  
  49.         {  
  50.             case "EwsUrl": EWSURL = xnSubChild.InnerText;  
  51.                 break;  
  52.         }  
  53.     }  
  54. }  
  55. ExchangeServiceBinding esExchangeServiceBinding = new ExchangeServiceBinding();  
  56. esExchangeServiceBinding.Credentials = ncCred;  
  57. esExchangeServiceBinding.Url = EWSURL;  
  58. esExchangeServiceBinding.CookieContainer = new CookieContainer();         
  59. DistinguishedFolderIdType[] dsFolderid = new DistinguishedFolderIdType[1];  
  60. dsFolderid[0] = new DistinguishedFolderIdType();  
  61. dsFolderid[0].Id = DistinguishedFolderIdNameType.inbox;  
  62. GetFolderType gfFolderType = new GetFolderType();  
  63. gfFolderType.FolderIds = dsFolderid;  
  64. gfFolderType.FolderShape = new FolderResponseShapeType();  
  65. gfFolderType.FolderShape.BaseShape = DefaultShapeNamesType.Default;  
  66. GetFolderResponseType gfGetFolderResponse = esExchangeServiceBinding.GetFolder(gfFolderType);  
  67. if (gfGetFolderResponse.ResponseMessages.Items[0].ResponseClass == ResponseClassType.Success) {  
  68.     FolderType Folder = (FolderType)((FolderInfoResponseMessageType)gfGetFolderResponse.ResponseMessages.Items[0]).Folders[0];  
  69.     Console.WriteLine(Folder.DisplayName);  
  70. }  

Monday, January 23, 2012

EWS Managed API and Powershell How-To series Part 2 - Folders

This is Part2 in my continuing EWS Managed API and Powershell how to series, in this post im going to look at using Mailbox Folders in Exchange Web Services. When you look at mailboxes from a structured content point of view the most obvious data structure is the folders within it. Dealing with these structures can provide a number of challenges which I'll go through.

Binding to and Accessing a Folder

To access a folder using EWS you must first know its folderId which from a useability perspective presents most people with a bit of a challenge. eg you may know the path to the folder you want to access is \Inbox\Subfolder but the FolderID itself is a large Base64 Enocded value.For all the standard folders in a mailbox such as the Calendar, Contacts, Inbox, SentItems folders there is a WellKnownFolderName enumeration that makes things easy eg so this basically provides you with a very easy way to bind to the Inbox, Calendar etc. For example to bind to the Inbox folder

 $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,"user@mailbox.com") 
$Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)

If your dealing with a folder outside of the WellKnownFolderName enumeration then you need to search and find the folder in question. There are a couple of ways you could do that one would be to use a DeepTraversal of all the folders in the Mailbox and then filter the results. The method I like to use is to split the search path and then do a Shallow traversal search of all the folders in the path eg


  1. ## Find and Bind to Folder based on Path  
  2. #Define the path to search should be seperated with \  
  3. $PathToSearch = "\Inbox\Blah"  
  4. #Bind to the MSGFolder Root  
  5. $folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,"user@domain.com")   
  6. $tfTargetFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)  
  7. #Split the Search path into an array  
  8. $fldArray = $PathToSearch.Split("\") 
  9. #Loop through the Split Array and do a Search for each level of folder 
  10. for ($lint = 1; $lint -lt $fldArray.Length; $lint++) { 
  11.         $fldArray[$lint] 
  12.         #Perform search based on the displayname of each folder level 
  13.         $fvFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(1) 
  14.         $SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$fldArray[$lint]) 
  15.         $findFolderResults = $service.FindFolders($tfTargetFolder.Id,$SfSearchFilter,$fvFolderView) 
  16.         if ($findFolderResults.TotalCount -gt 0){ 
  17.             foreach($folder in $findFolderResults.Folders){ 
  18.                 $tfTargetFolder = $folder                
  19.             } 
  20.         } 
  21.         else{ 
  22.             "Error Folder Not Found"  
  23.             $tfTargetFolder = $null  
  24.             break  
  25.         }     
  26. }  
  27. $tfTargetFolder  

The Tale of Two Folder Roots

If you have ever looked at a mailbox in a Mapi Editor like MFCMapi or OutlookSpy you know there are actually two different Folder Roots in a Mailbox the IPM_Subtree (represented by the "Top of the Information Store" in the below image) which is what the user sees in Outlook in their Folder Tree and the Non_IPM_Subtree which is the level above the IPM_Subtree where a lot of the Meta-Information for a mailbox gets stored (represented by the "Root Container" in the below image)


When you look at the WellKnowFolderName enum the Non_IPM_Subtree is

[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Root

and the IPM_Subtree is

[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot

This becomes import when you want to look at getting and modifying Mailbox configuration information also if your are trying to calculate the total mailbox size you need to make sure you include these folders in your calculation.

Properties on a Folder

When you Bind to a folder in EWS the standard folder properties provided by the Strongly typed Folder class are returned. This is good but you may find that a lot of the properties that you really want to access for example things like the FolderSize or the FolderPath aren't returned. To Access these you need to make use of the Extended properties on the Folder which allows you to access any of the underlying (Exchange/Mapi) properties of that folder object.. To use an Extended property you first need to define the particular property you want to access (as always i recommend you use a Mapi editor to work out what the Tag,Datatype of the property is you want to access a good), add that property to a PropertySet and then tell EWS to load those properties when ether you bind to the folder, search for the folder or use the Load Method.Some Extended properties that are usefull on Folders are

The PR_Folder_PathName property returns the fullpath to a folder that is delimited by the byte order mark FE-FF so if you want to return a \ delimited path you need to replace these values in the Unicode string that is returned when you retrieve this property.

The PR_MESSAGE_SIZE_EXTENDED property is a 64 Bit Integer that represents the size of the all Items in the Folder

PR_Folder_Type is a Interger property that tells you what type of folder this is which is useful if you want to tell if a folder is a SearchFolder

Enumerating all Folders in a Mailbox

One common building block of scripts is the ability to enumerate ever folder in a Mailbox which can be achieved using the FindFolders Operation in EWS with a DeepTraversal Scope. If the mailbox your enumerating has more then 1000 folders you need to take throttling into account in 2010. Here's an example that gets all the folders in a Mailbox deal with throttling and also show how to use the Extended Properties I mentioned above.

  1. #Define Function to convert String to FolderPath  
  2. function ConvertToString($ipInputString){  
  3.     $Val1Text = ""  
  4.     for ($clInt=0;$clInt -lt $ipInputString.length;$clInt++){  
  5.             $Val1Text = $Val1Text + [Convert]::ToString([Convert]::ToChar([Convert]::ToInt32($ipInputString.Substring($clInt,2),16)))  
  6.             $clInt++  
  7.     }  
  8.     return $Val1Text  
  9. }  
  10. #Define Mailbox to access  
  11. $MailboxName = "glen@mymailbox.domain.com"  
  12.   
  13. "Checking : " + $MailboxName  
  14. #Define Extended properties  
  15. $PR_FOLDER_TYPE = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(13825,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer);  
  16. $folderidcnt = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$MailboxName)  
  17. #Define the FolderView used for Export should not be any larger then 1000 folders due to throttling  
  18. $fvFolderView =  New-Object Microsoft.Exchange.WebServices.Data.FolderView(1000)  
  19. #Deep Transval will ensure all folders in the search path are returned  
  20. $fvFolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Deep;  
  21. $psPropertySet = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)  
  22. $PR_MESSAGE_SIZE_EXTENDED = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(3592,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Long);  
  23. $PR_DELETED_MESSAGE_SIZE_EXTENDED = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(26267,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Long);  
  24. $PR_Folder_Path = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(26293, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);  
  25. #Add Properties to the  Property Set  
  26. $psPropertySet.Add($PR_MESSAGE_SIZE_EXTENDED);  
  27. $psPropertySet.Add($PR_Folder_Path);  
  28. $fvFolderView.PropertySet = $psPropertySet;  
  29. #The Search filter will exclude any Search Folders  
  30. $sfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo($PR_FOLDER_TYPE,"1")  
  31. $fiResult = $null  
  32. #The Do loop will handle any paging that is required if there are more the 1000 folders in a mailbox  
  33. do {  
  34.     $fiResult = $Service.FindFolders($folderidcnt,$sfSearchFilter,$fvFolderView)  
  35.     foreach($ffFolder in $fiResult.Folders){  
  36.         "Processing : " + $ffFolder.displayName  
  37.         $TotalItemCount =  $TotalItemCount + $ffFolder.TotalCount;  
  38.         $FolderSize = $null;  
  39.         $FolderSizeValue = 0  
  40.         #Try to Get the FolderSize Value and output it to the $FolderSize varible  
  41.         if ($ffFolder.TryGetProperty($PR_MESSAGE_SIZE_EXTENDED,[ref] $FolderSize))  
  42.         {  
  43.             $FolderSizeValue = [Int64]$FolderSize  
  44.         }  
  45.         $foldpathval = $null  
  46.         #Try to get the FolderPath Value and then covert it to a usable String   
  47.         if ($ffFolder.TryGetProperty($PR_Folder_Path,[ref] $foldpathval))  
  48.         {  
  49.             $binarry = [Text.Encoding]::UTF8.GetBytes($foldpathval)  
  50.             $hexArr = $binarry | ForEach-Object { $_.ToString("X2") }  
  51.             $hexString = $hexArr -join ''  
  52.             $hexString = $hexString.Replace("FEFF""5C00")  
  53.             $fpath = ConvertToString($hexString)  
  54.         }  
  55.         "FolderPath : " + $fpath  
  56.         "FolderSize : " + $FolderSizeValue  
  57.         
  58.     } 
  59.     $fvFolderView.Offset += $fiResult.Folders.Count
  60. }while($fiResult.MoreAvailable -eq $true)  

Creating Folders 

After all the other stuff I've just covered creating folders is actually quite simple eg to create a subfolder under the Inbox folder all you need is

  1. #Define Mailbox to access  
  2. $MailboxName = "glen@mailbox.domain.com"  
  3.   
  4. ## Bind to the Inbox Sample  
  5.   
  6. $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)   
  7. $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)  
  8.   
  9. $NewFolder = new-object Microsoft.Exchange.WebServices.Data.Folder($service)  
  10. $NewFolder.DisplayName = "My New Folder123"  
  11. $NewFolder.Save($Inbox.Id)  
 To Check if a folder already exists before doing so you need to first do a search for the existing folder here's how you can do that

  1. #Define Folder Name to Search for  
  2. $FolderName = "My New Folder123"  
  3. #Define Folder Veiw Really only want to return one object  
  4. $fvFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(1)  
  5. #Define a Search folder that is going to do a search based on the DisplayName of the folder  
  6. $SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$FolderName)  
  7. #Do the Search  
  8. $findFolderResults = $service.FindFolders($Inbox.Id,$SfSearchFilter,$fvFolderView)  
  9. if ($findFolderResults.TotalCount -eq 0){  
  10.     "Folder Doesn't Exist"  
  11. }  
  12. else{  
  13.     "Folder Exist"  
  14. }  

Deleting a Folder

Deleting a folder is rather simple all you really need to do is call the delete Method but lets look at a more complicated sample. Given the above sample of creating a folder under the Inbox folder lets look at something that first binds to that folder which would have a path of "\Inbox\My New Folder123" and then deletes it.

  1. ## Find and Bind to Folder based on Path  
  2. #Define the path to search should be seperated with \  
  3. $PathToSearch = "\Inbox\My New Folder123"  
  4. #Bind to the MSGFolder Root  
  5. $folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$MailboxName)   
  6. $tfTargetFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)  
  7. #Split the Search path into an array  
  8. $fldArray = $PathToSearch.Split("\") 
  9. #Loop through the Split Array and do a Search for each level of folder 
  10. for ($lint = 1; $lint -lt $fldArray.Length; $lint++) { 
  11.         $fldArray[$lint] 
  12.         #Perform search based on the displayname of each folder level 
  13.         $fvFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(1) 
  14.         $SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$fldArray[$lint]) 
  15.         $findFolderResults = $service.FindFolders($tfTargetFolder.Id,$SfSearchFilter,$fvFolderView) 
  16.         if ($findFolderResults.TotalCount -gt 0){ 
  17.             foreach($folder in $findFolderResults.Folders){ 
  18.                 $tfTargetFolder = $folder                
  19.             } 
  20.         } 
  21.         else{ 
  22.             "Error Folder Not Found" 
  23.             $tfTargetFolder = $null 
  24.             break 
  25.         }    
  26. } 
  27. if($tfTargetFolder -ne $null){ 
  28.     #Delete the Folder using HardDelete 
  29.     $tfTargetFolder.Delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete) 
  30.     "Folder Deleted"  
  31. }  
Emptying or Deleting old items from a folder

In Exchange 2010 you can use the Empty  Method to empty all the Items from a folder (or essentially delete all the Items from a folder a once). The is pretty easy to do for example to delete all the Items in the Inbox and all subfolders you can use

$Inbox.Empty([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete, $true);

If your using 2007 or you want to delete Items in the Calendar folder you need to select the Items first and do a batch delete. For example to Clean all the Items in the Calendar folder


  1. # Bind to the Calendar folder  
  2. $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Calendar,$MailboxName)   
  3. $Calendar = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)  
  4. #Set Itemview to 1000 to deal with throttling  
  5. $ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)  
  6. $fiResult = ""  
  7. do{  
  8.     $fiResult = $Calendar.FindItems($ivItemView)  
  9.     #Define ItemIds collection  
  10.     $Itemids = [activator]::createinstance(([type]'System.Collections.Generic.List`1').makegenerictype([Microsoft.Exchange.WebServices.Data.ItemId]))  
  11.     "Delete " + $fiResult.Items.Count + " Items"  
  12.     foreach($Item in $fiResult.Items){  
  13.         $Itemids.Add($Item.Id)  
  14.     }  
  15.     #Delete the Items  
  16.     $Result = $service.DeleteItems($Itemids,[Microsoft.Exchange.WebServices.Data.DeleteMode]::SoftDelete,[Microsoft.Exchange.WebServices.Data.SendCancellationsMode]::SendToNone,[Microsoft.Exchange.WebServices.Data.AffectedTaskOccurrence]::AllOccurrences)  
  17.     [INT]$Rcount = 0  
  18.     foreach ($res in $Result){  
  19.         if ($res.Result -eq [Microsoft.Exchange.WebServices.Data.ServiceResult]::Success){  
  20.             $Rcount++  
  21.         }  
  22.     }  
  23.     $Rcount.ToString() + " Items deleted successfully"  
  24.     $ivItemView.offset += $fiResult.Items.Count  
  25. }while($fiResult.MoreAvailable -eq $true)  
Search Folders

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 tell if a folder is a search folder the extended property PR_Folder_Type  is useful if the PR_Folder_Type is set to 2 then this indicates its a search folder.

For an example of creating a search folder have a look at http://gsexdev.blogspot.com/2011/09/create-search-folder-using-ews-managed.html 

For an example of finding and binding to the AllItems search folder in Exchange 2010 see

http://gsexdev.blogspot.com/2011/08/using-allitems-search-folder-from.html

To find and delete a search folder see http://tst.social.technet.microsoft.com/Forums/en-US/exchangesvrdevelopment/thread/426a37c8-f1dd-4438-a081-e2b0e378bc5a

Public Folders  

When thinking about public folders its useful to consider them similar to a mailbox however unlike mailboxes you can't do a Deep Traversal of Public folders so when trying to access a public folder you need to either do a shallow traversal search for each folder level eg using a script similar to what i posted above. The other method of accessing a public folder directly is to use the Exchange Management Shell cmdlets and cover the HexEntryID you can get with the EMS to the EwsID.  To connect to the Root of the Public folder store you use the following enumeration.

  1. $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::PublicFoldersRoot,"user@mailbox.com")   
  2. $PublicfolderRoot = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)  

Other bits and pieces for folders

To Set the Homepage of folder in EWS see http://www.infinitec.de/post/2011/10/05/Setting-the-Homepage-of-an-Exchange-folder-using-the-EWS-Managed-API.aspx

That's about it for this post if you have any specific question about folders in EWS with powershell post a comment and I'll try to update this post. In the next post in this series well look into Items.

Tuesday, January 10, 2012

EWS Managed API and Powershell How-To series Part 1

I thought I'd start the year with a series of posts that goes back over the basics of using the EWS Managed API from Powershell and provides a modular remarked example that you can easily cut and paste to build your own scripts. Along the way in this series I'll show a whole bunch of examples around specific things.

As a starting point for versions this will be Powershell Version 2.0  and the EWS Managed API 1.1 (which will soon change to 1.2 once released) http://www.microsoft.com/download/en/details.aspx?id=13480.

The starting point for any EWS script your going to write is connecting to Exchange for which there are three important pieces of information you will need. Firstly you need to know the version of Exchange your running in this script its going to be held in the following variable

$ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1

Other valid values for Exchange 2007 would be

$ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2007_SP1

The next thing you need to know is what security credentials your going to use to connect to the Mailbox you want to access. There are two options available for this you can specify the credentials in your code (or prompt for them using Get-Credential). Or the other option is to use the default logged on user credentials (or from a scheduled task etc).

The last thing piece of information you need is the URL to the CAS (Client Access Server) you are going to send the EWS requests through which you can find using Autodiscover if this is setup or you can hard code the URL of a CAS server to use. One word of note with Auto-discover and the Managed API is by default it will first perform a SCP look-up of Active Directory to find the Autodiscover EndPoint and if that fails it will then perform a DNS lookup. If you want more control over this process have a look at another script I've posted http://gsexdev.blogspot.com/2011/07/using-autodiscover-in-powershell-with.html.

The last optional section in the connectivity script is an option to use EWS Impersonation this allows you to access another users mailbox with the effective security credentials of the owner of that mailbox. EWS Impersonation is configured via RBAC on 2010 see http://msdn.microsoft.com/en-us/library/bb204095%28v=exchg.140%29.aspx. For more information on impersonation see http://blogs.msdn.com/b/exchangedev/archive/2009/06/15/exchange-impersonation-vs-delegate-access.aspx


Thats about it from a connectivity and security perspective in the next post I'll go over folders inside and out. I've posted a copy of the connectivity script here the script itself looks like.



  1. ## EWS Managed API Connect Script
  2. ## Requires the EWS Managed API and Powershell V2.0 or greator  
  3.   
  4. ## Load Managed API dll  
  5. Add-Type -Path "C:\Program Files\Microsoft\Exchange\Web Services\1.1\Microsoft.Exchange.WebServices.dll"  
  6.   
  7. ## Set Exchange Version  
  8. $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1  
  9.   
  10. ## Create Exchange Service Object 
  11. $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)  
  12.   
  13. ## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials  
  14.   
  15. #Credentials Option 1 using UPN for the windows Account  
  16. $creds = New-Object System.Net.NetworkCredential("user@domain.com","password")   
  17. $service.Credentials = $creds      
  18.   
  19. #Credentials Option 2  
  20. #service.UseDefaultCredentials = $true  
  21.   
  22. ## Choose to ignore any SSL Warning issues caused by Self Signed Certificates  
  23.   
  24. ## 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
  25.   
  26. ## Set the URL of the CAS (Client Access Server) to use two options are availbe to use Autodiscover to find the CAS URL or Hardcode the CAS to use  
  27.   
  28. #CAS URL Option 1 Autodiscover  
  29. $service.AutodiscoverUrl("email@domain.com",{$true})  
  30. "Using CAS Server : " + $Service.url   
  31.    
  32. #CAS URL Option 2 Hardcoded  
  33.   
  34. #$uri=[system.URI] "https://casservername/ews/exchange.asmx"  
  35. #$service.Url = $uri    
  36.   
  37. ## Optional section for Exchange Impersonation  
  38.   
  39. #$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, "email@domain.com")