PowerShell Exchange Online and Recursive Function Goodness!

On this post, we will be reviewing a recursive function call in PowerShell that will help us extract all recipients for any distribution lists.

PowerShell Exchange Online and Recursive Function Goodness!

There are a lot of very powerful and helpful things that you can do with PowerShell, and today we will be looking into a small function that will allow us to extract all individual members from a specific Distribution List.

Our challenge is to extract all emails from any specified distribution list, including emails that are a part of any child distribution lists.  Sounds simple, right?  And it is, thanks to a neat logic trick called recursive functions!  So, without any further ado, let's get rolling and coding!


Requirements

Before we get started, please be sure that you meet all of the following requirements:

  1. You need administrator access to Exchange Online (Office 365 Tenant.)  If you are not an administrator, please consult with your IT team and request access and/or work directly with them to run the necessary scripts
  2. You need to ensure that you can run PowerShell scripts that were remotely signed on your computer.  See below for the shell snippet to get this accomplished
  3. You will need to install the module for Exchange Online.  See below for the shell snippet to get this accomplished.

Allowing PowerShell to execute remotely signed scripts:

Set-ExecutionPolicy RemoteSigned

Execution Policy Change
The execution policy helps protect you from scripts that you do not trust. Changing the execution policy might expose
you to the security risks described in the about_Execution_Policies help topic at
https:/go.microsoft.com/fwlink/?LinkID=135170. Do you want to change the execution policy?
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "N"):

Installing Exchange Online Management Module:

Install-Module ExchangeOnlineManagement

Untrusted repository
You are installing the modules from an untrusted repository. If you trust this repository, change its
InstallationPolicy value by running the Set-PSRepository cmdlet. Are you sure you want to install the modules from
'PSGallery'?
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "N"):

You might get prompted to accept the installation of the "NuGet provider" when installing modules in your PowerShell environment.  If you get prompted, please accept the installation as NuGet is the easiest way to handle PowerShell packages in Windows.


Let the fun begin!

Before we get started, create a new file in your favorite editor and save it as whatever name you would like, but with the .ps1 extension.  In our case, we have named the file expand_distro.ps1.

Step 1 - Connect to Exchange Online, if not already connected:

# Grab a list of Sessions that are currently active
$sessions = Get-PSSession | Select-Object -Property State, Name

# Validate if there is an Exchange Online session already active
$is_online = (@($sessions) -like '@{State=Opened; Name=ExchangeOnlineInternalSession*').Count -gt 0

# If there are no active Exchange Online Sessions, start one!
If ($is_online -ne "True") {
    Connect-ExchangeOnline
}

Step 2 - The MAGICAL recursive function that extracts the recipients and appends them to our result array

# This function will expand all of the recipients of any given distribution list
function expand_distro {
    # Grab the parameter (distribution list name)
    $distro = Get-DistributionGroup -Identity $args[0]
    
    # Loop through each member of the distribution list
    $distro_members = Get-DistributionGroupMember -Identity $distro.Identity
    foreach ($member in $distro_members) {
        If ($member.RecipientType -eq "MailUniversalDistributionGroup") {
            # This member is actually another distribution list, 
            # let's recursivelly call the function to further expand the distro
            expand_distro $member.Identity
        } Else {
            # Build a record with the relevant data and
            # Append the member to the result object
            $record = New-Object PSObject -Property @{
                ParentDistro = $distro.PrimarySmtpAddress
                DisplayName = $member.DisplayName
                EmailAddress = $member.PrimarySmtpAddress
            }
            $result.Add($record) | Out-Null
        }
    }
}

Step 3 - Create a result array to hold our export data, collect the distribution name from the user and set the export file name

# Let's create an ArrayList to hold our results
$result = New-Object System.Collections.ArrayList

# We need to collect the distribution list name from the user
$distro_name = $NULL
While($distro_name -eq $NULL -or 
      $distro_name -eq "") {
    $distro_name = Read-Host -Prompt "Distribution List Email Address"
}

# Set export filename to be the name of the distribution list with a date stamp
$date = Get-Date -Format "yyyyMMdd"
$export_name = $date + "_" + ($distro_name.replace("@", "-").replace(".", "")) + ".csv"

Step 4 - The following lines will actually execute the script, call the function and then save the result to our file.

# Call the function with the distribution list name provided
expand_distro $distro_name

# Export the results to our file
$result | Select-Object "EmailAddress", "DisplayName", "ParentDistro" | Sort-Object EmailAddress | Export-Csv -Path $export_name -NoTypeInformation

# Print the file name to the screen
Write-Host "Export file name : $($export_name)" 

We are done!

Our final script, when all assembled, should look like the code snippet below.

# Grab a list of Sessions that are currently active
$sessions = Get-PSSession | Select-Object -Property State, Name

# Validate if there is an Exchange Online session already active
$is_online = (@($sessions) -like '@{State=Opened; Name=ExchangeOnlineInternalSession*').Count -gt 0

# If there are no active Exchange Online Sessions, start one!
If ($is_online -ne "True") {
    Connect-ExchangeOnline
}

# This function will expand all of the recipients of any given distribution list
function expand_distro {
    # Grab the parameter (distribution list name)
    $distro = Get-DistributionGroup -Identity $args[0]
    
    # Loop through each member of the distribution list
    $distro_members = Get-DistributionGroupMember -Identity $distro.Identity
    foreach ($member in $distro_members) {
        If ($member.RecipientType -eq "MailUniversalDistributionGroup") {
            # This member is actually another distribution list, 
            # let's recursivelly call the function to further expand the distro
            expand_distro $member.Identity
        } Else {
            # Build a record with the relevant data and
            # Append the member to the result object
            $record = New-Object PSObject -Property @{
                ParentDistro = $distro.PrimarySmtpAddress
                DisplayName = $member.DisplayName
                EmailAddress = $member.PrimarySmtpAddress
            }
            $result.Add($record) | Out-Null
        }
    }
}

# Let's create an ArrayList to hold our results
$result = New-Object System.Collections.ArrayList

# We need to collect the distribution list name from the user
$distro_name = $NULL
While($distro_name -eq $NULL -or 
      $distro_name -eq "") {
    $distro_name = Read-Host -Prompt "Distribution List Email Address"
}

# Set export filename to be the name of the distribution list with a date stamp
$date = Get-Date -Format "yyyyMMdd"
$export_name = $date + "_" + ($distro_name.replace("@", "-").replace(".", "")) + ".csv"

# Call the function with the distribution list name provided
expand_distro $distro_name

# Export the results to our file
$result | Select-Object "EmailAddress", "DisplayName", "ParentDistro" | Sort-Object EmailAddress | Export-Csv -Path $export_name -NoTypeInformation

# Print the file name to the screen
Write-Host "Export file name : $($export_name)" 

This is what it looks like when we execute the script:

.\expand_distro.ps1
Distribution List Email Address: [email protected]
Export file name : 20210717_developers-pedroniscom.csv

and the content of the file should be similar to this:

"EmailAddress","DisplayName","ParentDistro"
"[email protected]","Luke Skywalker","[email protected]"
"[email protected]","Master Yoda","[email protected]"
"[email protected]","Darth Vader","[email protected]"
"[email protected]","Darth Maul","[email protected]"
"...","and more","..."

I hope that helps you with your PowerShell knowledge as well as the concept of a recursive function.

cheers!


the entire script can be found in the moshpit repo within my github account, directly linked here.

Featured image created by Hiroshi Kimura, downloaded from unsplash, licensed under the unsplash license.