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.