Skip to content

Export-EmailAliasReport

Exports all email addresses and aliases to Excel.

Example Output

Export-EmailAliasReport output

Syntax

.\Export-EmailAliasReport.ps1 [-Tenant <String>] [-Cloud <String>] [-Out <String>]

Description

Retrieves all mail-enabled recipients from Exchange Online (excluding contacts and guest mail users) along with Microsoft 365 Groups, and exports all email addresses (primary and aliases) to an Excel report.

Examples

Example 1

.\Export-EmailAliasReport.ps1
# Exports email aliases to an auto-named Excel file.

Example 2

.\Export-EmailAliasReport.ps1 -Tenant 'contoso.onmicrosoft.com' -Out 'C:\Reports\Aliases.xlsx'
# Exports email aliases for the specified tenant.

Parameters

-Tenant

Optional tenant ID or domain for specific tenant connection.

  • Type: String
  • Required: No

-Cloud

Cloud environment to connect to. Defaults to 'Global'. Valid values: Global, USGov, USGovDoD, Germany, China

  • Type: String
  • Required: No
  • Valid values: Global, GCC, GCCH, DoD, Germany, China

-Out

Output file path for the Excel export. If not specified, generates a filename based on tenant name and current timestamp.

  • Type: String
  • Required: No

Notes

Requires ExchangeOnlineManagement and ImportExcel modules.

Full Script

Copy this complete standalone script (includes all helper functions):

#Requires -Version 7.0
#Requires -Modules ExchangeOnlineManagement, ImportExcel

<#
.SYNOPSIS
    Exports all email addresses and aliases to Excel.
.DESCRIPTION
    Retrieves all mail-enabled recipients from Exchange Online (excluding contacts
    and guest mail users) along with Microsoft 365 Groups, and exports all email
    addresses (primary and aliases) to an Excel report.
.PARAMETER Tenant
    Optional tenant ID or domain for specific tenant connection.
.PARAMETER Cloud
    Cloud environment to connect to. Defaults to 'Global'.
    Valid values: Global, USGov, USGovDoD, Germany, China
.PARAMETER Out
    Output file path for the Excel export. If not specified, generates
    a filename based on tenant name and current timestamp.
.EXAMPLE
    .\Export-EmailAliasReport.ps1
    # Exports email aliases to an auto-named Excel file.
.EXAMPLE
    .\Export-EmailAliasReport.ps1 -Tenant 'contoso.onmicrosoft.com' -Out 'C:\Reports\Aliases.xlsx'
    # Exports email aliases for the specified tenant.
.NOTES
    Requires ExchangeOnlineManagement and ImportExcel modules.
#>

[CmdletBinding()]
param(
    [string]$Tenant,

    [ValidateSet('Global', 'GCC', 'GCCH', 'DoD', 'Germany', 'China')

#region Helper Functions

function Write-TimeLog {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, Position = 0)]
        [string]$Message,

        [Parameter()]
        [ConsoleColor]$Color = 'White'
    )

    Write-Host "[$(Get-Date -Format 'HH:mm')] $Message" -ForegroundColor $Color
}

function Connect-ExoIfNeeded {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]$Tenant,

        [Parameter()]
        [ValidateSet('Global', 'GCC', 'GCCH', 'DoD', 'Germany', 'China')]
        [string]$Cloud = 'Global'
    )

    $envMap = @{
        'Global'  = 'O365Default'
        'GCC'     = 'O365Default'
        'GCCH'    = 'O365USGovGCCHigh'
        'DoD'     = 'O365USGovDoD'
        'Germany' = 'O365GermanyCloud'
        'China'   = 'O365China'
    }

    try {
        $connections = Get-ConnectionInformation -ErrorAction SilentlyContinue

        if ($connections) {
            $exoConnection = $connections | Where-Object { $_.ConnectionUri -like '*outlook.office*' -or $_.ConnectionUri -like '*protection.office*' }

            if ($exoConnection) {
                $org = Get-OrganizationConfig -ErrorAction Stop
                Write-TimeLog "Using existing Exchange Online connection to $($org.DisplayName)" -Color Cyan
                return $org
            }
        }
    }
    catch {
        # Not connected or error checking - will connect below
    }

    $connectParams = @{
        ExchangeEnvironmentName = $envMap[$Cloud]
        ShowBanner              = $false
        ErrorAction             = 'Stop'
    }

    if ($Tenant) {
        $connectParams['Organization'] = $Tenant
    }

    try {
        Connect-ExchangeOnline @connectParams
        $org = Get-OrganizationConfig
        Write-TimeLog "Connected to Exchange Online: $($org.DisplayName)" -Color Green
        return $org
    }
    catch {
        Write-TimeLog "Failed to connect to Exchange Online: $($_.Exception.Message)" -Color Red
        throw
    }
}

function Get-DefaultExcelParams {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$Path
    )

    @{
        Path         = $Path
        TableStyle   = 'Medium2'
        AutoSize     = $true
        FreezeTopRow = $true
    }
}

function New-ReportPath {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]$Out,

        [Parameter(Mandatory)]
        [string]$FriendlyName,

        [Parameter(Mandatory)]
        [string]$OrgDisplayName,

        [Parameter()]
        [string]$Extension = 'xlsx'
    )

    if (!$Out) {
        "$OrgDisplayName-$FriendlyName-$(Get-Date -Format 'yyyy-MM-dd_HH-mm').$Extension"
    }
    elseif ($Out -notlike "*.$Extension") {
        "$Out.$Extension"
    }
    else {
        $Out
    }
}

#endregion Helper Functions
]
    [string]$Cloud = 'Global',

    [string]$Out
)

$org = Connect-ExoIfNeeded -Tenant $Tenant -Cloud $Cloud
$Out = New-ReportPath -Out $Out -FriendlyName 'EmailAliases' -OrgDisplayName $org.DisplayName

# All mail-enabled recipients, excluding contacts and guest mail users
Write-TimeLog "Fetching mailboxes..."
$recipients = Get-EXORecipient -ResultSize Unlimited -PropertySets All | Where-Object {
    $_.RecipientType -ne 'MailContact' -and
    $_.RecipientTypeDetails -ne 'GuestMailUser'
}
Write-TimeLog "Fetched $($recipients.Count) mailboxes"

# Microsoft 365 Groups with extended properties
Write-TimeLog "Fetching M365 groups..."
$unifiedGroups = Get-UnifiedGroup -ResultSize Unlimited -IncludeAllProperties
Write-TimeLog "Fetched $($unifiedGroups.Count) M365 groups"

# Map M365 group info by primary SMTP (lowercased)
$groupMeta = @{}
foreach ($g in $unifiedGroups) {
    if ($g.PrimarySmtpAddress) {
        $key = $g.PrimarySmtpAddress.ToString().ToLower()
        $groupMeta[$key] = $g
    }
}

$rows = [System.Collections.Generic.List[object]]::new()
$typeMap = @{
    'PrimarySmtpAddress' = 'Primary'
    'EmailAddresses'     = 'Alias'
}

Write-TimeLog "Processing recipients..."

foreach ($r in $recipients) {
    $seen = [System.Collections.Generic.HashSet[string]]::new()

    $primarySmtp = $null
    if ($r.PrimarySmtpAddress) {
        $primarySmtp = $r.PrimarySmtpAddress.ToString()
    }

    $primaryKey = $null
    if ($primarySmtp) {
        $primaryKey = $primarySmtp.ToLower()
    }

    $groupInfo = $null
    if ($primaryKey -and $groupMeta.ContainsKey($primaryKey)) {
        $groupInfo = $groupMeta[$primaryKey]
    }

    $groupType = $null
    switch ($r.RecipientTypeDetails) {
        'GroupMailbox' { $groupType = 'M365 Group' }
        'MailUniversalDistributionGroup' { $groupType = 'Distribution Group' }
        'MailUniversalSecurityGroup' { $groupType = 'Mail-enabled Security Group' }
        'DynamicDistributionGroup' { $groupType = 'Dynamic Distribution Group' }
        'RoomList' { $groupType = 'Room List' }
    }

    $mkRow = {
        param(
            [string]$Email,
            [string]$Source
        )

        if (-not $Email) { return }

        $key = $Email.ToLower()
        if ($seen.Contains($key)) { return }
        [void]$seen.Add($key)

        $localPart = $null
        $domain = $null
        if ($Email -like '*@*') {
            $localPart = $Email.Split('@')[0]
            $domain = $Email.Split('@')[1]
        }

        $friendlyType = $typeMap[$Source]
        if (-not $friendlyType) {
            $friendlyType = $Source
        }

        $rows.Add([pscustomobject]@{
                EmailAddress         = $Email
                LocalPart            = $localPart
                Domain               = $domain
                Type                 = $friendlyType
                UPN                  = $r.WindowsLiveID
                DisplayName          = $r.DisplayName
                Mailbox              = $r.RecipientTypeDetails
                Hidden               = $r.HiddenFromAddressListsEnabled

                GroupType            = $groupType
                AllowExternalSenders = if ($groupInfo) { -not $groupInfo.RequireSenderAuthenticationEnabled } else { $null }
                CopyToMemberInbox    = if ($groupInfo) { $groupInfo.AutoSubscribeNewMembers } else { $null }
                AccessType           = if ($groupInfo) { $groupInfo.AccessType } else { $null }
            })
    }

    # Primary SMTP
    if ($primarySmtp) {
        & $mkRow -Email $primarySmtp -Source 'PrimarySmtpAddress'
    }

    # All SMTP proxy addresses (primary + aliases)
    if ($r.EmailAddresses) {
        foreach ($addr in $r.EmailAddresses) {
            $s = $addr.ToString()

            if ($s -like 'SMTP:*' -or $s -like 'smtp:*') {
                $smtp = $s.Substring(5)
                & $mkRow -Email $smtp -Source 'EmailAddresses'
            }
        }
    }
}

# Export to Excel
$excelParams = Get-DefaultExcelParams -Path $Out

$rows |
Sort-Object EmailAddress, Mailbox, DisplayName |
Export-Excel @excelParams -WorksheetName 'Emails' -TableName 'Emails'

Write-TimeLog "Exported to: $Out" -Color Green