I was recently involved in a Exchange Migration that also involved an Enterprise Vault migration. The company in question currently had about 2000 archived that needed to be moved into the Exchange on-prem mailboxes before migrating them to Exchange Online.

For that migration, we used Archive Shuttle from Quadrotech which does an amazing job migrating the emails back into the Exchange mailbox. But we ran into one issue that was unacceptable for the customer:

If a mail was archived when it was flagged (a to do flag was added to the email) and the mail was unflagged while it was in the Enterprise Vault archive, it would come back as flagged. This organization heavily relied on flagged items and some test users would end up with 300 unnecessary flagged items. The amount of extra flagged items was problematic and we had to find a solution for this.

ArchiveShuttle has no workaround this by default, so I had to resort to Exchange Web Services Powershell to do the job.

The workflow is pretty simple:

Export all the current flagged items Start the archive migration Wait for the migration to finish Remove all unnecessary flagged items

Getting all flagged items

Getting all flagged items from a mailbox is relatively simple. First I tried getting all the folders in an Exchange mailbox like this:

$RootFolder= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Root,$ImpersonatedMailboxName) $fvFolderView = New-Object Microsoft.Exchange.WebServices.Data.FolderView(31000) $fvFolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Deep; $folders = $service.FindFolders($RootFolder,$fvFolderView)

But if you then loop over all the folders and filter for flagged items, you will get all the flagged items twice. That’s because there is a folder in every mailbox called ‘To-Do Search’. So we can simply loop over every folder until we find the To-Do Search folder and then get all the flagged items from that one folder. We do this with the following code:

if($folder.DisplayName -eq "To-Do Search" -or $folder.DisplayName -eq "Zoeken naar taken"){ Write-Log "[INFO] - Found To Do folder" try{ $view = New-Object Microsoft.Exchange.WebServices.Data.ItemView 300 $filter = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+Exists(new-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x1090, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer)) $items = $folder.FindItems($filter, $view) Write-Log "[INFO] - Got all items" } catch{ Write-Log "[ERROR] - Getting all items from folder $($folder.DisplayName)" Write-Log "$($_.Exception.Message)" } #Check all items to see which item is flagged Foreach($item in $items){ if($item.flag.FlagStatus -eq "Flagged"){ #Found a flagged item, add it to our collection $flagged += $item } } Break }

Now that we have all the flagged items we can easily export them to a CSV. Check out the full script on Github. This also does some other (smaller) tasks:

Get some basic info of the users

Export the flags when the user has an EV archive

Cleaning up the flags

After the migration we will have to compare the current flagged items vs the flagged items we previously exported. If an item is flagged after the migration, but wasn’t flagged before the migration (and wasn’t created after we exported the flags), it means this flag isn’t current and we will have to unflag it.

The code below does exactly that

Write-Log "[INFO] - Starting Function Invoke-FlagComparision" $CSVCreated = Get-Date $CSVCreated -Hour 0 -Minute 0 -Second 0 Write-Log "[INFO] - CSV Datetime is $CSVCreated" Write-Log "[INFO] - Starting foreach to enumerate over all current flagged items" foreach($flag in $currentFlags){ Write-Log "[INFO] - Checking current flagged item $($flag.Subject) received on $($flag.DateTimeReceived) with InternetMessageID $($flag.InternetMessageId)" if($flag.Flag.StartDate -ge (Get-Date $CSVCreated -Format "yyyy/MM/dd HH:mm:ss")){ Write-Log "[INFO] - Flag was created - $($flag.Flag.StartDate) - after CSV file was generated - $CSVCreated - so it is current" $flagCurrent = $true } else{ Write-Log "[INFO] - Flag wasn't been created - $($flag.Flag.StartDate) - after CSV file was generated - $CSVCreated" $flagCurrent = $false foreach($previousFlagged in $previousFlags){ Write-Log "[INFO] - Comparing item with previous flagged item $($previousFlagged.Subject) received on $($previousFlagged.DateTimeReceived) with InternetMessageID $($previousFlagged.InternetMessageId)" if($flag.InternetMessageId -eq $previousFlagged.InternetMessageId){ Write-Log "[INFO] - Match found, flag is current" $flagCurrent = $true Break } } } if($flagCurrent -eq $false){ Write-Log "[INFO] - No match found, flag is not current. Removing flag" Set-ItemCompleted -MailItem $flag } Write-Log "[INFO] ------- Foreach looped ------------" }

The first for-each loop will loop over every flag that is currently in the mailbox. If that flag was created after we exported the flags, we don’t want to remove it. If it wasn’t, we will loop over every flag we previously exported and compare it with the current flag by it’s InternetMessageID. If there is a match, we won’t remove the flag. It there isn’t a match, the flag isn’t current and should be removed.

I choose to not completely remove the flags, but to complete the flags. If something goes wrong, we have a backup. Completing the flag is pretty easy:

$MailItem.flag.FlagStatus = "Complete" $MailItem.Update([Microsoft.Exchange.WebServices.Data.ConflictResolutionMode]::AutoResolve);

All actions are logged in a text file off course if anything should go wrong.

The full ‘cleanup’ script is also available through GitHub.

Feel free to use this script in product or contact me if you want to customize it.

Happy scripting!