Move email messages from a source folder to a target folder with EWS Managed API 2.2, in Exchange Online (including archive mailbox folders)

I have recently heard that there are customers interested in finding a way to move hundreds of emails from a source folder located in their primary mailbox, to a target folder from within their archive mailbox. Although, I am still an apprentice when it comes to programming, I have managed to create the script below which helped me achieve this task by using EWS Managed API and Application Impersonation. The script is taking the email messages from one folder and moving them to another one, no matter if the folders are located in the primary mailbox or in the archive mailbox. I hope you'll find it useful as well.

 

Prerequisites:

-Create a new RBAC group from Exchange Admin Center – Permissions – Admin Roles. Add the ‘ApplicationImpersonation’ role to the group and add as member the service account that will impersonate the mailbox where the folders are created (it can be your Global Admin account).

-Create a log file under C:\Temp\Log.txt.

-Make sure that the source and the target folders have unique names within the folder structure they're part of (primary or archive mailbox folder structure). If they don't, you can simply rename them, run the script, and then give them back their original names.

 

 DISCLAIMER: This application is a sample application. The sample is provided "as is" without warranty of any kind. Microsoft further disclaims all implied warranties including without limitation any implied warranties of merchantability or of fitness for a particular purpose. The entire risk arising out of the use or performance of the samples remains with you. in no event shall Microsoft or its suppliers be liable for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss arising out of the use of or inability to use the samples, even if Microsoft has been advised of the possibility of such damages). Because some states do not allow the exclusion or limitation of liability for consequential or incidental damages, the above limitation may not apply to you.

# The script requires EWS Managed API 2.2, which can be downloaded here:
 https://www.microsoft.com/en-gb/download/details.aspx?id=42951
# Make sure the Import-Module command matches the Microsoft.Exchange.WebServices.dll location of EWS Managed API, chosen during the installation


 [string]$warning = "Yellow"                      # Color for warning messages
 [string]$error = "Red"                           # Color for error messages
 [string]$LogFile = "C:\Temp\Log.txt"             # Path of the Log File


#Read the data
$MailboxName = Read-Host -Prompt 'Please type your mailbox name'

$a = Read-Host -Prompt 'Please type the source folder name'
$b = Read-Host -Prompt 'Where is the source folder located? (Type "primary" or "archive")'
$c = Read-Host -Prompt 'Please type the target folder name'
$d = Read-Host -Prompt 'Where is the target folder located? (Type "primary" or "archive")'

$i=0;
$FolderId = @();
$FolderName = "$a","$c"
$Location = "$b","$d"

Import-Module -Name "C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll"

$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService -ArgumentList Exchange2013_SP1

#Provide the credentials
$credential = Get-Credential -Message "Provide the credentials of the O365 account that has impersonation rights on the mailbox $($MailboxName)"

$service.Credentials = new-object Microsoft.Exchange.WebServices.Data.WebCredentials -ArgumentList $credential.UserName, $credential.GetNetworkCredential().Password

#Exchange Online URL
$service.Url= new-object Uri("https://outlook.office365.com/EWS/Exchange.asmx")

#User to impersonate
$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress,$MailboxName)

While ($i -lt 2)
{
if ($Location[$i] -eq 'primary')
{
$FolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(100)

$FolderView.Traversal = [Microsoft.Exchange.Webservices.Data.FolderTraversal]::Deep

$SearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$FolderName[$i])

$FindFolderResults = $service.FindFolders([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$SearchFilter,$FolderView)

if($FindFolderResults.Id)
 {
Write-host "The folder" $FolderName[$i] "was successfully found in the primary mailbox" -ForegroundColor $warning

$FolderId += $FindFolderResults.Id

$i++;

continue;
}
else
{  Write-host "The folder" $FolderName[$i] "was not found in the primary mailbox" -ForegroundColor $error; $i++; continue;}
}
else
{
$j=0;

$Mbx = (Get-Mailbox $MailboxName)

if ($Mbx.ArchiveStatus -eq 'Active'){  $guid=($Mbx.ArchiveGuid).ToString();

(Get-MailboxFolderStatistics $guid).Name | % { $j++;

if ($FolderName[$i] -match $_) {

Write-host "The folder" $FolderName[$i] "was successfully found in the archive mailbox" -ForegroundColor $warning

$AFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(100)

$AFolderView.Traversal = [Microsoft.Exchange.Webservices.Data.FolderTraversal]::Deep

$ASearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::DisplayName,$FolderName[$i])

$AFindFolderResults = $service.FindFolders([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::ArchiveMsgFolderRoot,$ASearchFilter,$AFolderView)

$FolderId += $AFindFolderResults.Id

$i++;

continue;
 }

else { if ($j -eq (Get-MailboxFolderStatistics $guid).Count) { Write-Host "All the archive folders have been checked and" $FolderName[$i] "was not found" -ForegroundColor $warning; $i++; continue; } }
}
}
else { Write-host "The archive mailbox is not enabled." -ForegroundColor $warning; $i++; continue; }
}
 }

if($FolderId.Count -eq 2)  {

$ItemView = new-object Microsoft.Exchange.WebServices.Data.ItemView(1000)

do
 {
$FindItemResults = $service.FindItems($FolderId[0],$ItemView)

write-host $FindItemResults.TotalCount "items have been found in the Source folder and will be moved to the Target folder."

foreach ($Item in $FindItemResults.Items)
{
$Message = [Microsoft.Exchange.WebServices.Data.EmailMessage]::Bind($service,$Item.Id)

$Message.Move($FolderId[1])
}

$ItemView.offset += $FindItemResults.Items.Count

}while($FindItemResults.MoreAvailable -eq $true)
}
else {  Write-host "Check the source and the target folders. Make sure they're unique and they have valid names." -ForegroundColor $error }

#Catch the errors
 trap [System.Exception]
 {
 Write-host ("Error: " + $_.Exception.Message) -foregroundcolor $error;
 Add-Content $LogFile ("Error: " + $_.Exception.Message);
 continue;
 }

 

Note: In order to run the script above, you have to copy paste it into a Notepad file and save it with the extension ".ps1". Then, you have to connect to Exchange Online with PowerShell and run it: https://technet.microsoft.com/en-us/library/jj984289(v=exchg.160).aspx.