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.

42 comments:

mjolinor said...

Has anybody created a library of custom PS type accelerators for the commonly used object types?

@mjolinr

Glen said...

Not that I know of personally i like the idea but these type of things become very individual to particular people but I'm sure we will see one soon. I've built a number of modules over the year and pre-canned a whole bunch of useful stuff in one-liners and this does reduce the complexity and time when doing something. However what I find is the day to day complexity of problems that people want solved and the scope you can get to with these type of things its better to have the complexity and universality rather then the shortcut. But that's just my opinion.

Cheers
Glen

Joachim said...

Dear Glen,

thx alot for this great blog and article :)

What I can not manage is to enumerate all mail folders in a mailbox. I try to achieve to make a list of all items >25MB in all (mail) folders of a user. Thanks to your articles I can search in a known folder but your traverse does not work for me.

Maybe you could give me a hint?

Cheers
Joachim

Glen said...

>I can search in a known folder but your traverse does not work for me.

If you going to say something doesn't work you need to provide details of what doesn't work. The code itself is good if you don't tell me what doesn't work or what error you get i can't help

Cheers
Glen

Joachim said...

I need to traverse all possible mail folders in the mailbox. You code has no error, it just does not show how to find all items in all mail folders. And I am looking for a way to do that right now :)

Glen said...

You need to search each folder see http://gsexdev.blogspot.com.au/2012/02/ews-managed-api-and-powershell-how-to.html and maybe
http://gsexdev.blogspot.com.au/2011/08/using-allitems-search-folder-from.html or even create a new searchfolder for this http://gsexdev.blogspot.com.au/2011/09/create-search-folder-using-ews-managed.html

Cheers
Glen

Joachim said...
This comment has been removed by the author.
Tod said...

Hi Glen,

do you have an example on how to directly access a public folder if you have the EntryId from EMS? I don’t get it.

Cheers,
Tod

Glen said...

Which cmdlet are you using ? Get-PublicFolderStatistics returns the OWAid and Get-PublicFolder return the HexEntryId you need to take the and convert it to the EWSid and bind to it eg here a sample for doing a Mailbox folder from Get-Mailbox which return the OWAid

$Folder = Get-MailboxFolder glen:\Inbox

$aiItem = New-Object Microsoft.Exchange.WebServices.Data.AlternateId
$aiItem.Mailbox = $MailboxName
$aiItem.UniqueId = $Folder.FolderStoreObjectId.ToString()
$aiItem.Format = [Microsoft.Exchange.WebServices.Data.IdFormat]::OWAId;
$ewsid = $service.ConvertId($aiItem, [Microsoft.Exchange.WebServices.Data.IdFormat]::EWSId)
$Folderid = $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId($ewsid.UniqueId)

$ewsfolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$Folderid)
$ewsfolder

Myrandomstuff said...

Glen, thank you for this post. It has been most helpful. I am having troubles with binding the folder to the service
$folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$smtpid)
$Inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($exchService, $folderid)

I get the error Exception calling "Bind" with "2" argument(s): "The SMTP address has no mailbox associated with it."

Joe Smith said...

I was wondering if there is a way to filter folders on if their content has changed since a certain date?

R_C_III said...

@Myrandomstuff
I had the same error, until I realized that there are two places in the script where I needed to add the e-mail address.
Also, I was running this under my credentials against my own mailbox--are you?

Sanil T said...

Hello Glen

Let me start with saying what many have said, the posts for EWS have been the best resource onthe net. thank you.

i am using powershell with EWS 2.0 and trying to search meetings booked by certain users in a meeting room calendar and then delete them. the issue i am facing is for recurring meetings. when i find a recurring meeting and i try to find the master item or to delete teh entire series i am unable to
here are the two options i tried

For deleting the entire series i tried
$CalAppointment.Delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::SoftDelete,[Microsoft.Exchange.WebServices.Data.SendCancellationsMode]::SendToNone,[Microsoft.Exchange.WebServices.Data.AffectedTaskOccurrence]::AllOccurrences)

but EWS 2.0 says that 3rd argument is not expected for the method. if i try without the third option of AllOccuerences then that particular meeting does get deleted as expected.

the other option i tried was to try bind to the original master by using BindToRecurringMaster method but EWS says that method BindToRecurringMaster is not a known method.

$msMessage = [Microsoft.Exchange.WebServices.Data.Appointment]::Bind($ExService,$CalAppointment.id)
$msMEssage.id.ToString()
$MasterReturnValue = $msMEssage.BindToRecurringMaster($ExService, $msMEssage.id)

Appreciate any help you can provide with deleting entire series of meeting

Sanil T said...

Figured it out, looks like a Syntax thing. I was calling the method directly instead of
[Microsoft.Exchange.WebServices.Data.Appointment]::BindToRecurringMaster($ExService, $CalAppointment.Id)

this worked. i can now delete the original occurence. I would still appreciate your explanation. I refer to MSDN pages like http://msdn.microsoft.com/en-us/library/exchange/microsoft.exchange.webservices.data.appointment_members(v=exchg.80).aspx, for the checking on available members and methods but translating the methods to ews while using powershell is a struggle, atleast for me.


thanks again for the posts

Alberto Pascual said...

Hi, good post, i´m trying to empty a contact folder in Exchange online but if i run the empty command i get the error that says it´s only available in Exchange 2010 sp1 or later... any ideas? :S thanks in advance...

Glen Scales said...

You need to make sure you have set the version in

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

## Create Exchange Service Object
$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)

to Exchange2010_SP1 that will make sure you requests are versioned correctly.

Cheers
Glen

Roger said...

Hello Glen,

Do you have a ready-to-run script available to delete all empty folders within a mailbox (basically leftovers from continuous archiving). One that also takes only empty folders into account that have no subfolders/items within them.

Regards,
Roger

Ondrej Šebela said...
This comment has been removed by the author.
Glen Scales said...

see http://gsexdev.blogspot.com.au/2013/01/creating-new-calendarcontactstasks-or.html

Cheers
Glen

Ondrej Šebela said...

Thank you very much! Your blog is very helpful for me.

Anonymous said...

thanks for share.

Anonymous said...

I am getting the following error:

Cannot find an overload for "makegenerictype" and the argument count: "1".
At line:1 char:1
+ $Itemids = [activator]::createinstance(([type]'System.Collections.Generic.List`1 ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodCountCouldNotFindBest

Glen Scales said...

That's a powershell v3 thing you need to replace with

$type = ("System.Collections.Generic.List"+'`'+"1") -as "Type"
$type = $type.MakeGenericType("Microsoft.Exchange.WebServices.Data.ItemId" -as "Type")
$Itemids = [Activator]::CreateInstance($type)

Cheers
Glen

Normski said...

How you do you create subfolders?
E.g \\InBox\FolderL1\FolderL2\FolderL3


Neill Tinlin said...

Hi Glen, I know your post is from a while ago but I'm having trouble with the delete folder portion.
Trying to delete a sub-folder with 18,000 items but get the following error
'Exception calling "Delete" with "1" argument(s): "The request failed. The operation has timed out"

Anything I should be looking for?

Regards
Neill

Ratoka said...

How would you modify the URL for a folder?

Ratoka said...

Change the URL through PS that is...

Myrandomstuff said...

the issue was I had too many spaces in my binding and yes I was running it against my own mailbox - I was also adding full permissions for me to each mailbox

Glen Scales said...

By URL do you mean home page is so see http://social.technet.microsoft.com/Forums/en-US/08572767-9375-4b87-9f05-7ff3e9928f89/ews-powershell-set-homepageurl?forum=exchangesvrdevelopment

Cheers
Glen

Ratoka said...

Glen-

The link you have at the bottom of the post appears (to me at least) to be C#. I have a script that is creating a folder, but I need to add a URL to the folder.

Glen Scales said...

There is a powershell script posted in the forum post to do that read though all the posts

Cheers
Glen

Ratoka said...

Got it, and posed what I came up with to hopefully help someone else out. Thank you very much Glen!

Anonymous said...

I dont know if I am missing something, but where are you setting the folderclass after creating folder. what is the default value if you are not setting it explicitly

Anonymous said...

Can you post an example of the syntax for retrieving extended properties? In my case I'm interested in the values of PR_ASSOC_CONTENT_COUNT and PR_CONTENT_COUNT. I'm good with Powershell, but this EWS stuff is a whole other animal! :)

Anonymous said...

Glen, I've setup Impersonation, granted full mailbox access and send-as rights but when I try to do a shallow FindFolder as you describe above, it works on one account, but not on the other 7 in my lab tenant. I can't find anything different between them. Basically, the $findFolderResults doesn't get populated and the totalcount remains 0 even though the folder is in the mailbox. Here's what I am trying based on your example. What is required to perform the FindFolders search? Impersonation or permissions I'm missing? I have checked variables and everything is populated properly until it gets to FindFolders.

# Get all user mailboxes in the environment
$mailboxes = Get-Mailbox -ResultSize unlimited -Filter {userprincipalname -ne $O365Impersonate}| ?{$_.RecipientTypeDetails -eq 'UserMailbox'}
$EmailAddresses = $mailboxes.PrimarySmtpAddress
# Retrieve the Personal Retention Tag ID that you want to set on the folder defined above
$personalTag = Get-RetentionPolicyTag | ?{$_.name -eq $PersRetTagName}
$personalTagGUID = $personalTag.RetentionId
# Loop through all user mailboxes and perform the subsequent tasks
ForEach($EmailAddress in $EmailAddresses) {
# Bind to the Mailbox
$exchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013_SP1
$exchangeService = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($exchangeVersion)
$exchangeService.Credentials = New-Object Microsoft.Exchange.WebServices.Data.WebCredentials($EXOCreds)
$exchangeService.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $EmailAddress)
$exchangeService.Url = 'https://outlook.office365.com/EWS/Exchange.asmx'
$folderid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$EmailAddress)
$tfTargetFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($exchangeService,$folderid)
$fldArray = $PathToSearch.Split("\")
#Loop through the mailbox and do a Search at each level for the defined folder to search for
for ($lint = 1; $lint -lt $fldArray.Length; $lint++) {
$fvFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(1)
$fvFolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Deep
$SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$fldArray[$lint])
$findFolderResults = $exchangeService.FindFolders($tfTargetFolder.Id, $SfSearchFilter, $fvFolderView)
if ($findFolderResults.TotalCount -gt 0){

Anonymous said...

Oh, I added the Traversal::Deep line to see if it would help. Originally its not there.

Glen Scales said...

Sorry I don't really follow what your trying to do if you want to bind to a Folder from a path then using a function eg

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]::MsgFolderRoot,$MailboxName)
$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 "\Inbox\test"
$SubFolderId = new-object Microsoft.Exchange.WebServices.Data.FolderId($fldId)
$SubFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$SubFolderId)

sahal said...

hi glen, very useful series on powershell and ews. i am just trying to follow your series to lear ews in powershell. i have done question in this post, how did you get this "3592" as the tag for extended message size, i am not able to get to that from mfcmapi, please provide your feedback on this.

Manfred Preissner said...

Hello Glen first :this is awesom scripting that you are doing
And i hope you can give me a helping script hand
i have a list of known softdeleted items in public folders 1000+ and i need to recover them all can you be so kind and tell me ho to do that
i m still seeking the knowledge to understand how to call softdeleted items in a public folder / copy that items back to that folder and if copy has no error delete that file from dumpster..

so far i'm able to list the content of the folder

$fldId = FolderIdFromPath -FolderPath "\Folder1\Folder2\..."
$SubFolderId = new-object Microsoft.Exchange.WebServices.Data.FolderId($fldId)
$SubFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$SubFolderId)
if($SubFolder -ne $null){
#Define ItemView to retrive just 1000 Items
$ivItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)
$fiItems = $null
do{
$fiItems = $service.FindItems($SubFolder.Id,$ivItemView)
#[Void]$service.LoadPropertiesForItems($fiItems,$psPropset)
foreach($Item in $fiItems.Items){
$Item.Subject + " " + $Item.Start
}
$ivItemView.Offset += $fiItems.Items.Count
}while($fiItems.MoreAvailable -eq $true)
}

but how do i perform :

the access of the publicfolder "softdeleted" Items
copy paste back to the folder & delete that items

hope to hear from you...

Manfred Preissner

manfred@preissner.at




Manfred Preissner said...

PS im dealing with exchange 2013

Glen Scales said...

You can try copying them I've not done it myself public folders are complex and there are many consideration I would suggest starting with http://blogs.technet.com/b/exchange/archive/2013/08/23/recovering-public-folder-information-in-exchange-2013.aspx

Leopold Durrant said...

Hi Glen
amazing blog. I have about 1000 mailboxes with 25 additional calendar folders in their calendar. can you send me a sample script that I can use to run against all mailboxes and hard delete the folders I don't want. "I don't want it to delete the CALENDAR and BIRTHDAY folders but everything else. the root look like this.

calendar\folder1
calendar\folder2
etc..

Thanksgiving