Office365-AddBulkEmailAddressses.ps1

Microsoft Office 365 offers a truly great experience for hosting your e-mails. If you have small company adding domain and multiple different email addresses to every single person is easy enough and shouldn't take much time. However things get a bit complicated if you've got lots of people to handle (even 30 is a lot) and you have to add multiple combinations of email templates for each domain you support.

If you have Exchange On-Premise and Office 365 in Hybrid Scenario you can use On-Premise Exchange to create all your Email Address Policies. If you're just having Office 365 you have to either go thru PowerShell or do things manually. Below you can find a script which is supposed to simplify this a lot. I'll try to update it if there will be some interest.

Script Parameters

Script has multiple customization options that you can use during setup. It takes few parameters such as EmailTemplate, Commit, ExcludeUsers, ShowSummaryTable, ShowSummaryLines.

EmailTemplate – uses known to Microsoft Exchange products usage of %g (given name/first name) and %s (secondary name/last name) variables. So if we want to add all users on Office 365 online a new email template we can use combinations just like in standard Exchange Console. Feel free to explore possibilities. Generally script should cover most uses.

- "%g.%s@company.org.pl"  # would give przemyslaw.klys@company.org.pl
- "%1g.%s@company.org.pl" # would give p.klys@company.org.pl
- "%1g%s@company.org.pl"  # would give pklys@company.org.pl
- "%3g.%s@company.org.pl" # would give prz.klys@company.org.pl
- "%g.%1s@company.org.pl" # would give przemyslaw.k@company.org.pl
- "%g@company.pl"         # would give przemyslaw@company.pl

Commit – $true/$false (default $false) – is required to add new emails to existing users
ExcludeUsers – takes UPN (UserPrincipalName) and allows excluding of users that shouldn't be touched. Takes multiple UPN's. UPN's are also used to find mail accounts and match it with users.
ShowSummaryTable – $true/$false (default $true) – shows summary table
ShowSummaryLines – $true/$false (default $true) – shows summary lines

Additional fields are required to connect to Exchange Online (Connect-Ex function). Those are:

Login – is required to connect to online Exchange, user with administrative rights on Office 365 in form of email address

Password – is required to connect to online Exchange

SessionName – is optional but can be used to distinguish multiple Exchange connections

Script Usage

Usually usage should be something like this::

  • Connect to Exchange Online
  • Add 3 different types of email templates to users
  • Disconnects from Exchange Online

Example output:

Or something different:

Feel free to put your $login and $password directly in text form into script as it should be parsed correctly.

cls
$login = Read-Host "Please enter your login (in form of email)"
$password = Read-Host "Please enter your password"
$sessionName = Read-Host "Please enter session name (leave blank for default)"

Connect-Ex -login $login -Password $password -sessionName $sessionName
Add-EmailAddressToUsersFromTemplate -EmailTemplate "%g.%s@company.org.pl" -Commit $true -ExcludeUsers "company@companykatowice.onmicrosoft.com","kalendarz@company.org.pl"
Add-EmailAddressToUsersFromTemplate -EmailTemplate "%1g.%s@company.org.pl" -ExcludeUsers "company@companykatowice.onmicrosoft.com"
Add-EmailAddressToUsersFromTemplate -EmailTemplate "%1g%s@company.org.pl" -ShowSummaryTable $true -ShowSummaryLines $true -Commit $true -ExcludeUsers "company@companykatowice.onmicrosoft.com","kalendarz@company.org.pl","przemyslaw.klys@companykatowice.onmicrosoft.com"
Disconnect-EX -sessionName $sessionName

Script References

Function Remove-StringLatinCharacters – Non-latin characters in FirstName and LastName are replaced for easy generation of e-mails such us Przemysław Kłys will be converted to Przemyslaw Klys as Exchange wouldn't like it. 

Function Write-Color – allows for nice display of colors on one or multiple lines

Function Connect-Ex – allows to connect to Exchange Online remotely. It also allows to connect to On-Premise Exchange if parameters are given properly.

Function Disconnect-Ex – disconnects Exchange Online (or on-premise version).

PowerShell Script Code

# credit goes to:
# - Mike Pfeiffer for Add-EmailAddress
# - Paul Cunningham for his article and script on Office 365 email address policies (https://practical365.com/exchange-online/office-365-email-address-policies/)
# - Paul Cunningham for Add-SMTPAddresses.ps1 which I've based my script on
# - TessellatingHeckler for help with parsing templates

# Changelog
# ver 0.1 - 02.08.2016 -Initial script
# ver 0.2 - 03.08.2016 - Fix for $null-valued expression in Connect-Ex/Disconnect-Ex 

Function Connect-EX ([string] $login, [string] $password, [string] $URL = "https://ps.outlook.com/powershell", [string] $sessionName = "Exchange Online", $Authentication = "Basic", $SupressWarnings = $true) {
    if ($sessionName -eq $null -or $sessionName.Trim() -eq "") { $sessionName = "Exchange Online" }
    Write-Color -Text "[*] Connecting to $sessionName PowerShell" -Color Yellow
    # Kerberos for On-Premise Exchange on Active Directory joined machines, Basic for Office365 or non-domain joined computer
    $WarningPreference = $SupressWarnings
    $secpasswd = ConvertTo-SecureString $password -AsPlainText -Force
    $credentials = New-Object System.Management.Automation.PSCredential ($login, $secpasswd)
	$sessionOption = New-PSSessionOption -SkipRevocationCheck -SkipCACheck -SkipCNCheck 
    if ($SupressWarnings) { $suppress = "SilentlyContinue" } else { $suppress = "Continue" }
    $session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri $URL -Credential $Credentials -Authentication $Authentication -AllowRedirection -Name $sessionName -SessionOption $sessionOption -WarningAction $suppress
    $output = Import-PSSession $session -AllowClobber -DisableNameChecking 
    if (-not $SupressWarnings) { $output }
}
Function Disconnect-EX ($sessionName = "Exchange Online") { 
   if ($sessionName -eq $null -or $sessionName.Trim() -eq "") { $sessionName = "Exchange Online" }
	Remove-PSSession -Name $sessionName
	Write-Color -Text "[*] Disconnected from $sessionName" -Color Yellow
}
Function Remove-StringLatinCharacters {
    PARAM ([string]$String)
    [Text.Encoding]::ASCII.GetString([Text.Encoding]::GetEncoding("Cyrillic").GetBytes($String))
}
Function Write-Color([String[]]$Text, [ConsoleColor[]]$Color = "White", [int]$StartTab = 0, [int] $LinesBefore = 0,[int] $LinesAfter = 0) {
    $DefaultColor = $Color[0]
    if ($LinesBefore -ne 0) {  for ($i = 0; $i -lt $LinesBefore; $i++) { Write-Host "`n" -NoNewline } } # Add empty line before
    if ($StartTab -ne 0) {  for ($i = 0; $i -lt $StartTab; $i++) { Write-Host "`t" -NoNewLine } }  # Add TABS before text
    if ($Color.Count -ge $Text.Count) {
        for ($i = 0; $i -lt $Text.Length; $i++) { Write-Host $Text[$i] -ForegroundColor $Color[$i] -NoNewLine } 
    } else {
        for ($i = 0; $i -lt $Color.Length ; $i++) { Write-Host $Text[$i] -ForegroundColor $Color[$i] -NoNewLine }
        for ($i = $Color.Length; $i -lt $Text.Length; $i++) { Write-Host $Text[$i] -ForegroundColor $DefaultColor -NoNewLine }
    }
    Write-Host
    if ($LinesAfter -ne 0) {  for ($i = 0; $i -lt $LinesAfter; $i++) { Write-Host "`n" } }  # Add empty line after
}
Function Get-NameSection {
    # Returns the first $num characters of a name
    # unless $num is 0, missing or longer than the name
    # then returns the entire name

    param([string]$name, [int]$num)

    if (-not $num -or $num -gt $name.Length) { 
        $name 
    } else {
        $name.Substring(0, $num)
    }
}
Function Add-EmailAddress {
    param($Identity, $EmailAddress)

    begin {
        $mb = Get-Mailbox $Identity
        if($mb.EmailAddressPolicyEnabled) {
            Set-Mailbox $Identity -EmailAddressPolicyEnabled $false
            $policy += 1
        }
        $addresses = $mb.EmailAddresses += $EmailAddress
    }

    process {
        Set-Mailbox $Identity -EmailAddresses $addresses
    }

    end {
        if($policy) {Set-Mailbox $Identity -EmailAddressPolicyEnabled $true}
    }
}
Function ProcessEmail ($Mailbox, $FullEmail) {
    $RequireAdd = $true
    $addresses = $Mailbox.EmailAddresses
    foreach ($address in $addresses) {
        if ($address -imatch "sip:") { continue }
        if ($address -ireplace("smtp:","") -ieq $FullEmail) {
            #$FullEmail
            $requireAdd = $false
            break
        }
    }
   $Mailbox | Add-Member -MemberType NoteProperty -Name NewEmailToAdd -Value $FullEmail 
   $Mailbox | Add-Member -MemberType NoteProperty -Name NewEmailRequiresAdding -Value $RequireAdd  
   return ,$mailbox
}

Function Add-EmailAddressToUsersFromTemplate([string] $EmailTemplate, [bool] $ShowSummaryTable = $true, [bool] $ShowSummaryLines = $true, [bool] $Commit = $false, [string[]] $ExcludeUsers) {
    $collection = @()

    Get-User -ResultSize Unlimited | Where { $_.RecipientType -eq 'UserMailbox' } | ForEach { $Users = @{} } { $Users[$_.SamAccountName] = $_ }
    $AllMailboxes = Get-Mailbox -ResultSize Unlimited | Where { $_.RecipientTypeDetails -eq "UserMailbox" } | ForEach {
    $PrimarySmtpDomain = $_.PrimarySmtpAddress.split("@")
    $firstNameNonLatin = Remove-StringLatinCharacters -String $Users[$_.SamAccountName].FirstName
    $lastnameNonLatin = Remove-StringLatinCharacters -String $Users[$_.SamAccountName].LastName
    New-Object psobject | 
        Add-Member -PassThru NoteProperty Alias $_.Alias | 
        Add-Member -PassThru NoteProperty Name $_.Name |        
        Add-Member -PassThru NoteProperty DisplayName $_.DisplayName | 
        Add-Member -PassThru NoteProperty FirstName $Users[$_.SamAccountName].FirstName |
        Add-Member -PassThru NoteProperty LastName $Users[$_.SamAccountName].LastName  |
        Add-Member -PassThru NoteProperty FirstNameNonLatin $firstNameNonLatin |
        Add-Member -PassThru NoteProperty LastNameNonLatin $lastnameNonLatin |
        Add-Member -PassThru NoteProperty EmailAddressPolicyEnabled $_.EmailAddressPolicyEnabled | 
        Add-Member -PassThru NoteProperty PrimarySmtpAddress $_.PrimarySmtpAddress |
        Add-Member -PassThru NoteProperty PrimarySmtpDomain $PrimarySmtpDomain[1]          |
        Add-Member -PassThru NoteProperty EmailAddresses $_.EmailAddresses |   
        Add-Member -PassThru NoteProperty RecipientType $_.RecipientType |
        Add-Member -PassThru NoteProperty RecipientTypeDetails $_.RecipientTypeDetails | 
        Add-Member -PassThru NoteProperty IsResource $_.IsResource |
        Add-Member -PassThru NoteProperty IsShared $_.IsShared | 
        Add-Member -PassThru NoteProperty IsLinked $_.IsLinked | 
        Add-Member -PassThru NoteProperty ExchangeUserAccountControl $_.ExchangeUserAccountControl | 
        Add-Member -PassThru NoteProperty AddressBookPolicy $_.AddressBookPolicy | 
        Add-Member -PassThru NoteProperty Identity $_.Identity | 
        Add-Member -PassThru NoteProperty DistinguishedName $_.DistinguishedName |           
        Add-Member -PassThru NoteProperty OrganizationalUnit $_.OrganizationalUnit | 
        Add-Member -PassThru NoteProperty AddressListMembership $_.AddressListMembership | 
        Add-Member -PassThru NoteProperty RulesQuota $_.RulesQuota |
        Add-Member -PassThru NoteProperty UserPrincipalName $_.UserPrincipalName | 
        Add-Member -PassThru NoteProperty SamAccountName $_.SamAccountName |
        Add-Member -PassThru NoteProperty UseDatabaseQuotaDefaults $_.UseDatabaseQuotaDefaults 

    }

    foreach ($Mailbox in $AllMailboxes) {
       $skip = $false;
       foreach ($Excluded in $ExcludeUsers) {
            if ($($Mailbox.UserPrincipalName) -eq $Excluded) {
               $skip = $true;              
            }
       }
       if ($skip) { continue } 
       $FirstName = $Mailbox.FirstnameNonLatin
       $LastName = $Mailbox.LastNameNonLatin
       $Alias = $Mailbox.Alias
       $DisplayName = $Mailbox.DisplayName
       $FullEmail = $EmailTemplate
       $FullEmail = [regex]::Replace($FullEmail, '%(\d*)g', {param($m) Get-NameSection $FirstName $m.Groups[1].Value })
       $FullEmail = [regex]::Replace($FullEmail, '%(\d*)s', {param($m) Get-NameSection $LastName $m.Groups[1].Value })
       $Mailbox = ProcessEmail ($Mailbox) ($FullEmail)
       $collection += $Mailbox
    }
    if ($ShowSummaryTable) {
        $collection | ft UserPrincipalName, DisplayName, NewEmailToAdd, NewEmailRequiresAdding
    }
    if ($ShowSummaryLines) {
        foreach ($mail in $collection) {
            if ($($mail.NewEmailRequiresAdding) -eq $true) {
                Write-Color "Following e-mail will be added ", "$($mail.NewEmailToAdd)", " to user (UPN): ", "$($mail.UserPrincipalName)" -Color White,Green,White,Green
            } else {
                Write-Color "Following e-mail will be skipped ", "$($mail.NewEmailToAdd)", " to user (UPN): ", "$($mail.UserPrincipalName)" -Color White,Yellow,White,Yellow
            }
        }
    }

    if ($Commit) {
        Write-Color -LinesBefore 1
        foreach ($mail in $collection) {
            if ($($mail.NewEmailRequiresAdding) -eq $true) {
                Write-Color "Adding e-mail ", "$($mail.NewEmailToAdd)", " to user (UPN): ", "$($mail.UserPrincipalName)" -Color White,Green,White,Green
                Add-EmailAddress -Identity $($mail.UserPrincipalName) -EmailAddress $($mail.NewEmailToAdd)
            }
        }
    } else {
        Write-Color -Text "Changes not committed, re-run the script with the -Commit switch when you're ready to apply the changes." -LinesBefore 1
        Write-Color -Text "No changes made due to -Commit switch not being specified." -Color Magenta
    }

}

Notes