Skip to content

Export-EmailAliases

Exports all email addresses and aliases from Exchange Online to an Excel report.

Description

Connects to Exchange Online and retrieves all mail-enabled recipients (excluding contacts and guest mail users), including Microsoft 365 Groups. Outputs a flat list of every primary SMTP address and alias, enriched with recipient and group details.

Parameters

-ExchangeCloud

The Exchange Online environment to connect to. Defaults to 'O365Default'. Accepted values: O365Default, O365USGovGCCHigh, O365USGovDoD, O365GermanyCloud, O365China

Property Value
Type String
Required false
Default O365Default

-Tenant

Optional. The organization domain or tenant ID to pass to Connect-ExchangeOnline. Use this when running in a multi-tenant context.

Property Value
Type String
Required false

-Out

Optional. Output file path for the Excel report. Defaults to a timestamped file in the current user's Downloads folder, named after the Exchange organization. The .xlsx extension is appended if omitted.

Requires -Modules ExchangeOnlineManagement, ImportExcel

Property Value
Type String
Required false

Script

Download Export-EmailAliases.ps1

<#
.SYNOPSIS
    Exports all email addresses and aliases from Exchange Online to an Excel report.

.DESCRIPTION
    Connects to Exchange Online and retrieves all mail-enabled recipients (excluding
    contacts and guest mail users), including Microsoft 365 Groups. Outputs a flat
    list of every primary SMTP address and alias, enriched with recipient
    and group details.

.PARAMETER ExchangeCloud
    The Exchange Online environment to connect to. Defaults to 'O365Default'.
    Accepted values: O365Default, O365USGovGCCHigh, O365USGovDoD, O365GermanyCloud, O365China

.PARAMETER Tenant
    Optional. The organization domain or tenant ID to pass to Connect-ExchangeOnline.
    Use this when running in a multi-tenant context.

.PARAMETER Out
    Optional. Output file path for the Excel report. Defaults to a timestamped file
    in the current user's Downloads folder, named after the Exchange organization.
    The .xlsx extension is appended if omitted.
#>
#Requires -Modules ExchangeOnlineManagement, ImportExcel

[CmdletBinding()]
param(
    [ValidateSet('O365Default', 'O365USGovGCCHigh', 'O365USGovDoD', 'O365GermanyCloud', 'O365China')]
    [string]$ExchangeCloud = 'O365Default',
    [string]$Tenant,
    [string]$Out
)

function Log { param([string]$Msg, [ConsoleColor]$Color = 'White') Write-Host "[$(Get-Date -Format 'HH:mm')] $Msg" -ForegroundColor $Color }

Log "Connecting to ExchangeOnline..."
$connectParams = @{
    ExchangeEnvironmentName = $ExchangeCloud
    DisableWAM              = $true
    ShowBanner              = $false
    ErrorAction             = 'Stop'
}
if ($Tenant) { $connectParams['Organization'] = $Tenant }
Connect-ExchangeOnline @connectParams
$Org = Get-OrganizationConfig
Log "Connected to Exchange: $($Org.DisplayName)" Green

if (!$Out) {
    $friendlyName = "EmailAliases"
    $Out = Join-Path $env:USERPROFILE "Downloads\$($Org.DisplayName)-$friendlyName-$(Get-Date -Format 'yyyy-MM-dd_HH-mm').xlsx"
}
elseif ($Out -notlike '*.xlsx') { $Out += '.xlsx' }

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

# Microsoft 365 Groups with extended properties
$unifiedGroups = Get-UnifiedGroup -ResultSize Unlimited -IncludeAllProperties
Log "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'
}

Log "Processing..."

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 }
        Log "Processing: $Email"

        $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
$rows |
Sort-Object EmailAddress, Mailbox, DisplayName |
Export-Excel -Path $Out -WorksheetName "Emails" -TableName "Emails" -TableStyle "Medium2" -AutoSize -FreezeTopRow

Log "Exported report: $Out" Green

$answer = Read-Host "Open the report now? [Y/n]"
if ($answer -eq '' -or $answer -match '^y') {
    Start-Process $Out
}