Copy-StaticGAL.ps1

<#PSScriptInfo
 
.VERSION 1.3
 
.GUID 941ee5e7-c753-4bd5-acea-e2d73a37aa9e
 
.DESCRIPTION Create a static GAL of another organization's users for organizations that can't deploy FIM/MIM.
 
.AUTHOR Aaron Guilmette
 
.COMPANYNAME Microsoft
 
.COPYRIGHT 2022
 
.TAGS Global Address List, GAL, GALSync
 
.LICENSEURI
 
.PROJECTURI https://www.undocumented-features.com/2016/10/19/synchronizing-objects-between-tenants/
 
.ICONURI
 
.EXTERNALMODULEDEPENDENCIES
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES
#>


<#
.SYNOPSIS
Global Address List Export/Import.
 
This script will capture all recipients in a source tenant and recreate them
as contacts in a target tenant, excluding objects (by default) whose primary
SMTP address matches an accepted domain in the target tenant. This script is
useful in a divestiture scenario (where one environment is splitting into one or
more separate environments) and there is a need to maintain replyability for
objects that are cached in Outlook profiles.
 
If you have the ability to configure Forefront Identity Manager or AADConnect to
sync objects, it is preferred, as this is more of a "point in time" copy and
does not support delta updates or deletions in the target environment, since it
is not using the a sync API. While it doesn't support deltas, it will allow
you to run it multiple times and overwrite existing objects in the target
environment.
 
.PARAMETER ConfirmDeletes
Confirm deletes in target environment if ProcessDeletes switch is used.
 
.PARAMETER ExcludedDomains
List of primary SMTP domains. Objects whose PrimarySmtpAddress value matches
any domain in this list will NOT be imported as a contact.
 
.PARAMETER ExcludedObjects
List of source objects (as email address) to exclude. By default, the
DiscoverySearch mailbox is excluded.
 
.PARAMETER ExportFile
Specify a custom export file to save the GAL objects.
 
.PARAMETER ExportOnly
Create only an export file from the source environment.
 
.PARAMETER InputFile
Specify an input file to import a previously exported GALSync.
 
.PARAMETER ProcessTargetDeletes
Delete contact objects synced to target envrionment that do not appear in source
environment. Must include ConfirmDeletes switch to actually process deletes.
 
.PARAMETER RecipientTypes
Specify types of recipients to include in export/import.
 
.PARAMETER ResultSize
Use this to export a fixed number of objects. Useful for testing the
process on a small scale. Defaults to "Unlimited" if no value is specified. This
parameter has no effect on imports.
 
.PARAMETER SourceConnectionUri
Set the source connection URI.
 
.PARAMETER SourceCredential
Source environment credentials.
 
.PARAMETER SourceEnvironment
Determines authentication scheme used. Select ExchangeOnline (default) for Basic
or ExchangeOnPrem for Kerberos.
 
.PARAMETER SyncAttribute
Use this parameter to specify a custom attribute in which to store metadata or
information about the synced object. For example, you may want to use this
attribute to flag objects that were imported.
 
.PARAMETER SyncAttributeValue
Specify a value to place in the SyncAttribute.
 
.PARAMETER TargetConnectionUri
Set the target connection URI.
 
.PARAMETER TargetCredential
Target environment credential.
 
.PARAMETER TargetEnvironment
Determines authentication scheme used. Select ExchangeOnline (default) for Basic
or ExchangeOnPrem for Kerberos.
 
.EXAMPLE
.\Copy-StaticGAL.ps1 -RecipientTypes UserMailbox,MailUser -ResultSize 2
 
Copy 2 UserMailbox and MailUser objects from source tenant to target tenant as
contact objects and prompt for source/target credentials.
 
.EXAMPLE
.\Copy-StaticGAL.ps1 -RecipientTypes UserMailbox -ResultSize 2 -ExcludedObjects user1@fabrikam.com,user2@contoso.com
 
Copy 2 UserMailbox objects from source tenant to target tenant contact objects,
except user1@fabrikam.com and user2@contoso.com.
 
.EXAMPLE
.\Copy-StaticGAL.ps1 -InputFile GALExport.Csv -TargetConnectionUri 'https://ExchangeServer1/PowerShell/' TargetEnvironment ExchangeOnPrem
 
Import the file GALExport.csv as contacts into the target on-premises environment
via ExchangeServer1 with Kerberos authentication.
 
.EXAMPLE
.\Copy-StaticGAL.ps1 -ExportOnly
 
Export a copy of the GAL to CSV from an Office 365 tenant and prompt for
credentials.
 
.EXAMPLE
.\Copy-StaticGAL.ps1 -InputFile GALExport.csv -RecipientTypes MailUser -TargetCredential $TargetCred -SyncAttribute CustomAttribute15 -SyncAttributeValue StaticGAL
 
Import MailUser objects from file GALExport.csv into target tenant using stored
PSCredential object $TargetCred. Set CustomAttribute15 on target contact object
to 'StaticGAL'.
 
.EXAMPLE
.\Copy-StaticGAL.ps1 -InputFile GALExport.csv -TargetCredential $TargetCred -SyncAttribute CustomAttribute15 -SyncAttributeValue StaticGAL -ProcessTargetDeletes -ConfirmDeletes
 
Import objects from file GALExport.csv into target tenant using stored
PSCredential object $TargetCred. Set CustomAttribute15 on target contact object
to 'StaticGAL'. Delete objects in target environment with CustomAttribute15 set
to 'StaticGAL' that are not present in the GALExport.csv input file.
 
.LINK
 
.NOTES
$SourceCredential and $TargetCredential take standard PSCredential objects.
 
It would be best to test by setting -ResultSize to a low integer and running
several different RecipentTyes (default is all types) to ensure that the process
is working correctly between your tenants and that the target objects look as
anticipated.
 
Revision List:
2022-01-24 : Published to PS Gallery.
2016-07-23 : Added delete functionality
2016-07-22 : Updated ExcludedDomains syntax to always exclude accepted
                domains in target environment.
                 
                Added -ExportOnly switch.
#>

param(
    [switch]$ConfirmDeletes,
    [array]$ExcludedDomains,
    [array]$ExcludedObjects,
    [string]$ExportFile = "GALSyncExport.csv",
    [switch]$ExportOnly,
    [string]$InputFile,
    [switch]$ProcessTargetDeletes,
    [ValidateSet('UserMailbox','MailUser','MailContact','MailUniversalSecurityGroup','MailUniversalDistributionGroup')]
        [array]$RecipientTypes = ('UserMailbox','MailUser','MailContact','MailUniversalSecurityGroup','MailUniversalDistributionGroup'),
    [ValidatePattern("^([1-9]|[1-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]|[1-9][0-9][0-9][0-9][0-9]|[1-9][0-9][0-9][0-9][0-9][0-9]|Unlimited)$")]
        [string]$ResultSize = "Unlimited",
    [string]$SourceConnectionUri = "https://outlook.office365.com/powershell-liveid/",
    [System.Management.Automation.PSCredential]$SourceCredential,
    [ValidateSet('ExchangeOnline','ExchangeOnPrem')]
        [string]$SourceEnvironment = "ExchangeOnline",
    [ValidateSet('CustomAttribute1','CustomAttribute2','CustomAttribute3','CustomAttribute4','CustomAttribute5','CustomAttribute6','CustomAttribute7','CustomAttribute8','CustomAttribute9','CustomAttribute10','CustomAttribute11','CustomAttribute12','CustomAttribute13','CustomAttribute14','CustomAttribute15')]
        [string]$SyncAttribute,
    [string]$SyncAttributeValue = "StaticGAL",
    [string]$TargetConnectionUri = "https://outlook.office365.com/powershell-liveid/",
    [System.Management.Automation.PSCredential]$TargetCredential,
    [ValidateSet('ExchangeOnline','ExchangeOnPrem')]
        [string]$TargetEnvironment = "ExchangeOnline"
    )

Function SourceEnvironmentLogon
    {
    If (!$SourceCredential) { $SourceCredential = Get-Credential -Message "Souce Credential" }
    If ($SourceEnvironment -eq "ExchangeOnline") { Write-Host "Using Basic Authentication."; $Authentication = "Basic" }
    If ($SourceEnvironment -eq "ExchangeOnPrem") { Write-Host "Using Kerberos Authentication."; $Authentication = "Kerberos" }
    $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri $SourceConnectionUri -Credential $SourceCredential -Authentication $Authentication -AllowRedirection -ErrorAction Stop -WarningAction SilentlyContinue
    Import-PSSession $Session -ErrorAction Stop -WarningAction SilentlyContinue -DisableNameChecking | Out-Null
    }
    
Function TargetEnvironmentLogon
    {
    If (!$TargetCredential) {$TargetCredential = Get-Credential -Message "Target Credential"}
    If ($SourceEnvironment -eq "ExchangeOnline") { Write-Host "Using Basic Authentication."; $Authentication = "Basic" }
    If ($SourceEnvironment -eq "ExchangeOnPrem") { Write-Host "Using Kerberos Authentication."; $Authentication = "Kerberos" }
    $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri $TargetConnectionUri -Credential $TargetCredential -Authentication $Authentication -AllowRedirection -ErrorAction Stop -WarningAction SilentlyContinue
    Import-PSSession $Session -ErrorAction Stop -WarningAction SilentlyContinue -DisableNameChecking | Out-Null
    }

$StartDate = Get-Date
Write-Host "Started at $($StartDate)."

If (!$InputFile)
    {
    Write-Host "Connecting to Source Environment - $($SourceConnectionUri)."
    SourceEnvironmentLogon
    Write-Host "Building Recipient List. This may take a long time." 
    [array]$SourceGAL = @()
    Foreach ($Type in $RecipientTypes)
        {
        Switch ($Type)
            {
            UserMailbox
                {
                Write-Host "Exporting $($Resultsize) UserMailbox objects."
                $SourceGAL += Get-Mailbox -Resultsize $ResultSize -WarningAction SilentlyContinue -ErrorAction SilentlyContinue
                }
            MailUser
                {
                Write-Host "Exporting $($Resultsize) MailUser objects."
                $SourceGAL += Get-MailUser -Resultsize $ResultSize -WarningAction SilentlyContinue -ErrorAction SilentlyContinue
                }
            MailContact
                {
                Write-Host "Exporting $($Resultsize) MailContact objects."
                $SourceGAL += Get-MailContact -ResultSize $ResultSize -WarningAction SilentlyContinue -ErrorAction SilentlyContinue
                }
            MailUniversalSecurityGroup
                {
                Write-Host "Exporting $($Resultsize) MailUniversalSecurityGroup objects."
                $SourceGAL += Get-DistributionGroup -RecipientTypeDetails MailUniversalSecurityGroup -ResultSize $ResultSize -WarningAction SilentlyContinue -ErrorAction SilentlyContinue
                }
            MailUniversalDistributionGroup
                {
                Write-Host "Exporting $($Resultsize) MailUniversalDistributionGroup objects."
                $SourceGAL += Get-DistributionGroup -RecipientTypeDetails MailUniversalDistributionGroup -ResultSize $ResultSize -WarningAction SilentlyContinue -ErrorAction SilentlyContinue
                }
            } # End Switch
        } # End Foreach $Type
    
    Write-Host "Disconnecting from Source Environment."
    Get-PSSession | Remove-PSSession

    # Export CSVs for later review
    Write-Host "Exporting GAL objects to CSV $($ExportFile)."
    $SourceGAL | Select Alias,DisplayName,@{n="EmailAddresses";e={$_.EmailAddresses -join ";"}},ExternalEmailAddress,FirstName,HiddenFromAddressListsEnabled,LastName,LegacyExchangeDn,Name,PrimarySmtpAddress,RecipientType | Export-Csv -NoTypeInformation $ExportFile -Force 

    # If ExportOnly mode, exit.
    If ($ExportOnly)
        {
        Break
        } # End IF $ExportOnly
    } # End If $InputFile

Write-Host "Connecting to Target Environment - $($TargetConnectionUri)."
TargetEnvironmentLogon

# Build the domain-filtered list of objects to add to the GAL
If (!$ExcludedDomains)
    {
    [System.Collections.ArrayList]$DomainFilter = (Get-AcceptedDomain).DomainName | ? { $_ -notlike "*onmicrosoft.com" }
    }
Else
    {
    [System.Collections.ArrayList]$DomainFilter = (Get-AcceptedDomain).DomainName | ? { $_ -notlike "*onmicrosoft.com" }
    $DomainFilter += $ExcludedDomains
    }

Write-Host "Filtering out objects in the following domains:"
Write-Host -ForegroundColor Green $DomainFilter

Write-Host "Importing filtered object list."

# Build exclude list. By default, only includes the DiscoverySearchMailbox.
If (!$ExcludedObjects)
    {
    [array]$ExcludedObjects = (Get-Mailbox -anr DiscoverySearchMailbox).PrimarySmtpAddress
    } # End If !$ExcludedObjects

Write-Host -NoNewline "Exclude list contains ";Write-Host -NoNewline $ExcludedObjects.Count;Write-Host " objects."    

[int]$i = 1
[int]$MailContacts = 0
[int]$Mailboxes = 0
[int]$Mailusers = 0
[int]$DistributionGroups = 0
[int]$SecurityGroups = 0

If ($InputFile)
    {
    # If $RecipientTypes is specified in for Input file, filter the RecipientTypes
    If ($RecipientTypes)
        {
        $RecipientTypeFilter = â€˜(?i)^(‘ + (($RecipientTypes |foreach {[regex]::escape($_)}) -join "|") + â€˜)$’
        Write-Host "Applying RecipientTypes filter $($RecipientTypeFilter) to import list."
        $SourceGAL = Import-Csv $InputFile | ? { $_.RecipientType -match $RecipientTypeFilter }
        } # End If $RecipientTypes
    Else
        {
        $SourceGAL = Import-Csv $InputFile
        } # End If/Else $RecipientTypes
    Write-Host "Processing from file import."
    $objCount = $SourceGAL.Count
    Write-Host "$($objCount) total objects will be processed."
    Sleep 10
    Foreach ($GALObj in $SourceGAL)
        {
        # Filter out objects whose right-hand side (after @) are contained in the $DomainFilter Array or any object in the $ExcludedObjects array
        If ($DomainFilter -notcontains $GALObj.PrimarySmtpAddress.Split("@")[1] -or $ExcludedObjects -notcontains $GALObj.PrimarySmtpAddress)
            {
            $RecipientType = $GALObj.RecipientType
            Switch ($RecipientType)
                {
                UserMailbox
                    {
                    $Alias = $GALObj.Alias
                    $DisplayName = $GALObj.DisplayName
                    $ExternalEmailAddress = $GALObj.PrimarySmtpAddress
                    $LegDN = "x500:"+$GALObj.LegacyExchangeDN
                    [array]$EmailAddresses = $GALObj.EmailAddresses.Split(";") + $LegDN
                    [array]$EmailAddresses = $EmailAddresses | Sort -Unique
                    $EmailAddressesCount = $EmailAddresses.Count
                    $FirstName = $GALObj.FirstName
                    $LastName = $GALObj.LastName
                    $Name = $GALObj.Name
                    # Needed to convert "True" or "False" value from text to Boolean
                    $HiddenFromAddressListsEnabled = [System.Convert]::ToBoolean($GALObj.HiddenFromAddressListsEnabled)
                    Write-Host "[$i/$objCount]"
                    Write-Host -NoNewline "Source object is a ";Write-Host -NoNewLine -ForegroundColor DarkCyan "mailbox ";Write-Host "for $($ExternalEmailAddress)."
                    
                    # Provision the object
                    Try
                        {
                        Write-Host -NoNewline "Trying to create new contact for ";Write-Host -ForegroundColor Green $ExternalEmailAddress
                        New-MailContact -Alias $Alias -DisplayName $DisplayName -ExternalEmailAddress $ExternalEmailAddress -FirstName $FirstName -LastName $LastName -Name $Name -ea SilentlyContinue
                        Write-Host " Updating $($EmailAddressesCount) proxy addresses..."
                        Set-MailContact $Alias -HiddenFromAddressListsEnabled $HiddenFromAddressListsEnabled -EmailAddresses $EmailAddresses -Name $Name -ea SilentlyContinue
                        If ($SyncAttribute)
                            {
                            $cmd = "Set-MailContact $Alias -$SyncAttribute $SyncAttributeValue"
                            Invoke-Expression $cmd
                            }
                        Write-Host "-----`n"
                        }
                    Catch
                        {
                        Write-Host -NoNewline "Trying to update contact for ";Write-Host -ForegroundColor Green $ExternalEmailAddress
                        Set-MailContact $Alias -HiddenFromAddressListsEnabled $HiddenFromAddressListsEnabled -EmailAddresses $EmailAddresses -Name $Name -ea SilentlyContinue
                        If ($SyncAttribute)
                            {
                            $cmd = "Set-MailContact $Alias -$SyncAttribute $SyncAttributeValue"
                            Invoke-Expression $cmd
                            }
                        Write-Host "-----`n"
                        }
                    Finally
                        {
                        $Mailboxes++
                        }
                    } # End UserMailbox
              MailUser
                    {
                    $Alias = $GALObj.Alias
                    $DisplayName = $GALObj.DisplayName
                    $LegDN = "x500:"+$GALObj.LegacyExchangeDN
                    [array]$EmailAddresses = $GALObj.EmailAddresses.Split(";") + $LegDN
                    [array]$EmailAddresses = $EmailAddresses | Sort -Unique
                    $EmailAddressesCount = $EmailAddresses.Count
                    $ExternalEmailAddress = $GALObj.ExternalEmailAddress
                    $FirstName = $GALObj.FirstName
                    $LastName = $GALObj.LastName
                    $Name = $GALObj.Name
                    # Needed to convert "True" or "False" value from text to Boolean
                    $HiddenFromAddressListsEnabled = [System.Convert]::ToBoolean($GALObj.HiddenFromAddressListsEnabled)
                    Write-Host "[$i/$objCount]"
                    Write-Host -NoNewline "Source object is a ";Write-Host -NoNewLine -Fore DarkGreen "mailuser ";Write-Host "for $($ExternalEmailAddress)."
                    
                    # Provision the object
                    Try
                        {
                        Write-Host -NoNewline "Trying to create new contact for ";Write-Host -ForegroundColor Green $ExternalEmailAddress
                        New-MailContact -Alias $Alias -DisplayName $DisplayName -ExternalEmailAddress $ExternalEmailAddress -FirstName $FirstName -LastName $LastName -Name $Name -ea SilentlyContinue
                        Write-Host " Updating $($EmailAddressesCount) proxy addresses..."
                        Set-MailContact $Alias -HiddenFromAddressListsEnabled $HiddenFromAddressListsEnabled -EmailAddresses $EmailAddresses -Name $Name -ea SilentlyContinue
                        If ($SyncAttribute)
                            {
                            $cmd = "Set-MailContact $Alias -$SyncAttribute $SyncAttributeValue"
                            Invoke-Expression $cmd
                            }
                        Write-Host "-----`n"
                        }
                    Catch
                        {
                        Write-Host -NoNewline "Trying to update contact for ";Write-Host -ForegroundColor Green $ExternalEmailAddress
                        Set-MailContact $Alias -HiddenFromAddressListsEnabled $HiddenFromAddressListsEnabled -EmailAddresses $EmailAddresses -Name $Name -ea SilentlyContinue
                        If ($SyncAttribute)
                            {
                            $cmd = "Set-MailContact $Alias -$SyncAttribute $SyncAttributeValue"
                            Invoke-Expression $cmd
                            }
                        Write-Host "-----`n"
                        }
                    Finally
                        {
                        $Mailusers++
                        }
                    } # End MailUser
              MailContact
                    {
                    $Alias = $GALObj.Alias
                    $DisplayName = $GALObj.DisplayName
                    $LegDN = "x500:"+$GALObj.LegacyExchangeDN
                    [array]$EmailAddresses = $GALObj.EmailAddresses.Split(";") + $LegDN
                    [array]$EmailAddresses = $EmailAddresses | Sort -Unique
                    $ExternalEmailAddress = $GALObj.ExternalEmailAddress
                    $EmailAddressesCount = $EmailAddresses.Count
                    $FirstName = $GALObj.FirstName
                    $LastName = $GALObj.LastName
                    $Name = $GALObj.Name
                    # Needed to convert "True" or "False" value from text to Boolean
                    $HiddenFromAddressListsEnabled = [System.Convert]::ToBoolean($GALObj.HiddenFromAddressListsEnabled)
                    Write-Host "[$i/$objCount]"
                    Write-Host -NoNewline "Source object is a ";Write-Host -NoNewLine -ForegroundColor DarkMagenta "contact ";Write-Host "for $($ExternalEmailAddress)."
                    
                    # Provision the object
                    Try
                        {
                        Write-Host -NoNewline "Trying to create new contact for ";Write-Host -ForegroundColor Green $ExternalEmailAddress
                        New-MailContact -Alias $Alias -DisplayName $DisplayName -ExternalEmailAddress $ExternalEmailAddress -FirstName $FirstName -LastName $LastName -Name $Name -ea SilentlyContinue
                        Write-Host " Updating $($EmailAddressesCount) proxy addresses..."
                        Set-MailContact $Alias -HiddenFromAddressListsEnabled $HiddenFromAddressListsEnabled -EmailAddresses $EmailAddresses -Name $Name -ea SilentlyContinue
                        If ($SyncAttribute)
                            {
                            $cmd = "Set-MailContact $Alias -$SyncAttribute $SyncAttributeValue"
                            Invoke-Expression $cmd
                            }
                        Write-Host "-----`n"
                        }
                    Catch
                        {
                        Write-Host -NoNewline "Trying to update contact for ";Write-Host -ForegroundColor Green $ExternalEmailAddress
                        Set-MailContact $Alias -HiddenFromAddressListsEnabled $HiddenFromAddressListsEnabled -EmailAddresses $EmailAddresses -Name $Name -ea SilentlyContinue
                        If ($SyncAttribute)
                            {
                            $cmd = "Set-MailContact $Alias -$SyncAttribute $SyncAttributeValue"
                            Invoke-Expression $cmd
                            }
                        Write-Host "-----`n"
                        }
                    Finally
                        {
                        $MailContacts++
                        }        
                    } # End Contact
              MailUniversalSecurityGroup
                    {
                    $Alias = $GALObj.Alias
                    $DisplayName = $GALObj.DisplayName
                    $LegDN = "x500:"+$GALObj.LegacyExchangeDN
                    [array]$EmailAddresses = $GALObj.EmailAddresses.Split(";") + $LegDN
                    [array]$EmailAddresses = $EmailAddresses | Sort -Unique
                    $EmailAddressesCount = $EmailAddresses.Count
                    $ExternalEmailAddress = $GALObj.PrimarySmtpAddress
                    $Name = $GALObj.Name
                    # Needed to convert "True" or "False" value from text to Boolean
                    $HiddenFromAddressListsEnabled = [System.Convert]::ToBoolean($GALObj.HiddenFromAddressListsEnabled)
                    Write-Host "[$i/$objCount]"
                    Write-Host -NoNewline "Source object is a ";Write-Host -NoNewLine -Fore DarkRed "Mail Universal Security Group "; Write-Host "for $($ExternalEmailAddress)."
                
                    # Provision the object
                    Try
                        {
                        Write-Host -NoNewline "Trying to create new contact for ";Write-Host -ForegroundColor Green $ExternalEmailAddress
                        New-MailContact -Alias $Alias -DisplayName $DisplayName -ExternalEmailAddress $ExternalEmailAddress -FirstName $FirstName -LastName $LastName -Name $Name -ea SilentlyContinue
                        Write-Host " Updating $($EmailAddressesCount) proxy addresses..."
                        Set-MailContact $Alias -HiddenFromAddressListsEnabled $HiddenFromAddressListsEnabled -EmailAddresses $EmailAddresses -Name $Name -ea SilentlyContinue
                        If ($SyncAttribute)
                            {
                            $cmd = "Set-MailContact $Alias -$SyncAttribute $SyncAttributeValue"
                            Invoke-Expression $cmd
                            }
                        Write-Host "-----`n"
                        }
                    Catch
                        {
                        Write-Host -NoNewline "Trying to update contact for ";Write-Host -ForegroundColor Green $ExternalEmailAddress
                        Set-MailContact $Alias -HiddenFromAddressListsEnabled $HiddenFromAddressListsEnabled -EmailAddresses $EmailAddresses -Name $Name -ea SilentlyContinue
                        If ($SyncAttribute)
                            {
                            $cmd = "Set-MailContact $Alias -$SyncAttribute $SyncAttributeValue"
                            Invoke-Expression $cmd
                            }
                        Write-Host "-----`n"
                        }
                    Finally
                        {
                        $SecurityGroups++
                        }
                    } # End MailUniversalSecurityGroup
              MailUniversalDistributionGroup
                    {
                    $Alias = $GALObj.Alias
                    $DisplayName = $GALObj.DisplayName
                    $LegDN = "x500:"+$GALObj.LegacyExchangeDN
                    [array]$EmailAddresses = $GALObj.EmailAddresses.Split(";") + $LegDN
                    [array]$EmailAddresses = $EmailAddresses | Sort -Unique
                    $EmailAddressesCount = $EmailAddresses.Count
                    $ExternalEmailAddress = $GALObj.PrimarySmtpAddress
                    $Name = $GALObj.Name
                    # Needed to convert "True" or "False" value from text to Boolean
                    $HiddenFromAddressListsEnabled = [System.Convert]::ToBoolean($GALObj.HiddenFromAddressListsEnabled)
                    Write-Host "[$i/$objCount]"
                    Write-Host -NoNewline "Source object is a ";Write-Host -NoNewLine -Fore DarkYellow "Mail Universal Distribution Group ";Write-Host "for $($ExternalEmailAddress)."
                    
                    # Provision the object
                    Try
                        {
                        Write-Host -NoNewline "Trying to create new contact for ";Write-Host -ForegroundColor Green $ExternalEmailAddress
                        New-MailContact -Alias $Alias -DisplayName $DisplayName -ExternalEmailAddress $ExternalEmailAddress -FirstName $FirstName -LastName $LastName -Name $Name -ea SilentlyContinue
                        Write-Host " Updating $($EmailAddressesCount) proxy addresses..."
                        Set-MailContact $Alias -HiddenFromAddressListsEnabled $HiddenFromAddressListsEnabled -EmailAddresses $EmailAddresses -Name $Name -ea SilentlyContinue
                        If ($SyncAttribute)
                            {
                            $cmd = "Set-MailContact $Alias -$SyncAttribute $SyncAttributeValue"
                            Invoke-Expression $cmd
                            }
                        Write-Host "-----`n"
                        }
                    Catch
                        {
                        Write-Host -NoNewline "Trying to update contact for ";Write-Host -ForegroundColor Green $ExternalEmailAddress
                        Set-MailContact $Alias -HiddenFromAddressListsEnabled $HiddenFromAddressListsEnabled -EmailAddresses $EmailAddresses -Name $Name -ea SilentlyContinue
                        If ($SyncAttribute)
                            {
                            $cmd = "Set-MailContact $Alias -$SyncAttribute $SyncAttributeValue"
                            Invoke-Expression $cmd
                            }
                        Write-Host "-----`n"
                        }
                    Finally
                        {
                        $DistributionGroups++
                        }
                    } # End MailUniversalDistributionGroup
              } # End Switch
          } # End If
          $i++
          } # End ForEach
    } # End If $InputFile
Else
    {
    Write-Host "Processing from direct."
    $objCount = $SourceGAL.Count
    Write-Host "$($objCount) total objects will be processed."
    Sleep 10
    Foreach ($GALObj in $SourceGAL)
        {
        If ($DomainFilter -notcontains $GALObj.PrimarySmtpAddress.Split("@")[1] -or $ExcludedObjects -notcontains $GALObj.PrimarySmtpAddress)
            {
            $RecipientType = $GALObj.RecipientType
            Switch ($RecipientType)
                {
                UserMailbox
                    {
                    $Alias = $GALObj.Alias
                    $DisplayName = $GALObj.DisplayName
                    $ExternalEmailAddress = $GALObj.PrimarySmtpAddress
                    $LegDN = "x500:"+$GALObj.LegacyExchangeDN
                    [array]$EmailAddresses = $GALObj.EmailAddresses + $LegDN
                    [array]$EmailAddresses = $EmailAddresses | Sort -Unique
                    $EmailAddressesCount = $EmailAddresses.Count
                    $FirstName = $GALObj.FirstName
                    $LastName = $GALObj.LastName
                    $Name = $GALObj.Name
                    $HiddenFromAddressListsEnabled = $GALObj.HiddenFromAddressListsEnabled
                    Write-Host "[$i/$objCount]"
                    Write-Host -NoNewline "Source object is a ";Write-Host -NoNewLine -ForegroundColor DarkCyan "mailbox ";Write-Host "for $($ExternalEmailAddress)."
                    
                    # Provision the object
                    Try
                        {
                        Write-Host -NoNewline "Trying to create new contact for ";Write-Host -ForegroundColor Green $ExternalEmailAddress
                        New-MailContact -Alias $Alias -DisplayName $DisplayName -ExternalEmailAddress $ExternalEmailAddress -FirstName $FirstName -LastName $LastName -Name $Name -ea SilentlyContinue
                        Write-Host " Updating $($EmailAddressesCount) proxy addresses..."
                        Set-MailContact $Alias -HiddenFromAddressListsEnabled $HiddenFromAddressListsEnabled -EmailAddresses $EmailAddresses -Name $Name -ea SilentlyContinue
                        If ($SyncAttribute)
                            {
                            $cmd = "Set-MailContact $Alias -$SyncAttribute $SyncAttributeValue"
                            Invoke-Expression $cmd
                            }
                        Write-Host "-----`n"
                        }
                    Catch
                        {
                        Write-Host -NoNewline "Trying to update contact for ";Write-Host -ForegroundColor Green $ExternalEmailAddress
                        Set-MailContact $Alias -HiddenFromAddressListsEnabled $HiddenFromAddressListsEnabled -EmailAddresses $EmailAddresses -Name $Name -ea SilentlyContinue
                        If ($SyncAttribute)
                            {
                            $cmd = "Set-MailContact $Alias -$SyncAttribute $SyncAttributeValue"
                            Invoke-Expression $cmd
                            }
                        Write-Host "-----`n"
                        }
                    Finally
                        {
                        $Mailboxes++
                        }
                    } # End UserMailbox
              MailUser
                    {
                    $Alias = $GALObj.Alias
                    $DisplayName = $GALObj.DisplayName
                    $LegDN = "x500:"+$GALObj.LegacyExchangeDN
                    [array]$EmailAddresses = $GALObj.EmailAddresses + $LegDN
                    [array]$EmailAddresses = $EmailAddresses | Sort -Unique
                    $EmailAddressesCount = $EmailAddresses.Count
                    $ExternalEmailAddress = $GALObj.ExternalEmailAddress
                    $FirstName = $GALObj.FirstName
                    $LastName = $GALObj.LastName
                    $Name = $GALObj.Name
                    $HiddenFromAddressListsEnabled = $GALObj.HiddenFromAddressListsEnabled
                    Write-Host "[$i/$objCount]"
                    Write-Host -NoNewline "Source object is a ";Write-Host -NoNewLine -Fore DarkGreen "mailuser ";Write-Host "for $($ExternalEmailAddress)."
                    
                    # Provision the object
                    Try
                        {
                        Write-Host -NoNewline "Trying to create new contact for ";Write-Host -ForegroundColor Green $ExternalEmailAddress
                        New-MailContact -Alias $Alias -DisplayName $DisplayName -ExternalEmailAddress $ExternalEmailAddress -FirstName $FirstName -LastName $LastName -Name $Name -ea SilentlyContinue
                        Write-Host " Updating $($EmailAddressesCount) proxy addresses..."
                        Set-MailContact $Alias -HiddenFromAddressListsEnabled $HiddenFromAddressListsEnabled -EmailAddresses $EmailAddresses -Name $Name -ea SilentlyContinue
                        If ($SyncAttribute)
                            {
                            $cmd = "Set-MailContact $Alias -$SyncAttribute $SyncAttributeValue"
                            Invoke-Expression $cmd
                            }
                        Write-Host "-----`n"
                        }
                    Catch
                        {
                        Write-Host -NoNewline "Trying to update contact for ";Write-Host -ForegroundColor Green $ExternalEmailAddress
                        Set-MailContact $Alias -HiddenFromAddressListsEnabled $HiddenFromAddressListsEnabled -EmailAddresses $EmailAddresses -Name $Name -ea SilentlyContinue
                        If ($SyncAttribute)
                            {
                            $cmd = "Set-MailContact $Alias -$SyncAttribute $SyncAttributeValue"
                            Invoke-Expression $cmd
                            }
                        Write-Host "-----`n"
                        }
                    Finally
                        {
                        $Mailusers++
                        }
                    } # End MailUser
              MailContact
                    {
                    $Alias = $GALObj.Alias
                    $DisplayName = $GALObj.DisplayName
                    $LegDN = "x500:"+$GALObj.LegacyExchangeDN
                    [array]$EmailAddresses = $GALObj.EmailAddresses + $LegDN
                    [array]$EmailAddresses = $EmailAddresses | Sort -Unique
                    $EmailAddressesCount = $EmailAddresses.Count
                    $ExternalEmailAddress = $GALObj.ExternalEmailAddress
                    $FirstName = $GALObj.FirstName
                    $LastName = $GALObj.LastName
                    $Name = $GALObj.Name
                    $HiddenFromAddressListsEnabled = $GALObj.HiddenFromAddressListsEnabled
                    Write-Host "[$i/$objCount]"
                    Write-Host -NoNewline "Source object is a ";Write-Host -NoNewLine -ForegroundColor DarkMagenta "contact ";Write-Host "for $($ExternalEmailAddress)."
                    
                    # Provision the object
                    Try
                        {
                        Write-Host -NoNewline "Trying to create new contact for ";Write-Host -ForegroundColor Green $ExternalEmailAddress
                        New-MailContact -Alias $Alias -DisplayName $DisplayName -ExternalEmailAddress $ExternalEmailAddress -FirstName $FirstName -LastName $LastName -Name $Name -ea SilentlyContinue
                        Write-Host " Updating $($EmailAddressesCount) proxy addresses..."
                        Set-MailContact $Alias -HiddenFromAddressListsEnabled $HiddenFromAddressListsEnabled -EmailAddresses $EmailAddresses -Name $Name -ea SilentlyContinue
                        If ($SyncAttribute)
                            {
                            $cmd = "Set-MailContact $Alias -$SyncAttribute $SyncAttributeValue"
                            Invoke-Expression $cmd
                            }
                        Write-Host "-----`n"
                        }
                    Catch
                        {
                        Write-Host -NoNewline "Trying to update contact for ";Write-Host -ForegroundColor Green $ExternalEmailAddress
                        Set-MailContact $Alias -HiddenFromAddressListsEnabled $HiddenFromAddressListsEnabled -EmailAddresses $EmailAddresses -Name $Name -ea SilentlyContinue
                        If ($SyncAttribute)
                            {
                            $cmd = "Set-MailContact $Alias -$SyncAttribute $SyncAttributeValue"
                            Invoke-Expression $cmd
                            }
                        Write-Host "-----`n"
                        }
                    Finally
                        {
                        $MailContacts++
                        }        
                    } # End Contact
              MailUniversalSecurityGroup
                    {
                    $Alias = $GALObj.Alias
                    $DisplayName = $GALObj.DisplayName
                    $LegDN = "x500:"+$GALObj.LegacyExchangeDN
                    [array]$EmailAddresses = $GALObj.EmailAddresses + $LegDN
                    [array]$EmailAddresses = $EmailAddresses | Sort -Unique
                    $EmailAddressesCount = $EmailAddresses.Count
                    $ExternalEmailAddress = $GALObj.PrimarySmtpAddress
                    $Name = $GALObj.Name
                    $HiddenFromAddressListsEnabled = $GALObj.HiddenFromAddressListsEnabled
                    Write-Host "[$i/$objCount]"
                    Write-Host -NoNewline "Source object is a ";Write-Host -NoNewLine -Fore DarkRed "Mail Universal Security Group "; Write-Host "for $($ExternalEmailAddress)."
                
                    # Provision the object
                    Try
                        {
                        Write-Host -NoNewline "Trying to create new contact for ";Write-Host -ForegroundColor Green $ExternalEmailAddress
                        New-MailContact -Alias $Alias -DisplayName $DisplayName -ExternalEmailAddress $ExternalEmailAddress -FirstName $FirstName -LastName $LastName -Name $Name -ea SilentlyContinue
                        Write-Host " Updating $($EmailAddressesCount) proxy addresses..."
                        Set-MailContact $Alias -HiddenFromAddressListsEnabled $HiddenFromAddressListsEnabled -EmailAddresses $EmailAddresses -Name $Name -ea SilentlyContinue
                        If ($SyncAttribute)
                            {
                            $cmd = "Set-MailContact $Alias -$SyncAttribute $SyncAttributeValue"
                            Invoke-Expression $cmd
                            }
                        Write-Host "-----`n"
                        }
                    Catch
                        {
                        Write-Host -NoNewline "Trying to update contact for ";Write-Host -ForegroundColor Green $ExternalEmailAddress
                        Set-MailContact $Alias -HiddenFromAddressListsEnabled $HiddenFromAddressListsEnabled -EmailAddresses $EmailAddresses -Name $Name -ea SilentlyContinue
                        If ($SyncAttribute)
                            {
                            $cmd = "Set-MailContact $Alias -$SyncAttribute $SyncAttributeValue"
                            Invoke-Expression $cmd
                            }
                        Write-Host "-----`n"
                        }
                    Finally
                        {
                        $SecurityGroups++
                        }
                    } # End MailUniversalSecurityGroup
              MailUniversalDistributionGroup
                    {
                    $Alias = $GALObj.Alias
                    $DisplayName = $GALObj.DisplayName
                    $LegDN = "x500:"+$GALObj.LegacyExchangeDN
                    [array]$EmailAddresses = $GALObj.EmailAddresses + $LegDN
                    [array]$EmailAddresses = $EmailAddresses | Sort -Unique
                    $EmailAddressesCount = $EmailAddresses.Count
                    $ExternalEmailAddress = $GALObj.PrimarySmtpAddress
                    $Name = $GALObj.Name
                    $HiddenFromAddressListsEnabled = $GALObj.HiddenFromAddressListsEnabled
                    Write-Host "[$i/$objCount]"
                    Write-Host -NoNewline "Source object is a ";Write-Host -NoNewLine -Fore DarkYellow "Mail Universal Distribution Group ";Write-Host "for $($ExternalEmailAddress)."
                    
                    # Provision the object
                    Try
                        {
                        Write-Host -NoNewline "Trying to create new contact for ";Write-Host -ForegroundColor Green $ExternalEmailAddress
                        New-MailContact -Alias $Alias -DisplayName $DisplayName -ExternalEmailAddress $ExternalEmailAddress -FirstName $FirstName -LastName $LastName -Name $Name -ea SilentlyContinue
                        Write-Host " Updating $($EmailAddressesCount) proxy addresses..."
                        Set-MailContact $Alias -HiddenFromAddressListsEnabled $HiddenFromAddressListsEnabled -EmailAddresses $EmailAddresses -Name $Name -ea SilentlyContinue
                        If ($SyncAttribute)
                            {
                            $cmd = "Set-MailContact $Alias -$SyncAttribute $SyncAttributeValue"
                            Invoke-Expression $cmd
                            }
                        Write-Host "-----`n"
                        }
                    Catch
                        {
                        Write-Host -NoNewline "Trying to update contact for ";Write-Host -ForegroundColor Green $ExternalEmailAddress
                        Set-MailContact $Alias -HiddenFromAddressListsEnabled $HiddenFromAddressListsEnabled -EmailAddresses $EmailAddresses -Name $Name -ea SilentlyContinue
                        If ($SyncAttribute)
                            {
                            $cmd = "Set-MailContact $Alias -$SyncAttribute $SyncAttributeValue"
                            Invoke-Expression $cmd
                            }
                        Write-Host "-----`n"
                        }
                    Finally
                        {
                        $DistributionGroups++
                        }
                    } # End MailUniversalDistributionGroup
              } # End Switch
          } # End If
          # Increment $i for counter
        $i++
          } # End ForEach
    } # End Else

If ($ProcessTargetDeletes)
    {
    # Build source environment hash table based on Alias
    $SourceHash = @{}
    $TargetHash = @{}
        
    Write-Host "Step [1/4] - Building source object list for processing target environment deletes."
    Foreach ($GALObj in $SourceGAL)
        {
        If ($DomainFilter -notcontains $GALObj.PrimarySmtpAddress.Split("@")[1] -or $ExcludedObjects -notcontains $GALObj.PrimarySmtpAddress)
            {
            $SourceHash[$GALObj.Alias]=$GALObj.PrimarySmtpAddress
            }
        }
        
    # Build target environment hash table based on Alias and Get-Recipient
    Write-Host "Step [2/4] - Building target environment list for processing deleted items. This may take a while."
    If ($SyncAttribute)
        {
        Get-MailContact -ResultSize Unlimited | ? { $_.$($SyncAttribute) -eq $($SyncAttributeValue) }
        }
    Else
        {
        $TargetGAL = Get-MailContact -Resultsize Unlimited
        }

    $TargetHash[$TargetGAL.Alias]=$TargetGAL.ExternalEmailAddress
    $TargetHashCount = $TargetHash.Count
    Write-Host "Imported $($TargetHashCount) objects from target environment."
    
    # Perform the hash compare
    Write-Host "Step [3/4] - Preparing list of objects to delete. This may take a while."
    $Deletes = $TargetHash.Keys | ? { $_ -notin $SourceHash.Keys }
    $DeletesCount = $Deletes.Count
    
    # Delete target environment contacts
    $i = 0
    Write-Host "Step [4/4] - Processing deletes."
    $DeleteCmd = "Remove-MailContact $obj -confirm:$false -whatif | out-null"
    If ($ConfirmDeletes)
        {
        $DeleteCmd = "Remove-MailContact $obj -confirm:$false | out-null"
        }
    Foreach ($obj in $Deletes)
        {
        Write-Host "Processing [$i/$DeletesCount] - $($obj) for deletion."
        Invoke-Expression $DeleteCmd
        $i++
        }        
    } # End If ($ProcessTargetDeletes)
    
Write-Host "Disconnecting from Target Environment."
Get-PSSession | Remove-PSSession
$EndDate = Get-Date
Write-host "Ended at $($EndDate)."
Write-Host "Elapsed time:"
$EndDate - $StartDate
Write-Host "Total number of items processed:"
Write-Host "UserMailboxes: $($Mailboxes)"
Write-Host "MailUsers: $($MailUsers)"
Write-Host "MailContacts: $($MailContacts)"
Write-Host "Distribution Groups: $($DistributionGroups)"
Write-Host "Security Groups: $($SecurityGroups)"