Migrate From G Suite Into Office 365

This is a reference post on how to perform (as much as possible) of a mail migration from G Suite (formerly Google Apps) into Office 365 via PowerShell.
The Office 365 documentation for G Suite is great, where you might end up with sub-pages requiring a bunch of tabs or some circular page logic along the way… This is to hopefully condense it into a single page as a quick reference. You’ll also want to understand Office 365’s best practices.

G Suite to Office 365 will use IMAP which only migrates mail content. You will have a few limitations where you will need to determine a solution before leaving one service for another. The below bulleted list is from the above IMAP link, with emphasis added which may concern you (or other IT departments):

  • You can only migrate items in a user’s inbox or other mail folders. This type of migration doesn’t migrate contacts, calendar items, or tasks.
  • You can migrate a maximum of 500,000 items from a user’s mailbox (emails are migrated from newest to oldest).
  • The biggest email you can migrate is 35 MB.
  • If you limited the connections to your source email system, it’s a good idea to increase them to improve migration performance.
    Common connection limits include client/server total connections, per-user connections, and IP address connections on either the server or the firewall.

OK, figure out something to do for those, if you don’t want to upset your end users. Let’s continue (where this information is provided as is, at your own risk and no warranties implied!).

Step 1: Verify your own domain
This adds the domain name you want to absorb into your Office 365 tenant.

$o365admin = Get-Credential
$domain = ‘domain-name-to-move.com’
Import-Module -Name MSOnline
Connect-MSOLService -Credential $o365admin
New-MsolDomain -Name $domain -VerificationMethod DnsRecord
Get-MsolDomainVertificationDns -DomainName $domain -Mode DnsTxtRecord
<# And we hit the first step that requires something outside of PowerShell (maybe)! Add the Text value to your DNS infrastructure. Wait ~15 minutes for initial replication to occur or check it has replicated  on an external service. #>
Confirm-MsolDomain -DomainName $domain

 

Optional Step: Reduce email delays

Microsoft’s guide provides an optional suggestion of updating your MX record’s Time-To-Live (TTL) to 3600 (1 hour). This is noted to be performed before the migration request, but is listed after you start a migration batch in their current directions where you might have closed out your DNS management tool. I’ve moved it higher in the steps list if you want to make the modification at the same time as adding the TXT record in from the step above.

Step 2: Add Users to Office 365
Create a CSV with the following header information as outlined here:
User Name,First Name,Last Name,Display Name,Job Title,Department,Office Number,Office Phone,Mobile Phone,Fax,Address,City,State or Province,ZIP or Postal Code,Country or Region

<# Create the Office 365 accounts and assign a license for a mailbox to be created for copying from Gmail into Office 365. #>
Import-Csv -Path "C:\Office365Migration\NewAccounts.csv" | `
ForEach-Object -Process { New-MsolUser -DisplayName $PSItem.DisplayName -FirstName $PSitem.FirstName -LastName $PSitem.LastName -UserPrincipalName $PSitem.UserPrincipalName -UsageLocation $PSitem.UsageLocation -LicenseAssignment $PSitem.AccountSkuId
} | Export-Csv -Path ‘C:\Office365Migration\NewAccountResults.csv’

Step 3: Create a list of Gmail mailboxes
An abridged summary. Create a CSV with the following header columns:
EmailAddress,UserName,Password

EmailAddress is the Office 365 User Name (and mailbox) content should be copied into,
UserName is the sign-in name for the Gmail account,
Password is the mailbox’s Gmail password or an app password (which is recommended as it’s more secure) for Gmail. Save the file a CSV. Example C:\Office365Migration\GmailMailboxes.csv Keep this path handy for a step below.

Step 4: Connect Office 365 to Gmail (with extra info on what’s going on here and here)

$o365admin = Get-Credential
$Session = New-PSSession -ConfigurationName 'Microsoft.Exchange' -ConnectionUri 'https://outlook.office365.com/powershell-liveid/' -Credential $o365admin -Authentication 'Basic' -AllowRedirection
Import-PSSession -Session $Session

$gmail = ‘imap.gmail.com’
$port = 993 # Or 143
$security = ‘Ssl’ # Or ‘Tls’, or ‘None’
$endpointname = ‘GSuiteMigrations’
<#Test connectivity to the environment #>
Test-MigrationServerAvailability -IMAP -RemoteServer $gmail -Port $port -Security $security
<# Create the endpoint: #>
New-MigrationEndpoint -IMAP -Name $endpointname -RemoteServer $gmail -Port $port -Security $security -MaxConcurrentMigrations 50 -MaxConcurrentIncrementalSyncs 25 -Authentication 'Basic'
<# Verify the connector was created: #>
Get-MigrationEndpoint -Name $endpointname

 

Step 5: Create a migration batch and start migrating Gmail mailboxes (with extra info here)
You should probably test and migrate in a few “department champions” or test mailboxes first.
(Maybe move the IT team first.)
If you have a ton of mailboxes, you could migration in multiple “waves”.  Keep the same CSV headers, and split up the GmailMailboxes.csv into different groups if you’d like.

$batchname = ‘FirstWave’
<# Path to the CSV created in Step 3: #>
$path = ‘C:\Office365Migration\GmailMailboxes.csv’

New-MigrationBatch -Name $batchname -SourceEndpoint $endpointname -CSVData ([System.IO.File]::ReadAllBytes("$path")) -AutoStart
<# You can alternatively not copy over specific folders or apply some extra switch options #>
New-MigrationBatch -Name $batchname -SourceEndpoint $endpointname -CSVData ([System.IO.File]::ReadAllBytes("$path")) -AutoStart -ExcludeFolders ‘Junk’ -AllowIncrementalSyncs:$true
<# Check on status along the way: #>
Get-MigrationBatch -Name $batchname | Format-List
<# Complete a batch like so: #>
Complete-MigrationBatch -Name $batchname
<# Alternatively, instead of just sending to the single admin issued the completion (which is what happens when a single person runs the cmdlet, you could send it to a list or mail-enabled group: #>
$emailadmins = ‘email-admins-group@an-existing-office365-domain.com’
Complete-MigrationBatch -Name $batchname -NotificationEmails $emailadmins

 

Step 6: Update your DNS records to route Gmail directly to Office 365 (with more info here)

Okay, your mail should be copied over and you are ready to have the end users begin using their Office 365 mailbox and want to cut mail flow into your G Suite where it should only be delivered into Office 365.

[Wait, what? That’s not what I want to do!]
If you need to keep mailflow running in a different manner and didn’t read through the link at the top, you’ll need to look into additional options. TechNet has a ton more info for you to read depending on what you want to do… Otherwise, continue:

Update your MX record.
Your <domain-key> is your Office 365 domain name.
e.g. if you signed up with domain ‘contoso.com’, it would be: contoso-com.mail.protection.outlook.com

Name Type TTL Data
@ MX 3600 (1HR) <domain-key>.mail.protection.outlook.com.

 

Add (4) Required CNAME records.

Name Type TTL Data
autodiscover CNAME 3600 (1HR) autodiscover.outlook.com.
sip CNAME 3600 (1HR)  

sipdir.online.lync.com.

lyncdiscover CNAME 3600 (1HR) webdir.online.lync.com.
msoid CNAME 3600 (1HR) clientconfig.microsoftonline-p.net.

 

Update TXT record for SPF record to prevent spam.

Name Type TTL Data
@ TXT 3600 (1HR) v=spf1 include:spf.protection.outlook.com -all

 

Add SRV record for other Office 365 Services.

Name Type TTL Data
_sip._tls SRV 3600 (1HR)  

100 1 443 sipdir.online.lync.com.

_sipfederationtls._tcp SRV 3600 (1HR) 100 1 5061 sipfed.online.lync.com.

 

Mobile Device Management (MDM) requires (2) additional CNAME records.

Name Type TTL Data
enterpriseregistration CNAME 3600 (1HR) enterpriseregistration.windows.net.
enterpriseenrollment CNAME 3600 (1HR) enterpriseenrollment-s.manage.microsoft.com.

 

Step 7: Stop synchronization with Gmail

Once happy that mailflow is working as expected, you can remove the migration batch(es):

Remove-MigrationBatch -Identity $batchname -Confirm:$false

And alternatively the migration endpoint:

Remove-MigrationEndpoint -Identity $endpointname -Confirm:$false

 

Windows Anniversary – Enable Bash

Hurrah! Windows 10 (Version 10.0.14393) is here.

Prerequisites: you will need a 64-bit installation of Windows. Then you will need to have enable Developer Mode .

Start a new PowerShell window running as an Administrator. Run the following to enable the feature:

Enable-WindowsOptionalFeature -Online -FeatureName 'Microsoft-Windows-Subsystem-Linux'

Reboot your device and log back in.
Open the Start Menu, and type in “bash”.  (It points here: C:\Windows\System32\bash.exe).

The following prompt will appear:

-- Beta feature --
This will install Ubuntu on Windows, distributed by Canonical
and licensed under its terms available here:
https://aka.ms/uowterms
Type "y" to continue:

If you agree, type in “y” and press <Enter>. A download will begin:

Downloading from the Windows Store... 100%
Extracting filesystem, this will take a few minutes...
Please create a default UNIX user account. The username does not need to match your Windows username.
For more information visit: https://aka.ms/wslusers
Enter new UNIX username: <username>
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
Installation successful!
The environment will start momentarily...
Documentation is available at:  https://aka.ms/wsldocs
<username>@<hostname>:/mnt/c/Windows/System32$

Then go to town! You’ll get a new shortcut labeled ‘Bash on Ubuntu on Windows’ which points at the bash.exe and get the initial package updates out of the way.

<username>@<hostname>:/mnt/c/Windows/System32$ sudo apt-get update
<username>@<hostname>:/mnt/c/Windows/System32$ sudo apt-get upgrade

And does ‘cmatrix’ work…?

<username>@<hostname>:/mnt/c/Windows/System32$ sudo apt-get install cmatrix cmatrix-xfont
<username>@<hostname>:/mnt/c/Windows/System32$ cmatrix

It does!

If you want to poke around from Windows, the root filesystem is located here:

%USERPROFILE%\AppData\Local\Lxss\rootfs

PowerShell – Passing An Argument List Within A Script Block


$Options = @{'Variable'='test'}

Invoke-Command -ArgumentList $Options -ScriptBlock {
Param($Options)&{
Param(
[string]
$Variable
)
Write-Output -InputObject "ComputerName: $env:COMPUTERNAME"
Write-Output -InputObject "Variable: $Variable"
} @Options
} # End: Script block

# Sample Output:
# ComputerName: MY-LOCALHOST
# Variable: test

Thanks to this post:
http://stackoverflow.com/questions/28234509/powershell-splatting-the-argumentlist-on-invoke-command

PowerShell – Break vs. Continue Usage

For this example, we create two ArrayList objects and add a couple ints to them to run a loop to see if the value matches between the two and either continues a loop or breaks out of a loop:

$arraylist1 = New-Object -TypeName System.Collections.ArrayList
$arraylist1.Add(1) | Out-Null
$arraylist1.Add(2) | Out-Null
$arraylist1.Add(3) | Out-Null
$arraylist2 = New-Object -TypeName System.Collections.ArrayList
$arraylist2.Add(1) | Out-Null
$arraylist2.Add(2) | Out-Null
$arraylist2.Add(3) | Out-Null
Sample code for ‘continue’:
foreach ($item in $arraylist1)
{
    Write-Output -InputObject "item is: $item"
    foreach ($subitem in $arraylist2)
    {
        Write-Output -InputObject "subitem is: $subitem"
        if ($item -eq $subitem)
        {
            Write-Output -InputObject "$item matches $subitem! Continuing!"
            continue
        }
    }
}

…Which outputs:

item is: 1
subitem is: 1
1 matches 1! Continuing!
subitem is: 2
subitem is: 3
item is: 2
subitem is: 1
subitem is: 2
2 matches 2! Continuing!
subitem is: 3
item is: 3
subitem is: 1
subitem is: 2
subitem is: 3
3 matches 3! Continuing!
Sample code for ‘break’:
foreach ($item in $arraylist1)
{
    Write-Output -InputObject "item is: $item"
    foreach ($subitem in $arraylist2)
    {
        Write-Output -InputObject "subitem is: $subitem"
        if ($item -eq $subitem)
        {
            Write-Output -InputObject "$item matches $subitem! Breaking!"
            break
        }
    }
}

…which outputs:

item is: 1
subitem is: 1
1 matches 1! Breaking!
item is: 2
subitem is: 1
subitem is: 2
2 matches 2! Breaking!
item is: 3
subitem is: 1
subitem is: 2
subitem is: 3
3 matches 3! Breaking!

What can you use this for or when should I use what one? It depends on your use case. 🙂

PowerShell Desired State Configuration – Install Exchange Server 2013 Prerequisites

A simple DesiredStateConfiguration file that is used to install Exchange 2013 Prerequisites for both the mailbox and Client Access Server (CAS) roles on Windows Server 2013 R2. Use at your own peril!

Configuration Exchange2013PrerequisitesForServer2013MailboxRoleCASRole
{ 
       Node "localhost"
       { 
           WindowsFeature HTTPActivation 
           { 
                  Ensure    = “Present” 
                  Name      = “AS-HTTP-Activation” 
           } 

            WindowsFeature DesktopExperience 
           { 
                  Ensure    = “Present” 
                  Name      = “Desktop-Experience” 
           }

            WindowsFeature NETFramework45Features
           { 
                  Ensure    = “Present” 
                  Name      = “NET-Framework-45-Features” 
           } 

           WindowsFeature RPCoverHTTPProxy
           { 
                  Ensure    = “Present” 
                  Name      = “RPC-over-HTTP-Proxy"
           }
           WindowsFeature RSATClustering
           { 
                  Ensure    = “Present” 
                  Name      = “RSAT-Clustering"
           }
           WindowsFeature RSATClusteringCmdInterface
           { 
                  Ensure    = “Present” 
                  Name      = “RSAT-Clustering-CmdInterface"
           }
           WindowsFeature WebMgmtConsole
           { 
                  Ensure    = “Present” 
                  Name      = “Web-Mgmt-Console"
           }
           WindowsFeature WASProcessModel
           { 
                  Ensure    = “Present” 
                  Name      = “WAS-Process-Model"
           }
           WindowsFeature WebAspNet45
           { 
                  Ensure    = “Present” 
                  Name      = “Web-Asp-Net45"
           }
           WindowsFeature WebBasicAuth
           { 
                  Ensure    = “Present” 
                  Name      = “Web-Basic-Auth"
           }
           WindowsFeature WebClientAuth
           { 
                  Ensure    = “Present” 
                  Name      = “Web-Client-Auth"
           }
           WindowsFeature WebDigestAuth
           { 
                  Ensure    = “Present” 
                  Name      = “Web-Digest-Auth"
           }
           WindowsFeature WebDirBrowsing
           { 
                  Ensure    = “Present” 
                  Name      = “Web-Dir-Browsing"
           }
           WindowsFeature WebDynCompression
           { 
                  Ensure    = “Present” 
                  Name      = “Web-Dyn-Compression"
           }
           WindowsFeature WebHttpErrors
           { 
                  Ensure    = “Present” 
                  Name      = “Web-Http-Errors"
           }
           WindowsFeature WebHttpLogging
           { 
                  Ensure    = “Present” 
                  Name      = “Web-Http-Logging"
           }
           WindowsFeature WebHttpRedirect
           { 
                  Ensure    = “Present” 
                  Name      = “Web-Http-Redirect"
           }
           WindowsFeature WebHttpTracing
           { 
                  Ensure    = “Present” 
                  Name      = “Web-Http-Tracing"
           }
           WindowsFeature WebISAPIExt
           { 
                  Ensure    = “Present” 
                  Name      = “Web-ISAPI-Ext"
           }
           WindowsFeature WebISAPIFilter
           { 
                  Ensure    = “Present” 
                  Name      = “Web-ISAPI-Filter"
           }
           WindowsFeature WebLgcyMgmtConsole
           { 
                  Ensure    = “Present” 
                  Name      = “Web-Lgcy-Mgmt-Console"
           }
           WindowsFeature WebMetabase
           { 
                  Ensure    = “Present” 
                  Name      = “Web-Metabase"
           }
           WindowsFeature WebMgmtService
           { 
                  Ensure    = “Present” 
                  Name      = “Web-Mgmt-Service"
           }
           WindowsFeature WebNetExt45
           { 
                  Ensure    = “Present” 
                  Name      = “Web-Net-Ext45"
           }
           WindowsFeature WebRequestMonitor
           { 
                  Ensure    = “Present” 
                  Name      = “Web-Request-Monitor"
           }
           WindowsFeature WebServer
           { 
                  Ensure    = “Present” 
                  Name      = “Web-Server"
           }
           WindowsFeature WebStatCompression
           { 
                  Ensure    = “Present” 
                  Name      = “Web-Stat-Compression"
           }
           WindowsFeature WebStaticContent
           { 
                  Ensure    = “Present” 
                  Name      = “Web-Static-Content"
           }
           WindowsFeature WebWindowsAuth
           { 
                  Ensure    = “Present” 
                  Name      = “Web-Windows-Auth"
           }
           WindowsFeature WebWMI
           { 
                  Ensure    = “Present” 
                  Name      = “Web-WMI"
           }
           WindowsFeature WindowsIdentityFoundation
           { 
                  Ensure    = “Present” 
                  Name      = “Windows-Identity-Foundation"
           }

    } 
}

Load up the Configuration script into memory by entering:

Exchange2013PrerequesitiesForServer2013MailboxRoleCASRole

It generates a folder in your working directory and generates a .MOF file.

Run it on the Server 2012 server to install the Windows Features:

Start-DscConfiguration -Wait -Verbose -Path .\Exchange2013PrerequisitesForServer2013MailboxRoleCASRole

 

Reference full installation of Server 2013:
http://exchangeserverpro.com/install-exchange-2013-pre-requisites-windows-server-2012/
Reference for Desired State Configuration:
http://technet.microsoft.com/library/dn249918.aspx

Exchange – Device Related Cmdlets

Exchange 2010 – Get-ActiveSyncDevice:
http://technet.microsoft.com/en-us/library/dd335068(v=exchg.150).aspx

Exchange 2013 – Get-MobileDevice:
http://technet.microsoft.com/en-us/library/jj218706(v=exchg.150).aspx

Differences:
http://exchangeserverpro.com/mobile-device-management-cmdlets-exchange-2013/

Get-ActiveSyncDevice is deprecated for Get-MobileDevice.
A Task-Based Guide to Windows PowerShell Cmdlets:
http://technet.microsoft.com/en-us/scriptcenter/dd772285.aspx

Hidden Outlook Folder

When a user opens an attachment from within Outlook, the file is saved to a hidden location on the workstation they are using. Sometimes, an end-user would edit this copy of the document, save it, and then when they go to re-open it they can’t find it.

The hidden folder location is stored in the Registry under the “OutlookSecureTempFolder” key. The folder name is randomly generated. Search results do not bring up the location. Below is a quick-list of the registry entry you want to look at to give you the full folder name:

Outlook 97:

HKEY_CURRENT_USER\Software\Microsoft\Office\8.0\Outlook\Security

Outlook 98:

HKEY_CURRENT_USER\Software\Microsoft\Office\8.5\Outlook\Security

Outlook 2000:

HKEY_CURRENT_USER\Software\Microsoft\Office\9.0\Outlook\Security

Outlook 2002/XP:

HKEY_CURRENT_USER\Software\Microsoft\Office\10.0\Outlook\Security

Outlook 2003:

HKEY_CURRENT_USER\Software\Microsoft\Office\11.0\Outlook\Security

Outlook 2007:

HKEY_CURRENT_USER\Software\Microsoft\Office\12.0\Outlook\Security

Outlook 2010:

HKEY_CURRENT_USER\Software\Microsoft\Office\14.0\Outlook\Security

Microsoft KB:
http://support.microsoft.com/kb/817878