diff --git a/Copy Membership and Ownership Between DLs/CopyMembersandOwnersfromDL.ps1 b/Copy Membership and Ownership Between DLs/CopyMembersandOwnersfromDL.ps1 new file mode 100644 index 0000000..6b7f07a --- /dev/null +++ b/Copy Membership and Ownership Between DLs/CopyMembersandOwnersfromDL.ps1 @@ -0,0 +1,211 @@ +<# +============================================================================================= +Name: Copy Members from One Distribution List to Another +Version: 1.0 +Website: o365reports.com + + +Script Highlights: +~~~~~~~~~~~~~~~~~ +1. Fetches members and owners from the source DL and adds them to the target DL. +2. Helps to copy DL members from one to another. (if not present already). +3. Can copy managers from one DL to another. (if not present already). +4. The script automatically verifies and installs the Exchange Online PowerShell module (if not installed) upon your confirmation. +5. Exports an output log file in TXT format that shows the result of the operation. +6. The script can be executed with MFA enabled account too. +7. The script supports certificate-based authentication (CBA) too. +8. The script is schedular-friendly. + +For detailed Script execution: https://o365reports.com/2025/04/08/copy-members-from-one-distribution-list-to-another/ + + +============================================================================================ +#> +Param +( + [Parameter(Mandatory = $False)] + [string]$SourceGroup=$Null, + [string]$TargetGroup=$Null, + [Switch]$CopyOwnersOnly, + [Switch]$CopyMembersOnly, + [string]$UserName=$Null, + [string]$Password=$Null, + [string]$Organization, + [string]$ClientId, + [string]$CertificateThumbPrint +) + +#Connection module +Function Connect_Module { + #Check for Exchange Online module installation + $ExchangeModule = Get-Module ExchangeOnlineManagement -ListAvailable + if($ExchangeModule.count -eq 0) { + Write-Host ExchangeOnline module is not available -ForegroundColor Yellow + $confirm = Read-Host Do you want to Install ExchangeOnline module? [Y] Yes [N] No + if($confirm -match "[Yy]") { + Write-Host "Installing ExchangeOnline module ..." + Install-Module ExchangeOnlineManagement -Repository PSGallery -AllowClobber -Force -Scope CurrentUser + Import-Module ExchangeOnlineManagement + } + else { + Write-Host "ExchangeOnline Module is required. To Install ExchangeOnline module use 'Install-Module ExchangeOnlineManagement' cmdlet." + Exit + } + } + + #Connect Exchange Online + Write-Host "`nConnecting Exchange Online module ..." + if(($UserName -ne "") -and ($Password -ne "")) { + $SecuredPassword = ConvertTo-SecureString -AsPlainText $Password -Force + $Credential = New-Object System.Management.Automation.PSCredential $UserName,$SecuredPassword + Connect-ExchangeOnline -Credential $Credential + } + elseif($Organization -ne "" -and $ClientId -ne "" -and $CertificateThumbprint -ne "") { + Connect-ExchangeOnline -AppId $ClientId -CertificateThumbprint $CertificateThumbprint -Organization $Organization -ShowBanner:$false + } + else { + Connect-ExchangeOnline -ShowBanner:$false + } +} + +#Output log function +Function Log-Data { + param ([string]$UserName,$UserType,$Status,$Message = "") + $Result = if ($Status -eq "Failed") { + "- Failed to copy $UserName as a $UserType to the target group. Error: $Message" + } elseif(($Status -eq "Already a Member") -or ($Status -eq"Already a Owner")){ + "- Skipped adding $UserName as $UserType. The user is already a $UserType in the target group." + } else { + "- $UserName has been successfully copied as $UserType to the target group." + } + Add-Content -Path $OutputLog -Value $Result +} + +$Script:CopiedMembersCount = 0 +$Script:CopiedOwnersCount = 0 +$Script:ExistingMembersCount = 0 +$Script:ExistingOwnersCount = 0 + +#Funtion to copy members only to another distribution group +Function Copy_Members +{ + $ProcessedUserCount=0 + + $SourceGroupMemberNames = Get-DistributionGroupMember -Identity $SourceGroup + $TargetGroupMemberNames = Get-DistributionGroupMember -Identity $TargetGroup + + ForEach($SourceGroupMemberName in $SourceGroupMemberNames) + { + $SourceMemberName = $SourceGroupMemberName.DisplayName + Write-Progress -Activity "Copying Members From $SourceGroup to $TargetGroup " -Status "Processed member Count : $ProcessedUserCount" -CurrentOperation " Currently Processing Member $($SourceMemberName)" + Try + { + if($SourceGroupMemberName.Guid -in $TargetGroupMemberNames.Guid){ + Log-Data -UserName $SourceGroupMemberName.DisplayName -UserType "member" -Status "Already a Member" + $Script:ExistingMembersCount++ + } + else{ + Add-DistributiongroupMember -Identity $TargetGroup -Member $($SourceGroupMemberName.DistinguishedName) -ErrorAction Stop + Log-Data -UserName $SourceGroupMemberName.DisplayName -UserType "member" -Status "Successfully Copied" + $Script:CopiedMembersCount++ + } + } + Catch + { + Log-Data -UserName $Member.DisplayName -UserType "member" -Status "Failed" -Message $_.Exception.Message + } + $ProcessedUserCount++ + } +} + +#Funtion to copy owners only to another distribution group +Function Copy_Owners +{ + $ProcessedUserCount=0 + + $SourceGroupOwnerNames = Get-DistributionGroup -Identity $SourceGroup | Select -ExpandProperty ManagedBy + $TargetGroupOwnerNames = Get-DistributionGroup -Identity $TargetGroup | Select -ExpandProperty ManagedBy + + ForEach($SourceGroupOwnerName in $SourceGroupOwnerNames) + { + $Owner = Get-Recipient -Identity $SourceGroupOwnerName + Write-Progress -Activity "Copying owner From $SourceGroup To $TargetGroup " -Status "Processed owner Count: $ProcessedUserCount" -CurrentOperation "Currently Processing Owner $($Owner.Displayname)" + try{ + if($SourceGroupOwnerName -in $TargetGroupOwnerNames) + { + Log-Data -UserName $Owner.DisplayName -UserType "owner" -Status "Already a Owner" + $Script:ExistingOwnersCount++ + } + else + { + Set-DistributionGroup -Identity $TargetGroup -ManagedBy @{Add="$($Owner.PrimarySmtpAddress)"} -ErrorAction Stop + Log-Data -UserName $Owner.DisplayName -UserType "owner" -Status "Successfully Copied" + $Script:CopiedOwnersCount++ + } + } + catch{ + Log-Data -UserName $Owner.DisplayName -UserType "owner" -Status "Failed" -Message $_.Exception.Message + } + $ProcessedUserCount++ + } +} + +#Function to return output log file +Function OpenOutputLog +{ + Write-Host "`nSummary of Distribution List Membership Changes" -ForegroundColor DarkYellow + Write-Host "`n$($ExistingMembersCount) member(s) and $($ExistingOwnersCount) owner(s) from source DL is already present in target DL." + #Write-Host "`n$($ExistingMembersCount) members and $($ExistingOwnersCount) owners already present in both distribution lists." + Write-Host "`nThe script successfully copied $($CopiedMembersCount) members and $($CopiedOwnersCount) owners to the target DL." + + #Open Output file after execution + If((Test-Path -Path $OutputLog) -eq "True") + { + Write-Host `nThe log file available in :`n -NoNewline -ForegroundColor Yellow + Write-Host $OutputLog + Write-Host `n~~ Script prepared by AdminDroid Community ~~`n -ForegroundColor Green + Write-Host "~~ Check out " -NoNewline -ForegroundColor Green; Write-Host "admindroid.com" -ForegroundColor Yellow -NoNewline; Write-Host " to get access to 1800+ Microsoft 365 reports. ~~" -ForegroundColor Green `n`n + $Prompt = New-Object -ComObject wscript.shell + $UserInput = $Prompt.popup("Do you want to open log file?",` 0,"Open log File",4) + If($UserInput -eq 6) + { + Invoke-Item "$OutputLog" + } + } +} + +#Execution starts here +Connect_Module +If($SourceGroup -eq "" -or ($TargetGroup -eq "")) +{ + If($SourceGroup -eq "") + { + $SourceGroup = Read-Host "`nEnter the source DL mail Id" + } + If($TargetGroup -eq "") + { + $TargetGroup = Read-Host "`nEnter the target DL mail Id" + } +} + +$Location = Get-Location +$OutputLog="$Location\Copy_DL_Membership_Log_$((Get-Date -format yyyy-MMM-dd-ddd` hh-mm` tt).ToString()).txt" +If($CopyOwnersOnly.IsPresent) +{ + Copy_Owners +} +ElseIf($CopyMemberOnly.IsPresent) +{ + Copy_Members +} +Else +{ + Copy_Owners + Copy_Members +} + +#Open output log file +OpenOutputLog + +#Disconnect Exchange Online session +Disconnect-ExchangeOnline -Confirm:$false | Out-Null \ No newline at end of file