Exchange Auditing, Calendar Logging and @MigrationWiz mailbox migrations with @BitTitan

Before you read this post, please have a look at this Microsoft article about the Recoverable Items Folder in Exchange Online.

Two of the hidden folders you will find within the Recoverable Items, are there to log changes done to the mailbox:

  • Audits: If mailbox audit logging is enabled for a mailbox, this sub-folder contains the audit log entries. To learn more about mailbox audit logging, see Export mailbox audit logs in Exchange Online.
  • Calendar Logging: This sub-folder contains calendar changes that occur within a mailbox. This folder isn’t available to users.

Note: The folder “Versions” does keep track of multiple versions of a changed item, so in theory it also logs changes, but it’s not relevant for this post.

Dumpster01

Above you can see the structure of a mailbox and recoverable items.

So why is this important in the context of a migration?

When you leverage MigrationWiz to migrate mailboxes into a new Office 365 tenant, and because those tenants will have enabled by default both the Audit and the Calendar logging, the tool will create a lot of logs in those folders and in some extreme cases when the logs created are in a large number, it will slow down your migration.

It’s also important to state that the logs created on those folders are for the changes made by the migration tool. It logs what MigrationWiz changes in the process of the migration.

Dumpster02

The above is a warning thrown by MigrationWiz, stating that the folder “Audits”, in the recoverable deleted items, has more items than it should. Technically any folder in the recoverable deleted items can have up to 3 million items, but because MigrationWiz leverages EWS (Exchange Web Services), when the count goes over 100k items we will see and surface warning messages from Exchange.

Can you still create up to 3 million items? Yes, you should be able to, but the migration will slow down considerably.

Again, I want to stress that those items are probably in its majority the result of audit and calendar logging, and not necessarily items being migrated from the Audits source folder.

So how can we mitigate this?

Because the way to deal with Audit and Calendar logging is different, I am going to address it below, separately. Basically the solution, although applied separately and differently, is to disable Audit logging and/or disable Calendar logging, during the migration.

Before I proceed, and explain how you can do it, I have to state that both Audit and Calendar logging are security and compliance features in Exchange Online and on premises, so it’s ultimately up to you to decide if you should temporarily disable it or not. One thing that you should take into account, is if those mailboxes are being used by the end users or just for migration purposes? If it’s just migration purposes and if you are migrating mailboxes with very large item counts, then think of this as an option, since at that point there’s no end user actions to be logged.

Read more about Mailbox Audit logging in Exchange Server.

Microsoft doesn’t have a lot of official documentation on Calendar logging, but I’ll explain how you can disable it during the migration.

Mailbox Audit logs

There are several ways to disable Audit Logging in Exchange Online:

  • Disable Audit at the Organization Level
  • Disable Audit per mailbox

Read this article to understand how to Manage Mailbox auditing.

So let’s look at a mailbox without audit disabled:

AUdit1

As you can see above, the mailbox has audit enabled and is auditing all actions by admins, delegates and owner. We will not completely disable auditing in the mailbox, as it’s not needed. All we will do is disable Admin audit, since that is the only one that audits the impersonation access granted to MigrationWiz.

This is the recoverable items of the destination mailbox, before the migration:

Audit2

As you can see above, this being a brand new mailbox, the Audits folder is not even created.

And when migrating, we see the following:

Audit3

The count in the Audits folder went up to 6 items. Now lets see if that count matches what MigrationWiz migrated:

Audit4

Perfect match. So the bottom line here is that, as MigrationWiz copies the data into the destination mailbox, Exchange Online will Audit each action for each item as an Admin access to the destination mailbox. That can become a problem for mailboxes with hundreds of thousands of items, and a bigger problem when you are actually using MigrationWiz to move from recoverable items to recoverable items.

So now lets try the same migration but without Audit logging. Execute the following Exchange Management Shell cmdlet:

Set-Mailbox Peter.Smith -Auditadmin $null

Note: You might need to wait up to one hour (might take longer sometimes), after the changes are applied and before you migrate.

We will not completely disable auditing in the mailbox, as it’s not needed. All we will do is disable Admin audit, since that is the only one that audits the impersonation access granted to MigrationWiz.

Lets look at the results:

Audit9

I listed the entire mailbox just so you can see that the Inbox content was moved, but the Audits folder is still empty. Actually the Audits folder wasn’t even created because no audits were done and the mailbox is new.

Finally lets put the Auditing setting back to active. Don’t forget to re enable those settings, otherwise Admin auditing will stay disabled!

Execute the following Exchange Management Shell cmdlet:

Set-Mailbox Peter.Smith -DefaultAuditSet Delegate,Owner,Admin

To make sure the settings were applied you can run the command below:

Audit10

And that’s it regarding Audit logging. In summary, it’s up to you to run the migration with or without audit logging enabled for admin access, but in my opinion, temporarily disabling it during the migration might prevent some issues and be beneficial.

Calendar Logging

Exchange Online calendar logging will track changes of calendar items. Those changes will be stored in the recoverable deleted items, inside the “Calendar Logging” folder.

Just like with the Audits, when you are migrating data with MigrationWiz, in this specific case when you are migrating calendar items, the calendar logging folder can get a large volume of items, due to the logging feature being enabled.

The logic behind disabling it here is the exact same, and so are the reasons to consider it and decide if you want to do it or not.

Now lets look at how we disable calendar logging:

Audit5

As you can see above, there’s a property at the mailbox level named “CalendarVersionStoreDisabled“. By default that value is set to “False“. Lets see what happens when we migrate calendars with the option set like this:

Audit6

As you can see above the Calendar Logging is 7. Below you’ll see that the total number of calendar items migrated was 4. Depending on the meeting type (single, recurring, etc) the number of logged events in calendar might vary, and it’s not always 1 per event migrated.

Audit7

Again, above you can see in yellow the number of calendar events is 4. The default calendar (United States Holidays) migration does not get logged.

Now lets see how can we remove the logging. First start by running the command below:

Set-Mailbox Peter.Smith -CalendarVersionStoreDisabled $true

Note: You might need to wait up to one hour (might take longer sometimes), after the changes are applied and before you migrate.

Lets look at the destination mailbox after the migration, when logging is disabled:

Audit8

Above you’ll see that 4 calendar items got migrated but no Calendar logging was done.

Now how do you revert back the changes?

Set-Mailbox Peter.Smith -CalendarVersionStoreDisabled $false

Just set the value back to false. It’s very important to understand that those logging features should be enabled, so make sure you revert the changes done during migration.

The Calendar Logging done during migration is, just like for audits, even more problematic if you are migrating from recoverable items to recoverable items.

Bottom line

In this blog post we discussed how Auditing and Calendar logging in Exchange, might have an impact in your mailbox migration. It’s important to understand that those features are super important and should ideally be enabled, but consider the following:

  • do you really want to log 150 changes to calendar items in John’s mailbox when they were all done by a migration account in the context of a migration?
  • and how will that impact future log searches as part of a compliance process?
  • how about the 50000 audits of mailbox items being moved? do you need those, them being done in the context of a migration?
  • finally, if you’re migrating the recoverable items folder, you’re technically duplicating every audit log that exists in the source, because MigrationWiz will move the audit log entry and create a new one as part of auditing the move

The main reason for this blog post is to prepare you for some potential delays, if you are migrating large item counts with auditing enabled, but also to explain how you can disable it and, in my opinion, have a cleaner destination without a lot of logging that might not be as relevant.

Ultimately, it’s your decision to use this information as you see more fit for your organization.

 

 

 

 

 

 

How to use MigrationWiz to migrate Public Folder calendars into mailbox calendars

It’s very common to see, in Public Folder migrations, customers that want to migrate and transform that data. But how exactly is that done?

If you’re familiar with MigrationWiz, you’ll know that to migrate data all you have to do is follow some simple steps, like configuring access to source and destination, creating the migration project and defining, within the project, what’s the source and the destination.

The steps above are as simple as they sound, however, to transform data, you’ll need to do some advanced configurations. MigrationWiz gives you flexibility that probably no other tool does, by allowing you to filter or map (I’ll elaborate in a second), which are the foundation features to transform data, but to do so properly, you need to configure your project accordingly.

So how exactly should you configure a project, to migrate a Public Folder calendar into a mailbox calendar?

I won’t give you details about the basic steps to create a project, you can look for the migration guides in the BitTitan HelpCenter, but basically you need to create a normal Public Folder project and do some changes to it.

The first and more basic change you need to do is to set mailbox as a destination.

PFShare01

Within the advanced options of your MigrationWiz project, go to the Destination settings and select “Migrate to Shared Mailbox”.

Now that you have your destination defined, add the Calendar Public Folder that you want to migrate, to your MigrationWiz project, and the correspondent destination mailbox address.

PFShare02

So now that you have your 1:1 matching done in the project, can you migrate? The answer is no, but lets see what happens if you do.

PFShare03

What you are seeing above is the PowerShell output that lists all folders, after the migration, for the destination mailbox. So what happened?

Basically instead of putting all data into the default calendar folder at the destination, we created 2 new folders, of type IPF.Appointment (Calendar folders), in that mailbox.

What this means for the end user is that he will see 2 new calendars, “Folder1” that will be empty since it had no calendar data at the source and “MyCalendarFolder1” that will have all data. Additionally the default Calendar folder won’t have any migrated data.

The above is rarely the intended goal, so just migrating is usually not the solution. You’ll need some additional configurations. Lets get to it.

PFShare04

Edit the line item you added previously and in the Support options add a Folder mapping.

The regex in this folder mapping basically moves all source data to the destination folder called “Calendar”. Since the mapping is in place and it has a defined destination, we no longer create any folders in the destination. It’s also the mapping that makes all data be copied into that destination folder.

So with the configuration above all data will be into what eventually would be the folder you want. If you adjust the filter you can put it in whatever folder you want, having in mind that if the folder doesn’t exist we will create it.

Hope that helps and happy migrations!!

 

BitTitan SDK: Color code your MigrationWiz project users

The BitTitan SDK is a key feature for all Enterprise migration projects. Some tasks, in large migration projects, are better being automated. It will save you hundreds of hours of repetitive work.

Just recently I got asked by a partner for a way to easily execute actions in batches of users, within the same MigrationWiz project. Some times the best option is to divide those users into separate projects and just execute those actions for all the users, but that’s not always the best option.

Now imagine this scenario: You have a project with 10000 users and you need to start a migration to just 800 of them. What should you do? Color code those users, filter by that color and execute the action.

(More information about color coding here on the BitTitan help center)

So how can you categorize 800 users in a project with 10000? Using the BitTitan SDK, of course.

The script below, that you can also find here, can be used to automatically color code your MigrationWiz users, based on a CSV file.

Below there’s a sample of the CSV file. It’s needs two columns:

  • Source Email – the MigrationWiz source email address
  • Flags – A number between 1 and 6. Each has its individual color.

CSVCategories

The execution is as follows:

  1. Prompt to authenticate with BitTitan credentials
  2. Prompt you to select the BitTitan workgroup where the MigrationWiz project is
  3. Prompt you to select the BitTitan Customer where the MigrationWiz project is
  4. Prompt you to select the MigrationWiz project
  5. Enter the full path of the CSV file (i.e C:\scripts\MyUsers.csv)
<#

.DESCRIPTION

This script will move mailboxes from a mailbox project to a target project

    

.NOTES

    Author          Antonio Vargas

    Date         Jan/2019

    Disclaimer:     This script is provided 'AS IS'. No warrantee is provided either expressed or implied.

Version: 1.1

#>

### Function to create the working and log directories

Function Create-Working-Directory {

param

(

[CmdletBinding()]

[parameter(Mandatory=$true)] [string]$workingDir,

[parameter(Mandatory=$true)] [string]$logDir

)

if ( !(Test-Path-Path $workingDir)) {

        try {

            $suppressOutput = New-Item -ItemType Directory -Path $workingDir -Force -ErrorAction Stop

$msg="SUCCESS: Folder '$($workingDir)' for CSV files has been created."

Write-Host-ForegroundColor Green $msg

        }

        catch {

$msg="ERROR: Failed to create '$workingDir'. Script will abort."

Write-Host-ForegroundColor Red $msg

Exit

        }

}

if ( !(Test-Path-Path $logDir)) {

try {

$suppressOutput=New-Item-ItemType Directory -Path $logDir-Force -ErrorAction Stop

$msg="SUCCESS: Folder '$($logDir)' for log files has been created."

Write-Host-ForegroundColor Green $msg

}

catch {

$msg="ERROR: Failed to create log directory '$($logDir)'. Script will abort."

Write-Host-ForegroundColor Red $msg

Exit

}

}

}

### Function to write information to the Log File

Function Log-Write

{

param

(

[Parameter(Mandatory=$true)] [string]$Message

)

$lineItem="[$(Get-Date-Format "dd-MMM-yyyy HH:mm:ss") | PID:$($pid) | $($env:username) ] "+$Message

    Add-Content -Path $logFile -Value $lineItem

}

### Function to display the workgroups created by the user

Function Select-MSPC_Workgroup {

#######################################

# Display all mailbox workgroups

#######################################

$workgroupPageSize=100

  $workgroupOffSet = 0

    $workgroups = $null

Write-Host

Write-Host-Object "INFO: Retrieving MSPC workgroups ..."

do

{

$workgroupsPage=@(Get-BT_Workgroup-PageOffset $workgroupOffSet-PageSize $workgroupPageSize)




if($workgroupsPage) {

$workgroups+=@($workgroupsPage)

foreach($Workgroupin$workgroupsPage) {

Write-Progress-Activity ("Retrieving workgroups ("+$workgroups.Length+")") -Status $Workgroup.Id

}

$workgroupOffset+=$workgroupPageSize

}

} while($workgroupsPage)

if($workgroups-ne$null-and$workgroups.Length-ge1) {

Write-Host-ForegroundColor Green -Object ("SUCCESS: "+$workgroups.Length.ToString() +" Workgroup(s) found.")

}

else {

Write-Host-ForegroundColor Red -Object "INFO: No workgroups found."

Exit

}

#######################################

# Prompt for the mailbox Workgroup

#######################################

if($workgroups-ne$null)

{

Write-Host-ForegroundColor Yellow -Object "ACTION: Select a Workgroup:"

Write-Host-ForegroundColor Gray -Object "INFO: your default workgroup has no name, only Id."

for ($i=0; $i-lt$workgroups.Length; $i++)

{

$Workgroup=$workgroups[$i]

if($Workgroup.Name-eq$null) {

Write-Host-Object $i,"-",$Workgroup.Id

}

else {

Write-Host-Object $i,"-",$Workgroup.Name

}

}

Write-Host-Object "x - Exit"

Write-Host

do

{

if($workgroups.count-eq1) {

$result=Read-Host-Prompt ("Select 0 or x")

}

else {

$result=Read-Host-Prompt ("Select 0-"+ ($workgroups.Length-1) +", or x")

}




if($result-eq"x")

{

Exit

}

if(($result-match"^\d+$") -and ([int]$result-ge0) -and ([int]$result-lt$workgroups.Length))

{

$Workgroup=$workgroups[$result]

Return$Workgroup.Id

}

}

while($true)

}

}

### Function to display all customers

Function Select-MSPC_Customer {

param

(

[parameter(Mandatory=$true)] [String]$WorkgroupId

)

#######################################

# Display all mailbox customers

#######################################

$customerPageSize=100

  $customerOffSet = 0

    $customers = $null

Write-Host

Write-Host-Object "INFO: Retrieving MSPC customers ..."

do

{

$customersPage=@(Get-BT_Customer-WorkgroupId $WorkgroupId-IsDeleted False -IsArchived False -PageOffset $customerOffSet-PageSize $customerPageSize)




if($customersPage) {

$customers+=@($customersPage)

foreach($customerin$customersPage) {

Write-Progress-Activity ("Retrieving customers ("+$customers.Length+")") -Status $customer.CompanyName

}

$customerOffset+=$customerPageSize

}

} while($customersPage)

if($customers-ne$null-and$customers.Length-ge1) {

Write-Host-ForegroundColor Green -Object ("SUCCESS: "+$customers.Length.ToString() +" customer(s) found.")

}

else {

Write-Host-ForegroundColor Red -Object "INFO: No customers found."

Exit

}

#######################################

# {Prompt for the mailbox customer

#######################################

if($customers-ne$null)

{

Write-Host-ForegroundColor Yellow -Object "ACTION: Select a customer:"

for ($i=0; $i-lt$customers.Length; $i++)

{

$customer=$customers[$i]

Write-Host-Object $i,"-",$customer.CompanyName

}

Write-Host-Object "x - Exit"

Write-Host

do

{

if($customers.count-eq1) {

$result=Read-Host-Prompt ("Select 0 or x")

}

else {

$result=Read-Host-Prompt ("Select 0-"+ ($customers.Length-1) +", or x")

}

if($result-eq"x")

{

Exit

}

if(($result-match"^\d+$") -and ([int]$result-ge0) -and ([int]$result-lt$customers.Length))

{

$customer=$customers[$result]

Return$Customer.OrganizationId

}

}

while($true)

}

}

### Function to display all mailbox connectors

Function Select-MW_Connector {

param

(

[parameter(Mandatory=$true)] [guid]$customerId

)

#######################################

# Display all mailbox connectors

#######################################




$connectorPageSize=100

  $connectorOffSet = 0

    $connectors = $null

Write-Host

Write-Host-Object "INFO: Retrieving mailbox connectors ..."




do

{

$connectorsPage=@(Get-MW_MailboxConnector-ticket $global:mwTicket-OrganizationId $customerId-PageOffset $connectorOffSet-PageSize $connectorPageSize)




if($connectorsPage) {

$connectors+=@($connectorsPage)

foreach($connectorin$connectorsPage) {

Write-Progress-Activity ("Retrieving connectors ("+$connectors.Length+")") -Status $connector.Name

}

$connectorOffset+=$connectorPageSize

}

} while($connectorsPage)

if($connectors-ne$null-and$connectors.Length-ge1) {

Write-Host-ForegroundColor Green -Object ("SUCCESS: "+$connectors.Length.ToString() +" mailbox connector(s) found.")

}

else {

Write-Host-ForegroundColor Red -Object "INFO: No mailbox connectors found."

Exit

}

#######################################

# {Prompt for the mailbox connector

#######################################

if($connectors-ne$null)

{




for ($i=0; $i-lt$connectors.Length; $i++)

{

$connector=$connectors[$i]

Write-Host-Object $i,"-",$connector.Name

}

Write-Host-Object "x - Exit"

Write-Host

Write-Host-ForegroundColor Yellow -Object "ACTION: Select the source mailbox connector:"

do

{

$result=Read-Host-Prompt ("Select 0-"+ ($connectors.Length-1) +" o x")

if($result-eq"x")

{

Exit

}

if(($result-match"^\d+$") -and ([int]$result-ge0) -and ([int]$result-lt$connectors.Length))

{

$global:connector=$connectors[$result]

Break

}

}

while($true)

}

}

Function Add-MW_Category {

param

(

[parameter(Mandatory=$true)] [Object]$Connector

)

# add items to a MigrationWiz project

$count=0

Write-Host

Write-Host-Object ("Aplying categories to migration item(s) in the MigrationWiz project "+$connector.Name)

    $importFilename = (Read-Host -prompt "Enter the full path to CSV import file")

    # read csv file

    $users = Import-Csv -Path $importFilename

    foreach($user in $users)

    {

     $sourceEmail = $user.'Source Email'

$flags=$user.'Flags'

        if($sourceEmail -ne $null -and $sourceEmail -ne "" -and $flags -in 1..6)

        {

$count++

Write-Progress-Activity ("Applying category to migration item ("+$count+")") -Status $sourceEmail

$mbx=get-mw_mailbox-ticket $mwTicket-ExportEmailAddress $sourceEmail

if ($mbx)

{

$Category=";tag-"+$flags+";"

$result=Set-MW_Mailbox-Ticket $mwTicket-ConnectorId $connector.Id-mailbox $mbx-Categories $Category

}

else

{

Write-Host"Cannot find MigrationWiz line item with source address: '$($sourceEmail)'"-ForegroundColor Yellow

}

}

else {

Write-Host"The line item with the address '$($sourceEmail)' and the flag '$($flags)' is not valid."-ForegroundColor Yellow

}

    }




if($count-eq1)

{

Write-Host-Object "1 mailbox has been categorized in",$connector.Name-ForegroundColor Green

}

if($count-ge2)

{

Write-Host-Object $count," mailboxes have been categorized in",$connector.Name-ForegroundColor Green

}

}

#######################################################################################################################

# MAIN PROGRAM

#######################################################################################################################

#Working Directory

$workingDir = "C:\scripts"

#Logs directory

$logDirName = "LOGS"

$logDir = "$workingDir\$logDirName"

#Log file

$logFileName = "$(Get-Date -Format yyyyMMdd)_Move-MW_Mailboxes.log"

$logFile = "$logDir\$logFileName"

Create-Working-Directory -workingDir $workingDir -logDir $logDir

$msg = "++++++++++++++++++++++++++++++++++++++++ SCRIPT STARTED ++++++++++++++++++++++++++++++++++++++++"

Log-Write -Message $msg

# Authenticate

$creds = Get-Credential -Message "Enter BitTitan credentials"

try {

# Get a ticket and set it as default

$ticket=Get-BT_Ticket-Credentials $creds-ServiceType BitTitan -SetDefault

# Get a MW ticket

$global:mwTicket=Get-MW_Ticket-Credentials $creds

} catch {

$msg="ERROR: Failed to create ticket."

Write-Host-ForegroundColor Red $msg

Log-Write -Message $msg

Write-Host-ForegroundColor Red $_.Exception.Message

Log-Write -Message $_.Exception.Message

Exit

}

#Select workgroup

$WorkgroupId = Select-MSPC_WorkGroup

#Select customer

$customerId = Select-MSPC_Customer -Workgroup $WorkgroupId

#Select connector

Select-MW_Connector-customerId $customerId

$result = Add-MW_Category -Connector $connector

$msg = "++++++++++++++++++++++++++++++++++++++++ SCRIPT FINISHED ++++++++++++++++++++++++++++++++++++++++`n"

Log-Write -Message $msg

##END SCRIPT
This is the link for my GitHub Gist, where all comments are welcomed regarding the code. Use the comment section as well if you want something changed.
You can find all my BitTitan SDK scripts in my GitHub repository.

BitTitan SDK: Retry individual errors for all users in your MigrationWiz document migration project

The BitTitan SDK is a key feature for all Enterprise migration projects. Some tasks, in large migration projects, are better being automated. It will save you hundreds of hours of repetitive work.

The script below, that you can also find in here, can be used to automatically retry errors in all users of your MigrationWiz project.

To retry errors in a user, he needs to:

  • Be in a “Completed” state
  • have at least one item error

The execution is as follows:

  1. Prompt to authenticate with BitTitan credentials
  2. Prompt you to select your MigrationWiz document project
  3. Identifies number of users eligible for a retry errors pass
  4. Exports to a CSV, created in the same folder from where the script was executed, a list of all successfully initiated retry errors passes
<#



.DESCRIPTION

This script needs to be run on the BitTitan Command Shell

    

.NOTES

.Version        1.0

    Author          Antonio Vargas

    Date            Feb/13/2019

Disclaimer: This script is provided ‘AS IS’. No warrantee is provided either expresses or implied.

    Change Log

#>

######################################################################################################################################################

# Main Program

######################################################################################################################################################

$connectors = $null

#Working Directory

$global:workingDir = [environment]::getfolderpath("desktop")

#######################################

# Authenticate to MigrationWiz

#######################################

$creds = $host.ui.PromptForCredential("BitTitan Credentials", "Enter your BitTitan user name and password", "", "")

try {

$mwTicket=Get-MW_Ticket-Credentials $creds

} catch {

write-host"Error: Cannot create MigrationWiz Ticket. Error details: $($Error[0].Exception.Message)"-ForegroundColor Red

}

#######################################

# Display all document connectors

#######################################

Write-Host

Write-Host -Object "Retrieving Document connectors ..."

Try{

$connectors=get-mw_mailboxconnector-Ticket $mwTicket-RetrieveAll -ProjectType Storage -ErrorAction Stop

}

Catch{

Write-Host-ForegroundColor Red -Object "ERROR: Cannot retrieve document projects."

Exit

}

if($connectors -ne $null -and $connectors.Length -ge 1) {

Write-Host-ForegroundColor Green -Object ("SUCCESS: "+$connectors.Length.ToString() +" document project(s) found.")

}

else {

Write-Host-ForegroundColor Red -Object "ERROR: No document projects found."

Exit

}

#######################################

# {Prompt for the document connector

#######################################

if($connectors -ne $null)

{

Write-Host-ForegroundColor Yellow -Object "Select a document project:"

for ($i=0; $i-lt$connectors.Length; $i++)

{

$connector=$connectors[$i]

Write-Host-Object $i,"-",$connector.Name,"-",$connector.ProjectType

}

Write-Host-Object "x - Exit"

Write-Host

do

{

$result=Read-Host-Prompt ("Select 0-"+ ($connectors.Length-1) +" or x")

if($result-eq"x")

{

Exit

}

if(($result-match"^\d+$") -and ([int]$result-ge0) -and ([int]$result-lt$connectors.Length))

{

$connector=$connectors[$result]

Break

}

}

while($true)

#######################################

# Get mailboxes

#######################################

$mailboxes=$null

$MailboxesWithErrors=@()

$MailboxErrorCount=0

$ExportMailboxList=@()

Write-Host

Write-Host-Object ("Retrieving mailboxes for '$($connector.Name)':")

Try{

$mailboxes=@(Get-MW_Mailbox-Ticket $mwTicket-ConnectorId $connector.Id-RetrieveAll -ErrorAction Stop)

}

Catch{

Write-Host-ForegroundColor Red "ERROR: Failed to query users in project '$($connector.Name)'"

Exit

}

Foreach ($mailboxin$mailboxes){

$LastMigration=get-MW_MailboxMigration-ticket $mwTicket-MailboxID $mailbox.id|? {$_.Type-ne"Verification"} |Sort-Object-Property Startdate -Descending |select-object-First 1

if ($LastMigration.Status-eq"Completed"){

try{

$MailboxErrors=get-mw_mailboxerror-ticket $mwTicket-mailboxid $mailbox.id-severity Error -erroraction Stop

}

Catch{

Write-Host-ForegroundColor Yellow "WARNING: Cannot find errors for mailbox '$($mailbox.ExportEmailAddress)'"

}

if (-not ([string]::IsNullOrEmpty($MailboxErrors))){

$MailboxesWithErrors+=$mailbox

$MailboxErrorCount=$MailboxErrorCount+$MailboxErrors.count

}

}

}

if($MailboxesWithErrors-ne$null-and$MailboxesWithErrors.Length-ge1)

{

Write-Host-ForegroundColor Green -Object ("SUCCESS: "+$MailboxesWithErrors.Length.ToString() +" mailbox(es) elegible to retry errors found")

Write-Host-ForegroundColor Green -Object ("SUCCESS: '$($MailboxErrorCount)' individual errors found that will be retried")

$RetryMigrationsSuccess=0

Foreach ($mailboxwitherrorsin$MailboxesWithErrors){

try{

$RecountErrors=get-mw_mailboxerror-ticket $mwTicket-mailboxid $mailboxwitherrors.id-severity Error -erroraction Stop

$result=Add-MW_MailboxMigration-ticket $mwTicket-mailboxid $mailboxwitherrors.id-type Repair -ConnectorId $connector.id-userid $mwTicket.userid-ErrorAction Stop

write-host-ForegroundColor Green "INFO: Processing $($mailboxwitherrors.ExportEmailAddress) with $($RecountErrors.count) errors"

$ErrorLine=New-Object PSCustomObject

$ErrorLine|Add-Member-Type NoteProperty -Name MailboxID -Value $mailboxwitherrors.id

$ErrorLine|Add-Member-Type NoteProperty -Name "Source Address"-Value $mailboxwitherrors.ExportEmailAddress

$ErrorLine|Add-Member-Type NoteProperty -Name "Destination Address"-Value $mailboxwitherrors.ImportEmailAddress

$ErrorLine|Add-Member-Type NoteProperty -Name "Error Count"-Value $RecountErrors.count

$ExportMailboxList+=$ErrorLine

$RetryMigrationsSuccess=$RetryMigrationsSuccess+1

}

Catch{

Write-Host-ForegroundColor Red "ERROR: Failed to process $($mailboxwitherrors.ExportEmailAddress). Error details: $($Error[0].Exception.Message)"

}

}

if ($RetryMigrationsSuccess-ge1){

Write-Host-ForegroundColor Yellow "INFO: $($RetryMigrationsSuccess) retry migrations executed. Exporting List to CSV."

$ExportMailboxList|Export-CSV .\List-UsersWithErrors.csv -NoTypeInformation

}

Else{

Write-Host-ForegroundColor Yellow "INFO: No retry migration passes were executed with success."

}

}

else

{

Write-Host-ForegroundColor Yellow "INFO: no users in project '$($connector.Name)' qualify for a retry errors pass. Make sure the users are in a completed state and have individual item errors logged."

Exit

}

}
This is the link for my GitHub Gist, where all comments are welcomed regarding the code. Use the comment section as well if you want something changed.
You can find all my BitTitan SDK scripts in my GitHub repository.

Office 365: How to scope impersonation when migrating to and from Exchange Online

When you’re migrating to or from a Microsoft Exchange system, using an awesome tool like the BitTitan MigrationWiz, that leverages the Exchange Web Services (EWS) for the migration, you have 2 main options for administrator access to the mailboxes you’re migrating to and/or from: Impersonation and Delegation.

The best option depends on if the Exchange server is the online version (Office 365 multi tenant) or on premises. For Exchange on premises you should use delegation and for Exchange Online you should use impersonation. Why? Because you can’t create throttling policies in Exchange Online and impersonation is much less subject to throttling, when compared with delegation.

It’s important to understand that impersonation will also be subject to throttling, just not as much as delegation. When you’re migrating with delegation, all actions are done on behalf of the admin account, as opposed to when you’re migrating with impersonation, where actions are made on behalf of the account that is being migrated and impersonated.

Now that we can all agree that impersonation is the best authentication method for Exchange multi tenant systems (by the way this also applies to hosted Exchange systems outside of Office 365, but setting up impersonation on those systems might be somethings Hosters won’t do, unless they scope it, which they usually don’t), lets discuss the topic of this post: How can you scope impersonation? What exactly does that mean? And when will this be useful?

The answer for the first question is that you can scope impersonation by using management scopes and management role assignments.

As for the second question, scoping the impersonation rights means basically that the admin account will only be able to impersonate the accounts you define within that scope filter.

Finally the third question: this is useful when, for security reasons, you (or someone from the security team of the source or destination tenant) don’t want the admin account, that will perform the migration, to have access to impersonate all users in the tenant. This is a very common scenario in mergers, acquisitions and divestitures, where the admin user doesn’t need access to users that are not part of the migration.

Now lets translate all of this into a step by step guide of what you need to do, in order to scope impersonation in your Office 365 tenant.

Step 1: Create a distribution group

There are many different ways to apply a filter into a scope, and limit a management role assignment such as Application Impersonation, to a specific scope. I will teach you a simple way: via group membership.

You can create the group via the 365 management console or via the PowerShell. I recommend that you create a simple @tenantname.onmicrosoft.com group as shown below (apologies, my current Office 365 tenant is in Portuguese, but I guess you can all recognize and understand the UI 🙂 ):

Group1

TIP: If you create this group just for the purpose of scoping impersonation, I recommend that you hide the group from the Global address list.

Now that the group is created, lets retrieve its DistinguishedName property:

Get-DistributionGroup -Identity AllowImpersonationDistributionGroup |fl name, dist*

group2

Note: In the command above use your own Distribution group name, as you created it, or just run the Get-DistributionGroup without specifying the identity, and grab the DistinguishedName from the correct group (all will be listed).

Step 2: Create a Management Scope

Create a new management scope, use the “RecipientRestrictionFilter” parameter and the “MemberOfGroup” filter:

New-ManagementScope RestrictedMigrationScope -RecipientRestrictionFilter {MemberOfGroup -eq ‘CN=AllowImpersonationDistributionGroup,OU=tenantname.onmicrosoft.com,OU=Microsoft Exchange Hosted Organizations,DC=EU
RP193A002,DC=PROD,DC=OUTLOOK,DC=COM’}

group3

Note: To run the command above you might need to enable the Organization customization in your Office 365 tenant.

Step 3: Create the Management Role Assignment

Now that we have the scope created the last step is to create the management role assignment and associate it with the admin migration account:

New-ManagementRoleAssignment -Name:MyMigration -Role:ApplicationImpersonation -User:user10@
myexchlab.com -CustomRecipientWriteScope:RestrictedMigrationScope

group4.JPG

Note: In the command above use the scope name and the admin account that you are using for the migration. For my migration the admin is user10@myexchlab.com

And that is it, job done. Lets do some testing.

Below you can see users 1 to 5, that I will be migrating, and user10 that I will use as an admin.

group5

Now looking at the group membership you can see that only user1, user3 and user5 are withing the scope, which means that user10 won’t be able to impersonate users 2 and 4.

group6

Finally the result in MigrationWiz, in a project configured to use impersonation and with user10 as the source admin.

group7

As you can see above, users 2 and 4 failed, and here’s the detailed error.

group8

Bingo… MigrationWiz failed to impersonate at the source. Of course now that you read this that error will never happen to you!! 🙂

Happy migrations and as usual if you have any questions let me know.

 

Public Folders to Office 365 groups, anyone?

Just recently, the Microsoft Exchange Team reached out to the public again asking if partners or customers out there are interested in moving from the well known ancient Exchange Public Folders (and yes they can be modern or legacy) to the new Office 365 groups.

In the Microsoft Exchange Team Blog they state that the main focus will for now be to migrate mail and calendar items, but they do leave the “door open” to future support to be extended to other item types. How awesome would it be to convert Public Folder posts into OneNote in the Office 365 groups, right? 🙂

As a final note, they also state that, this will be focused in source Public Folders that can be on-premises or in Office 365.

Now before we look at all migration (and in some cases conversion) options, lets look at all the item types that you can have in when you create a Public Folder:

  • Mail and Post
  • Calendar
  • Contact
  • Note
  • Tasks
  • Journal
  • InfoPath forms
  • Documents

So what can you move to Office 365 groups and how? Ideally you would migrate mails and calendars into the Office 365 group mailbox, documents into the Office 365 group document library and Posts into OneNote. That I’d say would cover a huge percentage of the Public Folder usage we have out there today, don’t you agree? Hopefully if you’re reading this post, and thinking on doing the conversion from Public Folders to Office 365 groups, the Public Folder usage of your organization is focused in mail, calendars, contacts, posts and documents.

So now your question might be: is there any option out there that can migrate at least part of this content, from Public folders to Office 365 groups? And the answer is YES!

BitTitan MigrationWiz will help you do the following migrate from Public Folders (Exchange 2007+ on premises or Exchange Online) into Office 365 groups. Not all content will be moved, due to limitations in the EWS API, currently used by MigrationWiz to migrate Public Folders as a source, but here’s what you should expect:

  • Emails get migrated from mail enabled Public Folders into the Office 365 group mailbox. They will show in the conversation section.
  • Posts get migrated from Public Folders into the Office 365 group mailbox (attachments included). They will show in the conversation section.
  • Calendar items get migrated from the Public Folder calendar into the Office 365 group mailbox calendar.
  • Office 365 groups do not have a way of showing shared contacts and tasks, therefore you should not try and migrate them.
  • Documents do not get migrated from Public Folders to the Office 365 group SharePoint library.

Migrating data between different systems (Public Folders vs Office 365 groups) is not all about the question “Can the data be migrated or not?”. A good example is what I highlighted above about the tasks and contacts, why?

  1. MigrationWiz can migrate the tasks and the contacts
  2. The tasks and the contacts, after being migrated, will show up in the statistics of the mailbox (that you can extract via PowerShell)
  3. The design of the Office 365 groups won’t allow your users to see those tasks and contacts in any of the clients where you can use to access the groups
  4. That makes migrating contacts and tasks a pointless exercise

Finally some high level tasks on how to perform the migration:

  1. Create MigrationWiz Public Folder project
  2. Edit the advanced options a change the destination to shared mailbox
  3. add in the advanced support options: RemoveFilterBasedOnFolderType=1
  4. Add one line item per item type: One for email and one for calendars
  5. Add folder mapping to each line item to map the items into the correct folder in the destination (i.e FolderMapping=”^.*$->Calendar”)

Like stated in the disclaimer below, BitTitan does not have currently a migration guide for this scenario. If you want more detailed steps please leave a comment and I will reach out.

Disclaimer: To migrate from Public Folders to Office 365 groups I used the BitTitan feature of migration from Public Folders to Shared mailboxes. Technically an Office 365 group has a mailbox in many ways similar to a shared mailbox, but with some differences.

BitTitan does not have a migration guide nor it states (at the day and time that this blog was written) that it supports the migration from Public Folders to Office 365 groups.

This blog post was written after careful testing and verification and the migration worked without any issues. If you are interested in using MigrationWiz to migrate from Public Folders to Office 365 groups I highly recommend that you contact BitTitan and engage in a POC (proof of concept).

As always, any comments are welcomed and let me know how that migration went.

Understand and script the Get-MailboxFolderStatistics cmdlet

This blog post is going to give you some insight on how you can script the Get-MailboxFolderStatistics cmdlet, but also how to understand it’s output, as well as how is it important to plan a mailbox migration project.

Why are the mailbox folder statistics important to plan a migration project?

That is indeed the first thing you need to consider: Why do I need those statistics? To better answer that let me start by showing you an output of the command.

1

What you see above is the output of a mailbox, where I filtered the Folder Path, the number of items in the folder and sub folders, as well as the size of each folder. So let me bullet point why is this important for a migration project planning:

  • It will give you item counts per user and per item type (Mail, calendar, contacts). Those numbers are important, specially if extremely large, to estimate migration timelines when using the Bittitan MigrationWiz mailbox migration.
  • It will show you the size of every folder as well as the entire mailbox size. This is important to identify which folders are larger on a very big mailbox. I’ve seen examples of some of those folder being considered irrelevant for a migration (i.e deleted items, sent items) and with a flexible tool like the Bittitan MigrationWiz you can filter them out.
  • It will show you not only the folders visible to the end user, but also the recoverable deleted items folder. This might be very important in scenarios where you have in place hold active. It will give you the exact estimate of how many items are under in place hold on the recoverable items, that you will eventually need to move to the same folder on the destination mailbox.

So in summary, and giving a quick example, if you’re moving 1000 mailbox from Exchange Online tenantA to tenantB, you should use the Bittitan MigrationWiz tool, that will be not only fast but also flexible on what you can include and exclude in the migration, leveraging the Exchange Web Services API. To do that the insight of what’s in the mailbox is fundamental to plan the entire migration. You can plan the timelines and how long it will take, what type of licensing you will need at the destination tenant and many other important variables for the project.

How can I filter the output per item type?

When running the cmdlet you need to use the “-FolderScope” parameter to filter the output per item type. That parameter allows you to enter many different folder scope types, the most common being:

  • All
  • Calendar
  • Contacts
  • RSSSubscriptions
  • RecoverableItems

See the entire list in the official Technet article of the Get-MailboxFolderStatistics cmdlet.

Some cmdlet examples

See below some examples on how to obtain specific data:

Get total number of items in the mailbox (does not include recoverable deleted items):

Get-MailboxFolderStatistics user@domain.com | Where-Object {$_.foldertype -eq ‘root’} |ft folderpath, itemsinfolderandsubfolders, folderandsubfoldersize

2

Get total number of contacts in the mailbox:

Get-MailboxFolderStatistics user@domain.com -FolderScope Contacts |ft folderpath, items*, folderandsubfoldersize

3

Note: the above will work for calendar items if you change the folder scope from “Contacts” to “Calendar”

Get total number of items in the Recoverable deleted items folders:

Get-MailboxFolderStatistics user@domain.com| where {$_.FolderType -eq ‘RecoverableItemsRoot’}|ft folderpath, itemsinfolderandsubfolders, folderandsubfoldersize

4

And finally… the script that exports all of this to a csv:

Now that we discussed how important it is to get the statistics, and I gave you same examples on how to run some one line commands to get some insight on the mailbox, I will share with you a script that was done by me and my colleague and friend Alberto Nunes.

Below are the details of what the script does:

  • The script will run the statistics against a list of users that needs to be specified on a file called users.csv (see csv format below). The file needs to be on the same folder as the script.
  • The script runs Exchange PowerShell commands, so make sure you’re connected to the Exchange PowerShell (online or on premises)
  • The script will export the results to a file statistics.csv
  • The script will ask you for a source or destination parameter. The statistics for the recoverable deleted items folders will only be included if you select Destination.
  • This script is provided as is and there’s no guarantee that it will work in your environment.

The Users.csv file:

5

Note: Make sure you name the column A “EmailAddress”

The Script:

Copy the text below (in red) to a notepad. Save it as .ps1 and run it from a PowerShell session connected to Exchange.

#Start Script
Param
(
[Parameter(Position=0,Mandatory = $true)][ValidateSet(‘Source’,’Destination’,’MigrationWiz’)][String]$Location,
[Parameter(Mandatory = $false)][String]$Folder = “.”,
[Parameter(Mandatory = $false)][String]$UsersCSV = “Users.csv”,
[Parameter(Mandatory = $false)][String]$OutputCSV = “Statistics.csv”
)
Get-Date
$ErrorActionPreference = “SilentlyContinue”
$SourceFile = $Folder + “\” + $UsersCSV
$OutputFile = $Folder + “\” + $OutputCSV
$mailboxes = Import-Csv -Path “$SourceFile”
$CSV = @()
Foreach ($mailbox in $mailboxes)
{
Write-Host “Working on $($mailbox.EmailAddress)”

$AllStats = Get-MailboxFolderStatistics $($mailbox.EmailAddress)| Where-Object {$_.foldertype -eq ‘root’}

$ContactStats = Get-MailboxFolderStatistics $($mailbox.EmailAddress) -FolderScope Contacts
$TotalContactsItems = ($ContactStats | select -expand ItemsInFolder |Measure-Object -Sum).Sum

$CalendarStats = Get-MailboxFolderStatistics $($mailbox.EmailAddress) -FolderScope Calendar
$TotalCalendarItems = ($CalendarStats | select -expand ItemsInFolder |Measure-Object -Sum).Sum

$RSSFeeds = Get-MailboxFolderStatistics $($mailbox.EmailAddress) -FolderScope RssSubscriptions -ErrorAction SilentlyContinue
If ($RSSFeeds)
{
$TotalRSSFeeds = ($RSSFeeds | select -expand ItemsInFolder | Measure-Object -Sum).Sum
}
Else
{
$TotalRSSFeeds = 0
}

# In the source, we don’t need to know the Size of the RecovableItems
If ($Location -eq “Destination”)
{
$recoverableItems = Get-MailboxFolderStatistics $($mailbox.EmailAddress)| where {$_.FolderType -eq ‘RecoverableItemsRoot’}
[double]$recoverableSize = $recoverableItems.FolderAndSubfolderSize.TrimEnd(” bytes)”).Split(“(“)[1].replace(‘,’,”) |%{“{0:N2}” -f ($_ /1mb)}
}

$TotalMailItems = $AllStats.ItemsInFolderAndSubfolders – $TotalContactsItems – $TotalCalendarItems – $TotalRSSFeeds

$ISSize = $AllStats.FolderAndSubfolderSize.tostring()
[double]$ISSize =$ISSize.TrimEnd(” bytes)”).Split(“(“)[1].replace(‘,’,”) |%{“{0:N2}” -f ($_ /1mb)}

$Output = New-Object PSObject
$Output | Add-Member -MemberType NoteProperty -Name “User” -Value $mailbox.EmailAddress
$Output | Add-Member -MemberType NoteProperty -Name “Location” -Value $Location
$Output | Add-Member -MemberType NoteProperty -Name “TotalItems” -Value $AllStats.ItemsInFolderAndSubfolders
$Output | Add-Member -MemberType NoteProperty -Name “MailboxSize MB” -Value $ISSize
$Output | Add-Member -MemberType NoteProperty -Name “TotalContactItems” -Value $TotalContactsItems
$Output | Add-Member -MemberType NoteProperty -Name “TotalCalendarItems” -Value $TotalCalendarItems
$Output | Add-Member -MemberType NoteProperty -Name “TotalMailItems” -Value $TotalMailItems
$Output | Add-Member -MemberType NoteProperty -Name “TotalRSSFeeds” -Value $TotalRSSFeeds
If ($Location -eq “Destination”)
{
$Output | Add-Member -MemberType NoteProperty -Name “RecovableSize in MB” -Value $recoverableSize
$Output | Add-Member -MemberType NoteProperty -Name “RecovableItems” -Value $recoverableItems.ItemsInFolderAndSubfolders
$Output | Add-Member -MemberType NoteProperty -Name “Total Mailbox Size including Recovable” -Value $($ISSize + $recoverableSize)
$Output | Add-Member -MemberType NoteProperty -Name “Total Mailbox Count including Recovable” -Value $($AllStats.ItemsInFolderAndSubfolders + $recoverableItems.ItemsInFolderAndSubfolders)
}
$CSV += $Output
Write-Host “Completed $($mailbox.EmailAddress)” -ForegroundColor Green
}

$CSV | export-CSV “$OutputCSV” -NoTypeInformation
#End Script

To run the script you need to specify the location parameter:

.\MailboxFolderStats.ps1 -Location Source

.\MailboxFolderStats.ps1 -Location Destination (includes recoverable deleted items statistics)

As always I hope the above is helpful. Thanks for reading.

Raise your Exchange Server EWS limits prior to starting your MigrationWiz project

There’s an excellent Microsoft article on TechNet that explains how to configure client-specific message size limits, in Exchange 2013.

There are a lot of good reasons to raise your client limits, as there are also a lot of reasons not to. As long as you’re aware of what your on premises Exchange Server can handle, and you’re not exposing the server to overload or degradation of the service, by raising those limits, than from my perspective, you’re fine doing it.

UPDATE: Michael de Rooij just brought to my attention an excellent script he has, that automates everything described on this post. You can read his article here and download the script here. If for some reason you still want to change it manually, continue reading the steps described below.

On this blog post we are going to change the Exchange Web Services limits. The reason behind it is simple: MigrationWiz leverages the Exchange Web Services to move data to and from your Exchange based systems (on premises or in the cloud), and the default limits on an Exchange 2013 will make the migration of every item over ~35MB fail. The solution to have those items migrated is to increase the EWS limits on your target Exchange Server.

Now let me show you what to do that.

To get the limits raised you need to edit the web.config file on both your Exchange 2013 Client Access and Mailbox Servers. The values you need to edit are:

Serve role Configuration file Keys and default values Size
Client Access %ExchangeInstallPath%FrontEnd\HttpProxy\ews\web.config maxAllowedContentLength=”67108864″ bytes
Mailbox %ExchangeInstallPath%ClientAccess\exchweb\ews\web.config maxAllowedContentLength=”67108864″ bytes
Mailbox %ExchangeInstallPath%ClientAccess\exchweb\ews\web.config 14 instances of maxReceivedMessageSize=”67108864″ bytes

In my case I will raise the limit to 200MB. Go here to convert the 200MB in bytes.

200MB = 209715200 Bytes

Let’s edit the web.config

1

Above you can see the path on the Client Access Server.

2

Search for the value to change on the CAS server, and edit it to match the 200MB.

Do the exact same change on the mailbox web.config file, on your mailbox server, or if you have both CAS and Mailbox on the same server, go to the path: %ExchangeInstallPath%ClientAccess\exchweb\ews\web.config

Once the maxAllowedContentLenght change is made on both web.config files, you need to change 14 instances of the maxReceivedMessageSize value, on the Mailbox Server web.config

3

Go to the Mailbox server web.config file location (also used on the maxAllowedContentLenght change).

4

Search and replace the 14 instances.

Once the changes are made, save the web.config file and do an IISReset from the command prompt.

Note: In the web.config file on Mailbox servers, there are also two instances of the value maxReceivedMessageSize=”1048576″ forUMLegacyMessageEncoderSoap11Element bindings that you don’t need to modify.

Now your on premises Exchange Server will be able to handle much larger files via EWS.

Office 365 Public Folders Migration: Why and when to use MigrationWiz and not the Microsoft native tool

Migrating legacy Microsoft Exchange Public Folders to Office 365 is a challenge that many Organizations are facing at the moment, and the first question that comes to mind is: “What tool shall I use?”

From my perspective and experience, you have two valid options to push your Public Folders to Exchange Online:

  1. The Microsoft Native tool
  2. MigrationWiz from BitTitan

Reasons to use the MigrationWiz Tool

When the source is a Hosted Exchange

Most of the steps you need to follow on the Microsoft TechNet article, to migrate the legacy public folders to Office 365, require full control on the source system. No Exchange hoster will give you the control and permissions you need to use the Microsoft Native tool, therefore using MigrationWiz will be the best option in this scenario.

When the source is Exchange 2013

Microsoft states the following, on the official technet article:

“Exchange supports moving your public folders to Office 365 and Exchange Online from the following legacy versions of Exchange Server:

  • Exchange 2010 SP3 RU8 or later
  • Exchange 2007 SP3 RU15 or later”

This means that when the source is Exchange 2013, at the moment there is no official and supported migration path to move the public folders to Office 365, as you can also read on the BitTitan’s blog article, that describes the experience of the Microsoft Exchange expert and blogger Todd Nelson.

Because you don´t need to patch your existing Exchange Servers

As described above, and stated by Microsoft, to run the native tool your source servers need to be patched with at least Exchange 2010 SP3 RU8 or Exchange 2007 RU15. Patching Exchange servers might require downtime, and if you´re moving to Office 365, chances are you will decommission all Exchange Servers, keeping only one for management if you are using AADSync. So why schedule a maintenance window and patch servers there are soon to be decommissioned? MigrationWiz supports major versions with no specific requirements on the service pack or update rollup.

Support for large Public folders

MigrationWiz adds great value, when you have large individual public folders, and/or the total amount of public folder data to be migrated is very large.

But let’s start with the individual public folders. Microsoft clearly states the following, on the native migration TechNet article:

“Before migration, if any public folder in your organization is greater than 2 GB, we recommend either deleting content from that folder or splitting it up into multiple public folders. If either of these options isn’t feasible, we recommend that you do not move your public folders to Office 365 and Exchange Online.”

Which means that they do not support the migration of public folders larger than 2GB, or at least if and when you have a problem with your migration (and believe me, chances are you will), related to folders bigger than 2GB, the Microsoft support will probably give you an answer based on the statement above.

By default the Public folders on Office 365 cannot be larger than 2GB, but as you can see on the Public Folder limits Technet article, that limit can be extended up to 10GB. If you have public folders with more than 2GB (and of course less than the 10GB limit), all MigrationWiz requires is that you prepare your target environment, by following this article.

Regarding the large total amount of data to migrate, the great value of the MigrationWiz tool, when compared to the native tool, is the capability of, in case of error, getting more detailed information, and more importantly, not having to restart a migration. I was forced to restart very big migrations, done with the Microsoft Native tool, after getting very vague errors, and because the Resume-PublicFolderMigrationRequest was not working, to get my failed migration back in motion. As you can imagine having to restart a 400GB Public folder migration that was 50% done, it´s not a good experience 🙂

1

As you can see on the example above, the errors on the MigrationWiz portal are very clear and detailed, which helps a lot when troubleshooting. Public Folder mailboxes in Office 365 have a limit of 50GB per mailbox, therefore, if you´re migrating more than 50GB of data you need to follow this MigrationWiz article.

More details and statistics during the migration

Although with the native tool, you can run a Get-PublicFolderMigrationRequestStatistics, with the “-includereport” option, and export it to csv, that is nothing compared to what the MigrationWiz portal provides you.

2

You can see both the total number and size, per item type, with errors or migrated successfully.

3

There’s also information on the data migrated, items migrated or transfer speed, for you to have a better understanding of how things are progressing and estimate total migration times.

4

You can see the migration history, and all kinds of statistics, including a very useful data and item speed per hour statistic.

All the above is, in my opinion and based on my experience, very important when you are migrating a large amount of data.

It´s easy to migrate

The way I see it, unless you have very good technical knowledge of both Exchange online and on-premises, you should go for a tool like MigrationWiz that makes your life much easier.

I’ve seen a lot of issues when using the Microsoft Native tool (and you can find some posts in my blog on how to solve them), and if you don’t have advance knowledge, and you want to use the tool, you should find someone that has that knowledge and experience, and can assist you with the migration.

To be clear:

Is the Microsoft native tool, to migrate public folders, a good tool? Yes!

Does it work? Yes!

Is it challenging from a technical perspective? Yes!

Reasons to use the Microsoft Native Tool

If none of the above is relevant to you or your Organization, and you are an expert on Exchange online and on-premises, then you can use the native tool.

The bottom line is that, everything I stated on this post is based on the large experience that I have with Office 365, and more specifically with migrating Public Folders to Exchange Online. I´ve helped dozens of Organizations to achieve that, and learned a lot in the process.

I hope this post was helpful and as always, let me know if you have questions!

MigrationWiz: Public Folder migration fails to check destination credentials “No Public folder mailbox found” error

Are you trying to migrate your Public Folders to Office 365 and getting the error below?

1

Of course the first thing I recommend is obviously for you to check if you do have Public Folder mailboxes created in your Office 365 tenant. Log into the 365 portal, click to Admin Exchange, and then go to Public Folders > Public Folder Mailboxes.

4

You might need to wait up to 5/10 minutes after you create the Public Folder mailboxes and before running the MigrationWiz Public Folder project.

Also please make sure your MigrationWizAdmin user has the necessary permissions on the Public Folder structure. Read here for guidance.

Now what if everything described above is configured correctly, and you still have the same “No public folder mailbox found” error? Of course all the configurations described above are mandatory, but the real reason for this post is that something else might cause this error.

If all your users were moved to Office 365, and are accessing the Public Folders on premises, before you move those public folders to Office 365, I am sure you followed the Microsoft official article “Configure Legacy on-premises public folders for a Hybrid deployment“.

To enable the access to the legacy public folders, amongst other things, you need to run the following cmdlet:

Set-OrganizationConfig -PublicFoldersEnabled Remote -RemotePublicFolderMailboxes PFMailbox1,PFMailbox2,PFMailbox3

What the above cmdlet will do is, at the organization level set the Public Folders to remote, and more importantly, by setting the “RemotePublicFolderMailboxes” parameter and the OrganizationConfig level, it will change the “DefaultPublicFolderMailbox” parameter at the mailbox level, for all user mailboxes.

2

Above you can see that the RemotePublicFolderMailboxes is set to a mailbox called admin.vargas, and in your scenario it should be the name of an on premises mailbox, “dirsynced” to Office 365.

3

And if you look at the DefaultPublicFolderMailbox on your users, including the MigrationWiz admin user, that value will be set for the on-premises mailbox setting to match the “RemotePublicFolderMailboxes” parameters on the Organization Config.

So how does this affect the MigrationWiz project? Simple. Looking at the example above, the DefaultPublicFolderMailbox of the migwizadmin user is an on-premises “dirsynced” to 365 mailbox named “Admin.Vargas”, and that causes MigrationWiz to assume that there is no available public folder mailbox on 365. The solution is simple. Run the following cmdlet on your Office 365 Exchange PowerShell:

Set-Mailbox -identity migwizadmin@domain.onmicrosoft.com -DefaultPublicFolderMailbox PFMBX

4

You can see your Public Folder mailbox name, on your Office 365 Exchange Admin Centre, as shown above.

5

The cmdlet and the output, from the Exchange Online PowerShell.

Once the change is made, retry the MigrationWiz project, and job done!

There are a lot of good reasons for you to be in the scenario described above – Using MigrationWiz under a hybrid scenario, to move your public folders to Office 365 – especially if you have Public Folder with more than 2GB and a total amount of Public Folder data of more than 50GB. I will soon blog about all the advantages of skipping the Microsoft native tool to migrate the public folders and using MigrationWiz. Stay tuned!