maik.ing | the terminal garden
February 27th, 2026

Recursively Get All Members of an Exchange Online Distribution List (Including Dynamic & M365 Groups)

Getting a complete, flat list of end-users from a massive, nested distribution list in Exchange Online can be surprisingly frustrating.

If you try to use the standard Get-DistributionGroupMember cmdlet, you will quickly notice two things:

  1. It lacks a native -Recursive parameter.
  2. If your main distribution list contains nested Microsoft 365 Groups (Unified Groups) or Dynamic Distribution Groups, a simple recursive loop will crash and flood your console with red ManagementObjectNotFoundException errors.

Different group types in Exchange Online require completely different cmdlets. Here is a robust PowerShell function that dynamically identifies the group type, uses the correct cmdlet to fetch its members, and gracefully skips broken or orphaned objects without crashing.

The PowerShell Script

Copy and paste this function into your Exchange Online PowerShell session:

PowerShell

Function Get-DistributionGroupMemberRecursive {
param (
[Parameter(Mandatory=$true)]
[string]$Identity,
[string]$GroupType = "DistributionGroup" # Default starting type
) $Members = $null try {
# 1. Select the correct cmdlet based on the group type
if ($GroupType -match "GroupMailbox|UnifiedGroup") {
# For Microsoft 365 Groups
$Members = Get-UnifiedGroupLinks -Identity $Identity -LinkType Members -ErrorAction Stop
}
elseif ($GroupType -match "DynamicDistributionGroup") {
# For Dynamic Distribution Groups
$Members = Get-DynamicDistributionGroupMember -Identity $Identity -ErrorAction Stop
}
else {
# For Classic Distribution Lists / Security Groups
$Members = Get-DistributionGroupMember -Identity $Identity -ResultSize Unlimited -ErrorAction Stop
}
}
catch {
# Gracefully handle orphaned objects or resolution errors
Write-Warning "Failed to read group '$Identity' ($GroupType). It may be an orphaned object."
return
} # 2. Iterate through members and evaluate
if ($null -ne $Members) {
foreach ($Member in $Members) {

$MemberType = $Member.RecipientTypeDetails # Check if the member is a nested group and recurse accordingly
if ($MemberType -match "GroupMailbox|UnifiedGroup") {
Get-DistributionGroupMemberRecursive -Identity $Member.PrimarySmtpAddress -GroupType "GroupMailbox"
}
elseif ($MemberType -match "DynamicDistributionGroup") {
Get-DistributionGroupMemberRecursive -Identity $Member.PrimarySmtpAddress -GroupType "DynamicDistributionGroup"
}
elseif ($MemberType -match "Group") {
Get-DistributionGroupMemberRecursive -Identity $Member.PrimarySmtpAddress -GroupType "DistributionGroup"
}
else {
# Output the actual end-user (UserMailbox, SharedMailbox, MailContact, etc.)
$Member | Select-Object Name, PrimarySmtpAddress, RecipientTypeDetails
}
}
}
}

Why This Approach Works

  • Smart Cmdlet Switching: The script checks the RecipientTypeDetails of every nested group. It seamlessly switches to Get-UnifiedGroupLinks for M365 Groups and Get-DynamicDistributionGroupMember when it needs to calculate dynamic rule-based memberships.
  • Error Handling: Real-world Active Directories are messy. The try/catch block ensures that if a nested group contains a deleted user or an orphaned SID, the script throws a gentle yellow warning instead of a fatal error, allowing the rest of the extraction to continue.

How to Use and Export

Once the function is loaded into your session, you can run it against your parent group. To make the output useful, you can pipe the results directly into a CSV file.

Note: Because users might be members of multiple nested groups, adding Select-Object -Unique is highly recommended to filter out duplicates.

PowerShell

# Run the script and export a clean, deduplicated list to a CSV file
Get-DistributionGroupMemberRecursive -Identity "company.news@yourdomain.com" |
Select-Object Name, PrimarySmtpAddress, RecipientTypeDetails -Unique |
Export-Csv -Path "C:\temp\CompanyNews_Members.csv" -NoTypeInformation -Encoding UTF8
powered by Scribbles