Search code examples
powershelloutlookexchangewebservices

Finding a FolderID using EWS


I want to move an email from one folder to another. I know I need to find the folder ID. That's where my issue is. I see there is a FindFolders method for the inbox, but I just need to find the ID of the folder in the inbox.

[void] [Reflection.Assembly]::LoadFile("C:\Program Files (x86)\Microsoft\Exchange\Web Services\2.1\Microsoft.Exchange.WebServices.dll")

$s = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2007_SP1)
$s.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials
$s.AutodiscoverUrl($email)

$inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($s,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox)
$psPropertySet = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$psPropertySet.RequestedBodyType = [Microsoft.Exchange.WebServices.Data.BodyType]::Text;

$items = $inbox.FindItems($inbox.TotalCount)
# this doesn't work
$TargetFolder = $inbox.FindFolders('MyFolder')

foreach ($item in $items.Items) {
    $item.Move($TargetFolder)
}

I also looked up the FolderID using VB in Outlook. But this didn't work either. I think I need to find the ID through PowerShell?

$TargetFolder = '00000000DBDD150E618BD0489CDE09859DC24F7A0100949BEDD21F6B4245BEEA6999720A0B090013516500830000'
$item.Move($TargetFolder)

I added the using namespace and converted the to Data.FolderID. However, I get an error that it wants it to be a well known folder. I created a customer main folder.

$TargetFolder = '00000000DBDD150E618BD0489CDE09859DC24F7A0100949BEDD21F6B4245BEEA6999720A0B090013516500830000'
$id = [Data.FolderId]::new($TargetFolder)
$item.Move($id.UniqueId)

I get these errors:

Cannot convert argument "destinationFolderName", with value: "00000000DBDD150E618BD0489CDE09859DC24F7A0100949BEDD21F6B4245BEEA6999720A0B090013516500830000", for "Move" to type "Microsoft.Exchange.WebServices.Data.WellKnownFolderName":

Cannot convert value "00000000DBDD150E618BD0489CDE09859DC24F7A0100949BEDD21F6B4245BEEA6999720A0B090013516500830000" to type "Microsoft.Exchange.WebServices.Data.WellKnownFolderName".

Error: "Unable to match the identifier name 00000000DBDD150E618BD0489CDE09859DC24F7A0100949BEDD21F6B4245BEEA6999720A0B090013516500830000 to a valid enumerator name. Specify one of the following enumerator names and try again:

Calendar, Contacts, DeletedItems, Drafts, Inbox, Journal, Notes, Outbox, SentItems, Tasks, MsgFolderRoot, PublicFoldersRoot, Root, JunkEmail, SearchFolders, VoiceMail, RecoverableItemsRoot, RecoverableItemsDeletions, RecoverableItemsVersions, RecoverableItemsPurges, ArchiveRoot, ArchiveMsgFolderRoot, ArchiveDeletedItems, ArchiveRecoverableItemsRoot, ArchiveRecoverableItemsDeletions, ArchiveRecoverableItemsVersions, ArchiveRecoverableItemsPurges, SyncIssues, Conflicts, LocalFailures, ServerFailures, RecipientCache, QuickContacts, ConversationHistory, ToDoSearch""

At C:\Users\jpb\Desktop\literatum\literatum.ps1:261 char:1
+ $item.Move($id.UniqueId)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodException
    + FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument

When I try your code with my login way I still error out.

Using namespace "Microsoft.Exchange.WebServices"

[CmdletBinding()]
param(
    [parameter(Mandatory=$true)]
    [string]$MailAddress
)

# need to download this!!!!!!!!!!!!
[void] [Reflection.Assembly]::LoadFile("C:\Program Files (x86)\Microsoft\Exchange\Web Services\2.1\Microsoft.Exchange.WebServices.dll")

$s = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2007_SP1)
$s.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials
$s.AutodiscoverUrl($MailAddress)
$objExchange = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($s,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::msgFolderRoot)  ###Inbox

<#
    Define Extended properties
    PR_FOLDER_TYPE: Contains a constant that indicates the folder type.
        https://msdn.microsoft.com/en-us/library/office/cc815373.asp
    PR_MESSAGE_SIZE_EXTENDED: Contains the sum, in bytes, of the sizes of all properties on a message object.
    int64 version of PR_MESSAGE_SIZE
        https://msdn.microsoft.com/en-us/library/office/cc839933.aspx
    PR_DELETED_MESSAGE_SIZE_EXTENDED: Could not find official reference.
    PR_FOLDER_PATH: Could not find official reference.
#>
$PR_FOLDER_TYPE = [Data.ExtendedPropertyDefinition]::new(13825,[Data.MapiPropertyType]::Integer)
$PR_MESSAGE_SIZE_EXTENDED = New-Object Data.ExtendedPropertyDefinition(3592,[Data.MapiPropertyType]::Long);
$PR_DELETED_MESSAGE_SIZE_EXTENDED = New-Object Data.ExtendedPropertyDefinition(26267,[Data.MapiPropertyType]::Long);
$PR_FOLDER_PATH = New-Object Data.ExtendedPropertyDefinition(26293, [Data.MapiPropertyType]::String);

$folderIDCount = [Data.FolderId]::new([Data.WellKnownFolderName]::MsgFolderRoot,$MailAddress)

# Define the FolderView used for Export. Should not be any larger then 1000 folders due to throttling
$folderView = [Data.FolderView]::new(1000)

# Deep Traversal will ensure all folders in the search path are returned
$folderView.Traversal = [Data.FolderTraversal]::Deep;
$ewsPropertySet = [Data.PropertySet]::new([Data.BasePropertySet]::FirstClassProperties)

# Add Properties to the  Property Set
$ewsPropertySet.Add($PR_MESSAGE_SIZE_EXTENDED);
$ewsPropertySet.Add($PR_FOLDER_PATH);
$folderView.PropertySet = $ewsPropertySet;

# Exclude any Search Folders in the filter
$searchFilter = [Data.SearchFilter+IsEqualTo]::new($PR_FOLDER_TYPE,"1")

# The Do loop will handle any paging that is required if there are more the 1000 folders in a mailbox
do {
    $filterResult = $objExchange.FindFolders($folderIDCount, $searchFilter, $folderView)
    foreach ($singleFolder in $filterResult.Folders) {
        # Try to get the FolderPath Value and then covert it to a usable String
        $folderPathValue = $null
        $singleFolder.TryGetProperty($PR_FOLDER_PATH, [ref]$folderPathValue) | Out-Null

        # Output folder object to pipeline
        $singleFolder | Select-Object Id,DisplayName,@{Name="FolderPath";Expression={$folderPathValue}},FolderClass,ParentFolderId
    }
    $folderView.Offset += $filterResult.Folders.Count
} while($filterResult.MoreAvailable)

I get this error for the FindFolders method:

Cannot find an overload for "FindFolders" and the argument count: "3".

At line:50 char:9
+         $filterResult = $objExchange.FindFolders($folderIDCount, $sea ...
+         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodException
    + FullyQualifiedErrorId : MethodCountCouldNotFindBest

I can connecting for $objExchange and I get these values and more:

Id              : AAMkADk5ZDNmODk2LTk0NzAtNDZkNi05Mjk1LTNhMjNlYmYzNzg1ZAAuAAAAAADb3RUOYYvQSJzeCYWdwk96AQD+Gq9HeAtSSLFM1nhOxNIuAAAAMAqcAAA=
ParentFolderId  : AAMkADk5ZDNmODk2LTk0NzAtNDZkNi05Mjk1LTNhMjNlYmYzNzg1ZAAuAAAAAADb3RUOYYvQSJzeCYWdwk96AQD+Gq9HeAtSSLFM1nhOxNIuAAAAMAqbAAA=
ChildFolderCount: 20

Solution

  • There is going to be a little more information here than required but I want to show how I get this information in my cmdlets that I made for interacting with my mailbox.

    If you look at the overloads for FindFolders() they want more than just a string to get the results you are looking for. My example below uses the last overload

    FindFolders(WellKnownFolderName, SearchFilter, FolderView)
    

    Searches a well-known folder by using a specified search filter and a specified folder view.

    This is a function I have to get me all folders in a mailbox.

    [CmdletBinding()]
    param(
        [parameter(Mandatory=$true)]
        [string]$MailAddress
    )
    
    # Create a reference to Exchange 2010 to match our current environment.
    $objExchange = Connect-ExchangeService -MailAddress $MailAddress -DefaultCredentials
    
    <#
        Define Extended properties  
        PR_FOLDER_TYPE: Contains a constant that indicates the folder type.
            https://msdn.microsoft.com/en-us/library/office/cc815373.asp
        PR_MESSAGE_SIZE_EXTENDED: Contains the sum, in bytes, of the sizes of all properties on a message object. int64 version of PR_MESSAGE_SIZE
            https://msdn.microsoft.com/en-us/library/office/cc839933.aspx
        PR_DELETED_MESSAGE_SIZE_EXTENDED: Could not find official reference.
        PR_FOLDER_PATH: Could not find official reference.
    #>
    $PR_FOLDER_TYPE = [Data.ExtendedPropertyDefinition]::new(13825,[Data.MapiPropertyType]::Integer)
    $PR_MESSAGE_SIZE_EXTENDED = New-Object Data.ExtendedPropertyDefinition(3592,[Data.MapiPropertyType]::Long);  
    $PR_DELETED_MESSAGE_SIZE_EXTENDED = New-Object Data.ExtendedPropertyDefinition(26267,[Data.MapiPropertyType]::Long);  
    $PR_FOLDER_PATH = New-Object Data.ExtendedPropertyDefinition(26293, [Data.MapiPropertyType]::String);  
    
    $folderIDCount = [Data.FolderId]::new([Data.WellKnownFolderName]::MsgFolderRoot,$MailAddress)  
    
    # Define the FolderView used for Export. Should not be any larger then 1000 folders due to throttling  
    $folderView = [Data.FolderView]::new(1000)
    
    # Deep Traversal will ensure all folders in the search path are returned  
    $folderView.Traversal = [Data.FolderTraversal]::Deep;  
    $ewsPropertySet = [Data.PropertySet]::new([Data.BasePropertySet]::FirstClassProperties)
    
    # Add Properties to the  Property Set  
    $ewsPropertySet.Add($PR_MESSAGE_SIZE_EXTENDED);  
    $ewsPropertySet.Add($PR_FOLDER_PATH);  
    $folderView.PropertySet = $ewsPropertySet;
    
    # Exclude any Search Folders in the filter
    $searchFilter = [Data.SearchFilter+IsEqualTo]::new($PR_FOLDER_TYPE,"1")  
    
    #The Do loop will handle any paging that is required if there are more the 1000 folders in a mailbox  
    do {  
        $filterResult = $objExchange.FindFolders($folderIDCount, $searchFilter, $folderView)       
        foreach($singleFolder in $filterResult.Folders){              
            #Try to get the FolderPath Value and then covert it to a usable String
            $folderPathValue = $null     
            $singleFolder.TryGetProperty($PR_FOLDER_PATH, [ref]$folderPathValue) | Out-Null
    
            # Output folder object to pipeline
            $singleFolder | Select-Object Id,DisplayName,@{Name="FolderPath";Expression={$folderPathValue}},FolderClass,ParentFolderId
        } 
        $folderView.Offset += $filterResult.Folders.Count
    }while($filterResult.MoreAvailable)
    

    Connect-ExchangeService is just a function that does more or less what your first few lines are doing. Using FindFolders() I search from the root folder and return all folders with some custom properties into $filterResult. $filterResult now contains information about each folder. Specifically their ID's.

    So down the line if I wanted to move a mail item to another folder I could do something like this.

    $targetFolderID = ($folders | Where-Object{$_.Displayname -eq $sourceFolder}).ID.UniqueID
    $item.Move($targetFolderID)
    

    Plenty of the EWS parameters want a typedID, not just a string. In you last example I think that should have triggered an error stating something similar. Either way if you have a string cast it like this to get something EWS will work with

    $id = [Data.FolderId]::new($targetFolderID)
    

    You might notice that the type names here are shorter. I have Using namespace "Microsoft.Exchange.WebServices" in my script to keep my line length down.

    Extra reading

    I had a tough time getting my head wrapped around EWS. This blog helped immensely with many example showing how its done. The author is also a member here that frequently answers EWS questions.