Over the this series of posts I’m going to go through the entire process of migrating an Exchange 2010 server to Office 365. The method we are going to use is a hybrid migration so that we can migrate users over a longer time period. I am going to use Azure Active Directory Sync for SSO (or Single Sign On) so that users do not need to use a separate set of credentials for Office 365. I am also going to implement an Exchange 2016 server into the configuration. The reason for this is that the Exchange 2016 server is going to be my Hybrid server or Migration Endpoint and will connect directly to Office 365. The advantage of this is that I do not need to mess with the live Exchange 2010 server and can do all the work for the hybrid migration on the unused Exchange 2016 server. You then also have the advantage of having an Exchange 2016 server left behind for management when the migration is finished. This is a real world example so all the steps that you see were used on a live working setup.









Identify Tasks For Your Environment

Before you can actually start with any of the configuration work you need to spend some time making sure you understand your current setup really well. You need to know exactly how mail flows through firewalls, smart hosts and maybe other Exchange servers. Based on the setup in this organisation here is a list of steps that I identified I needed to complete to get all of the users migrated to Office 365. You can just use this as an example, obviously your own environment will vary and so will your steps.

No. Task 1 Create an Office 365 tenant by creating an account at Office.com and registering a tenant name. 2 Run IDFIX and correct any problems 3 Buy E3 + EMS licences to get all the extra portal options required for config 4 Build an Exchange 2016 server on Windows Server 2016. Use Hybrid Key. 5 Configure certificates/DNS records and network for new Exchange 2016 server 6 Create an autodiscover & hybrid DNS record for the email domain 7 Move current Public Folder items to resource objects in Exchange (remove all public folders) 8 Ensure all the relevant ports have been opened and corresponding IP addresses on the firewalls for Office 365 9 Ensure that Office 365 is completely bypassed by the web filtering service (Proxy) 10 Build new machins for Azure AD Connect Sync 11 Identify groups of users to be migrated. People who work together need to be migrated together. Calendars can’t be edited when user A in Exchange user B in Office 365 12 Convert applicable mailboxes to shared type to save on licence costs 13 Check Exchange health on Exchange 2010 server 14 Change all users UPN to match their email addresses 15 Configure EWS for Exchange 2016 server 16 Install and configure Azure AD Connect 17 Implement Skype For Business 18 Install and configure the Hybrid Migration Wizard 19 Migrate DNS from internal name servers to AWS Route 53 20 Configure Exchange Online DLP policies 21 Configure antivirus and antispam policies (and email alerting) 22 Configure Multi-factor authentication and location based access 23 Move any production systems currently using the Exchange server for relay to AWS SES service. 24 Clients on Office 2013 need to be on at least Dec 2015 release. Start deploying Office 365 to users as soon as licences are available. 25 Start Migrating Users to Office 365 26 Infosec signoff on Office 365 Security Configuration 27 Configure Self Service Password Reset 28 Configure Airwatch for migrated Office 365 users 29 After migration decommission Exchange 2010 and route email through Exchange online









Proposed Hybrid Mail flow Configuration

The proposed mail flow is fairly simple. The existing Exchange 2010 server and its current mail flow remains untouched for minimal impact. I will introduce an Exchange 2016 server to proxy for the Exchange 2010 server to Office 365. As users are migrated across to Office 365 the Hybrid configuration will continue to route all mail for Office 365 users through the Exchange 2010 configuration.

Using IDFIX to prepare Active Directory Objects for Azure

You can download the IDFIX tool from here. The tool is pretty simple to run, just run it as a domain administrator from your Windows 10 machine. Once it has loaded just click ‘Query’. IDFIX will then list all the problems it has found with your Active Directory objects. The most common issue seems to be the mailnickname (which is the Exchange Alias Attribute). Most of the issues seemed to revolve around the same mailnickname being used for a group and a user. I just changed one of them to resolve. We will come to userprincipalname errors later on.









Sign up for Office 365 tenant and verify domain

During this step I created an account for Office 365 called admin@mydomain.onmicrosoft.com . Then logged in to the console and went to Setup> Domains > Add domain. At this stage I could then run through the verify domain wizard. This simply required me to create a TXT record in the mydomain.com domain with the code that Microsoft supplied. Once the domain was verified it shows as ‘Setup Complete’. You can now create mydomain.com accounts within Office 365.

Deciding on which licences to buy

If you are reading this article you will be looking to buy one of the flavours of the Office 365 Enterprise Plans and possibly some of the add-ons. We ended up using the E3 plan and also purchased the E3 EMS add-on. for us EMS was essential so that we could setup conditional access rules within Azure AD. I found this rather useful info-graphic recently that gives you a good idea of what you get with each licence.

Building the Exchange 2016 server

To build your Exchange 2016 Hybrid server follow the steps below:

1. Install Windows Server 2016

2. Prepare the AD schema using:

Setup.exe /PrepareSchema /IAcceptExchangeServerLicenseTerms

3. Prepare AD using:

Setup.exe /PrepareAD /OrganizationName:”mydomain plc” /IAcceptExchangeServerLicenseTerms

We need to add the server to the existing organisation (which I picked up with this command: Get-OrganizationConfig | select name)

4. Prepare the domains using Setup.exe /PrepareAllDomains /IAcceptExchangeServerLicenseTerms

5. Verify these changes:

rangeUpper is located in the Schema naming context in the ms-Exch-Schema-Version-Pt container.

objectVersion (Default) is located in the Default naming context in the Microsoft Exchange System Objects container.

objectVersion (Configuration) is located in the Configuration naming context in the CN=<your organization>, CN=Microsoft Exchange, CN=Services, CN=Configuration, DC=<domain> container.

6. Install .NET Framework 4.5.2

7. Install the RSAT tools using Install-WindowsFeature RSAT-ADDS

8. Install final pre-requisites (Windows Server 2012):

Install-WindowsFeature AS-HTTP-Activation, Desktop-Experience, NET-Framework-45-Features, RPC-over-HTTP-proxy, RSAT-Clustering, RSAT-Clustering-CmdInterface, RSAT-Clustering-Mgmt, RSAT-Clustering-PowerShell, Web-Mgmt-Console, 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

9. Pre-requisites for Windows Server 2016:

Install-WindowsFeature NET-Framework-45-Features, RPC-over-HTTP-proxy, RSAT-Clustering, RSAT-Clustering-CmdInterface, RSAT-Clustering-Mgmt, RSAT-Clustering-PowerShell, Web-Mgmt-Console, 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

Also download and install the Unified Communications Runtime 4.0 from here.









10. Install the default selected roles as below: (no Edge transport)

Configure the virtual directories in Exchange 2016 to reflect the external host name:









Office 365 IP Addresses URLs & Firewall Rules

Microsoft publish a list of the IP addresses and URLs that they user here. You may want to do what I did which was download all these addresses in .xml format from here. For your hybrid server to function properly you will need to ensure that you allow outbound/inbound traffic to all of these IP address on your firewall for ports 25, 80 and 443. The IP addressed and URLs that Microsoft use for Office 365 does change fairly frequently. I would suggest keeping track of the changes by subscribing to the RSS feed here.

You should also think about a way of automatically (or at least an easy way of) feeding the updated IPs into your firewall.

If you are using a Cisco firewall you will find the below script useful. It basically downloads the .xml file from Microsoft an formats it so that it can be directly imported into a Cisco firewall:

# CHANGE PATH $Path = 'C:\Temp' $Report = "$Path\Report.txt" $date = Get-Date -format "yyyyMMdd" $dateReport = Get-Date -format "dd.MM.yyyy" $Link = 'https://support.content.office.net/en-us/static/O365IPAddresses.xml' $LastFile = Get-Content ( Get-ChildItem -Path $Path -Filter "*- o365Rules.txt" | sort CreationTime | select -Last 1).FullName $ChangeDate = Get-Content $Report $Last = $ChangeDate [-1].Substring( $ChangeDate [-1].Length -10, $ChangeDate [-1].Length - ( $ChangeDate [-1].Length -10)) try { [xml] $o365IpList = Invoke-RestMethod $Link } catch { $_ | Out-File "$Path\$date - Errors.txt" Add-Content $Report "$dateReport Error!!!" } if (!( Test-Path "$Path\$date - Errors.txt" )) { $Result = @ () $ErrorList = @ () foreach ( $Product in $o365IpList .Products.product) { foreach ( $Addresslist in $Product .addresslist) { if ( $Addresslist .type -eq "IPV4" ) { foreach ( $Address in $Addresslist .address) { try { [string] $Parse = $Address [string] $IPAddress = $Parse .Substring(0, $Parse .IndexOf( "/" )) [string] $Cidr = $Parse .Substring( $Parse .IndexOf( "/" )+1, $Parse .Length - $Parse .IndexOf( "/" )-1) [int] $Prefix = $Cidr if ( $Prefix -eq 32) { [string] $Value = "network-object host $IPAddress" } else { $mask = ( [Math] :: Pow(2, $Prefix )-1) * [Math] :: Pow(2,(32- $Prefix )) $bytes = [BitConverter] :: GetBytes( [UInt32] $mask ) $IPMask = (( $bytes .Count-1)..0 | ForEach -Object { [String] $bytes [ $_ ]}) -join "." [string] $Value = "network-object $IPAddress $IPMask" } $Result += $Value .Trim() } catch { $ErrorList += "$Address --> $_" } } } } } $Final = , "object-group network o365Rules" + ( $Result | Sort-Object -Unique) $Final | ft | Out-File "$Path\o365Rules.txt" $Final | ft | Out-File "$Path\$date - o365Rules.txt" if ( $ErrorList ) { $ErrorList | Out-File "$Path\$date - Errors.txt" Add-Content $Report "$dateReport Error!!!" } else { $Missing = @ () $Changed = ( $o365IpList .Products.updated).Split( '/' ) $ChangedFormated = "{0}.{1}.{2}" -f $Changed [1], $Changed [0], $Changed [2] $Text = "$dateReport OK, last change $ChangedFormated" Add-Content $Report $Text $Current = $Text .Substring( $Text .Length -10, $Text .Length - ( $Text .Length -10)) if (!( $Current -eq $Last )) { $LatestRules = Get-Content "$Path\$date - o365Rules.txt" foreach ( $line in $LatestRules ) { if ( $LastFile -notcontains $line ) { $Missing += $line } } foreach ( $line in $LastFile ) { if ( $LatestRules -notcontains $line ) { $Missing += "no $line" } } $FinalMissing = , "object-group network o365Rules" + ( $Missing | sort) $FinalMissing | Out-File "$Path\$date - Changes.txt" } } }









It gives you a list in the format:

object-group network o365Rules

network-object 104.146.0.0 255.255.224.0

network-object 104.146.128.0 255.255.128.0

network-object 104.209.144.16 255.255.255.248

If you are using Palo Alto the process is a bit more involved. There is an application created by Palo Alto called MineMeld which is able to parse the data in the attached .xml file and allow you create security policies from it in PAN-OS. There is a link to how to set this up here.

Proxy Servers

You can use the same list of URLs from the .xml file provided by Microsoft above to add an exceptions list to your Web filtering Proxy server. We chose to completely bypass the internet proxy server for all Office 365 URLs.

Internal DNS

We need to add the following records to internal DNS. I did not change the autodiscover CNAME or the MX records at this stage though as we are using our existing Exchange 2010 server to route mail in the hybrid setup.

Type Priority Host name Points to address or value TTL MX 0 @ mydomain-com.mail.protection.outlook.com 1 Hour CNAME – autodiscover autodiscover.outlook.com 1 Hour CNAME – sip sipdir.online.lync.com 1 Hour CNAME – lyncdiscover webdir.online.lync.com 1 Hour

Type Service Protocol Port Weight Priority TTL Name Target SRV _sip _tls 443 1 100 1 Hour @ sipdir.online.lync.com

External DNS

1. Add the below DNS records before migration:

Type Priority Host name Points to address or value TTL A – hybrid.mydomain.com External IP address of Exchange 2016 server 1 Hour A – autodiscover.mydomain.com External IP address of Exchange 2016 server 1 Hour

2. After Migration change the MX record to the below:

Type Priority Host name Points to address or value TTL MX 0 @ mydomain-com.mail.protection.outlook.com 1 Hour









Create a Migration Schedule

This step is just common sense really. After you have migrated your test users and then the IT team you need to start moving users within the organisation. We found that it really helped to plan this step and to create an entire schedule for every user in the organisation and arranged users into groups of migration batches.

Outlook Anywhere

Contrary to what I read in many places and was told by various consultants you do not have to enable Outlook Anywhere on your Exchange 2010 server if you do not have it enabled already. You would enable this if users need to access each other’s free/busy calendar information from on-premise to Office 365 and vice versa. Beware for this to be turned on the EWS URL and a public certificate should be configured. If not your clients will get certificate errors when opening Outlook.

Enabling Outlook Anywhere is simple, open the EMC and go to Server Configuration>Client Access and click Enable Outlook Anywhere from the action pane. Again before you do this make sure you have a 3rd party certificate installed that is trusted by all your clients or you will have problems.

It should complete successfully.









Changing User’s UPN

Before I could run the AAD Sync tool I needed to change the UPN for all users to match the users email address. Unfortunately when Active Directory was setup in this particular organisation the SamAccountName and login name used was first initial then surname i.e. JSmith. This meant that all UPNs were JSmith@mydomain.com. I needed to change all of these so that the UPN matched the user’s email address. This article gives a good description here of why we should do this.

Essentially the main reasons we want to do this are:

It simplifies the login for the user – Skype, O365 email, Airwatch/Intune will all ask the user for their email address (not their UPN suffix! and they will not know this). Skype for business uses the UPN as the primary contact method. When users give out this information they want to give their email address as john.smith@mydomain.com not their UPN.

The plan I used for this was:

• Change UPN to match email address for a test user

• Test login (should be no change as we are not changing SamAccountName)

• Investigate all 3rd party applications that integrate with AD and check for reliance on UPN

• Create a script to change UPN for the I.T department

• Test over a period of days

• Begin to roll out to groups of users

Users should still be able to login with their old UPN as this is the implicit UPN. See below from Microsoft:

‘A UPN can be implicitly or explicitly defined. An implicit UPN is of the form UserName**@DNSDomainName.com**. An implicit UPN is always associated with the user’s account, even if an explicit UPN is not defined. An explicit UPN is of the form Name**@**Suffix, where both the name and suffix strings are explicitly defined by the administrator.’

The script I used is below, I ran this on each OU one at a time and it updated all the users UPNs to the correct format. I had no issues with any of the users after running it.

param ( [ Parameter ( mandatory = $true )] [string] $OU ) Import-Module ActiveDirectory foreach ( $user in ( Get-ADUser -Filter * -SearchBase $OU )) { $userdetails = $user | get-aduser -properties * $user | set-aduser -UserPrincipalName $userdetails .EmailAddress }

If you are a bit concerned about running the above script you can just run a report first that shows you all the UPNs that you need to change:

param ( [ Parameter ( mandatory = $true )] [string] $OU ) $logpath = "c:\temp" Get-Mailbox -OrganizationalUnit $OU -ResultSize Unlimited | Select DisplayName, Alias ,UserPrincipalName,PrimarySMTPAddress | export-csv -NoTypeInformation "$logpath\Users-UPNs-OU.csv" -append









You may be concerned that you are using the existing user’s UPNs somewhere in a particular application or that some users are using this as their login method. I used the below method to find out if the existing UPNs were being used anywhere:

1. Install Microsoft Message Analyzer

2. Run the below command on the domain controller:

netsh trace start capture=yes correlation=no overwrite=yes tracefile=\\vmepwks01\logs$\vmdc04.etl persistent=yes maxsize=300

This will start the network trace (the 20000 is in MB so make sure there is adequate disk space).

3. Run the below command to stop the network trace when ready:

netsh trace stop

4. An .etl file is generated, drag and drop this in to the Message Analyzer

5. Press CTRL + F and enter the following filter:

((*Summary contains “@mydomain.com”)) AND (KerberosV5.Cname) AND ((*Summary contains “krbtgt/mydomain.com”))

You will see the UPN login as below

This should complete all the prerequisites for the migration, in the next post we will go through all he necessary steps to sync your on-premise AD with Azure AD.







