Migrating OnPrem DLs to Cloud DLs

If you find that you need to migrate an onprem DL into a cloud DL, you can use the script below….

<#

.SYNOPSIS

       This command converts a Federated DL to a Cloud-only DL

 

.DESCRIPTION

    This command accepts the DISPLAY NAME of a on-prem DL that has been synchronized to Azure and appears in Exchange Online as a Federated DL.

      

    It will then gather the ManagedBy, AcceptMessagesFrom, RejectMessagesFrom, PrimarySMTP address and group members from the federated DL, delete the

    original Federated DL from Exchange Online, delete the MSOL group object AND remove from the Azure recycle bin, create a CLOUD DL in Exchange Online

    then connect to the on-premises Active Directory clear the MAIL and DisplayName attributes from the on-prem DL (so DirSync will not Sync it) and hide

    the DL from the Address Book so it no longer appears in the on-prem GAL.   

 

.PARAMETER Group

       DisplayName of the DL

 

.EXAMPLE

    Delete-FederatedDLandReplaceWithDuplicateCloudDL.ps1 "Global Distribution List"

   

    Converts the DL "Global Distribution List" from a Federated to Cloud DL.

 

.INPUTS

       Group

 

.OUTPUTS

       Nothing upon success

 

.NOTES

       NAME:  Delete-FederatedDLandReplaceWithDuplicateCloudDL.ps1

       AUTHOR:       Darryl Kegg

       DATE:  19 May, 2014

       EMAIL: dkegg@microsoft.com

 

       REQUIREMENTS:

        – MSONLINE ps module available on the system

        – ACTIVEDIRECTORY module available on the system

             

 

       VERSION HISTORY:

       1.0 19 May, 2014

              Initial Version

        1.1 4 Sept, 2014

              Added check to see if group is a DL or security group

                Added creation of contact object with target address of new cloud DL – named CT-DLNAME

              Added creation of new DL onprem with single member, the CT-DLNAME contact

              Added set of Custom Attribute 13 to NO so it can be filtered by DirSync

        1.2 7 Sept, 2014

              Added parameters for Group OU and Contact OU locations

              Added pause in cloud DL creation for retrieval of @tenant.onmicrosoft.com email address

              Added parameter to prompt for AD credentials

       1.3 8 Sept, 2014

              Updated script to use Exchange remote Powershell for Group, Contact and membership functions

              Set default action for Exchange Online and Exchange On-Prem powershell is to load credentials from file

       1.4 11 Sept, 2014

              Updated script to grab @tenant.mail.onmicrosoft.com address from onprem group to be used as a proxy on cloud group

              Updated script to use this proxy address as the new contact target address

 

 

                             THIS CODE AND ANY ASSOCIATED INFORMATION ARE PROVIDED “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR

                             IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR

                             PURPOSE. THE ENTIRE RISK OF USE, INABILITY TO USE, OR RESULTS FROM THE USE OF THIS CODE REMAINS WITH THE USER.

 

 

#>

 

Function Delete-FederatedDLandReplaceWithDuplicateCloudDL

{

 

[CmdletBinding()]

param

(   

    [Parameter(

       Position=0,

       Mandatory = $true,

       ValueFromPipeline=$true,

       ValueFromPipelineByPropertyName=$true,

       HelpMessage="Provide the DISPLAY NAME source mail-enabled security group that you will be converting"

    )]

       [Alias('DisplayName')]

       [String]$group,

       [Parameter(Mandatory = $false)]

       [switch]$AllowSecurityGroup,

       [Parameter(Mandatory = $false,

       HelpMessage="Enter the DN of the Organization Unit where Groups are to be placed.  e.g. OU=Groups,DC=Contoso,DC=com"

    )]

       [string]$GroupOU,

       [Parameter(Mandatory = $false,

       HelpMessage="Enter the DN of the Organization Unit where Contacts are to be placed.  e.g. OU=Contacts,DC=Contoso,DC=com"

    )]

       [string]$ContactOU,

       [Parameter()]

       [ValidateNotNull()]

       [System.Management.Automation.PSCredential]

       [System.Management.Automation.Credential()]

       $ADCredentials = [System.Management.Automation.PSCredential]::Empty

)

 

BEGIN {

 

Write-verbose "Checking AD Credentials value and if present creating splat hashtable and adding them."

 

$credsplat = @{}

if ($ADCredentials -ne [System.Management.Automation.PSCredential]::Empty)

{

  $CredSplat['Credential'] = $ADCredentials

}

 

$DomainRootDN = ([adsi]'').distinguishedname

 

if($groupOU)

{

  Write-verbose "Group OU parameter provided, checking to ensure it's correctly formatted"

  if ($GroupOU -notlike "*,$DomainRootDN")

   {

     Write-host -fore red "Group OU provided does not contain $DomainRootDN, exiting …"

     break

   }

}

 

if($ContactOU)

{

  Write-verbose "Group OU parameter provided, checking to ensure it's correctly formatted"

  if ($ContactOU -notlike "*,$DomainRootDN")

   {

     Write-host -fore red "Contact OU provided does not contain $DomainRootDN, exiting …"

     break

   }

}

 

if (!$GroupOU) { write-verbose "No Group OU provided, setting default to Users container";$GroupOU = "CN=Users,$DomainRootDN"}

if (!$ContactOU) { write-verbose "No Contact OU provided, setting default to Users container";$ContactOU = "CN=Users,$DomainRootDN"}

 

 

write-verbose "Domain Root DN : $domainrootDN"

write-Verbose "$($credsplat.credential)"

write-Verbose "Allow security group : $allowsecuritygroup"

write-verbose "group : $group"

Write-verbose "Contact OU : $contactOU"

write-verbose "Group OU : $groupOU"

Write-verbose "AD Credentials : $adcredentials"

 

# Check to see if EXO powershell commands are already present, if so this means we don't need to connect to the tenant again.

if ((!(get-pssession | where {$_.ConfigurationName -match "Microsoft.Exchange"})) -or (!(get-module -name MSOnline)))

{

 

# Connect to MSOnline

write-verbose "Getting Credentials to connect to MSOL and EXO from Input file"

 

$msolCredentials = $null

$msoluser = cat C:\PowerShell\365user.txt

$msolpass = cat C:\PowerShell\365securestring.txt | ConvertTo-SecureString

$msolCredentials = new-object -typename System.Management.Automation.PSCredential -argumentlist $msoluser,$msolpass

 

if(!(get-module -name MSOnline)){import-module MSOnline}

write-verbose "Conecting to the MSOL service"

Connect-MsolService -Credential $msolCredentials

 

# Connect to Exchange Online

Write-Verbose "Connecting to Exchange Online"

$msoExchangeURL = "https://ps.outlook.com/powershell/"

$session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri $msoExchangeURL -Credential $msolCredentials -Authentication Basic -AllowRedirection

Import-PSSession $session -prefix EXO -AllowClobber

}

 

Write-Verbose "Loading credentials to connect to On-Premises Exchange fom Input file"

 

$ADuser= cat C:\PowerShell\ADuser.txt

$ADpass= cat C:\PowerShell\ADsecurestring.txt | ConvertTo-SecureString

$ADCreds = new-object -typename System.Management.Automation.PSCredential -argumentlist $ADuser,$ADpass

$ADExchangeURL = "http://exchange.contoso.com/powershell"

$session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri $ADExchangeURL -Credential $ADCreds

Import-PSSession $session

 

write-verbose "Testing for Active Directory PS module"

if (!(get-module ActiveDirectory)) { import-module ActiveDirectory }

 

}

 

PROCESS{

 

if ($PSBoundParameters['Verbose'])  {write-verbose "Verbose checked"}

if (!$PSBoundParameters['Verbose'])  {write-host -fore yellow "Verbose not checked"; $verbosemode = $false}

 

# get the name of the group that's an on-prem mail-enabled security group

Write-Verbose "Getting the DL requested"

 

#  for testing $msoluser = $( try {Get-MsolUser -UserPrincipalName $UserPrincipalName -ErrorAction SilentlyContinue} catch {$null})

 

$from = $( try {get-EXODistributionGroup $group} catch {$null} )

 

# confirm the group exists

write-verbose "Confirming Group exists in Cloud"

if (!$from) {write-host -ForegroundColor Red "Group Does not Exist in Exchange Online, Exiting …";break}

 

Write-verbose "Confirming Group exists in On-Prem AD"

$adgroup = $( try {get-adgroup @credsplat $from.name -properties * -ErrorAction SilentlyContinue } catch {$null} )

 

if (!$adgroup) {Write-host -fore Red "Group Does not Exist in On-Premises AD, Exiting …";break}

 

# Check the grouptype atttribute, we need to warn about Security groups and give the option to abort

 

write-verbose "Checking group type – $($adgroup.grouptype)"

if ($adgroup.grouptype -eq "-2147483646" -or $adgroup.grouptype -eq "-2147483640" -and !$AllowSecurityGroup)

{

  Write-host -fore red "This group is a Security Group, and will not be processed by default."

  Write-host -fore red "Please use the -AllowSecurityGroup switch to allow processing of a Security Group"

  break

}

 

# capture the member list from the original group

Write-Verbose "Group has been confirmed in Exchange Online and in On-Premises AD, continuing"

 

Write-Verbose "Getting list of DL members"

$FromGroupID = [string]$from.guid

$FromMembers = Get-EXODistributionGroupMember -Identity $fromgroupID

 

Write-Verbose "Getting the @tenant.mail.onmicrosoft.com address from the DL for use on the contact later"

$fromProxies = $from.emailaddresses

$NewContactTarget = $null

foreach ($fromProxy in $fromProxies)

{

  if ($fromProxy -like "*mail.onmicrosoft.com")

  {

    $NewContactProxy = $fromProxy

  }

}

if ($!NewContactProxy)

{

  Write-host -fore red "WARNING : No proxy address containing mail.onmicrosoft.com was found on the cloud DL"

  Write-host -fore red "          Stopping Script before anything is deleted"

  break

}

 

if ($NewContactProxy)

{

  write-verbose "Found $NewContactProxy on the DL, we will add this to the new cloud DL and use for the proxy"

}

 

# Delete MSOL object from Azure

Write-Verbose "Removing group from Azure – this will cause the Exchange Online group to be deleted"

$removeMSOL = remove-msolgroup -ObjectId $from.externaldirectoryobjectID -Force

 

# loop GET command until fails, which means EXO DL is gone

write-verbose "Checking against Exchange Online until the object is gone"

write-host "Waiting for AD group to disappear from the cloud" -nonewline

do

  {

   write-host "." -nonewline

   $testForDL = $( try { get-EXODistributionGroup -identity $fromgroupid -ErrorAction SilentlyContinue } catch {$null} )

   start-sleep -seconds 2

  }

while ($testforDL)

 

write-host

write-verbose "Group is now gone from Exchange Online, resuming …"

 

write-verbose "Creating new CLOUD DL"

$to = New-EXODistributionGroup -Name "$($from.displayname)" -Notes "CloudDL created as duplicate of on-prem group named $group" -PrimarySmtpAddress $from.PrimarySmtpAddress

 

$togroupID = [string]$to.guid

 

# Add $NewContactProxy to the cloud DL we've created

Write-Verbose "Adding $NewContactProxy to our new cloud DL"

$to.EmailAddresses += $NewContactProxy

$setnew = Set-EXODistributionGroup $togroupID -EmailAddresses $to.EmailAddresses

if(!$setnew)

{

  Write-Verbose "WARNING : Failed to add $NewContactProxy to the new cloud DL"

  Write-Verbose "          Be Aware that the rest of the script will most likely have errors"

  Write-Verbose "          However we will proceed since the new cloud DL has already been created"

}

 

# apply the member list to the new group

Write-Verbose "Populating CLOUD DL member list from old group member list"

foreach ($member in $frommembers)

{Add-ExoDistributionGroupMember -Identity $togroupid -Member $member.PrimarySmtpAddress}

 

# apply the AcceptMessagesOnlyFrom array values to new group

Write-Verbose "Adding ACCEPTMESSAGES and REJECTMESSAGES values to CLOUD DL from old group"

Set-ExoDistributionGroup -Identity $togroupID -AcceptMessagesOnlyFrom $from.AcceptMessagesOnlyFrom -AcceptMessagesOnlyFromDLMembers $from.AcceptMessagesOnlyFromDLMembers

Set-ExoDistributionGroup -identity $togroupID -RejectMessagesFrom $from.RejectMessagesFrom -RejectMessagesFromDLMembers $from.RejectMessagesFromDLMembers

 

Write-Verbose "Setting Managed-By Value on Cloud Group"

Set-ExoDistributionGroup -identity $toGroupID -ManagedBy $from.ManagedBy

 

Write-Verbose "Locating GUID value of onprem group"

$onpremGUID = $adgroup.objectGUID

 

<#

 

This section was originally built to use AD to create the group, contact and update group with the contact as a member, however the only way this will work is if the AD account used to invoke the script has full rights in AD.  I added a -ADCredentials command, which works fine with the get-adgroup, new-adobject and new-adgroup commands, however there is a limitation in the built-in ActiveDirectory cmdlets where add-adgroupmember will not supporting adding a contact to a group.  ADSI accelerator can be used to update membership, however you can't pass credentials to the command.   The only workaround for this would be to use the Quest ActiveRoles AD commands.  Instead, I have updated the script to use remote Exchange powershell to create the group, contact and add the contact as a member to the group.   The AD commands can be added back in, in lieu of the Exchange powershell, in cases where you dont have Powershell remoting configured.

 

Here's the original AD based commands, which are tested and work fine using the -ADCredentials switch for everything but the $joinGroup.Add function at the bottom.  Again, you could import the Quest ActiveRoles Powershell cmdlets and update the final command to use those.

 

# Delete Onprem group

write-Verbose "Removing On-Prem AD group"

remove-adgroup -identity $onpremGUID @credsplat -confirm

 

# Create Contact Object to represent Cloud DL

Write-Verbose "Creating Contact Object, Hidden from address book"

new-adobject @credsplat -name "CT-$($from.name)" -type Contact -DisplayName "CT-$($from.displayname)" -path $ContactOU -OtherAttributes @{'mailnickname'=$from.PrimarySMTPAddress;'mail'=$from.PrimarySMTPAddress;'TargetAddress'=$from.PrimarySMTPAddress;'msExchHideFromAddressLists'=$true;'extensionAttribute13'="NO"} -confirm

 

# Create onprem DL with single contact member

Write-Verbose "Creating new AD group to replace old group"

new-adgroup @credsplat -Name $from.name -GroupScope Global -GroupCategory Distribution -Displayname $from.Displayname -path $groupOU -Description "AD Group created to represent Cloud group $($from.name)" -OtherAttributes @{'mailnickname'=$from.PrimarySMTPAddress;'mail'=$from.PrimarySMTPAddress;'extensionAttribute13'="NO"}  -confirm

 

Write-Verbose "Adding Contact Object to new group"

$joinGroup = [ADSI] "LDAP://CN=$($from.name),$GroupOU"

$joinGroup.add("LDAP://CN=CT-$($from.name),$contactOU")

 

#>

 

Write-verbose "Using Exchange Remote Powershell connection to $ADExchangeURL"

 

write-Verbose "Removing On-Prem group"

Remove-DistributionGroup -Identity $group

 

Write-Verbose "Creating Contact Object, Hidden from address book, using $NewContactProxy as target address"

New-MailContact -Name "CT-$($from.name)" -ExternalEmailAddress $NewContactProxy -OrganizationalUnit $contactOU -confirm 

Set-MailContact -Identity "CT-$($from.name)" -HiddenFromAddressListsEnabled:$true

 

Write-Verbose "Creating new on-prem group to replace old group"

New-DistributionGroup -Name "$($from.name)" -OrganizationalUnit $groupOU -Type "Distribution" -notes "AD Group created to represent Cloud group $($from.name)" -primarySMTPaddress "$($from.PrimarySMTPAddress)" -confirm

 

Write-Verbose "Adding Contact Object to new group"

Add-DistributionGroupMember -Identity "$($from.name)" -Member "$($from.PrimarySMTPAddress)" -confirm

 

 

  } # Process

} # Function