none
Move mail from Inbox to subfolder with Exchange Web Services 2.0 and Powershell

    Question

  • Hi

    I have created a script (With help from Google and some blogs :-)) that basically does the following:
    1. Finds all mails with attachments
    2. Saves the attachment
     2.1 If it's a .ZIP file, the file is extracted
    3. Prints the attachments
    4. Moves the file to a subfolder called Archive (This part doesn't work

    The steps from 1 to 3 is working fine, but the part about moving the items to the subfolder it fails.

    I have tested it with my own username and it's working as expected, but when I run the script in production with the service account I have created with impersonation rights it fails.
    As far as I can see the problem is that the service account can't get the ID of the subfolder. I use the following code:

    $FolderName = "Archive"
    $oFolder = new-object Microsoft.Exchange.WebServices.Data.Folder($EWSService)
    $oFolder.DisplayName = $FolderName
    
    #Define Folder View 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 = $EWSService.FindFolders($InboxID,$SfSearchFilter,$fvFolderView)
    
    If ($findFolderResults.TotalCount -ne 0)
    {	
    	$DestID = $findFolderResults.ID
    	$Email.Move($DestID) | Out-Null
    }

    The "$findFolderResults.TotalCount" actually returns 1 if the folder is present but "$findFolderResults.ID" is empty, and therefor the "$Email.Move($DestID)" fails.

    Any ideas?

    Lasse


    /Lasse

    Tuesday, June 10, 2014 7:35 AM

Answers

  • I got what I wanted by using $findFolderResults.Folders[0].Id.UniqueID

    Sometimes it's hard to see the forest for all the trees :-)


    /Lasse

    Monday, June 16, 2014 9:24 AM

All replies

  • FindFolderResults is an array.

    $DestID =$findFolderResults[0].ID


    ¯\_(ツ)_/¯

    Tuesday, June 10, 2014 11:46 AM
  • Hi

    Thanks for the reply.

    Tried your suggestion, but it returns the following error:

    Unable to index into an object of type Microsoft.Exchange.WebServices.Data.FindFoldersResults.
    At C:\Temp\Mail.ps1:326 char:33
    +             $DestID = $findFolderResults[ <<<< 0].ID
        + CategoryInfo          : InvalidOperation: (0:Int32) [], RuntimeException
        + FullyQualifiedErrorId : CannotIndex


    /Lasse

    Tuesday, June 10, 2014 1:40 PM
  • I have tried running the script in a PS console directly on the server.

    When I run the following:
    $findFolderResults = $EWSService.FindFolders($InboxID,$SfSearchFilter,$fvFolderView)

    and afterwards just type $findFolderResults, it returns all the information of the variable including the ID of the folder.
    If I write $findfolderResults.id it returns nothing, so the information is gathered correctly, but I can't figure out how to pull it  out :-)


    /Lasse

    Tuesday, June 10, 2014 2:06 PM
  • Try this:

    foreach ($Folder in $findFolderResults)
    {$Folder.ID}

    and see if you don't get a folder ID.


    [string](0..33|%{[char][int](46+("686552495351636652556262185355647068516270555358646562655775 0645570").substring(($_*2),2))})-replace " "

    Tuesday, June 10, 2014 2:09 PM
    Moderator
  • I have tried running the script in a PS console directly on the server.

    When I run the following:
    $findFolderResults = $EWSService.FindFolders($InboxID,$SfSearchFilter,$fvFolderView)

    and afterwards just type $findFolderResults, it returns all the information of the variable including the ID of the folder.
    If I write $findfolderResults.id it returns nothing, so the information is gathered correctly, but I can't figure out how to pull it  out :-)


    /Lasse

    It is an array.  you need to either enumerate or index it.

    $findFolderResults[0].ID

    That will get you the first and only member of the array.


    ¯\_(ツ)_/¯

    Tuesday, June 10, 2014 2:27 PM
  • Hi Guys,

    actually, as the official documentation says, the FindFoldersResults class has a property named Folders, which contains the results collection.

    Cheers,
    Fred


    There's no place like 127.0.0.1

    Tuesday, June 10, 2014 2:36 PM
  • Actually, that appears to return a collection in the Folders property

                                                                                          

    TypeName: Microsoft.Exchange.WebServices.Data.FindFoldersResults

    Name           MemberType Definition                                                                                                  
    ----           ---------- ----------                                                                                                  
    Equals         Method     bool Equals(System.Object obj)                                                                              
    GetEnumerator  Method     System.Collections.Generic.IEnumerator[Microsoft.Exchange.WebServices.Data.Folder] GetEnumerator(), Syste...
    GetHashCode    Method     int GetHashCode()                                                                                           
    GetType        Method     type GetType()                                                                                              
    ToString       Method     string ToString()                                                                                           
    Folders        Property   System.Collections.ObjectModel.Collection[Microsoft.Exchange.WebServices.Data.Folder] Folders {get;}        
    MoreAvailable  Property   bool MoreAvailable {get;}                                                                                   
    NextPageOffset Property   System.Nullable[int] NextPageOffset {get;}                                                           
    TotalCount     Property   int TotalCount {get;}

    $ffResponse.Folders.GetType()

    IsPublic IsSerial Name                                     BaseType                                                                   
    -------- -------- ----                                     --------                                                                   
    True     True     Collection`1                             System.Object   

    So:

    foreach ($Folder in 

    foreach ($Folder in $findFolderResults.Folders) {$Folder.ID}

    Or

    $findFolderResults.Folders.ID


    [string](0..33|%{[char][int](46+("686552495351636652556262185355647068516270555358646562655775 0645570").substring(($_*2),2))})-replace " "




    Tuesday, June 10, 2014 2:44 PM
    Moderator
  • Hi Guys,

    actually, as the official documentation says, the FindFoldersResults class has a property named Folders, which contains the results collection.

    Cheers,
    Fred


    There's no place like 127.0.0.1

    But that should be the default property.  If not then use $findFolderResults.Folders[0].ID


    ¯\_(ツ)_/¯

    Tuesday, June 10, 2014 2:45 PM
  • Yo peeps

    Thanks for all the input.

    I have tried running the script manually, one line at a time. If I type $findFolderResults I can see the ID that I want to save.

    I get the following result when using $findFolderResults.Folders[0].ID:

    PS C:\Users\lahf> $findFolderResults.Folders[0].ID

    FolderName          Mailbox             UniqueId            ChangeKey
    ----------          -------             --------            ---------
                                            AAMkAGI1MjgyMTFl... AQAAABYAAACSdxiH...

    I can't figure out how I can save just the ID under UniqueID in a variable?


    /Lasse

    Monday, June 16, 2014 9:10 AM
  • I got what I wanted by using $findFolderResults.Folders[0].Id.UniqueID

    Sometimes it's hard to see the forest for all the trees :-)


    /Lasse

    Monday, June 16, 2014 9:24 AM
  • Hi

    I have created a script (With help from Google and some blogs :-)) that basically does the following:
    1. Finds all mails with attachments
    2. Saves the attachment
     2.1 If it's a .ZIP file, the file is extracted
    3. Prints the attachments
    4. Moves the file to a subfolder called Archive (This part doesn't work

    The steps from 1 to 3 is working fine, but the part about moving the items to the subfolder it fails.

    I have tested it with my own username and it's working as expected, but when I run the script in production with the service account I have created with impersonation rights it fails.
    As far as I can see the problem is that the service account can't get the ID of the subfolder. I use the following code:

    $FolderName = "Archive"
    $oFolder = new-object Microsoft.Exchange.WebServices.Data.Folder($EWSService)
    $oFolder.DisplayName = $FolderName
    
    #Define Folder View 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 = $EWSService.FindFolders($InboxID,$SfSearchFilter,$fvFolderView)
    
    If ($findFolderResults.TotalCount -ne 0)
    {	
    	$DestID = $findFolderResults.ID
    	$Email.Move($DestID) | Out-Null
    }

    The "$findFolderResults.TotalCount" actually returns 1 if the folder is present but "$findFolderResults.ID" is empty, and therefor the "$Email.Move($DestID)" fails.

    Any ideas?

    Lasse


    /Lasse

    In your example, where is the $inboxID coming from?

    $findFolderResults = $EWSService.FindFolders($InboxID,$SfSearchFilter,$fvFolderView)

    Friday, February 24, 2017 6:40 PM
  • Hi,

    either it was found by first finding the correct folder or it was using one of the default folders. For example you could just use "Inbox" in its place, if you want to search the default inbox.

    Cheers,
    Fred


    There's no place like 127.0.0.1

    Monday, February 27, 2017 8:18 AM
  • Hi,

    I can't remember from the top of my head, but if you have a little patience I will look in my archieves and find the full script.

    Freds assumption is most likely correct, but will check.


    /Lasse

    Monday, February 27, 2017 4:30 PM
  • Found it :-)

    $InboxID = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox)

    Here is the initial part of my script:

    $EWSURL = "http://s-exchange/EWS/Exchange.asmx"
    $EWSManagedApiPath = "C:\Program Files\Microsoft\Exchange\Web Services\2.0\Microsoft.Exchange.WebServices.dll"
    $IgnoreSSLCertificate = $true
    $ExchangeVersion = "Exchange2010_SP1"
    
    ######################################################################################################################
    # Creates the EWS service object
    ######################################################################################################################
    Function New-EWSServiceObject
    {
    	<#
    	.SYNOPSIS
    	Returns an EWS Service Object
    	.DESCRIPTION
    	Creates a EWS service object, with the option of using impersonation and/or An EWS URL or fall back to Autodiscover
    	.PARAMETER SMTPAddress
    	The SMTP address of the Exchange mailbox that is associated with the EWS object
    	.EXAMPLE
    	PS C:\powershell> New-EWSServiceObject -SMTPAddress "John.Doe@Company.com"
    
    	This will return and EWS object that uses impersonation with a specifed URL
    	#>
    	[CmdletBinding(DefaultParameterSetName="OtherUser")]
    	Param() 
    
    	Write-Verbose "Creating EWS Service connection object"
    	$EWSService = new-object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::$ExchangeVersion)
    
    	If ($EWSTracing)
    	{
    		Write-Verbose "EWS Tracing enabled"
    		$EWSService.traceenabled = $true
    	}
    	 
    	if ($IgnoreSSLCertificate)
    	{
    		Write-Verbose "Ignoring any SSL certificate errors"
    		[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true };
    	} 
    	 
    	If ($SMTPAddress)
    	{
    		Write-Verbose "Creating service for the following address $SMTPAddress"
    	}
    	Else
    	{
    		Write-Verbose "Creating service for the following runtime user $ENV:UserName"
    	}
    	 
    	Write-Verbose "Checking if the runtime or a seperate account will be used for impersonation"
    	If ($ImpersonationUserName -and $ImpersonationPassword)
    	{
    		Write-Verbose "Secondary account for Impersonation specifed ($ImpersonationUserName)"
    		If ($ImpersonationDomain)
    		{
    			#If a domain is presented then use that as well
    			$EWSService.Credentials = New-Object Microsoft.Exchange.WebServices.Data.WebCredentials($ImpersonationUserName,$ImpersonationPassword,$ImpersonationDomain)
    		}
    		Else
    		{
    			#Otherwise leave the domain blank
    			$EWSService.Credentials = New-Object Microsoft.Exchange.WebServices.Data.WebCredentials($ImpersonationUserName,$ImpersonationPassword)
    		}
    		Write-Verbose "Saving impersonation credentials for EWS service"
    		$EWSService.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $SMTPAddress)
    	}
    	Else
    	{
    		Write-Verbose "Runtime account specifed for impersonation ($ENV:Username)"
    		$EWSService.UseDefaultCredentials = $true 
    	}
    	 
    	#Set the EWS URL, the EWS URL is needed if the the runtime user is specifed since they may not have a mailbox
    	If ($EWSURL)
    	{
    		Write-Verbose "Using the specifed EWS URL of $EWSURL"
    		$EWSService.URL = New-Object Uri($EWSURL)
    	}
    	Else
    	{
    		Write-Verbose "Using the AutoDiscover to find the EWS URL"
    		$EWSService.AutodiscoverUrl($SMTPAddress, {$True})
    	}
    	#Now Return the Service Object
    	Return $EWSService
    }
    ######################################################################################################################
    
    # Check if EWS Managed API available, if not then exit the script
    Try {Get-Item -Path $EWSManagedApiPath -ErrorAction Stop | Out-Null}
    Catch {Write-LogFile "Missing: Exchange Web Service API"}
    
    # Load EWS Managed API
    [void][Reflection.Assembly]::LoadFile($EWSManagedApiPath);
    
    #Create EWS Service
    $EWSservice = New-EWSServiceObject
    
    #Attach to the inbox to find the total number of items
    #This will be used on in the search query so that we search the entire inbox
    $InboxID = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox)
    $InboxFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($EWSservice,$InboxID)

    Check the MSDN documentation here: https://msdn.microsoft.com/en-us/library/office/dd877037(v=exchg.140).aspx

    Hopes this helps, otherwise feel free to ask, and I will try my best to help :-)


    /Lasse

    Monday, February 27, 2017 6:03 PM
  • EDIT: I got it working by changing:

    $DestID = $findFolderResults.Folders[0].Id.UniqueID

    to:

    $DestID = $findFolderResults.Folders[0].Id

    The code pretty much works for me, $DestID gets filled with the ID of the subfolder I want to move my mail to. Ben when I want to execute te .Move command I get an error: Cannot convert argument"destinationFolderName", with value: ......

    So maybe my $Email variable isn't filled with the right object? How did you fill that?

    This is how I fill $Email:

    # Load the assembly
    [void] [Reflection.Assembly]::LoadFile("C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll")
    
    # Set ref to exchange 2010 (default)
    $s = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService
    
    # Set credentials
    $s.Credentials = New-Object Net.NetworkCredential($username, $password, $domain)
    
    # Discover the url from your email address
    $s.AutodiscoverUrl($email)
    
    # Get a handle to the inbox
    $inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($s,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox)
    
    $Email = $inbox.FindItems(1)

    I hope you, or someone else can help me out with this!



    • Edited by Slinkos Thursday, April 20, 2017 4:04 PM Added solution
    Thursday, April 20, 2017 3:51 PM
  • Hi,

    I have been back looking at my script again :-)

    Below is what should be necessary to have it work. $Email is part of my Foreach in the bottom of the code.

    I populate $DestID as part of my "Foreach ($Email in $FoundMails)".

    I hope this helps, otherwise feel free to write me back :-)

    # Load EWS Managed API
    [void][Reflection.Assembly]::LoadFile($EWSManagedApiPath);
    
    #EWS requires double back whacks "\\" for each normal back whack "\" when
    #it comes to the file path to save to
    #E.g. C:\Windows\System32 needs to look like C:\\Windows\\System32
    $EWSSavePath = $SavePath -replace "\\","\\"
    
    #Create EWS Service
    $EWSservice = New-EWSServiceObject
    
    #Attach to the inbox to find the total number of items
    #This will be used on in the search query so that we search the entire inbox
    $InboxID = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox)
    $InboxFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($EWSservice,$InboxID)
    
    $NumEmailsToReturn = $InboxFolder.TotalCount
    If ($InboxFolder.TotalCount -eq 0)
    {
    	Exit
    }
    
    #Create mailbox view
    $view = New-Object -TypeName Microsoft.Exchange.WebServices.Data.ItemView -ArgumentList $NumEmailsToReturn
    #Define properties to pull for each item in the view
    $propertyset = New-Object Microsoft.Exchange.WebServices.Data.PropertySet (
    [Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties,
    [Microsoft.Exchange.WebServices.Data.ItemSchema]::Subject,
    [Microsoft.Exchange.WebServices.Data.ItemSchema]::HasAttachments,
    [Microsoft.Exchange.WebServices.Data.ItemSchema]::DisplayTo,
    [Microsoft.Exchange.WebServices.Data.ItemSchema]::DisplayCc,
    [Microsoft.Exchange.WebServices.Data.ItemSchema]::DateTimeSent,
    [Microsoft.Exchange.WebServices.Data.ItemSchema]::DateTimeReceived
    #[Microsoft.Exchange.WebServices.Data.ItemSchema]::From
    )
    
    #Assign view to porperty set
    $view.PropertySet = $propertyset
    
    #Define the search quuery
    #An EWS exact search puts the search term in "" so if we need that then add the qoutes again)
    $query = "HasAttachments:True"
    
    #Perform the search
    $FoundEmails = $EWSservice.FindItems("Inbox",$query,$view)
    
    If ($FoundEmails)
    {
    	Foreach ($Email in $FoundEmails)
    	{
    		# DO YOUR STUFF :-)
    	}
    }

    /Lasse

    Monday, April 24, 2017 12:34 PM