Skip to main content

Public Folder EWS How-To rollup Part 1

As Public Folders are now new again in Exchange 2013, I thought I'd put together a post that covered how you can go about accessing and doing certain things with Public folders via EWS and Powershell. Although the underlying way in which Public Folders are now delivered in Exchange 2013 is vastly different from previous version of Exchange the way in which you access them Programmatically in EWS hasn't changed since Exchange 2007. So if your worried about your 2007 EWS code not working against 2013 don't only some minor changes maybe needed. (although if you have WebDAV code your going to need to rewrite this).

Choosing the Right API Method for what you want to do

The big thing when your considering creating a Public Folder application is to understand and choose the correct API for what you want to do. Eg if you want to write an application that is going to manage Public Folders eg Size/creation/deletion/Mail-Enable and folders permissions then EWS or MAPI are not the correct API to use as these are client API's. For any admin style applications you should be using the Exchange Management Shell cmdlets which give you a fully functional entry point to do administration tasks and also more importantly the ability to assert Delegated Exchange Admin Rights. Which means you can manage the folder without the need to have explicit client permissions on the folders. When you using a client API like EWS or MAPI you will only be able to perform client based actions where you have rights granted on the DACL of that particular folder (or inherited client permissions from the Parent). Unlike WebDAV in 2003,2007 there is no Virtual Admin root with EWS where Delegated Admin rights can be asserted so the EMS cmdlets via Remote Powershell is the best solution for Admin Apps.

EWS Public Folder operations

There are no dedicated Public Folder operations in EWS, so everything you do is with a Subset of the existing Mailbox operations. There is a full list of EWS Operations that work with Public Folder and notes around the restriction in http://msdn.microsoft.com/en-us/library/exchange/jj945067%28v=exchg.150%29.aspx. One of the big improvements in 2013 is now the ability to use Content Indexes to query Public Folder content which includes the ability to scan Attachment content where an IFilter is available for that attachment type.

Getting the Public Folder Hierarchy

When you want to return the full folder hierarchy of  a Mailbox with EWS you use a FindFolder operation where you set the FolderView to tell the server to perform a deep traversal which means that all folders and subfolder will be queried. If you had more then 1000 folders in a Mailbox you would need to make multiple request to get all the folders back paged 1000 at a time to ensure you stayed under the throttling limits. With Public Folders because Deep Traversals aren't supported in a FindFolder operation it presents a challenge to getting the Full folder hierarchy.Those people who are migrating from a MAPI application where they have used "GetHierarchyTable" in MAPI, may find EWS a little irritating as there is no easy way of quickly returning a full folder hierarchy in a short time amount of time like MAPI. However its not all bad news, one example of how to get the hierarchy is the method used in OWA in 2013 which makes use of multiple FindFolder operations to show the Public Folder tree. OWA just does an on demand query when the user clicks each client node which means there is a slight pause while the operation completes. It just means living with a slightly less responsive UI.

In Powershell if you want to get the Full Folder hierarchy you would need to do multiple shallow traversals like the following example

  1. function ConvertToString($ipInputString){    
  2.     $Val1Text = ""    
  3.     for ($clInt=0;$clInt -lt $ipInputString.length;$clInt++){    
  4.             $Val1Text = $Val1Text + [Convert]::ToString([Convert]::ToChar([Convert]::ToInt32($ipInputString.Substring($clInt,2),16)))    
  5.             $clInt++    
  6.     }    
  7.     return $Val1Text    
  8. }   
  9.   
  10. function GetPublicFolders{  
  11.     param (  
  12.             $RootFolderId = "$( throw 'FolderId is a mandatory Parameter' )"  
  13.           )  
  14.     process{  
  15.         $RootFolderId  
  16.         $folderCollection = @()  
  17.         #Define Extended properties    
  18.         $PR_FOLDER_TYPE = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(13825,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer);    
  19.         #Define the FolderView used for Export should not be any larger then 1000 folders due to throttling    
  20.         $fvFolderView =  New-Object Microsoft.Exchange.WebServices.Data.FolderView(1000)    
  21.         #Deep Transval will ensure all folders in the search path are returned    
  22.         $fvFolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Shallow;    
  23.         $psPropertySet = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)    
  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_Folder_Path);    
  27.         $fvFolderView.PropertySet = $psPropertySet;    
  28.         #The Search filter will exclude any Search Folders    
  29.         $sfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo($PR_FOLDER_TYPE,"1")    
  30.         $fiResult = $null    
  31.         do {    
  32.             $fiResult = $Service.FindFolders($RootFolderId,$sfSearchFilter,$fvFolderView)    
  33.             foreach($ffFolder in $fiResult.Folders){    
  34.                 $foldpathval = $null    
  35.                 $folderCollection += $ffFolder  
  36.                 #Try to get the FolderPath Value and then covert it to a usable String     
  37.                 if ($ffFolder.TryGetProperty($PR_Folder_Path,[ref] $foldpathval))    
  38.                 {    
  39.                     $binarry = [Text.Encoding]::UTF8.GetBytes($foldpathval)    
  40.                     $hexArr = $binarry | ForEach-Object { $_.ToString("X2") }    
  41.                     $hexString = $hexArr -join ''    
  42.                     $hexString = $hexString.Replace("FEFF""5C00")    
  43.                     $fpath = ConvertToString($hexString)    
  44.                 }    
  45.                 "FolderPath : " + $fpath    
  46.                 if($ffFolder.ChildFolderCount -gt 0){  
  47.                     $Childfolders = GetPublicFolders -RootFolderId $ffFolder.Id  
  48.                     foreach($Childfolder in $Childfolders){  
  49.                         $folderCollection += $Childfolder  
  50.                     }  
  51.                 }  
  52.             }  
  53.             $fvFolderView.Offset += $fiResult.Folders.Count  
  54.         }while($fiResult.MoreAvailable -eq $true)    
  55.         return $folderCollection  
  56.     }  
  57. }  
  58.   
  59. $pfRoot = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::PublicFoldersRoot)    
  60. $folderHi = GetPublicFolders -RootFolderId $pfRoot  
  61. #Example Use
  62. foreach($fld in $folderHi){  
  63.     $fld.DisplayName  
  64. }  
Binding to a specific Public folder

If you want to bind to a specific public folder given a path as \FolderLevel1\Level2\Level3\Target you need to know the particular folders EWSId. There are a few ways of getting the FolderId if your using Outlook you could take the HexEntryId can get from OWA and convert that into an EWSId eg the following is an example of using the AlternatePublicFolderId to covert the HexEntryId to an EWSId

  1. function ConvertId{      
  2.     param (  
  3.             $HexId = "$( throw 'HexId is a mandatory Parameter' )"  
  4.           )  
  5.     process{  
  6.         $aiItem = New-Object Microsoft.Exchange.WebServices.Data.AlternatePublicFolderId       
  7.         $aiItem.FolderId = $HexId     
  8.         $aiItem.Format = [Microsoft.Exchange.WebServices.Data.IdFormat]::HexEntryId        
  9.         $convertedId = $service.ConvertId($aiItem, [Microsoft.Exchange.WebServices.Data.IdFormat]::EwsId)   
  10.         return $convertedId.UniqueId  
  11.     }  
  12. }  
The other method you can use is to search for your target Folder using multi find folder operations and by splitting the path you want to search for eg.

  1. function FolderIdFromPath{  
  2.     param (  
  3.             $FolderPath = "$( throw 'Folder Path is a mandatory Parameter' )"  
  4.           )  
  5.     process{  
  6.         ## Find and Bind to Folder based on Path    
  7.         #Define the path to search should be seperated with \    
  8.         #Bind to the MSGFolder Root    
  9.         $folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::PublicFoldersRoot)     
  10.         $tfTargetFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)    
  11.         #Split the Search path into an array    
  12.         $fldArray = $FolderPath.Split("\")  
  13.          #Loop through the Split Array and do a Search for each level of folder  
  14.         for ($lint = 1; $lint -lt $fldArray.Length; $lint++) {  
  15.             #Perform search based on the displayname of each folder level  
  16.             $fvFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(1)  
  17.             $SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$fldArray[$lint])  
  18.             $findFolderResults = $service.FindFolders($tfTargetFolder.Id,$SfSearchFilter,$fvFolderView)  
  19.             if ($findFolderResults.TotalCount -gt 0){  
  20.                 foreach($folder in $findFolderResults.Folders){  
  21.                     $tfTargetFolder = $folder                 
  22.                 }  
  23.             }  
  24.             else{  
  25.                 "Error Folder Not Found"   
  26.                 $tfTargetFolder = $null   
  27.                 break   
  28.             }      
  29.         }   
  30.         if($tfTargetFolder -ne $null){ 
  31.             return $tfTargetFolder.Id.UniqueId.ToString() 
  32.         } 
  33.     } 
  34. } 
  35.  
  36. #Example use 
  37. $fldId = FolderIdFromPath -FolderPath "\firstlevel\secondlevel\target"  
  38. $SubFolderId =  new-object Microsoft.Exchange.WebServices.Data.FolderId($fldId)  
  39. $PubFolder  = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$SubFolderId)  
  40. $PubFolder  
Once you have bound to the Folder you can then perform any of the normal Item operations which I covered in the following post 

In Part 2 I'll go into some more depth some of the harder aspects of Public Folders.

Popular posts from this blog

The MailboxConcurrency limit and using Batching in the Microsoft Graph API

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

Sending a Message in Exchange Online via REST from an Arduino MKR1000

This is part 2 of my MKR1000 article, in this previous post  I looked at sending a Message via EWS using Basic Authentication.  In this Post I'll look at using the new Outlook REST API  which requires using OAuth authentication to get an Access Token. The prerequisites for this sketch are the same as in the other post with the addition of the ArduinoJson library  https://github.com/bblanchon/ArduinoJson  which is used to parse the Authentication Results to extract the Access Token. Also the SSL certificates for the login.windows.net  and outlook.office365.com need to be uploaded to the devices using the wifi101 Firmware updater. To use Token Authentication you need to register an Application in Azure https://msdn.microsoft.com/en-us/office/office365/howto/add-common-consent-manually  with the Mail.Send permission. The application should be a Native Client app that use the Out of Band Callback urn:ietf:wg:oauth:2.0:oob. You need to authorize it in you tenant (eg build a small ap

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

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

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