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
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
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.
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.
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
- function ConvertToString($ipInputString){
- $Val1Text = ""
- for ($clInt=0;$clInt -lt $ipInputString.length;$clInt++){
- $Val1Text = $Val1Text + [Convert]::ToString([Convert]::ToChar([Convert]::ToInt32($ipInputString.Substring($clInt,2),16)))
- $clInt++
- }
- return $Val1Text
- }
- function GetPublicFolders{
- param (
- $RootFolderId = "$( throw 'FolderId is a mandatory Parameter' )"
- )
- process{
- $RootFolderId
- $folderCollection = @()
- #Define Extended properties
- $PR_FOLDER_TYPE = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(13825,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer);
- #Define the FolderView used for Export should not be any larger then 1000 folders due to throttling
- $fvFolderView = New-Object Microsoft.Exchange.WebServices.Data.FolderView(1000)
- #Deep Transval will ensure all folders in the search path are returned
- $fvFolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Shallow;
- $psPropertySet = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
- $PR_Folder_Path = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(26293, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);
- #Add Properties to the Property Set
- $psPropertySet.Add($PR_Folder_Path);
- $fvFolderView.PropertySet = $psPropertySet;
- #The Search filter will exclude any Search Folders
- $sfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo($PR_FOLDER_TYPE,"1")
- $fiResult = $null
- do {
- $fiResult = $Service.FindFolders($RootFolderId,$sfSearchFilter,$fvFolderView)
- foreach($ffFolder in $fiResult.Folders){
- $foldpathval = $null
- $folderCollection += $ffFolder
- #Try to get the FolderPath Value and then covert it to a usable String
- if ($ffFolder.TryGetProperty($PR_Folder_Path,[ref] $foldpathval))
- {
- $binarry = [Text.Encoding]::UTF8.GetBytes($foldpathval)
- $hexArr = $binarry | ForEach-Object { $_.ToString("X2") }
- $hexString = $hexArr -join ''
- $hexString = $hexString.Replace("FEFF", "5C00")
- $fpath = ConvertToString($hexString)
- }
- "FolderPath : " + $fpath
- if($ffFolder.ChildFolderCount -gt 0){
- $Childfolders = GetPublicFolders -RootFolderId $ffFolder.Id
- foreach($Childfolder in $Childfolders){
- $folderCollection += $Childfolder
- }
- }
- }
- $fvFolderView.Offset += $fiResult.Folders.Count
- }while($fiResult.MoreAvailable -eq $true)
- return $folderCollection
- }
- }
- $pfRoot = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::PublicFoldersRoot)
- $folderHi = GetPublicFolders -RootFolderId $pfRoot
- #Example Use
- foreach($fld in $folderHi){
- $fld.DisplayName
- }
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
- function ConvertId{
- param (
- $HexId = "$( throw 'HexId is a mandatory Parameter' )"
- )
- process{
- $aiItem = New-Object Microsoft.Exchange.WebServices.Data.AlternatePublicFolderId
- $aiItem.FolderId = $HexId
- $aiItem.Format = [Microsoft.Exchange.WebServices.Data.IdFormat]::HexEntryId
- $convertedId = $service.ConvertId($aiItem, [Microsoft.Exchange.WebServices.Data.IdFormat]::EwsId)
- return $convertedId.UniqueId
- }
- }
- function FolderIdFromPath{
- param (
- $FolderPath = "$( throw 'Folder Path is a mandatory Parameter' )"
- )
- process{
- ## Find and Bind to Folder based on Path
- #Define the path to search should be seperated with \
- #Bind to the MSGFolder Root
- $folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::PublicFoldersRoot)
- $tfTargetFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
- #Split the Search path into an array
- $fldArray = $FolderPath.Split("\")
- #Loop through the Split Array and do a Search for each level of folder
- for ($lint = 1; $lint -lt $fldArray.Length; $lint++) {
- #Perform search based on the displayname of each folder level
- $fvFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(1)
- $SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$fldArray[$lint])
- $findFolderResults = $service.FindFolders($tfTargetFolder.Id,$SfSearchFilter,$fvFolderView)
- if ($findFolderResults.TotalCount -gt 0){
- foreach($folder in $findFolderResults.Folders){
- $tfTargetFolder = $folder
- }
- }
- else{
- "Error Folder Not Found"
- $tfTargetFolder = $null
- break
- }
- }
- if($tfTargetFolder -ne $null){
- return $tfTargetFolder.Id.UniqueId.ToString()
- }
- }
- }
- #Example use
- $fldId = FolderIdFromPath -FolderPath "\firstlevel\secondlevel\target"
- $SubFolderId = new-object Microsoft.Exchange.WebServices.Data.FolderId($fldId)
- $PubFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$SubFolderId)
- $PubFolder
In Part 2 I'll go into some more depth some of the harder aspects of Public Folders.