Migrating Distro Groups from Exchange to Office 365, part 2

In part one (see here) I mentioned a PowerShell script (Export_DistroGroup.ps1) that I wrote to export certain settings of distribution groups in preparation for migrating them to Office 365.

1. Gather data. I exported a list of all distribution groups and their properties to a csv file. Then I wrote a PowerShell script (Export_DistroGroup.ps1) to export key details to multiple csv files for each group, which I would later use to populate and configure the new online groups. I created this folder structure to accommodate the script: C:\Temp\Groups\Completed. (Details to come in part 2.) [Exerpted from part 1.]

Export_DistroGroup.ps1 creates a set of 1-7 files (depending on which group attributes are populated) that will be used by Import_DistroGroup.ps1 to configure the new groups in Exchange Online. Each file is prefixed by the name of the source group. For example, running the script against the Human Resources group like this…

Export_DistroGroup "Human Resources"

…will create the following files:

File 1: Human Resources-Managers.csv – Contains the contents of the ManagedBy attribute.

File 2: Human Resources-AcceptFrom.csv – Contains the contents of the AcceptMessagesOnlyFrom attribute.

File 3: Human Resources-AcceptFromDL.csv – Contains the contents of the AcceptMessagesOnlyFromDLMembers attribute.

File 4: Human Resources-MemberOf.csv – Contains a list of groups of which “Human Resources” is a member.

File 5: Human Resources-Members.csv – Contains a list of the group members.

File 6: Human Resources-Addresses.csv – Contains a list of all group email addresses plus the LegacyExchangeDN attribute.

File 7: Human Resources-Settings.csv – Contains the group’s HiddenFromAddressListsEnabled and RequireSenderAuthenticationEnabled attributes. It’s simple to add more attributes to this file if you need them. Just edit the Export_DistroGroup.ps1 and Import_DistroGroup.ps1 scripts as required.

If any of these attributes are empty, the script skips creating that file.

Migrating distribution groups from an on-premise Exchange organization to Office 365.

Remember: Evaluate this code and your environment carefully before using it. No guarantees. No promises. Use at your own risk.

# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Filename: Export_DistroGroup.ps1
# Version:     2016.03.15
# Purpose: Exports the managers, allowed senders, allowed sender 
# groups, member-of groups, group members, and group email addresses to
# a collection of csv files at the path specified by the OutPath 
# variable. Leave the output files in this location when this script is
# complete. The Import_DistroGroup.ps1 script will move these files as
# the exported data is imported to the new distribution groups in 
# Exchange Online.
# Note: This script uses the Quest ActiveRoles PowerShell snapin for
# retrieving the groups that this group is a member of.
# Usage:
# Export_DistroGroup "Group Name"


# Specify your desired output path here.
$OutPath = "C:\Temp\Groups\"

# Load the Quest AD snapin.
if ((get-pssnapin -name quest.activeroles.admanagement -EA 0) `
  -eq $null) {
    Write-Host "Loading Quest..."
    Add-PsSnapin quest.activeroles.admanagement

# Get the distribution group and save it to a variable.
$GroupObject = (Get-DistributionGroup $Group)

# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# *** Group Managers ***
# Loop each entry in the group's ManagedBy attribute to get the 
# UserPrincipalName for the manager(s).
# If the manager is a user, save it as $Manager.
# If the manager is a group, save it as $ManagerGroup.
# Get the UserPrincipalName for the Manager or ManagerGroup.
$Managers = ($GroupObject.ManagedBy | Foreach { 
    $Manager = (Get-User $_.DistinguishedName -EA 0)
    $ManagerGroup = (Get-Group $_.DistinguishedName -EA 0)
    if ($Manager) {$Manager | Select UserPrincipalName}
    elseif ($ManagerGroup) {$ManagerGroup | Select UserPrincipalName}

# Export the groups managers to a file called 
# -Managers.csv. If there are no managers, skip the
# file creation.
if (-not $Managers) {
    Write-Host "The ManagedBy attribute for $Group is empty." `
      -ForegroundColor Yellow
else {
    Write-Host "Exporting the group's owners..."
    $ManagersFile = $OutPath + $Group + "-Managers.csv"
    $Managers | Export-CSV $ManagersFile -NoTypeInformation
    Remove-Variable Managers
    Remove-Variable ManagersFile

# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# *** Allowed Senders ***
# Loop each entry in the group's AcceptMessagesOnlyFrom attribute to
# get the UserPrincipalName for all entries.
$AcceptFrom = ($GroupObject.AcceptMessagesOnlyFrom | Foreach {
    $Accepted = (Get-User $_.DistinguishedName -EA 0)
    $AcceptedGroup = (Get-Group $_.DistinguishedName -EA 0)
    if ($Accepted) {$Accepted | Select UserPrincipalName}
    elseif ($AcceptedGroup) {$AcceptedGroup | Select UserPrincipalName}

# Export the allowed senders to a file called 
# -AcceptFrom.csv. If there are no allowed senders, 
# skip the file creation.
if (-not $AcceptFrom) {
    Write-Host `
      "The AcceptMessagesOnlyFrom attribute for $Group is empty." `
      -ForegroundColor Yellow
else {
    Write-Host `
      "Exporting the group's AcceptMessagesOnlyFrom attribute..."
    $AcceptFromFile = $OutPath + $Group + "-AcceptFrom.csv"
    $AcceptFrom | Export-CSV $AcceptFromFile -NoTypeInformation
    Remove-Variable AcceptFrom
    Remove-Variable AcceptFromFile

# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# *** Allowed Sender DLs ***
# Export the allowed sender distro groups to a file called 
# -AcceptFromDL.csv. If there are no allowed sender distro 
# groups, skip the file creation.
$AcceptFromDL = ($GroupObject.AcceptMessagesOnlyFromDLMembers `
  | Select Name)
if (-not $AcceptFromDL) {
    Write-Host `
      "The AcceptMessagesOnlyFromDLMembers attribute for $Group is empty." `
      -ForegroundColor Yellow
else {
    Write-Host `
      "Exporting the group's AcceptMessagesOnlyFromDLMembers attribute..."
    $AcceptFromDLFile = $OutPath + $Group + "-AcceptFromDL.csv"
    $AcceptFromDL | Export-CSV $AcceptFromDLFile -NoTypeInformation
    Remove-Variable AcceptFromDL
    Remove-Variable AcceptFromDLFile

# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# *** Nested Group Memberships ***
# Export the groups that this group is a member of to a file called
# -MemberOf.csv. If the group isn't a member of any
# other groups, skip the file creation.
$Memberof = (Get-QADMemberOf $Group | Select GroupName)
if (-not $MemberOf) {
    Write-Host "The MembersOf attribute for $Group is empty." `
      -ForegroundColor Yellow
else {
    Write-Host "Exporting the group's up-stream nested membership."
    $MemberOfFile = $OutPath + $Group + "-MemberOf.csv"
    $MemberOf | Export-CSV $MemberOfFile -NoTypeInformation
    Remove-Variable MemberOf
    Remove-Variable MemberOfFile

# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# *** Group Members ***
# Get a list of group members in a hashtable with a single column
# called UserPrincipalName. Different types of members will be saved
# using a different field, but they will all be saved in the 
# UserPrincipalName attribute of the hashtable. 
# User = UserPrincipalName
# Group = Name (Appended with OL to indicate the On-Line distro
#     group that will be created later in this process. See step 2 in 
#     the previous blog post.)
# Contact = PrimarySmtpAddress
# Dynamic Group = Name (Appended with DL so that this line will be
#     ignored by Import_DistroGroup.ps1 later. Dynamic groups will 
#     have to be re-added to up-level nested groups at a later time.)
$Members = (Get-Group $Group).Members | Foreach {
    $Member = (Get-User $_.DistinguishedName -EA 0)
    if (-not $member) {
        $MemberGroup = (Get-Group $_.DistinguishedName -EA 0)
        if (-not $MemberGroup) {
            $MemberContact = (Get-MailContact $_.DistinguishedName -EA 0)
        else {
            $MemberDynGroup = `
              (Get-DynamicDistributionGroup $_.DistinguishedName -EA 0)
    if ($Member) {$Member | Select UserPrincipalName}
    elseif ($MemberGroup) {
        $MemberGroup | Select `
          @{Name="UserPrincipalName";Expression={$_.Name + " OL"}}
    elseif ($MemberContact) {
        $MemberContact | Select `
    elseif ($MemberDynGroup) {
        $MemberDynGroup | Select `
          @{Name="UserPrincipalName";Expression={$_.Name + " DL"}}

# Export the list of members to a file named 
# -Members.csv. If there are no group members, the file
# creation is skipped.
if (-not $Members) {
    Write-Host "$Group has no members."    -ForegroundColor Yellow
else {
    Write-Host "Exporting the group's members..."
    $MembersFile = $OutPath + $Group + "-Members.csv"
    $Members | Export-CSV $MembersFile -NoTypeInformation
    Remove-Variable Members
    Remove-Variable MembersFile

# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# *** Group Addresses ***
# Get a list of the group's addresses and add the group's 
# legacyExchangeDN to the list of addresses. Export this list to a file
# called -Addresses.csv.
Write-Host "Exporting the group's addresses..."
$AddressesFile = $OutPath + $Group + "-Addresses.csv"
$Addresses = $GroupObject.EmailAddresses
$Legacy = New-Object -TypeName PsObject
$Legacy | Add-Member -MemberType NoteProperty -Name `
  AddressString -Value $GroupObject.LegacyExchangeDN
$Legacy | Add-Member -MemberType NoteProperty -Name `
  ProxyAddressString -Value ("X500:" + $GroupObject.LegacyExchangeDN)
$Legacy | Add-Member -MemberType NoteProperty -Name `
  Prefix -Value "X500"
$Legacy | Add-Member -MemberType NoteProperty -Name `
  IsPrimaryAddress -Value $False
$Legacy | Add-Member -MemberType NoteProperty -Name `
  PrefixString -Value "X500"
$Addresses += $Legacy
$Addresses | Export-CSV $AddressesFile -NoTypeInformation

# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# *** Group Settings ***
# Get the group's HiddenFromAddressListsEnabled and 
# RequireSenderAuthenticationEnabled attributes. If you need to save 
# other non-list attributes, this is a good place to add them.
Write-Host "Exporting the group's miscellaneous..."
$SettingsFile = $OutPath + $Group + "-Settings.csv"
$GroupObject | Select HiddenFromAddressListsEnabled, `
  RequireSenderAuthenticationEnabled | Export-CSV $SettingsFile `

Write-Host ""
Write-Host "$Group export complete. 
Review output files at $OutPath." -ForegroundColor Cyan

Let me know if you see any errors. It’s possible I made some mistakes while formatting the code for the blog and it’s equally possible that WordPress or my theme has introduced errors in its own format second-guessing.

Part 1 – Overview of the process I used to migrate my Exchange distribution groups to Office 365.
Part 2 – Exporting your on-premises distro group settings to CSV files.
Part 3 – Importing your group settings from the CSV files to Exchange Online.
Part 4 – Renaming and addressing your online distro groups.
Part 5 – Rebuilding your Dynamic Distribution groups online.

[Update 4/1/2016: Correction to export of LegacyExchangeDN.]

Leave a Reply

Your email address will not be published. Required fields are marked *