Mail Enabled Public Folders In A Hybrid Deployment: A Sad Story
The scenario that I will discuss today is not really a fancy one.
The story began with exchange on premise and a hybrid deployment where public folders are migrated to the Office365, 90% of users are migrated to the cloud.
The main question is how to make the mail enabled public folders appear in the on premise GAL and on the office365 GAL.
We already know that DirSync process won’t synchronize mail enabled public folders from the cloud to the on premise environment, that’s a fact.
The only options is to create a mail enabled contact in the on premise environment et voila
Sounds quick easy and achievable BUT real life will carry more complicity with other staffs like distribution groups.
In our scenario all distribution Groups were created originally in the on premise and are synched to the cloud, let’s call them Cloud Groups.
The issue here is that you can’t add cloud folders directly to the group because the group was originally created in the on premise
If we try to add the mail contact to the on premise group, the contact will appear to the on premise users but will never appear as a member to the cloud users.
Ouch, what shall we do then?
I would think of two options however I won’t say I like any of them but those are what we have unfortunately:
1- Mirror the distribution Groups using a script and keep evaluating the membership.
2- Recreate all the distribution groups in the office365 as 90% of users are already in cloud.
In this article we will cover the second option unfortunately
There are many things to cover here:
1- Handle email addresses + LegacyExchangeDN(X500)
2- Handle Multi Value attributes and convert them from distinguishedName to Email address
2.a GrantSendOnBehalfTo
2.b ManagedBy
2.c ModeratedBy
2.d AcceptMessagesOnlyFromSendersOrMembers
2.e BypassModerationFromSendersOrMembers
2.f RejectMessagesFromSendersOrMembers
3- Handle SendAs and SendOnBehalf permissions
4- Handle group membership
5- Handle single value attributes that can be set in the Office365
They are defined in the script in the array $Listofsingleprops
I skipped some properties that can’t be changed in office365 like "MaxSendSize","MaxReceiveSize"
As the process is a bit complex, I decided to split it into several stages so that it’s also possible to use
the same process to recreate distribution groups in a different forest.
Stage 0: One time configuration
1- Create a new OU in active directory “NoDirSync”
2- Exclude this OU form the DirSync configuration
Stage1: Preparation
I developed a script to export all the desired informed to a csv file, the prepare-DGToOffice365.ps1 script.
1- This script will export the following attributes in addition to the group membership and the sendAs permission
$listOfprops=@("SamAccountName","EmailAddresses","ManagedBy","AcceptMessagesOnlyFrom","AcceptMessagesOnlyFromDLMembers",
"AcceptMessagesOnlyFromSendersOrMembers","BypassModerationFromSendersOrMembers",
"ModeratedBy","RejectMessagesFrom","RejectMessagesFromDLMembers",
"RejectMessagesFromSendersOrMembers","GrantSendOnBehalfTo","BypassNestedModerationEnabled",
"MemberJoinRestriction","MemberDepartRestriction","ReportToManagerEnabled",
"ReportToOriginatorEnabled","SendOofMessageToOriginatorEnabled","HiddenFromAddressListsEnabled",
"ModerationEnabled","RequireSenderAuthenticationEnabled","SendModerationNotifications","Alias",
"CustomAttribute1","CustomAttribute10","CustomAttribute11","CustomAttribute12",
"CustomAttribute13","CustomAttribute14","CustomAttribute15","CustomAttribute2",
"CustomAttribute3","CustomAttribute4","CustomAttribute5","CustomAttribute6","CustomAttribute7",
"CustomAttribute8","CustomAttribute9","DisplayName","MaxSendSize",
"MaxReceiveSize","PrimarySmtpAddress","SimpleDisplayName","MailTip")
<#
.SYNOPSIS
Use the Prepare-DGToOffice365.ps1 script to export the distribution groups information to csv file
.DESCRIPTION
The ultimate objective is to delete all distribution groups from exchange on-premise and create them in the Office365 cloud.
This sample script is the 1st script to export the distribution groups information from exchange on-premise
.NOTES
Author : Ahmed Ashour - ahmed.ashour@hotmail.fr
Creation Date: v1.0 4 Jan 2015
Last Update : -----------------
# DISCLAIMER:
# THIS CODE IS SAMPLE CODE. THESE SAMPLES ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.
# MICROSOFT FURTHER DISCLAIMS ALL IMPLIED WARRANTIES INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR OF FITNESS FOR
# A PARTICULAR PURPOSE. THE ENTIRE RISK ARISING OUT OF THE USE OR PERFORMANCE OF THE SAMPLES REMAINS WITH YOU. IN NO EVENT SHALL
# MICROSOFT OR ITS SUPPLIERS BE LIABLE FOR ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF BUSINESS PROFITS,
# BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR INABILITY TO USE THE
# SAMPLES, EVEN IF MICROSOFT HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. BECAUSE SOME STATES DO NOT ALLOW THE EXCLUSION OR LIMITATION
# OF LIABILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES, THE ABOVE LIMITATION MAY NOT APPLY TO YOU.
.PARAMETER GroupName
If the group name is not specified the script will export all the informations for all distribution groups
.PARAMETER OrganizationUnit
You need to specify the NoDirSyn Organization unit that on premise distribution groups will be temporary moved to in order to stop the dirsync for those groups
.PARAMETER Verbose
Enabled verbose logging
.Example
.\Prepare-DGToOffice365.ps1 -OrganizationUnit NoDirSync
This will export the information for all distribution group and move them all to the NoDirSync organization unit
.\Prepare-DGToOffice365.ps1 -GroupName "testDG01"
This will export the information for the group testdg01 and move it to the NoDirSync organization unit
.\Prepare-DGToOffice365.ps1 -GroupName "testDG01" -verbose
This will export the information for the group testdg01, move it to the NoDirSync organization unit and enable verbose logging
.OUTPUTS
Results will be saved to export.csv
Errors will be saved to errors.txt
#>
[CmdletBinding(SupportsShouldProcess=$true)]
Param(
[parameter(mandatory=$false)]$GroupName,
[parameter(mandatory=$True)]$OrganizationUnit
)
$ExportArray=@()
$ValueToStr=""
$DateStamp = Get-Date -f "hh:mm-dd-MM-yyyy"
import-module activedirectory
#Change the following line to search for all groups or a specific group
if($GroupName)
{
$ListOfGroups=get-distributiongroup -identity $GroupName -ResultSize unlimited
}
else
{
$ListOfGroups=get-distributiongroup -ResultSize unlimited
}
"-------------------------Start: Prepare-DGToOffice365 Script $($DateStamp)--------------------------------" | out-file -append errors.txt
if($ListOfGroups)
{
Foreach($Group in $ListOfGroups)
{
"processing $($Group)"
$listOfprops=@("SamAccountName","EmailAddresses","ManagedBy","AcceptMessagesOnlyFrom","AcceptMessagesOnlyFromDLMembers","AcceptMessagesOnlyFromSendersOrMembers","BypassModerationFromSendersOrMembers","ModeratedBy","RejectMessagesFrom","RejectMessagesFromDLMembers","RejectMessagesFromSendersOrMembers","GrantSendOnBehalfTo","BypassNestedModerationEnabled","MemberJoinRestriction","MemberDepartRestriction","ReportToManagerEnabled","ReportToOriginatorEnabled","SendOofMessageToOriginatorEnabled","HiddenFromAddressListsEnabled","ModerationEnabled","RequireSenderAuthenticationEnabled","SendModerationNotifications","Alias","CustomAttribute1","CustomAttribute10","CustomAttribute11","CustomAttribute12","CustomAttribute13","CustomAttribute14","CustomAttribute15","CustomAttribute2","CustomAttribute3","CustomAttribute4","CustomAttribute5","CustomAttribute6","CustomAttribute7","CustomAttribute8","CustomAttribute9","DisplayName","MaxSendSize","MaxReceiveSize","PrimarySmtpAddress","SimpleDisplayName","MailTip")
$ListOfsingleprops=@("SamAccountName","BypassNestedModerationEnabled","MemberJoinRestriction","MemberDepartRestriction","ReportToManagerEnabled","ReportToOriginatorEnabled","SendOofMessageToOriginatorEnabled","Alias","CustomAttribute1","CustomAttribute10","CustomAttribute11","CustomAttribute12","CustomAttribute13","CustomAttribute14","CustomAttribute15","CustomAttribute2","CustomAttribute3","CustomAttribute4","CustomAttribute5","CustomAttribute6","CustomAttribute7","CustomAttribute8","CustomAttribute9","DisplayName","HiddenFromAddressListsEnabled","MaxSendSize","MaxReceiveSize","ModerationEnabled","PrimarySmtpAddress","RequireSenderAuthenticationEnabled","SimpleDisplayName","SendModerationNotifications","MailTip")
$GroupToStr = New-Object PSObject
$GroupToStr | Add-Member NoteProperty -Name "Name" -Value $Group.Name
#1-Handle Members
Write-Verbose "processing membership"
$listOfMembers=(Get-DistributionGroupMember -Identity $Group.DisplayName | where{$_.recipienttype -eq "UserMailbox" -or $_.recipientType -eq "MailUniversalDistributionGroup"}) #Flash:only mailbox + Groups
if($listOfMembers)
{
foreach($Member in $listOfMembers)
{
$ValueToStr=$ValueToStr+";"+(Get-recipient $Member.DistinguishedName).PrimarySmtpAddress.tostring()
$ValueToStr=$ValueToStr.trimstart(";")
}
}
else
{
write-warning "No members"
$ValueToStr="Null"
}
$GroupToStr | Add-Member NoteProperty -Name "Members" -Value $ValueToStr
######################Handle Properties#################################
Foreach($prop in $Listofprops)
{
write-verbose "processing $($prop)"
$ValueToStr=""
if([string]::IsNullOrEmpty($group.$($prop)))
{
write-warning "Null value for $($prop)"
$ValueToStr="Null"
}
else
{
Switch($prop)
{
#2-Handle email addresses & X500
EmailAddresses {
write-verbose "processing email addresses"
$ProxyAddresses=$Group.EmailAddresses -split ";"
$X500Address="X500:"+"$($Group.LegacyExchangeDN)"
$ProxyAddresses+=$X500Address
$ValueToStr=""
Foreach($Address in $ProxyAddresses)
{
$ValueToStr=$ValueToStr+";"+$Address
$ValueToStr=$ValueToStr.trimstart(";")
}
}
#3-Handle Single-Values
{$ListOfsingleprops -icontains $prop} {write-verbose "single prop $($prop)";$ValueToStr=$Group.$($prop)}
#4-Handle Multi-Values
Default {
write-verbose "Found Multi $($prop)"
$ValueToStr=""
foreach($AdMember in $Group.$($prop))
{
$ValueToStr=$ValueToStr+";"+(Get-recipient $AdMember.DistinguishedName).PrimarySmtpAddress.tostring()
$ValueToStr=$ValueToStr.trimstart(";")
}
}
}
}
#5-Populate the PSObject properties
$GroupToStr | Add-Member NoteProperty -Name $prop -Value $ValueToStr
}
#6-Handle SendAs permission
Write-Verbose "prcoessing sendAs permisisons"
[String]$ValueToStr=""
$ListOfPermissions=$group| Get-ADPermission | where{($_.Isinherited -eq $false) -and ($_.extendedrights -like "*Send-As*")}
if($ListOfPermissions)
{
Foreach($Assignee in $ListOfPermissions)
{
$ValueToStr=$ValueToStr+";"+(Get-recipient $Assignee.User.ToString()).PrimarySmtpAddress.tostring()
}
$ValueToStr=$ValueToStr.trimstart(";")
$GroupToStr | Add-Member NoteProperty -Name "sendAs" -Value $ValueToStr
}
#7-Add Group to the export array
$ExportArray += $GroupToStr
"Finsihed $($Group)"
#8-Move the Group to the NoDirSync OU
Write-Verbose "Moving the Group to the $($OrganizationUnit)"
$NoDirSyncOU=Get-OrganizationalUnit $OrganizationUnit
if($NoDirSyncOU)
{
Move-ADObject $Group.DistinguishedName -TargetPath $NoDirSyncOU.DistinguishedName
}
else
{
"$($Group)#Move#Couldn't find the NoDirSync $($OrganizationUnit)"| out-file -append errors.txt
}
"Finished processing $($Group.Name)"
"--------------------------------------------------------"
}
#8-export all groups to csv file
$ExportArray|export-csv export.csv
}
"--------------------Finish: Prepare-DGToOffice365 Script ---------------------------------------------------" | out-file -append errors.txt
2- If you didn’t specify a group name when you run the script, this script will search for all distribution groups
3- After the script completes, the group will be moved to the NoDirSync OU.
4- The script will save any errors to errors.txt file
Stage2: Validation:
1- Run DirSync process
2- Check Office365 and make sure that the group is no more available in Office365
Stage3: Creation
1- From Azure PowerShell module, you can run the second script .\Create-DGToOffice365.ps1 –verbose
<#
.SYNOPSIS
Use the Create-DGToOffice365.ps1 script to create the distribution groups in Office365 based on a csv file
.DESCRIPTION
The ultimate objective is to delete all distribution groups from exchange on-premise and create them in the Office365 cloud.
This sample script is the 1st script to create the distribution groups was previously removed from exchange on premise into Office365.
.NOTES
Author : Ahmed Ashour - ahmed.ashour@hotmail.fr
Creation Date: v1.0 4 Jan 2015
Last Update : -----------------
# DISCLAIMER:
# THIS CODE IS SAMPLE CODE. THESE SAMPLES ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.
# MICROSOFT FURTHER DISCLAIMS ALL IMPLIED WARRANTIES INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR OF FITNESS FOR
# A PARTICULAR PURPOSE. THE ENTIRE RISK ARISING OUT OF THE USE OR PERFORMANCE OF THE SAMPLES REMAINS WITH YOU. IN NO EVENT SHALL
# MICROSOFT OR ITS SUPPLIERS BE LIABLE FOR ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF BUSINESS PROFITS,
# BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR INABILITY TO USE THE
# SAMPLES, EVEN IF MICROSOFT HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. BECAUSE SOME STATES DO NOT ALLOW THE EXCLUSION OR LIMITATION
# OF LIABILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES, THE ABOVE LIMITATION MAY NOT APPLY TO YOU.
.PARAMETER Verbose
Enabled verbose logging
.Example
.\Create-DGToOffice365.ps1
This will create the distribution groups based on the csv file.
.\Create-DGToOffice365.ps1 -verbose
This will create the distribution groupsbased on the csv file and enable verbose logging
.Inputs
Script will require a csv file with all the attributes
Name Members SamAccountName EmailAddresses ManagedBy AcceptMessagesOnlyFrom AcceptMessagesOnlyFromDLMembers AcceptMessagesOnlyFromSendersOrMembers BypassModerationFromSendersOrMembers ModeratedBy RejectMessagesFrom RejectMessagesFromDLMembers RejectMessagesFromSendersOrMembers GrantSendOnBehalfTo BypassNestedModerationEnabled MemberJoinRestriction MemberDepartRestriction ReportToManagerEnabled ReportToOriginatorEnabled SendOofMessageToOriginatorEnabled HiddenFromAddressListsEnabled ModerationEnabled RequireSenderAuthenticationEnabled SendModerationNotifications Alias CustomAttribute1 CustomAttribute10 CustomAttribute11 CustomAttribute12 CustomAttribute13 CustomAttribute14 CustomAttribute15 CustomAttribute2 CustomAttribute3 CustomAttribute4 CustomAttribute5 CustomAttribute6 CustomAttribute7 CustomAttribute8 CustomAttribute9 DisplayName MaxSendSize MaxReceiveSize PrimarySmtpAddress SimpleDisplayName MailTip sendAs
.OUTPUTS
Errors will be saved to errors.txt
#>
[CmdletBinding(SupportsShouldProcess=$true)]
Param()
#Function to open the import csv file
Function Get-FileName($initialDirectory)
{
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") |
Out-Null
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.initialDirectory = $initialDirectory
$OpenFileDialog.filter = "All files (*.*)| *.*"
$OpenFileDialog.ShowDialog() | Out-Null
return $OpenFileDialog.filename
}
$ImportFile=Get-FileName -initialDirectory "c:\"
#Function to connect to Office365
Function New-O365ExchangeSession()
{
param([parameter(mandatory=$true)]$365master)
#close any old remote session
Get-PSSession | Remove-PSSession -Confirm:$false
#start a new office 365 remote session
$365session = New-PSSession -ConfigurationName "Microsoft.Exchange" -ConnectionUri 'https://ps.outlook.com/powershell' -Credential $365master -Authentication Basic -AllowRedirection
$office365 = Import-PSSession $365session -AllowClobber
}
$365master = get-credential ashour@myelephantdev.onmicrosoft.com
New-O365ExchangeSession $365master
Function DoYExist()
{
param(
[array]$RecordList,
$Operation,
$ParentGroup
)
$ValidValueToStr=@()
Foreach($Record in $RecordList)
{
if(Get-Recipient $Record | where{$_.RecipientType -eq "UserMailbox" -or $_.RecipientType -eq "MailUniversalDistributionGroup"})
{
write-verbose "Found $($Record)"
$ValidValueToStr+=$Record
}
else
{
Write-Verbose "wrong user$($Record)"
"$($ParentGroup)#$($Operation)#$($Record)#invalid User or Group" | out-file -append errors.txt
}
}
return ,$ValidValueToStr
}
#Load CSV File
$DateStamp = Get-Date -f "dd-MM-yyyy"
$ListOfGroups=import-csv $ImportFile
"-------------------------Start: Create-DGToOffice365 Script $($DateStamp)--------------------------------" | out-file -append errors.txt
if($ListOfGroups)
{
ForEach($Group in $ListOfGroups)
{
"Processing $($Group.name)"
If(Get-DistributionGroup $Group.Name)
{
#1a-Group is available, exit
Write-Verbose "$($Group.Name) already exist"
}
else
{
write-verbose "Creating Group $($Group.Name)"
#1-Create the Group
$Error.Clear()
New-DistributionGroup -Name $Group.Name -DisplayName $Group.displayName -PrimarySmtpAddress $Group.PrimarySmtpAddress -Alias $Group.alias
If ($Error) {"$($Group.displayName)#Creation#$($Error[0].Exception)" | out-file -Append errors.txt}
}
"Finished processing $($Group.Name)"
"--------------------------------------------------------"
}
}
else
{
"No entries in the csv file" | out-file -append errors.txt
}
"--------------------Finish: Create-DGToOffice365 Script---------------------------------------------------" | out-file -append errors.txt
2- The script will ask you to provide the csv file will be used to create the distribution groups in office365
Stage4:Update
1- After all the distribution groups are created in Office365, it’s now possible to update all the properties that have group to group relations
2- From Azure PowerShell module, you can run the script .\Update-DGToOffice365.ps1 –verbose
<#
.SYNOPSIS
Use the Update-DGToOffice365.ps1 script to update the distribution groups attributes from a csv file
.DESCRIPTION
The ultimate objective is to delete all distribution groups from exchange on-premise and create them in the Office365 cloud.
This sample script is the 3rd script to update the distribution groups created on Office365 with the old information exported from exchange on-premise
.NOTES
Author : Ahmed Ashour - ahmed.ashour@hotmail.fr
Creation Date: v1.0 4 Jan 2015
Last Update : -----------------
# DISCLAIMER:
# THIS CODE IS SAMPLE CODE. THESE SAMPLES ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.
# MICROSOFT FURTHER DISCLAIMS ALL IMPLIED WARRANTIES INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR OF FITNESS FOR
# A PARTICULAR PURPOSE. THE ENTIRE RISK ARISING OUT OF THE USE OR PERFORMANCE OF THE SAMPLES REMAINS WITH YOU. IN NO EVENT SHALL
# MICROSOFT OR ITS SUPPLIERS BE LIABLE FOR ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF BUSINESS PROFITS,
# BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR INABILITY TO USE THE
# SAMPLES, EVEN IF MICROSOFT HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. BECAUSE SOME STATES DO NOT ALLOW THE EXCLUSION OR LIMITATION
# OF LIABILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES, THE ABOVE LIMITATION MAY NOT APPLY TO YOU.
.PARAMETER Verbose
Enabled verbose logging
.Example
.\update-DGToOffice365.ps1
This will set all the distribution groups properties based on the csv file.
.\update-DGToOffice365.ps1 -verbose
This will set all the distribution groups properties based on the csv file and enable verbose logging
.Inputs
Script will require a csv file with all the attributes
Name Members SamAccountName EmailAddresses ManagedBy AcceptMessagesOnlyFrom AcceptMessagesOnlyFromDLMembers AcceptMessagesOnlyFromSendersOrMembers BypassModerationFromSendersOrMembers ModeratedBy RejectMessagesFrom RejectMessagesFromDLMembers RejectMessagesFromSendersOrMembers GrantSendOnBehalfTo BypassNestedModerationEnabled MemberJoinRestriction MemberDepartRestriction ReportToManagerEnabled ReportToOriginatorEnabled SendOofMessageToOriginatorEnabled HiddenFromAddressListsEnabled ModerationEnabled RequireSenderAuthenticationEnabled SendModerationNotifications Alias CustomAttribute1 CustomAttribute10 CustomAttribute11 CustomAttribute12 CustomAttribute13 CustomAttribute14 CustomAttribute15 CustomAttribute2 CustomAttribute3 CustomAttribute4 CustomAttribute5 CustomAttribute6 CustomAttribute7 CustomAttribute8 CustomAttribute9 DisplayName MaxSendSize MaxReceiveSize PrimarySmtpAddress SimpleDisplayName MailTip sendAs
.OUTPUTS
Errors will be saved to errors.txt
#>
[CmdletBinding(SupportsShouldProcess=$true)]
Param()
#Function to open the import csv file
Function Get-FileName($initialDirectory)
{
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") |
Out-Null
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.initialDirectory = $initialDirectory
$OpenFileDialog.filter = "All files (*.*)| *.*"
$OpenFileDialog.ShowDialog() | Out-Null
return $OpenFileDialog.filename
}
$ImportFile=Get-FileName -initialDirectory "c:\"
#Function to connect to Office365
Function New-O365ExchangeSession()
{
param([parameter(mandatory=$true)]$365master)
#close any old remote session
Get-PSSession | Remove-PSSession -Confirm:$false
#start a new office 365 remote session
$365session = New-PSSession -ConfigurationName "Microsoft.Exchange" -ConnectionUri 'https://ps.outlook.com/powershell' -Credential $365master -Authentication Basic -AllowRedirection
$office365 = Import-PSSession $365session -AllowClobber
}
$365master = get-credential #ashour@myelephantdev.onmicrosoft.com
New-O365ExchangeSession $365master
#Function DoIExist
Function DoYExist()
{
param(
[array]$RecordList,
$Operation,
[String]$ParentGroup,
[Parameter(Mandatory=$True)][string]$ImportFile
)
$ValidValueToStr=@()
Foreach($Record in $RecordList)
{
if(Get-Recipient $Record -erroraction "silentlycontinue"| where{$_.RecipientType -eq "UserMailbox" -or $_.RecipientType -eq "MailUniversalDistributionGroup"})
{
write-verbose "Found $($Record)"
$ValidValueToStr+=$Record
}
else
{
Write-Verbose "wrong user$($Record)"
"$($ParentGroup)#$($Operation)#$($Record)#invalid User or Group" | out-file -append errors.txt
}
}
return ,$ValidValueToStr
}
#Load CSV File
$ValueToArray=""
$DateStamp = Get-Date -f "hh:mm-dd-MM-yyyy"
$ListOfGroups=import-csv $ImportFile
"-------------------------Start: Update-DGToOffice365 Script $($DateStamp)--------------------------------" | out-file -append errors.txt
if($ListOfGroups)
{
ForEach($Group in $ListOfGroups)
{
"Processing $($Group.name)"
If(Get-DistributionGroup $Group.Name -erroraction silentlycontinue)
{
Write-Verbose "Found $($Group.Name)"
#1-Add X500 and smtp addresses
write-verbose "Email addresses $($Group.Name)"
$ValueToArray=@($Group.EmailAddresses.split(";"))
$Error.Clear()
Set-DistributionGroup -Identity $Group.Name -EmailAddresses $ValueToArray
If ($Error) {"$($Group.displayName)#Email Addresses#$($Error[0].Exception)" | out-file -Append errors.txt}
#2-Processing single value attribues
Write-Verbose "Processing Single Value attributes"
$ListOfSplat=@{}
#"MaxSendSize","MaxReceiveSize" are valid for onpremise but are not available for office365 set-distributingroup command
$Listofsingleprops=@("BypassNestedModerationEnabled","MemberJoinRestriction","MemberDepartRestriction","ReportToManagerEnabled","ReportToOriginatorEnabled","SendOofMessageToOriginatorEnabled","HiddenFromAddressListsEnabled","ModerationEnabled","RequireSenderAuthenticationEnabled","SendModerationNotifications","CustomAttribute1","CustomAttribute10","CustomAttribute11","CustomAttribute12","CustomAttribute13","CustomAttribute14","CustomAttribute15","CustomAttribute2","CustomAttribute3","CustomAttribute4","CustomAttribute5","CustomAttribute6","CustomAttribute7","CustomAttribute8","CustomAttribute9","DisplayName","Alias","PrimarySmtpAddress","SimpleDisplayName","MailTip")
Foreach($Kei in $Listofsingleprops)
{
if($Group.$($kei) -eq "Null")
{
write-verbose "Skip $($kei)"
}
else
{
Switch($Group.$($kei))
{
True {$Kval=$True}
False {$Kval=$False}
Default{$Kval=$Group.$($kei)}
}
$ListOfSplat.Add($kei,$Kval)
}
}
$Error.Clear()
Set-distributiongroup -identity $Group.Name @ListOfSplat
If ($Error) {"$($Group.displayName)#SingleProps#$($Error[0].Exception)" | out-file -Append errors.txt}
#3- Processing MultiValue attributes "GrantSendOnBehalfTo","ManagedBy","ModeratedBy","AcceptMessagesOnlyFromSendersOrMembers","BypassModerationFromSendersOrMembers","RejectMessagesFromSendersOrMembers"
Write-Verbose "Processing Multi Value attributes"
$ListOfMultiProps=@("GrantSendOnBehalfTo","ManagedBy","ModeratedBy","AcceptMessagesOnlyFromSendersOrMembers","BypassModerationFromSendersOrMembers","RejectMessagesFromSendersOrMembers")
Foreach($Prop in $ListOfMultiProps)
{
Write-Verbose "processing $($prop)"
$ValueToArray=$Group.$($prop).split(";") | unique
$ValidValueToStr=(DoYExist $ValueToArray $prop $($Group.displayName))
if($ValidValueToStr)
{
$HashSplat=@{}
$HashSplat.Add($prop,$ValidValueToStr)
$Error.Clear()
Set-DistributionGroup -Identity $($group.name) @HashSplat
If ($Error) {"$($Group.displayName)#$($prop)#$($Error[0].Exception)" | out-file -Append errors.txt}
}
}
#4-Process MemberOf
write-verbose "Processing MemberOf $($Group.Name)"
if(!$Group.Members -eq "Null")
{
$ValueToArray=$Group.members.split(";") | unique
$ValidValueToStr=(DoYExist $ValueToArray "Members" $($Group.displayName))
Foreach($GroupMember in $ValidValueToStr)
{
write-verbose "adding the member $($GroupMember)"
$Error.Clear()
Add-DistributionGroupMember -Identity $Group.Name -Member $GroupMember
If ($Error) {"$($Group.displayName)#Memberof#$($Error[0].Exception)" | out-file -Append errors.txt}
}
}
#4-Process sendAs permission
write-verbose "Processing the sendAs $($Group.Name)"
$ListOfTrustee=$Group.SendAs.split(";") | unique
If($ListOfTrustee)
{
Foreach($Trustee in $ListOfTrustee)
{
#Cloud only Add-RecipientPermission $Group.Name -AccessRights SendAs -Trustee $Trustee -Confirm:$false
#On-Premise only Add-Adpermission $Group.Name -ExtendedRights Send-As -user $Trustee -Confirm:$false
$Error.Clear()
Write-Verbose "processing $($Trustee)"
Add-RecipientPermission $Group.Name -AccessRights SendAs -Trustee $Trustee -Confirm:$false
If ($Error) {"$($Group.displayName)#SendAs#$($Trustee)#$($Error[0].Exception)" | out-file -Append errors.txt}
}
}
}
else
{
Write-Verbose "$($Group.Name) doesn't exist"
"$($Group.displayName)#Search for the Group#Doesn't Exist" | out-file -Append errors.txt
}
"Finished processing $($Group.Name)"
"--------------------------------------------------------"
}
}
else
{
"No entries in the csv file" | out-file -append errors.txt
}
"--------------------Finish: update-DGToOffice365 Script ---------------------------------------------------" | out-file -append errors.txt
3- The script will ask you to provide the csv file will be used to create the distribution group in office365
4- The script will save any errors to errors.txt file
Stage5:Cleanup
Once you feel everything is working fine you can connect to active directory and remove all groups in the NoDirSync OU.
Enjoy
# THIS CODE IS SAMPLE CODE. THESE SAMPLES ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.
# MICROSOFT FURTHER DISCLAIMS ALL IMPLIED WARRANTIES INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR OF FITNESS FOR
# A PARTICULAR PURPOSE. THE ENTIRE RISK ARISING OUT OF THE USE OR PERFORMANCE OF THE SAMPLES REMAINS WITH YOU. IN NO EVENT SHALL
# MICROSOFT OR ITS SUPPLIERS BE LIABLE FOR ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF BUSINESS PROFITS,
# BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, OR OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR INABILITY TO USE THE
# SAMPLES, EVEN IF MICROSOFT HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. BECAUSE SOME STATES DO NOT ALLOW THE EXCLUSION OR LIMITATION
# OF LIABILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES, THE ABOVE LIMITATION MAY NOT APPLY TO YOU.