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