Migrate your Exchange 2010 to Exchange 2019 On-Prem – Part one (from 2010 to 2016)

I know that in the last two year all want to migrate yours Exchange infrastructures to the Microsoft’s cloud, but sometimes someone still asks to stay on-prem.

So, let’s go on this journey and start we me in this simple lab where, starting from an old Exchange 2010, we will reach the final step, passing Exchange 2016 , the Exchange 2019 On-Prem.

Our lab enviroment is made of:

  • One Windows 2019 Domain Controller
  • One Windows 2008 R2 Domain Joined
    • On which is installed Exchange 2010 Standard
  • A Windows 10 client fully configured to work with Exchange 2010 with Outlook 2019 connected to it

Some requirements

Exchange versionSO RequiredAD forest LevelCoexistance
Exchange 2010Windows Server 2008 R2Windows Server 2003Exchange 2003
Exchange 2007
Exchange 2016Windows Server 2012 R2 => Windows 2016 (don’t use Windows 2019)Windows Server 2012 R2Exchange 2010 SP3 UR11
Exchange 2013
Exchange 2019 Windows Server 2019 Windows Server 2012 R2Exchange 2013
Exchange 2016

The steps that we’ll go through are:

So now, let’s start.

Prepare your Win 2016 with all requirement (need also .NET Framework 4.8 , Visual C++ Redistributable Package for Visual Studio 2012 , Visual C++ Redistributable Package for Visual Studio 2013 and Microsoft Unified Communications Managed API 4.0)

Install-WindowsFeature Server-Media-Foundation, NET-Framework-45-Features, RPC-over-HTTP-proxy, RSAT-Clustering, RSAT-Clustering-CmdInterface, RSAT-Clustering-Mgmt, RSAT-Clustering-PowerShell, WAS-Process-Model, Web-Asp-Net45, Web-Basic-Auth, Web-Client-Auth, Web-Digest-Auth, Web-Dir-Browsing, Web-Dyn-Compression, Web-Http-Errors, Web-Http-Logging, Web-Http-Redirect, Web-Http-Tracing, Web-ISAPI-Ext, Web-ISAPI-Filter, Web-Lgcy-Mgmt-Console, Web-Metabase, Web-Mgmt-Console, Web-Mgmt-Service, Web-Net-Ext45, Web-Request-Monitor, Web-Server, Web-Stat-Compression, Web-Static-Content, Web-Windows-Auth, Web-WMI, Windows-Identity-Foundation, RSAT-ADDS

Now, we will check our Domain/Forest level..

Get-ADDomain | fl Name,DomainMode
Get-ADForest | fl Name,ForestMode

And we will raise to meet the minimal requirement (see table) About this command and forest level..

Set-ADForestMode -Identity lab-icr.local -ForestMode Windows2012R2Forest

Check again, the result …

Now we’ll do some check on our Exchange 2010…

Check if Outlook Anywhere is enabled or not

Get-OutlookAnywhere |select external*,internal*

So, enable it choosing the right name space (in our case is mail.labincloudres.com)

TO Enable
Enable-OutlookAnywhere -Server 'LAB-EXCH10' -ExternalHostname 'mail.labincloudres.com' -Default
AuthenticationMethod 'Ntlm' -SSLOffloading $false

OR to Set if just Eanbled
Set-OutlookAnywhere -IISAuthenticationMethods 'Ntlm' -ClientAuthenticationMethod 'Ntlm' -Ident
ty 'LAB-EXCH10\Rpc (Default Web Site)'

At this point we are ready to install our Exchange 2019…

Mount the ISO on the server where will install it and prepare the Domain and Forest…

Setup.exe /IAcceptExchangeServerLicenseTerms /PrepareSchema
---and after--- 
Setup.exe /IAcceptExchangeServerLicenseTerms /PrepareAD  /OrganizationName:"XXX"

If you need to find the Organization Name , issue this command on exchnage console…

Get-OrganizationConfig |select *name*

Now we are ready to install our Exchange 2016

Setup.exe /IAcceptExchangeServerLicenseTerms /Mode:Install /Roles:Mailbox 


Now reboot check and change the SCP to point to autodiscover DNS record (that at this point is still Exchange 2010! ).

get-ClientAccessService -Identity LAB-EXCH16 |select *discover*

Change it to point to FQDN of autodiscover

set-ClientAccessService -Identity LAB-EXCH16 -AutoDiscoverServiceInternalUri "https://mail.labincloudres.com/Autodiscover/Au

Now check it again…

get-ClientAccessService |select name,*discover*

Now we have some things to do… set all virtual directories to the right FQDN and, before start, we can show the situation:

Get-OABVirtualDirectory |select externalURL,InternalURl,identity |ft -autosize
Get-WebServicesVirtualDirectory |select externalURL,InternalURl,identity |ft -autosize
Get-ActiveSyncVirtualDirectory |select externalURL,InternalURl,identity |ft -autosize
Get-OWAVirtualDirectory |select externalURL,InternalURl,identity |ft -autosize
Get-ECPVirtualDirectory |select externalURL,InternalURl,identity |ft -autosize
Get-PowerShellVirtualDirectory |select externalURL,InternalURl,identity |ft -autosize 
Get-ClientAccessService |select AutoDiscoverServiceInternalUri ,identity |ft -autosize
Get-AutodiscoverVirtualDirectory |select externalURL,InternalURl,identity |ft -autosize

And so, we need all that is related to our new Exchange 2016..

Set-OABVirtualDirectory -identity "LAB-EXCH16\OAB (Default Web Site)" -ExternalURL https://mail.labincloudres.com/OAB -InternalUrl https://mail.labincloudres.com/OAB

Set-WebServicesVirtualDirectory -identity "LAB-EXCH16\EWS (Default Web Site)" -ExternalURL https://mail.labincloudres.com/EWS/Exchange.asmx -InternalUrl https://mail.labincloudres.com/ews/Exchange.asmx

Set-ActiveSyncVirtualDirectory -Identity "LAB-EXCH16\Microsoft-Server-ActiveSync (Default Web Site)" -ExternalURL https://mail.labincloudres.com/Microsoft-Server-ActiveSync -InternalUrl https://mail.labincloudres.com/Microsoft-Server-ActiveSync

Set-OWAVirtualDirectory -Identity "LAB-EXCH16\owa (Default Web Site)" -ExternalURL https://mail.labincloudres.com/owa -InternalUrl https://mail.labincloudres.com/owa

Set-ECPVirtualDirectory -Identity "LAB-EXCH16\ecp (Default Web Site)" -ExternalURL https://mail.labincloudres.com/ecp -InternalURl https://mail.labincloudres.com/ecp

Set-PowerShellVirtualDirectory -Identity "LAB-EXCH16\PowerShell (Default Web Site)" -ExternalURL https://mail.labincloudres.com/powershell -InternalURl https://mail.labincloudres.com/powershell

Set-ClientAccessService  -Identity LAB-EXCH16 -AutoDiscoverServiceInternalUri https://autodiscover.labincloudres.com/Autodiscover/Autodiscover.xml

Set-AutodiscoverVirtualDirectory -identity "LAB-EXCH16\Autodiscover (Default Web Site)" -ExternalUrl https://autodiscover.labincloudres.com/Autodiscover/Autodiscover.xml -InternalUrl https://autodiscover.labincloudres.com/Autodiscover/Autodiscover.xml

Double check the MAPI (MapiVirtualDirectory) and OutlookAnywhere on your Exchange 2016.

Change it too


Set-MapiVirtualDirectory -Identity "LAB-EXCH16\mapi (Default Web Site)" -InternalUrl https://mail.labincloudres.com/mapi -ExternalUrl https://mail.labincloudres.com/mapi -IISAuthenticationMethods NTLM,Negotiate,OAuth

And, finally set OutlookAnywhere on the Exchange 2016…


Set-OutlookAnywhere -Identity "LAB-EXCH16\RPC (Default Web Site)" -ExternalHostname mail.labincloudres.com -InternalHostname mail.labincloudres.com -ExternalClientsRequireSsl $true -InternalClientsRequireSsl $true -DefaultAuthenticationMethod NTLM

Now, we need to export the SSL certificare from Exchange 2010 and importo to Exchange 2016

Remember that Exchange 2010 has a “bug” in the Export-ExchangeCertificate because “-path” is missing… so we need to fallow this useful suggestion from Nick

First extract from Exchange 2010 the thumbprint of the cerfificate.

Get-ExchangeCertificate |fl

Now, fallowing Nick, we will use these commands to export.

$file = Export-ExchangeCertificate -Thumbprint 2F78EE3AE1B3D26AADBF58734A374B1A8EF7D30B -BinaryEncoded -Password (ConvertTo-SecureString -String 'TOPOLINO' -AsPlainText -Force)
Set-Content -Path "c:\certexch\exch2010cert.pfx" -Value $file.FileData -Encoding Byte

Now copy this exported certificate to your Exchange 2016, import it and assign to IIS and SMTP.

Import-ExchangeCertificate -Server LAB-EXCH16 -FileName "\\localhost\c$\certexch\exch2010cert.pfx" -PrivateKeyExportable:$true -Password (ConvertTo-SecureString -String "TOPOLINO" -AsPlainText -Force)

Enable-ExchangeCertificate -Thumbprint 2F78EE3AE1B3D26AADBF58734A374B1A8EF7D30B -Services SMTP,IIS

Set the default OAB per DB on the Exchange 2010

Get-MailboxDatabase |select *offline*
Get-MailboxDatabase |Set-mailboxdatabase -OfflineAddressBook "Default Offline Address Book"

On Exchange 2016

Create an OAB for the Exchange 2016 DB

Get-MailboxDatabase | Set-MailboxDatabase -OfflineAddressBook “Default Offline Address List (Ex2013)”

And set it as the default OAB for the Exchange 2016 DBs

Get-MailboxDatabase | Set-MailboxDatabase -OfflineAddressBook "\Default Offline Address Book"

Now we will do some task preparing to move mailboxes (NB disable Circular logging and increases database limits…) :

First we need to move the Arbitration mailboxes from 2010 to 2016 to avoid problem access to OAB after moving mailboxes..

Get-Mailbox -Arbitration -Server lab-exch10
Get-Mailbox -Arbitration -Server lab-exch10 | New-MoveRequest -TargetDatabase "Mailbox Database

At this point we could test a mailbox move, but, before, we can switch the DNS record to point to our Exchange 2016.

If you want only do a test with a pc, you could change the HOSTS file to force the resolution of SCP FQDN , in my case mail.labincloudres.com , to your Exchange 2016 instead of 2010

Now we will do a test moving a mailbox and check that everything works..

TO LIST all Mailboxes with DBs...
Get-Mailbox -Identity * |select identity,database,ServerName |ft
TO MOVE a mailbox
New-MoveRequest -Identity 'Gatto Goloso' -TargetDatabase "Mailbox Database 0127998180"
TO GET the move request status..
TO CLEAR completed move requestes...
Get-MoveRequest -movestatus completed | remove-moverequest

On the client side, Outlook popup a warning, you have to close and reopen Outlook.

So it works… now we proceed to move all mailbox with this command:

Get-Mailbox -Database "Mailbox Database 0884215156" | New-MoveRequest -TargetDatabase "Mailbox
Database 0127998180" -BatchName "Exch2010-DB_to_Exch2016-DB"

Before starting mooving all mailboxes, we need to garantee the ability, for moved mailboxes to access to Public Folders.

tO do this, we need to implement a PF Mailbox Proxy meccanism

  1. On the Exchage 2010 we need to create a DB dedicated to this particular topics
New-MailboxDatabase -Server 'YOUR EXCHANGE 2010' -Name "MDB NAME' -IsExcludedFromProvisioning $true

2. Now, we create a mailbox into this Db and hide it.

New-Mailbox -UserPrincipalName PF-PROXY-MAILBOX01@yourdomain.local -Alias PF-PROXY-MAILBOX01 -Name PF-PROXY-MAILBOX01 -Database "PF-PROXY" -OrganizationalUnit Users -ResetPasswordOnNextLogon $false

Set-Mailbox -Identity 'PF-MAIBOX' -HiddenFromAddressListsEnabled $true

3. Now switch to your Exchange 2016 and change from Local to Remote pointing to previous mailbox created on Exchange 2010.

Set-OrganizationConfig -PublicFoldersEnabled Remote -RemotePublicFolderMailboxes PF-PROXY-MAILB

Now, we moved all the mailboxes and we will migrate the public folder.

first, download the script from Microsoft

  1. Now take a snapshot of Structure,Statistics and Permission. These XML will be useful later.
Get-PublicFolder -Recurse -ResultSize Unlimited | Export-CliXML C:\PFMigration\Legacy_PFStructure.xml

Get-PublicFolderStatistics -ResultSize Unlimited | Export-CliXML C:\PFMigration\Legacy_PFStatistics.xml

Get-PublicFolder -Recurse -ResultSize Unlimited | Get-PublicFolderClientPermission | Select-Object Identity,User -ExpandProperty AccessRights | Export-CliXML C:\PFMigration\Legacy_PFPerms.xml

Now check if there are a previous migration in progress…

Get-OrganizationConfig | Format-List PublicFoldersLockedforMigration, PublicFolderMigrationComplete

Now, we will map a drive on the Exchange 2010 that is pointed to a folder/share of Exchange 2016. We do this, because we need to launch the scripts from Exchange 2010, but we need to put the output on the Exchange 2016.

In my case i created a folder called PFMigration con C:\

Map it from Exchange 2010

Now, open an Ex Console with Admin rights, and launch the first script

.\Export-PublicFolderStatistics.ps1 k:\exp_stats.csv lab-exch10

Now we can open the CSV just created to see the “size” of the current PF. in my case i have only 133.967 Byte that correspond to 0,13 Mb.. very small, but this is a LAB. Basically, you need to make a total to know how you will need to specify in the second script to permit “the public folder mailbox has room to grow.

Now we run the script that will create a CSV to map the PF to a mailbox/mailboxes

.\PublicFolderToMailboxMapGenerator.ps1 10000000 k:\exp_stats.csv k:\exp_map.csv

At this point we need to create the Public Folder Mailbox on the Exchange 2016, so, this command will be executed on the Exchange 2016 console <<<<!!!!

.\Create-PublicFolderMailboxesForMigration.ps1 -FolderMappingCsv exp_map.csv -EstimatedNumberOfConcurrentUsers:1000

Now issue the command to start the migration.

New-MigrationBatch -Name PFMigration -SourcePublicFolderDatabase (Get-PublicFolderDatabase -Server LAB-EXCH10) -CSVData (Get-Content exp_map.csv -Encoding Byte) -NotificationEmails mschiavon@labincloudres.com

Set limits and start the batch

Set-MigrationBatch PFMigration -LargeItemLimit 100 -Baditemlimit 50
Start-MigrationBatch PFMigration

Check the status..

Get-MigrationUser -BatchId PFMigration

Check also the report sent to the mail address specified on the batch..

So, we are ready to finish or migration.

Go back to Exchange 2010 console and lock down the public folder. (this interrupt normal users operations on the public folders…. Do it off hours if you can.)

Set-OrganizationConfig -PublicFoldersLockedForMigration:$true

Now, go back to Exchange 2016 console and finalize the migration batch…

Set-OrganizationConfig -PublicFoldersEnabled Remote
Complete-MigrationBatch PFMigration

Note that in this case , with the “complete-migrationbatch” system return and error.. to resolve it quickly, restart the Information Store on the Exchange 2010.

After restarted, everything works.

Now the final steps..

Get-Mailbox -PublicFolder | Set-Mailbox -PublicFolder -IsExcludedFromServingHierarchy $false
Set-OrganizationConfig -PublicFolderMigrationComplete:$true
Set-OrganizationConfig -PublicFoldersEnabled Local

Test the outlook…


Decommissioning exchange 2010.

I assumed that you moved/repoint receive and sent connectors

So now we remove the public and private DBs.

Get-PublicFolderDatabase -Server LAB-EXCH10 
Get-PublicFolderDatabase -Server LAB-EXCH10 | Remove-PublicFolderDatabase

Do the same for the MailboxDb

Get-MailboxDatabase -Server LAB-EXCH10
Get-MailboxDatabase -Server LAB-EXCH10 | Remove-MailboxDatabase

Remove the OAB

Now, finally, uninstall it.

Finally check the presence of the Exchage server.

Get-ExchangeServer | Select Name, AdminDisplayVersion




Leave a Reply

Your email address will not be published.