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
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.
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
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
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.
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
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.
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.
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
- ## Find and Bind to Folder based on Path
- #Define the path to search should be seperated with \
- $PathToSearch = "\Inbox\Blah"
- #Bind to the MSGFolder Root
- $folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,"user@domain.com")
- $tfTargetFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
- #Split the Search path into an array
- $fldArray = $PathToSearch.Split("\")
- #Loop through the Split Array and do a Search for each level of folder
- for ($lint = 1; $lint -lt $fldArray.Length; $lint++) {
- $fldArray[$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
- }
- }
- $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.
- #Define Function to convert String to FolderPath
- 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
- }
- #Define Mailbox to access
- $MailboxName = "glen@mymailbox.domain.com"
- "Checking : " + $MailboxName
- #Define Extended properties
- $PR_FOLDER_TYPE = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(13825,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer);
- $folderidcnt = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$MailboxName)
- #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]::Deep;
- $psPropertySet = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
- $PR_MESSAGE_SIZE_EXTENDED = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(3592,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Long);
- $PR_DELETED_MESSAGE_SIZE_EXTENDED = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(26267,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Long);
- $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_MESSAGE_SIZE_EXTENDED);
- $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
- #The Do loop will handle any paging that is required if there are more the 1000 folders in a mailbox
- do {
- $fiResult = $Service.FindFolders($folderidcnt,$sfSearchFilter,$fvFolderView)
- foreach($ffFolder in $fiResult.Folders){
- "Processing : " + $ffFolder.displayName
- $TotalItemCount = $TotalItemCount + $ffFolder.TotalCount;
- $FolderSize = $null;
- $FolderSizeValue = 0
- #Try to Get the FolderSize Value and output it to the $FolderSize varible
- if ($ffFolder.TryGetProperty($PR_MESSAGE_SIZE_EXTENDED,[ref] $FolderSize))
- {
- $FolderSizeValue = [Int64]$FolderSize
- }
- $foldpathval = $null
- #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
- "FolderSize : " + $FolderSizeValue
- }
- $fvFolderView.Offset += $fiResult.Folders.Count
- }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
- #Define Mailbox to access
- $MailboxName = "glen@mailbox.domain.com"
- ## Bind to the Inbox Sample
- $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)
- $Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
- $NewFolder = new-object Microsoft.Exchange.WebServices.Data.Folder($service)
- $NewFolder.DisplayName = "My New Folder123"
- $NewFolder.Save($Inbox.Id)
- #Define Folder Name to Search for
- $FolderName = "My New Folder123"
- #Define Folder Veiw Really only want to return one object
- $fvFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(1)
- #Define a Search folder that is going to do a search based on the DisplayName of the folder
- $SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$FolderName)
- #Do the Search
- $findFolderResults = $service.FindFolders($Inbox.Id,$SfSearchFilter,$fvFolderView)
- if ($findFolderResults.TotalCount -eq 0){
- "Folder Doesn't Exist"
- }
- else{
- "Folder Exist"
- }
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.
- ## Find and Bind to Folder based on Path
- #Define the path to search should be seperated with \
- $PathToSearch = "\Inbox\My New Folder123"
- #Bind to the MSGFolder Root
- $folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$MailboxName)
- $tfTargetFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
- #Split the Search path into an array
- $fldArray = $PathToSearch.Split("\")
- #Loop through the Split Array and do a Search for each level of folder
- for ($lint = 1; $lint -lt $fldArray.Length; $lint++) {
- $fldArray[$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){
- #Delete the Folder using HardDelete
- $tfTargetFolder.Delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete)
- "Folder Deleted"
- }
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
- # Bind to the Calendar folder
- $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Calendar,$MailboxName)
- $Calendar = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)
- #Set Itemview to 1000 to deal with throttling
- $ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)
- $fiResult = ""
- do{
- $fiResult = $Calendar.FindItems($ivItemView)
- #Define ItemIds collection
- $Itemids = [activator]::createinstance(([type]'System.Collections.Generic.List`1').makegenerictype([Microsoft.Exchange.WebServices.Data.ItemId]))
- "Delete " + $fiResult.Items.Count + " Items"
- foreach($Item in $fiResult.Items){
- $Itemids.Add($Item.Id)
- }
- #Delete the Items
- $Result = $service.DeleteItems($Itemids,[Microsoft.Exchange.WebServices.Data.DeleteMode]::SoftDelete,[Microsoft.Exchange.WebServices.Data.SendCancellationsMode]::SendToNone,[Microsoft.Exchange.WebServices.Data.AffectedTaskOccurrence]::AllOccurrences)
- [INT]$Rcount = 0
- foreach ($res in $Result){
- if ($res.Result -eq [Microsoft.Exchange.WebServices.Data.ServiceResult]::Success){
- $Rcount++
- }
- }
- $Rcount.ToString() + " Items deleted successfully"
- $ivItemView.offset += $fiResult.Items.Count
- }while($fiResult.MoreAvailable -eq $true)
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.
- $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::PublicFoldersRoot,"user@mailbox.com")
- $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.