T2Tscripts.psm1

$script:ModuleRoot = $PSScriptRoot
$script:ModuleVersion = (Import-PowerShellDataFile -Path "$($script:ModuleRoot)\T2Tscripts.psd1").ModuleVersion

# Detect whether at some level dotsourcing was enforced
$script:doDotSource = Get-PSFConfigValue -FullName T2Tscripts.Import.DoDotSource -Fallback $false
if ($T2Tscripts_dotsourcemodule) { $script:doDotSource = $true }

<#
Note on Resolve-Path:
All paths are sent through Resolve-Path/Resolve-PSFPath in order to convert them to the correct path separator.
This allows ignoring path separators throughout the import sequence, which could otherwise cause trouble depending on OS.
Resolve-Path can only be used for paths that already exist, Resolve-PSFPath can accept that the last leaf my not exist.
This is important when testing for paths.
#>


# Detect whether at some level loading individual module files, rather than the compiled module was enforced
$importIndividualFiles = Get-PSFConfigValue -FullName T2Tscripts.Import.IndividualFiles -Fallback $false
if ($T2Tscripts_importIndividualFiles) { $importIndividualFiles = $true }
if (Test-Path (Resolve-PSFPath -Path "$($script:ModuleRoot)\..\.git" -SingleItem -NewChild)) { $importIndividualFiles = $true }
if ("<was compiled>" -eq '<was not compiled>') { $importIndividualFiles = $true }
    
function Import-ModuleFile
{
    <#
        .SYNOPSIS
            Loads files into the module on module import.
         
        .DESCRIPTION
            This helper function is used during module initialization.
            It should always be dotsourced itself, in order to proper function.
             
            This provides a central location to react to files being imported, if later desired
         
        .PARAMETER Path
            The path to the file to load
         
        .EXAMPLE
            PS C:\> . Import-ModuleFile -File $function.FullName
     
            Imports the file stored in $function according to import policy
    #>

    [CmdletBinding()]
    Param (
        [string]
        $Path
    )
    
    $resolvedPath = $ExecutionContext.SessionState.Path.GetResolvedPSPathFromPSPath($Path).ProviderPath
    if ($doDotSource) { . $resolvedPath }
    else { $ExecutionContext.InvokeCommand.InvokeScript($false, ([scriptblock]::Create([io.file]::ReadAllText($resolvedPath))), $null, $null) }
}

#region Load individual files
if ($importIndividualFiles)
{
    # Execute Preimport actions
    foreach ($path in (& "$ModuleRoot\internal\scripts\preimport.ps1")) {
        . Import-ModuleFile -Path $path
    }
    
    # Import all internal functions
    foreach ($function in (Get-ChildItem "$ModuleRoot\internal\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore))
    {
        . Import-ModuleFile -Path $function.FullName
    }
    
    # Import all public functions
    foreach ($function in (Get-ChildItem "$ModuleRoot\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore))
    {
        . Import-ModuleFile -Path $function.FullName
    }
    
    # Execute Postimport actions
    foreach ($path in (& "$ModuleRoot\internal\scripts\postimport.ps1")) {
        . Import-ModuleFile -Path $path
    }
    
    # End it here, do not load compiled code below
    return
}
#endregion Load individual files

#region Load compiled code
<#
This file loads the strings documents from the respective language folders.
This allows localizing messages and errors.
Load psd1 language files for each language you wish to support.
Partial translations are acceptable - when missing a current language message,
it will fallback to English or another available language.
#>

Import-PSFLocalizedString -Path "$($script:ModuleRoot)\en-us\*.psd1" -Module 'T2Tscripts' -Language 'en-US'

function Assert-ServiceConnection {
    <#
    .SYNOPSIS
    Checks current connection status for SCC, EXO and AzureAD
     
    .DESCRIPTION
    Checks current connection status for SCC, EXO and AzureAD
     
    .PARAMETER Services
    List of the desired services to assert the connection to. Current available services: EXO, SCC, MicrosoftTeams, MSOnline, AzureAD, AzureADPreview, Azure, ExchangeLocal, ExchangeRemote, AD.
 
    .EXAMPLE
    PS C:\> Assert-ServiceConnection -Services ExchangeLocal, AD
    Checks current connection status for Exchange Onprem in local machine and Active Directory.
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidGlobalVars", "")]
    [CmdletBinding()]
    param (
        [ValidateSet('EXO', 'SCC', 'MicrosoftTeams', 'MSOnline', 'AzureAD', 'AzureADPreview', 'Azure', 'ExchangeLocal', 'ExchangeRemote', 'AD')]
        [String[]]
        $Services
    )
    $Sessions = Get-PSSession
    $ServicesToConnect = New-Object -TypeName "System.Collections.ArrayList"

    Switch ( $Services ) {
        Azure {}
        AzureAD {
            try {
            $Null = Get-AzureADCurrentSessionInfo -ErrorAction Stop
            }
            catch {
                $null = $ServicesToConnect.add("AzureAD")
            }
        }
        AzureADPreview {}
        MSOnline {}
        MicrosoftTeams {}
        SCC {
            if ( -not ($Sessions.ComputerName -match "ps.compliance.protection.outlook.com") ) { $null = $ServicesToConnect.add("SCC") }
        }
        EXO {
            if ( $Sessions.ComputerName -notcontains "outlook.office365.com" ) { $null = $ServicesToConnect.add("EXO") }
        }
        ExchangeLocal {
            if ( $sessions.name -notmatch "Session" ) { $null = $ServicesToConnect.add("ExchangeLocal") }
        }
        ExchangeRemote {
            if ( $sessions.name -notmatch "WinRM" ) { $null = $ServicesToConnect.add("ExchangeRemote") }
        }
        AD {
            if ( -not (Get-Module ActiveDirectory -ListAvailable) ) {
            
                $null = $ServicesToConnect.add("AD")
            
            } else {

                    Import-Module ActiveDirectory
                    # Variable to be used when the machine is not
                    # an Exchange but the AD module is installed
                    [switch]$Global:LocalAD = $True

            }
        }
    }
    return $ServicesToConnect
    return $LocalAD
}

function Connect-OnlineServices {
    <#
    .SYNOPSIS
        Connect to Online Services.
 
    .DESCRIPTION
        Use this function to connect to EXO, Exchange Onprem and Active Directory.
 
    .PARAMETER AdminUPN
        Passes the administrator's UPN to be used in the authentication prompts.
 
    .PARAMETER Services
        List of the desired services to connect to. Current available services: EXO, ExchangeLocal, ExchangeRemote, AD.
 
    .PARAMETER ExchangeHostname
        Used to inform the Exchange Server FQDN that the script will connect.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation before executing any operations that change state.
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages will be displayed that explain what would happen if the command were to run.
    
    .EXAMPLE
        PS C:\> Connect-OnlineServices -Services EXO
        Connects to Exchange Online.
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "")]
    [CmdletBinding(SupportsShouldProcess = $True, ConfirmImpact = 'Low')]
    param(
        [String]
        $AdminUPN,

        [ValidateSet('EXO','ExchangeLocal', 'ExchangeRemote', 'AD')]
        [String[]]
        $Services,
     
        [Parameter(Mandatory = $false, HelpMessage="Enter the remote exchange hostname")]
        [string]$ExchangeHostname
     )

    Switch ( $Services ) {

        EXO {
            Invoke-PSFProtectedCommand -Action "Connecting to Exchange Online" -Target "EXO" -ScriptBlock {
                Write-PSFMessage -Level Output -Message "Connecting to Exchange Online"
                try {

                    Connect-ExchangeOnline -UserPrincipalName $AdminUPN -ShowProgress:$True -ShowBanner:$False -Prefix EX
                    Write-PSFMessage -Level Output -Message "Connected to Exchange Online"

                }
                catch {
                    return $_
                }
            } -EnableException $true -PSCmdlet $PSCmdlet
        }

        ExchangeLocal {
            Invoke-PSFProtectedCommand -Action "Connecting to Exchange Onprem locally." -Target "ExchangeLocal" -ScriptBlock {
                Write-PSFMessage -Level Output -Message "Connecting to Exchange Onprem locally."
                try {
                    Add-PSSnapin Microsoft.Exchange.Management.PowerShell.SnapIn
                    Write-PSFMessage -Level Output -Message "Connected to Exchange Onprem locally."
                }
                catch { Write-PSFMessage -Level Output -Message "Error: The function could not connect on local Exchange" }
            } -EnableException $true -PSCmdlet $PSCmdlet
        }

        ExchangeRemote {
            Invoke-PSFProtectedCommand -Action "Connecting to Exchange Onprem remotely." -Target "ExchangeRemote" -ScriptBlock {
                Write-PSFMessage -Level Output -Message "Connecting to Exchange Onprem remotely."
                try {
                    $exOnPremSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://$ExchangeHostname/PowerShell/ -Authentication Kerberos
                    Import-PSSession $exOnPremSession -AllowClobber -DisableNameChecking | Out-Null
                    Write-PSFMessage -Level Output -Message "Connected to Exchange Onprem remotely."
                 }
                catch { Write-PSFMessage -Level Output -Message "Error: The function could not connect on remote Exchange" }
            } -EnableException $true -PSCmdlet $PSCmdlet
        }

        AD {
            Invoke-PSFProtectedCommand -Action "Connecting to Active Directory." -Target "AD" -ScriptBlock {
                Write-PSFMessage -Level Output -Message "Connecting to Active Directory."
                try {
                    $sessionAD = New-PSSession -ComputerName $env:LogOnServer.Replace("\\","")
                    Invoke-Command { Import-Module ActiveDirectory } -Session $sessionAD
                    Export-PSSession -Session $sessionAD -CommandName *-AD* -OutputModule RemoteAD -AllowClobber -Force | Out-Null
                    Remove-PSSession -Session $sessionAD
                    
                    # Create copy of the module on the local computer
                    Import-Module RemoteAD -Prefix Remote -DisableNameChecking -ErrorAction Stop
                
                } catch {
                    
                    # Sometimes the following path is not registered as system variable for PS modules path, thus we catch explicitly the .psm1
                    Import-Module "$env:USERPROFILE\Documents\WindowsPowerShell\Modules\RemoteAD\RemoteAD.psm1" -Prefix Remote -DisableNameChecking
                        
                } finally {

                    If (Get-Module -Name RemoteAD) {

                        Write-PSFMessage -Level Output -Message "AD Module was succesfully installed."

                    } else {

                        Write-PSFMessage -Level Error -Message "AD module failed to load. Please run the script from an Exchange Server."
                        throw

                    }
                }
            } -EnableException $true -PSCmdlet $PSCmdlet
        }
    }
}

Function Convert-Source {
    <#
    .SYNOPSIS
        Function to convert RemoteMailbox to MailUser
        and update the targetAddress to destination.
 
    .DESCRIPTION
        This function is called by Update-T2TPostMigration function through
        -Source param. Basically we need to save a bunch of Exchange attributes
        before the convertion, as there is no easy way to do that in a supported
        we must disable the remotemailbox and enable the MEU, but doing so all
        Exchange attributes will be deleted so the function needs to re-assign.
 
    .EXAMPLE
        PS C:\> Convert-Source
        The following example run this function.
    #>

    
    # region local variables
    $MigratedUsersImportCheck = Get-CSVStatus -UsersMigrated
    if ($MigratedUsersImportCheck -eq 0) {Break}
    if ($SnapshotPath) {$outFile = "$SnapshotPath"}
    else {$outFile = "$home\desktop\"}
    [int]$UsersCount = ($updatelist | Measure-Object).count
    [int]$counter = 0
    $Properties = @(
        'extensionAttribute1'
        'extensionAttribute2'
        'extensionAttribute3'
        'extensionAttribute4'
        'extensionAttribute5'
        'extensionAttribute6'
        'extensionAttribute7'
        'extensionAttribute8'
        'extensionAttribute9'
        'extensionAttribute10'
        'extensionAttribute11'
        'extensionAttribute12'
        'extensionAttribute13'
        'extensionAttribute14'
        'extensionAttribute15'
    )
    
    # region loop each user
    foreach ($i in $updatelist)
    {

        $counter++
        Write-Progress -Activity "Converting RemoteMailbox to MEU and changing ExternalEmailAddress" -Status "Working on $($i.ExternalEmailAddress)" -PercentComplete ($counter * 100 / $UsersCount)

        if ($i.MoveRequestStatus -eq 'Completed')
        {

            # save user properties to $user variable before disable remote mailbox
            try
            {
                if ($SnapshotToXML.IsPresent)
                {
                    $user = Get-RemoteMailbox -Identity $i.Alias -ErrorAction Stop
                    if ($?)
                    {
                        $user | Export-Clixml -Path "$outFile\$($i.Alias).xml" -Force -Confirm:$false
                    }
                }
                else
                {
                    $user = Get-RemoteMailbox -Identity $i.Alias -ErrorAction Stop
                }

                # save custom attributes to $aduser variable
                if ($LocalMachineIsNotExchange.IsPresent -and $Null -eq $LocalAD)
                {
                    $aduser = Get-RemoteADUser $i.Alias -Server $PreferredDC -Properties $Properties -ErrorAction Stop
                }
                else
                {
                    $aduser = Get-ADUser $i.Alias -Server $PreferredDC -Properties $Properties -ErrorAction Stop
                }
            }
            catch
            {
                Write-PSFMessage  -Level Output -Message "The RemoteMailbox $($i.PrimarySMTPAddress) was not found."
            }

            # disable remote mailbox
            if ($user)
            {
                Disable-RemoteMailbox -Identity $i.Alias -IgnoreLegalHold -Confirm:$false
                if ($?)
                {
                    Write-PSFMessage  -Level Output -Message "RemoteMailbox $($i.PrimarySMTPAddress) successfully converted to MailUser."

                    # if both -UseMOERATargetAddress and -KeepOldPrimarySMTPAddress are present, object should be like this:
                    # PrimarySMTPAddress: bill@source.com
                    # ExternalEmailAddress: bill@destination.mail.onmicrosoft.com
                    if ($UseMOERATargetAddress.IsPresent -and $KeepOldPrimarySMTPAddress.IsPresent)
                    {
                        [void](Enable-MailUser -Identity $i.Alias -ExternalEmailAddress $i.ExternalEmailAddress -PrimarySMTPAddress $user.PrimarySmtpAddress)
                    }
                    # if -UseMOERATargetAddress is preset, object should be like this:
                    # PrimarySMTPAddress: bill@destination.mail.onmicrosoft.com
                    # ExternalEmailAddress: bill@destination.mail.onmicrosoft.com
                    elseif ($UseMOERATargetAddress.IsPresent)
                    {
                        [void](Enable-MailUser -Identity $i.Alias -ExternalEmailAddress $i.ExternalEmailAddress)
                    }
                    # if -KeepOldPrimarySMTPAddress is preset, object should be like this:
                    # PrimarySMTPAddress: bill@source.com
                    # ExternalEmailAddress: bill@destination.com
                    elseif ($KeepOldPrimarySMTPAddress.IsPresent)
                    {
                        [void](Enable-MailUser -Identity $i.Alias -ExternalEmailAddress $i.PrimarySMTPAddress -PrimarySMTPAddress $user.PrimarySmtpAddress)
                    }
                    # everything else should be like this:
                    # PrimarySMTPAddress: bill@destination.com
                    # ExternalEmailAddress: bill@destination.com
                    else
                    {
                        [void](Enable-MailUser -Identity $i.Alias -ExternalEmailAddress $i.PrimarySMTPAddress)
                    }

                    # convert legacyDN to X500 and set proxyAddresses
                    $x500 = "x500:" + $user.legacyExchangeDN
                    $proxy = $user.EmailAddresses
                    $ProxyArray = @()
                    $ProxyArray = $Proxy -split "," -replace "SMTP:","smtp:"
                    $ProxyArray = $ProxyArray + $x500
                    Set-MailUser -Identity $i.Alias -EmailAddresses @{Add=$ProxyArray} -HiddenFromAddressListsEnabled $user.HiddenFromAddressListsEnabled

                    # if there were custom attribues before, readd 'em all
                    $Replace = @{}
                    foreach ($element in $Properties)
                    {
                        if ($aduser.$element)
                        {
                            $Replace.Add($element,$aduser.$element)
                        }
                    }
                    # replace only if there is a Custom Attribute being used
                    if ($Replace.Count -gt 0 -and $LocalMachineIsNotExchange.IsPresent -and $Null -eq $LocalAD)
                    {
                        Set-RemoteADUser -Identity $i.Alias -Server $PreferredDC -Replace $Replace
                    }
                    elseif ($Replace.Count -gt 0)
                    {
                        Set-ADUser -Identity $i.Alias -Server $PreferredDC -Replace $Replace
                    }
                }
            }
        }
    }

    # region clean up variables and sessions
    Disconnect-ExchangeOnline -Confirm:$false -InformationAction Ignore -ErrorAction SilentlyContinue
    Get-PSSession | Remove-PSSession
    Remove-Variable * -ErrorAction SilentlyContinue

}

Function Convert-Target {
    <#
    .SYNOPSIS
        Function to convert the targetAddress and
        object type from MailUser to RemoteMailbox
 
    .DESCRIPTION
        This function is called by Convert-T2TPostMigration
        function through the parameter -Destination
 
    .EXAMPLE
        PS C:\> Convert-Target
        The following example run this function.
    #>


    # region import CSV
    $UserListToImportCheck = Get-CSVStatus -User
    if ($UserListToImportCheck -eq 0) {Break}

    # region local variables
    if ($MigratedUsersOutputPath) {$outFile = "$MigratedUsersOutputPath\MigratedUsers.csv"}
    else {$outFile = "$home\desktop\MigratedUsers.csv"}
    [System.Collections.ArrayList]$outArray = @()
    [System.Collections.ArrayList]$MEU = @()
    [System.Collections.ArrayList]$BreakLoop = @()
    $MEU = @(($ImportUserList).ExternalEmailAddressPostMove)
    $BreakLoop = @(($ImportUserList).ExternalEmailAddressPostMove)

    # Loop until all move requests from the MigratedUsers.csv
    # are Completed, CompletedWithWarning or Failed
    while ($BreakLoop.Count -gt 0)
    {
        ForEach ($i in $MEU)
        {
            Write-Progress -Activity "Verifying move request status" -Status "Verifying user $($i)"

            # define variables
            $MoveRequest = $Null
            $object = [ordered]@{}

            # Get the move request. If it doesn't exist, set MoveRequestStatus as NotFound
            try
            {
                $MoveRequest = (Get-EXMoveRequest -Identity $i -ErrorAction Stop).Status
            }
            catch
            {
                Write-PSFMessage  -Level Output -Message "MoveRequestNotExist: No move request for the user $($i) was found."
                [void]$object.Add("ExternalEmailAddress",$i)
                [void]$object.Add("PrimarySMTPAddress",$Null)
                [void]$object.Add("Alias",$Null)
                [void]$object.Add("MoveRequestStatus","MoveRequestNotExist")
                $BreakLoop.Remove($i)
            }
            finally
            {
                if ($MoveRequest -like "Completed*")
                {
                    # We must resolve the Alias because ExternalEmailAddress isn't a valid
                    # identity. We also need resolve the PrimarySMTPAddress cause it might
                    # be used in Convert-Source if "-UseMOERATargetAddress" is not present
                    try
                    {
                        $user = Get-MailUser -identity $i -ErrorAction Stop
                        if ($?)
                        {
                            [void]$object.Add("ExternalEmailAddress",$i)
                            [void]$object.Add("PrimarySMTPAddress",$user.PrimarySMTPAddress)
                            [void]$object.Add("Alias",$user.Alias)
                            $Null = Enable-RemoteMailbox -identity $user.Alias -RemoteRoutingAddress $i -ErrorAction Stop
                            if ($?)
                            {
                                Write-PSFMessage  -Level Output -Message "Converted MailUser $($i) to RemoteMailbox and changed ExternalEmailAddress successfully."
                                [void]$object.Add("MoveRequestStatus","Completed")
                                $BreakLoop.Remove($i)
                            }
                        }
                    }
                    catch
                    {
                        if (Get-RemoteMailbox -Identity $user.Alias)
                        {
                            Write-PSFMessage -Level Output -Message "AlreadyRemoteMailbox: User $($i) was not converted to RemoteMailbox because the object type is already RemoteMailbox."
                            [void]$object.Add("ExternalEmailAddress",$i)
                            [void]$object.Add("PrimarySMTPAddress",$Null)
                            [void]$object.Add("Alias",$Null)
                            [void]$object.Add("MoveRequestStatus","IsAlreadyRemoteMailbox")
                            $BreakLoop.Remove($i)
                        }
                        else
                        {
                            Write-PSFMessage -Level Output -Message "MEUNotFound: The MailUser $($i) was not found."
                            [void]$object.Add("ExternalEmailAddress",$i)
                            [void]$object.Add("PrimarySMTPAddress",$Null)
                            [void]$object.Add("Alias",$Null)
                            [void]$object.Add("MoveRequestStatus","MEUNotFound")
                            $BreakLoop.Remove($i)
                        }
                    }
                }

                if ($MoveRequest -like "Failed")
                {
                    Write-PSFMessage  -Level Output -Message "MoveRequestFailed: The $($i) move request was failed"
                    [void]$object.Add("ExternalEmailAddress",$i)
                    [void]$object.Add("PrimarySMTPAddress",$Null)
                    [void]$object.Add("Alias",$Null)
                    [void]$object.Add("MoveRequestStatus","MoveRequestFailed")
                    $BreakLoop.Remove($i)
                }
            }
            
            # add object to array only if there is MoveRequestStatus
            # to avoid empty lines while the move does not finished.
            if ($object.MoveRequestStatus)
            {
                $outPSObject = New-Object -TypeName PSObject -Property $object
                [void]$outArray.Add($outPSObject)
            }
        }

        # Remove from $MEU array any object that has MoveRequestStatus value.
        # it means that the object should not go through the iteration anymore
        ForEach ($element in $outArray)
        {
            if ($element.MoveRequestStatus)
            {
                $MEU.Remove($element.ExternalEmailAddress)
            }
        }
    }

    # region export to a CSV
    Write-PSFMessage -Level Output -Message "Saving CSV on $($outfile)"
    try
    {
        $outArray | Export-CSV $outfile -NoTypeInformation -ErrorAction Stop
    }
    catch
    {
        Write-PSFMessage -Level Output -Message "The path $($outfile) could not be found. The file will be saved on $($home)\desktop\MigratedUsers.csv"
        $outArray | Export-CSV "$home\desktop\MigratedUsers.csv" -NoTypeInformation
    }

    # region clean up variables and sessions
    Disconnect-ExchangeOnline -Confirm:$false -InformationAction Ignore -ErrorAction SilentlyContinue
    Get-PSSession | Remove-PSSession
    Remove-Variable * -ErrorAction SilentlyContinue

}

Function Export-ADPersonalAttribute {
    <#
    .SYNOPSIS
    Function to handle the export of a set AD attributes
 
    .DESCRIPTION
    The function is called by Export-T2TAttributes when any of the following param are
    passed: -IncludeGeneral, -IncludeAddress, -IncludePhones, -IncludeOrganization or
    -IncludeManager. Basically the function dump the a set of attribute to the $object
    variable and for return it to the Export-T2TAttributes.
 
    .EXAMPLE
    PS C:\> Export-ADPersonalAttribute
    The cmdlet above will dump the necessary attributes based on what param was passed.
    #>


    # region -IncludeGeneral
    if ($IncludeGeneral.IsPresent)
    {
        if ($ADUser.physicalDeliveryOfficeName.Length -gt 0)
        {
            [void]$object.Add("physicalDeliveryOfficeName",$ADUser.physicalDeliveryOfficeName.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("physicalDeliveryOfficeName",$Null)
        }

        if ($ADUser.wWWHomePage.Length -gt 0)
        {
            [void]$object.Add("wWWHomePage",$ADUser.wWWHomePage.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("wWWHomePage",$Null)
        }

        if ($ADUser.url.Length -gt 0)
        {
            $url = $ADUser.url -Join ";"
            [void]$object.Add("url",$url.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("url",$Null)
        }

        if ($ADUser.description.Length -gt 0)
        {
            [void]$object.Add("description",$ADUser.Description.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("description",$Null)
        }
    }

    # region -IncludeAddress
    if ($IncludeAddress.IsPresent)
    {
        if ($ADUser.streetAddress.Length -gt 0)
        {
            [void]$object.Add("streetAddress",$ADUser.streetAddress.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("streetAddress",$Null)
        }

        if ($ADUser.postOfficeBox.Length -gt 0)
        {
            $postOfficeBox = $ADUser.postOfficeBox -Join ";"
            [void]$object.Add("postOfficeBox",$postOfficeBox.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("postOfficeBox",$Null)
        }
            
        if ($ADUser.l.Length -gt 0)
        {
            [void]$object.Add("l",$ADUser.l.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("l",$Null)
        }
            
        if ($ADUser.postalCode.Length -gt 0)
        {
            [void]$object.Add("postalCode",$ADUser.postalCode.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("postalCode",$Null)
        }
            
        if ($ADUser.c.Length -gt 0)
        {
            [void]$object.Add("c",$ADUser.c)
        }
        else
        {
            [void]$object.Add("c",$Null)
        }
            
        if ($ADUser.co.Length -gt 0)
        {
            [void]$object.Add("co",$ADUser.co)
        }
        else
        {
            [void]$object.Add("co",$Null)
        }
            
        if ($ADUser.countryCode -gt 0)
        {
            [void]$object.Add("countryCode",$ADUser.countryCode.ToString())
        }
        else
        {
            [void]$object.Add("countryCode",$Null)
        }

        if ($ADUser.st.Length -gt 0)
        {
            [void]$object.Add("st",$ADUser.st.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("st",$Null)
        }
    }

    # region -IncludePhones
    if ($IncludePhones.IsPresent)
    {
        if ($ADUser.telephoneNumber.Length -gt 0)
        {
            [void]$object.Add("telephoneNumber",$ADUser.telephoneNumber.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("telephoneNumber",$Null)
        }

        if ($ADUser.otherTelephone.Length -gt 0)
        {
            $otherTelephone = $ADUser.otherTelephone -Join ";"
            [void]$object.Add("otherTelephone",$otherTelephone.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("otherTelephone",$Null)
        }

        if ($ADUser.homePhone.Length -gt 0)
        {
            [void]$object.Add("homePhone",$ADUser.homePhone.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("homePhone",$Null)
        }
            
        if ($ADUser.otherHomePhone.Length -gt 0)
        {
            $otherHomePhone = $ADUser.otherHomePhone -Join ";"
            [void]$object.Add("otherHomePhone",$otherHomePhone.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("otherHomePhone",$Null)
        }
            
        if ($ADUser.pager.Length -gt 0)
        {
            [void]$object.Add("pager",$ADUser.pager.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("pager",$Null)
        }

        if ($ADUser.otherPager.Length -gt 0)
        {
            $otherPager = $ADUser.otherPager -Join ";"
            [void]$object.Add("otherPager",$otherPager.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("otherPager",$Null)
        }

        if ($ADUser.mobile.Length -gt 0)
        {
            [void]$object.Add("mobile",$ADUser.mobile.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("mobile",$Null)
        }
            
        if ($ADUser.otherMobile.Length -gt 0)
        {
            $otherMobile = $ADUser.otherMobile -Join ";"
            [void]$object.Add("otherMobile",$otherMobile.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("otherMobile",$Null)
        }

        if ($ADUser.facsimileTelephoneNumber.Length -gt 0)
        {
            [void]$object.Add("facsimileTelephoneNumber",$ADUser.facsimileTelephoneNumber.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("facsimileTelephoneNumber",$Null)
        }

        if ($ADUser.otherFacsimileTelephoneNumber.Length -gt 0)
        {
            $otherFacsimileTelephoneNumber = $ADUser.otherFacsimileTelephoneNumber -Join ";"
            [void]$object.Add("otherFacsimileTelephoneNumber",$otherFacsimileTelephoneNumber.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("otherFacsimileTelephoneNumber",$Null)
        }
            
        if ($ADUser.ipPhone.Length -gt 0)
        {
            [void]$object.Add("ipPhone",$ADUser.ipPhone.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("ipPhone",$Null)
        }

        if ($ADUser.otherIpPhone.Length -gt 0)
        {
            $otherIpPhone = $ADUser.otherIpPhone -Join ";"
            [void]$object.Add("otherIpPhone",$otherIpPhone.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("otherIpPhone",$Null)
        }
            
        if ($ADUser.info.Length -gt 0)
        {
            [void]$object.Add("info",$ADUser.info.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("info",$Null)
        }
    }

    # region -IncludeOrganization
    if ($IncludeOrganization.IsPresent)
    {
        if ($ADUser.title.Length -gt 0)
        {
            [void]$object.Add("title",$ADUser.title.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("title",$Null)
        }

        if ($ADUser.department.Length -gt 0)
        {
            [void]$object.Add("department",$ADUser.department.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("department",$Null)
        }

        if ($ADUser.company.Length -gt 0)
        {
            [void]$object.Add("company",$ADUser.company.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("company",$Null)
        }
    }

    # region -IncludeManager. We must
    # resolve the manager's CN to alias
    if ($Null -ne $user.Manager -and $IncludeManager.IsPresent)
    {
        $Manager = (Get-Recipient $user.Manager).Alias
        [void]$object.Add("Manager",$Manager)
    }
    elseif ($Null -eq $user.Manager -and $IncludeManager.IsPresent)
    {
        [void]$object.Add("Manager",$Null)
    }

    # region -IncludeCustomAttributes
    if ($IncludeCustomAttributes.IsPresent)
    {
        if ($ADUser.extensionAttribute1.Length -gt 0)
        {
            [void]$object.Add("extensionAttribute1",$ADUser.extensionAttribute1.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("extensionAttribute1",$Null)
        }

        if ($ADUser.extensionAttribute2.Length -gt 0)
        {
            [void]$object.Add("extensionAttribute2",$ADUser.extensionAttribute2.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("extensionAttribute2",$Null)
        }

        if ($ADUser.extensionAttribute3.Length -gt 0)
        {
            [void]$object.Add("extensionAttribute3",$ADUser.extensionAttribute3.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("extensionAttribute3",$Null)
        }

        if ($ADUser.extensionAttribute4.Length -gt 0)
        {
            [void]$object.Add("extensionAttribute4",$ADUser.extensionAttribute4.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("extensionAttribute4",$Null)
        }

        if ($ADUser.extensionAttribute5.Length -gt 0)
        {
            [void]$object.Add("extensionAttribute5",$ADUser.extensionAttribute5.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("extensionAttribute5",$Null)
        }

        if ($ADUser.extensionAttribute6.Length -gt 0)
        {
            [void]$object.Add("extensionAttribute6",$ADUser.extensionAttribute6.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("extensionAttribute6",$Null)
        }

        if ($ADUser.extensionAttribute7.Length -gt 0)
        {
            [void]$object.Add("extensionAttribute7",$ADUser.extensionAttribute7.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("extensionAttribute7",$Null)
        }

        if ($ADUser.extensionAttribute8.Length -gt 0)
        {
            [void]$object.Add("extensionAttribute8",$ADUser.extensionAttribute8.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("extensionAttribute8",$Null)
        }

        if ($ADUser.extensionAttribute9.Length -gt 0)
        {
            [void]$object.Add("extensionAttribute9",$ADUser.extensionAttribute9.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("extensionAttribute9",$Null)
        }

        if ($ADUser.extensionAttribute10.Length -gt 0)
        {
            [void]$object.Add("extensionAttribute10",$ADUser.extensionAttribute10.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("extensionAttribute10",$Null)
        }

        if ($ADUser.extensionAttribute11.Length -gt 0)
        {
            [void]$object.Add("extensionAttribute11",$ADUser.extensionAttribute11.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("extensionAttribute11",$Null)
        }

        if ($ADUser.extensionAttribute12.Length -gt 0)
        {
            [void]$object.Add("extensionAttribute12",$ADUser.extensionAttribute12.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("extensionAttribute12",$Null)
        }

        if ($ADUser.extensionAttribute13.Length -gt 0)
        {
            [void]$object.Add("extensionAttribute13",$ADUser.extensionAttribute13.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("extensionAttribute13",$Null)
        }

        if ($ADUser.extensionAttribute14.Length -gt 0)
        {
            [void]$object.Add("extensionAttribute14",$ADUser.extensionAttribute14.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("extensionAttribute14",$Null)
        }

        if ($ADUser.extensionAttribute15.Length -gt 0)
        {
            [void]$object.Add("extensionAttribute15",$ADUser.extensionAttribute15.Replace(",","---"))
        }
        else
        {
            [void]$object.Add("extensionAttribute15",$Null)
        }
    }

    return $object
}

Function Get-CSVStatus {
    <#
    .SYNOPSIS
        Checks CSV Status
     
    .DESCRIPTION
        This functions is used to import CSV and check if the CSV was successfully imported.
     
    .PARAMETER User
        Import UserListToImport.csv and check if the file was successfully imported
 
    .PARAMETER UsersMigrated
        Import UsersMigrated.csv and check if the file was successfully imported
 
    .PARAMETER Contact
        Import ContactListToImport.csv and check if the file was successfully imported
 
    .PARAMETER MappingFile
        Import the domain mapping file and check if the file was successfully imported
     
    .EXAMPLE
        PS C:\> Get-CSVStatus -MappingFile
        Import the CSV mapping file and provide a return to the main function if the file was successfully imported.
    #>


    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidGlobalVars", "")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseOutputTypeCorrectly", "")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "")]
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$false)]
        [switch]$User,
        [Parameter(Mandatory=$false)]
        [switch]$UsersMigrated,
        [Parameter(Mandatory=$false)]
        [switch]$Contact,
        [Parameter(Mandatory=$false)]
        [switch]$MappingFile
    )

    if ( $User.IsPresent ) {

        $UserListToImportCheck = 1
        if ( $UserListToImport ) {
        
            Try {

                $global:ImportUserList = Import-CSV "$UserListToImport" -ErrorAction Stop
                Write-PSFMessage -Level Output -Message  "The UserListToImport.csv successfully imported."
            }
            catch
            {

                $UserListToImportCheck = 0
                Write-PSFMessage -Level Output -Message  "The UserListToImport.csv does not exist. Please import a valid CSV file e re-run the function."

            }

            } else {

                Try
                {

                    $global:ImportUserList = Import-CSV "$home\desktop\UserListToImport.csv" -ErrorAction Stop
                    Write-PSFMessage -Level Output -Message  "The UserListToImport.csv successfully imported from $($home)\Desktop."

                }
                catch
                {

                    $UserListToImportCheck = 0
                    Write-PSFMessage -Level Output -Message  "The UserListToImport.csv was not found in $($home)\Desktop'. Please import a valid CSV file e re-run the function."

                }
            }

        return $UserListToImportCheck

        }

    if ( $UsersMigrated.IsPresent ) {

        $MigratedUsersImportCheck = 1
        if ( $MigratedUsers ) {
        
            Try {

                $Global:updatelist = Import-CSV "$MigratedUsers" -ErrorAction Stop
                Write-PSFMessage -Level Output -Message  "The MigratedUsers.csv successfully imported."
            }
            catch
            {

                $MigratedUsersImportCheck = 0
                Write-PSFMessage -Level Output -Message  "The MigratedUsers.csv does not exist. Please import a valid CSV file e re-run the function."

            }

            } else {

                Try
                {

                    $Global:updatelist = Import-CSV "$home\desktop\MigratedUsers.csv" -ErrorAction Stop
                    Write-PSFMessage -Level Output -Message  "The MigratedUsers.csv successfully imported from $($home)\Desktop."

                }
                catch
                {

                    $MigratedUsersImportCheck = 0
                    Write-PSFMessage -Level Output -Message  "The MigratedUsers.csv was not found in $($home)\Desktop'. Please import a valid CSV file e re-run the function."

                }
            }

        return $MigratedUsersImportCheck

        }

    if ( $Contact.IsPresent ) {

        $ContactListToImportCheck = 1
        if ( $ContactListToImport ) {

            Try
            {
                
                $global:ImportContactList = Import-CSV "$ContactListToImport" -ErrorAction Stop
                Write-PSFMessage -Level Output -Message  "The ContactListToImport.csv successfully imported."
                $ContactListToImportCheck = 1

            }
            catch
            {

                $ContactListToImportCheck = 0
                Write-PSFMessage -Level Output -Message  "The ContactListToImport.csv does not exist. Please import a valid CSV file e re-run the function."

            }

        } else {

            Try
            {

                $global:ImportContactList = Import-CSV "$home\desktop\ContactListToImport.csv" -ErrorAction Stop
                Write-PSFMessage -Level Output -Message  "The ContactListToImport.csv successfully imported from $($home)\Desktop."
                $ContactListToImportCheck = 1

            }
            catch
            {

                $ContactListToImportCheck = 0
                Write-PSFMessage -Level Output -Message  "ContactListToImport.csv was not found in $($home)\Desktop'. No Mail Contact will be imported."

            }
        }

        return $ContactListToImportCheck
    
    }

    if ( $MappingFile.IsPresent ) {
        $CSVMappingExist = 1
        Try {

            Import-CSV -Path $DomainMappingCSV -ErrorAction Stop

        }
        catch
        {

            $CSVMappingExist = 0
            Write-PSFMessage -Level Output -Message  "The Domain File Mapping does not exist. Please import a valid CSV file e re-run the function."
            if ($CSVMappingExist) { return $CSVMappingExist }

        }

        return $CSVMappingExist

    }

}

Function Get-Requirement {
    <#
    .SYNOPSIS
    Checks requirements
     
    .DESCRIPTION
    Checks requirements
     
    .PARAMETER Requirements
    Lists the available Services requirements to be checked. Currently Available is 'AADConnect'.
     
    .EXAMPLE
    PS C:\> Get-Requirement -Requirements AADConnect
    Prompt Azure AD Connect questionary
    #>


    [CmdletBinding()]
    param (
        [ValidateSet('AADConnect')]
        [String[]]
        $Requirements
    )

    Switch ($Requirements)
    {
        AADConnect
        {
            $title    = Write-PSFMessage -Level Output -Message  "AD Sync status"
            $question = Write-PSFMessage -Level Output -Message  "Did you stopped the Azure AD Connect sync cycle?"
            $choices  = '&Yes', '&No'
            $decision = $Host.UI.PromptForChoice($title, $question, $choices, 1)

            if ($decision -eq 0)
            {
                Write-PSFMessage -Level Output -Message  "Loading parameters..."
            } else {
            
                Write-PSFMessage -Level Output -Message  "AD sync cycle should be stopped before moving forward."
            
                $title1    = Write-PSFMessage -Level Output -Message  ""
                $question1 = Write-PSFMessage -Level Output -Message  "Type 'Yes' if you want that we automatically stop AD Sync cycle or type 'No' if you want to stop yourself."
                $choices1  = '&Yes', '&No'
                $decision1 = $Host.UI.PromptForChoice($title1, $question1, $choices1, 1)

                if ($decision1 -eq 0)
                {
                    $AADC = Read-Host "$(Get-Date -Format "HH:mm:ss") - Please enter the Azure AD Connect server hostname"
                    Write-PSFMessage -Level Output -Message  "Disabling AD Sync cycle..."
                    $sessionAADC = New-PSSession -ComputerName $AADC
                    Invoke-Command {
                        Import-Module ADSync
                        Set-ADSyncScheduler -SyncCycleEnabled $false
                    } -Session $sessionAADC

                    $SynccycleStatus = Invoke-Command {
                        Import-Module ADSync
                        Get-ADSyncScheduler | Select-Object SyncCycleEnabled
                    } -Session $sessionAADC

                    if ($SynccycleStatus.SyncCycleEnabled -eq $false)
                    {
                        Write-PSFMessage -Level Output -Message  "Azure AD sync cycle succesfully disabled."
                        $AADCStoped = 1
                    }
                    else
                    {
                        Write-PSFMessage -Level Output -Message  "Azure AD sync cycle could not be stopped, please stop it manually with the following cmdlet: Set-ADSyncScheduler -SyncCycleEnabled 0"
                        $AADCStoped = 0
                    }
                }
                else
                {
                    $AADCStoped = 0
                    Write-PSFMessage -Level Output -Message  "Please stop the AD sync cycle and run the script again."
                }
            }

            Get-PSSession | Remove-PSSession
        }
    }

    return $AADCStoped
}

Function Import-ADPersonalAttribute {
    <#
    .SYNOPSIS
    Function to handle the import of many AD attribute
 
    .DESCRIPTION
    The function is called by Import-T2TAttributes when any of the following bool are
    $true: $IncludeGeneral, $IncludeAddress, $IncludePhones, $IncludeOrganization or
    -IncludeManager. Basically, the function must add a set of values and properties
    to the $Replace hashtable. The function also will handle the split in array cases
    and the replace of "---" to "," whenever would be necessary.
 
    .EXAMPLE
    PS C:\> Import-ADPersonalAttribute
    The cmdlet above will add a set of attributes to the $Replace array and return it to Import-T2TAttributes.
    #>


    # region -IncludeGeneral
    if ($CheckGeneral)
    {
        # In scenarios where the function doesn't have RSAT and AD module was exported from a
        # DC, there is a bug which prevent us from add all values as hashtable but we need to
        # loop the array and add one by one. This happens only for AD multi-value properties.
        if ($user.description -and $LocalMachineIsNotExchange.IsPresent -and $Null -eq $LocalAD)
        {
            [System.Collections.ArrayList]$descriptionArr = $user.description.Split(";")
            ForEach ($description in $descriptionArr)
            {
                # We check the $ResolvedGUID to understand if it's a contact. Everything else should be user.
                if ($ResolvedGUID.Guid)
                {
                    Set-RemoteADObject -Identity $ResolvedGUID.Guid -Server $PreferredDC -Add @{description=$description.Replace("---",",")}
                }
                else
                {
                    Set-RemoteADUser -Identity $user.SamAccountName -Server $PreferredDC -Add @{description=$description.Replace("---",",")}
                }
            }
        }
        elseif ($user.description)
        {
            $descriptionArr = $user.description.Split(";")
            [void]$Replace.Add("description",$descriptionArr.Replace("---",","))
        }

        if ($user.physicalDeliveryOfficeName)
        {
            [void]$Replace.Add("physicalDeliveryOfficeName",$user.physicalDeliveryOfficeName.Replace("---",","))
        }

        if ($user.wWWHomePage)
        {
            [void]$Replace.Add("wWWHomePage",$user.wWWHomePage.Replace("---",","))
        }

        if ($user.url -and $LocalMachineIsNotExchange.IsPresent -and $Null -eq $LocalAD)
        {
            [System.Collections.ArrayList]$urlArr = $user.url.Split(";")
            ForEach ($url in $urlArr)
            {
                if ($ResolvedGUID.Guid)
                {
                    Set-RemoteADObject -Identity $ResolvedGUID.Guid -Server $PreferredDC -Add @{url=$url.Replace("---",",")}
                }
                else
                {
                    Set-RemoteADUser -Identity $user.SamAccountName -Server $PreferredDC -Add @{url=$url.Replace("---",",")}
                }
            }
        }
        elseif ($user.url)
        {
            $urlArr = $user.url.Split(";")
            [void]$Replace.Add("url",$urlArr.Replace("---",","))
        }
    }

    # region -IncludeAddress
    if ($CheckAddress)
    {
        if ($user.streetAddress)
        {
            [void]$Replace.Add("streetAddress",$user.streetAddress.Replace("---",","))
        }

        if ($user.l)
        {
            [void]$Replace.Add("l",$user.l.Replace("---",","))
        }

        if ($user.st)
        {
            [void]$Replace.Add("st",$user.st.Replace("---",","))
        }

        if ($user.postalCode)
        {
            [void]$Replace.Add("postalCode",$user.postalCode.Replace("---",","))
        }

        if($user.c)
        {
            [void]$Replace.Add("c",$user.c)
        }

        if ($user.co)
        {
            [void]$Replace.Add("co",$user.co)
        }

        if ($user.countryCode)
        {
            [void]$Replace.Add("countryCode",$user.countryCode)
        }

        if ($user.postOfficeBox -and $LocalMachineIsNotExchange.IsPresent -and $Null -eq $LocalAD)
        {
            [System.Collections.ArrayList]$postOfficeBoxArr = $user.postOfficeBox.Split(";")
            ForEach ($postOfficeBox in $postOfficeBoxArr)
            {
                if ($ResolvedGUID.Guid)
                {
                    Set-RemoteADObject -Identity $ResolvedGUID.Guid -Server $PreferredDC -Add @{postOfficeBox=$postOfficeBox.Replace("---",",")}
                }
                else
                {
                    Set-RemoteADUser -Identity $user.SamAccountName -Server $PreferredDC -Add @{postOfficeBox=$postOfficeBox.Replace("---",",")}
                }
            }
        }
        elseif ($user.postOfficeBox)
        {
            $postOfficeBoxArr = $user.postOfficeBox.Split(";")
            [void]$Replace.Add("postOfficeBox",$postOfficeBoxArr.Replace("---",","))
        }
    }

    # region -IncludePhones
    if ($CheckPhones)
    {
        if ($user.telephoneNumber)
        {
            [void]$Replace.Add("telephoneNumber",$user.telephoneNumber.Replace("---",","))
        }

        if ($user.otherTelephone -and $LocalMachineIsNotExchange.IsPresent -and $Null -eq $LocalAD)
        {
            [System.Collections.ArrayList]$otherTelephoneArr = $user.otherTelephone.Split(";")
            ForEach ($otherTelephone in $otherTelephoneArr)
            {
                if ($ResolvedGUID.Guid)
                {
                    Set-RemoteADObject -Identity $ResolvedGUID.Guid -Server $PreferredDC -Add @{otherTelephone=$otherTelephone.Replace("---",",")}
                }
                else
                {
                    Set-RemoteADUser -Identity $user.SamAccountName -Server $PreferredDC -Add @{otherTelephone=$otherTelephone.Replace("---",",")}
                }
            }
        }
        elseif ($user.otherTelephone)
        {
            $otherTelephoneArr = $user.otherTelephone.Split(";")
            [void]$Replace.Add("otherTelephone",$otherTelephoneArr.Replace("---",","))
        }

        if ($user.homePhone)
        {
            [void]$Replace.Add("homePhone",$user.homePhone.Replace("---",","))
        }

        if ($user.otherHomePhone -and $LocalMachineIsNotExchange.IsPresent -and $Null -eq $LocalAD)
        {
            [System.Collections.ArrayList]$otherHomePhoneArr = $user.otherHomePhone.Split(";")
            ForEach ($otherHomePhone in $otherHomePhoneArr)
            {
                if ($ResolvedGUID.Guid)
                {
                    Set-RemoteADObject -Identity $ResolvedGUID.Guid -Server $PreferredDC -Add @{otherHomePhone=$otherHomePhone.Replace("---",",")}
                }
                else
                {
                    Set-RemoteADUser -Identity $user.SamAccountName -Server $PreferredDC -Add @{otherHomePhone=$otherHomePhone.Replace("---",",")}
                }
            }
        }
        elseif ($user.otherHomePhone)
        {
            $otherHomePhoneArr = $user.otherHomePhone.Split(";")
            [void]$Replace.Add("otherHomePhone",$otherHomePhoneArr.Replace("---",","))
        }

        if ($user.pager)
        {
            [void]$Replace.Add("pager",$user.pager.Replace("---",","))
        }
        
        if ($user.otherPager -and $LocalMachineIsNotExchange.IsPresent -and $Null -eq $LocalAD)
        {
            [System.Collections.ArrayList]$otherPagerArr = $user.otherPager.Split(";")
            ForEach ($otherPager in $otherPagerArr)
            {
                if ($ResolvedGUID.Guid)
                {
                    Set-RemoteADObject -Identity $ResolvedGUID.Guid -Server $PreferredDC -Add @{otherPager=$otherPager.Replace("---",",")}
                }
                else
                {
                    Set-RemoteADUser -Identity $user.SamAccountName -Server $PreferredDC -Add @{otherPager=$otherPager.Replace("---",",")}
                }
            }
        }
        elseif ($user.otherPager)
        {
            $otherPagerArr = $user.otherPager.Split(";")
            [void]$Replace.Add("otherPager",$otherPagerArr.Replace("---",","))
        }

        if ($user.mobile)
        {
            [void]$Replace.Add("mobile",$user.mobile.Replace("---",","))
        }

        if ($user.otherMobile -and $LocalMachineIsNotExchange.IsPresent -and $Null -eq $LocalAD)
        {
            [System.Collections.ArrayList]$otherMobileArr = $user.otherMobile.Split(";")
            ForEach ($otherMobile in $otherMobileArr)
            {
                if ($ResolvedGUID.Guid)
                {
                    Set-RemoteADObject -Identity $ResolvedGUID.Guid -Server $PreferredDC -Add @{otherMobile=$otherMobile.Replace("---",",")}
                }
                else
                {
                    Set-RemoteADUser -Identity $user.SamAccountName -Server $PreferredDC -Add @{otherMobile=$otherMobile.Replace("---",",")}
                }
            }
        }
        elseif ($user.otherMobile)
        {
            $otherMobileArr = $user.otherMobile.Split(";")
            [void]$Replace.Add("otherMobile",$otherMobileArr.Replace("---",","))
        }

        if ($user.facsimileTelephoneNumber)
        {
            [void]$Replace.Add("facsimileTelephoneNumber",$user.facsimileTelephoneNumber.Replace("---",","))
        }

        if ($user.otherFacsimileTelephoneNumber -and $LocalMachineIsNotExchange.IsPresent -and $Null -eq $LocalAD)
        {
            [System.Collections.ArrayList]$otherFacsimileTelephoneNumberArr = $user.otherFacsimileTelephoneNumber.Split(";")
            ForEach ($otherFacsimileTelephoneNumber in $otherFacsimileTelephoneNumberArr)
            {
                if ($ResolvedGUID.Guid)
                {
                    Set-RemoteADObject -Identity $ResolvedGUID.Guid -Server $PreferredDC -Add @{otherFacsimileTelephoneNumber=$otherFacsimileTelephoneNumber.Replace("---",",")}
                }
                else
                {
                    Set-RemoteADUser -Identity $user.SamAccountName -Server $PreferredDC -Add @{otherFacsimileTelephoneNumber=$otherFacsimileTelephoneNumber.Replace("---",",")}
                }
            }
        }
        elseif ($user.otherFacsimileTelephoneNumber)
        {
            $otherFacsimileTelephoneNumberArr = $user.otherFacsimileTelephoneNumber.Split(";")
            [void]$Replace.Add("otherFacsimileTelephoneNumber",$otherFacsimileTelephoneNumberArr.Replace("---",","))
        }

        if ($user.ipPhone)
        {
            [void]$Replace.Add("ipPhone",$user.ipPhone.Replace("---",","))
        }

        if ($user.otherIpPhone -and $LocalMachineIsNotExchange.IsPresent -and $Null -eq $LocalAD)
        {
            [System.Collections.ArrayList]$otherIpPhoneArr = $user.otherIpPhone.Split(";")
            ForEach ($otherIpPhone in $otherIpPhoneArr)
            {
                if ($ResolvedGUID.Guid)
                {
                    Set-RemoteADObject -Identity $ResolvedGUID.Guid -Server $PreferredDC -Add @{otherIpPhone=$otherIpPhone.Replace("---",",")}
                }
                else
                {
                    Set-RemoteADUser -Identity $user.SamAccountName -Server $PreferredDC -Add @{otherIpPhone=$otherIpPhone.Replace("---",",")}
                }
            }
        }
        elseif ($user.otherIpPhone)
        {
            $otherIpPhoneArr = $user.otherIpPhone.Split(";")
            [void]$Replace.Add("otherIpPhone",$otherIpPhoneArr.Replace("---",","))
        }

        if ($user.info)
        {
            [void]$Replace.Add("info",$user.info.Replace("---",","))
        }
    }

    # region -IncludeOrganization
    if ($CheckOrganization)
    {
        if ($user.title)
        {
            [void]$Replace.Add("title",$user.title.Replace("---",","))
        }

        if ($user.department)
        {
            [void]$Replace.Add("department",$user.department.Replace("---",","))
        }

        if ($user.company)
        {
            [void]$Replace.Add("company",$user.company.Replace("---",","))
        }
    }

    # region -IncludeCustomAttributes
    if ($CheckCustomAttributes)
    {
        if ($user.extensionAttribute1)
        {
            [void]$Replace.Add("extensionAttribute1",$user.extensionAttribute1.Replace("---",","))
        }

        if ($user.extensionAttribute2)
        {
            [void]$Replace.Add("extensionAttribute2",$user.extensionAttribute2.Replace("---",","))
        }

        if ($user.extensionAttribute3)
        {
            [void]$Replace.Add("extensionAttribute3",$user.extensionAttribute3.Replace("---",","))
        }

        if ($user.extensionAttribute4)
        {
            [void]$Replace.Add("extensionAttribute4",$user.extensionAttribute4.Replace("---",","))
        }

        if ($user.extensionAttribute5)
        {
            [void]$Replace.Add("extensionAttribute5",$user.extensionAttribute5.Replace("---",","))
        }

        if ($user.extensionAttribute6)
        {
            [void]$Replace.Add("extensionAttribute6",$user.extensionAttribute6.Replace("---",","))
        }

        if ($user.extensionAttribute7)
        {
            [void]$Replace.Add("extensionAttribute7",$user.extensionAttribute7.Replace("---",","))
        }

        if ($user.extensionAttribute8)
        {
            [void]$Replace.Add("extensionAttribute8",$user.extensionAttribute8.Replace("---",","))
        }

        if ($user.extensionAttribute9)
        {
            [void]$Replace.Add("extensionAttribute9",$user.extensionAttribute9.Replace("---",","))
        }

        if ($user.extensionAttribute10)
        {
            [void]$Replace.Add("extensionAttribute10",$user.extensionAttribute10.Replace("---",","))
        }

        if ($user.extensionAttribute11)
        {
            [void]$Replace.Add("extensionAttribute11",$user.extensionAttribute11.Replace("---",","))
        }

        if ($user.extensionAttribute12)
        {
            [void]$Replace.Add("extensionAttribute12",$user.extensionAttribute12.Replace("---",","))
        }

        if ($user.extensionAttribute13)
        {
            [void]$Replace.Add("extensionAttribute13",$user.extensionAttribute13.Replace("---",","))
        }

        if ($user.extensionAttribute14)
        {
            [void]$Replace.Add("extensionAttribute14",$user.extensionAttribute14.Replace("---",","))
        }

        if ($user.extensionAttribute15)
        {
            [void]$Replace.Add("extensionAttribute15",$user.extensionAttribute15.Replace("---",","))
        }
    }

    # region CheckContactManager. We can leverage this function to
    # import only contact managers' as MEU are previous created.
    if ($CheckContactManager -and $user.Manager)
    {
        try
        {
            # we must resolve the manager DN. Better using try/catch to avoid
            # scenarios that manager does not exist in the target environment
            $ManagerResolved = Get-ADUser -Identity $user.Manager -Properties distinguishedName -ErrorAction Stop
            if ($?)
            {
                [void]$Replace.Add("manager",$ManagerResolved.DistinguishedName)
            }
        }
        catch
        {
            Write-PSFMessage -Level Output -Message "Error: The manager $($user.Manager) could not be found and was not added to the contact $($user.DisplayName)."
        }
    }

    return $Replace
}

Function Import-Manager {
    <#
    .SYNOPSIS
        Import Manager Attribute
     
    .DESCRIPTION
        Function called by Import-T2TAttributes if we found the Manager property on the UserListToImport.csv and/or ContactsListToImport.csv
     
    .PARAMETER ObjType
        Type of object that the function will work, valid values are MEU or Contact
     
    .EXAMPLE
        PS C:\> Import-Manager -ObjType MEU, Contacts
        Import the manager attribute valies from from the UserListToImport.csv and ContactsListToImport.csv
    #>


    [CmdletBinding()]
    param (
    [ValidateSet('MEU','Contact')]
    [String]$ObjType
    )

    Switch ($ObjType)
    {
        MEU
        {
            Write-PSFMessage -Level Output -Message  "Starting Manager attribute import"
    
            [int]$counter = 0
            $ManagerCount = ($ImportUserList | Measure-Object).count
            ForEach ($i in $ImportUserList)
            {
                $counter++
                Write-Progress -Activity "Importing Manager Attribute" -Status "Working on $($i.DisplayName)" -PercentComplete ($counter * 100 / $ManagerCount)

                if ($i.Manager)
                {
                    Try
                    {
                        Set-User -Identity $i.SamAccountName -Manager $i.Manager -ErrorAction Stop
                    }
                    catch
                    {
                        Write-PSFMessage -Level Output -Message "Failed to add the user's $($i.DisplayName) manager attribute"
                    }
                }
            }
        }

        # Contacts case is used only by Import-ToCloud because
        # Import-ToOnPrem relies on Import-ADPersonalAttribute
        Contact
        {
            Write-PSFMessage -Level Output -Message  "MailContacts - Starting manager attribute import"

            [int]$counter = 0
            $ManagerCount = ($ImportContactList | Measure-Object).count
            ForEach ($i in $ImportContactList)
            {
                $counter++
                Write-Progress -Activity "MailContacts - Importing Manager Attribute" -Status "Working on $($i.DisplayName)" -PercentComplete ($counter * 100 / $ManagerCount)

                Try
                {
                    Set-Contact -Identity $i.Alias -Manager $i.Manager -ErrorAction Stop
                }
                catch
                {
                    Write-PSFMessage -Level Output -Message "MailContacts - Failed to add the user's $($i.DisplayName) manager attribute"
                }
            }
        }
    }
}

Function Move-Contact {
    <#
    .SYNOPSIS
    Function to handle export and import of mail enable contacts
 
    .PARAMETER Sync
    Decide to perform the export or import of mail contacts
 
    .DESCRIPTION
    Similar to the Export-T2TAttributes, this function dumps attributes
    from the source AD but only External Contacts. We rely on the same
    CustomAttributed passed through Export-T2TAttributes to filter which
    contacts will be fetched by this function. From the Import-T2TAttributes
    user must pass through param the CSV to import the mail contacts.
 
    .EXAMPLE
    PS C:\> Move-Contacts -Sync Export
    The cmdlet above perform an export of mail contacts filtered by the custom attribute chosen.
    #>


    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "")]
    [CmdletBinding()]
    param (
        [ValidateSet('Export','Import')]
        [String[]]
        $Sync
    )

    Switch ($Sync)
    {

        Export {
            # region variables
            [int]$counter = 0
            $outArray = [System.Collections.ArrayList]::new()
            $outFile = "$home\desktop\ContactListToImport.csv"
            $ContactCustomAttribute = $CustomAttribute
            $ContactCustomAttributeValue = $CustomAttributeValue

            # region get Contacts filtering by custom attribute
            $Contacts = (Get-MailContact -ResultSize Unlimited).Where({$_.$CustomAttribute -like $CustomAttributeValue})
            Write-PSFMessage -Level Output -Message "$($Contacts.Count) Mail Contacts with $($ContactCustomAttribute) as $($ContactCustomAttributeValue) were returned"
            $ContactCount = ($Contacts | Measure-Object).count

            # region iterate objects
            ForEach ($i in $Contacts)
            {
                $counter++
                Write-Progress -Activity "Exporting Mail Contacts to CSV" -Status "Working on $($i.DisplayName)" -PercentComplete ($counter * 100 / $ContactCount)
        
                $user = get-Recipient $i.alias
                $object = [ordered]@{
                    PrimarySMTPAddress=$i.PrimarySMTPAddress
                    alias=$i.alias
                    FirstName=$User.FirstName
                    LastName=$User.LastName
                    DisplayName=$User.DisplayName
                    Name=$i.Name
                    legacyExchangeDN=$i.legacyExchangeDN
                }

                # ExternalEmailAddress should contains "SMTP:" depending on the
                # deserialization, we just try a replace to avoid that scenario
                [string]$j = $i.ExternalEmailAddress
                [void]$object.Add("ExternalEmailAddress",$j.Replace("SMTP:",""))

                # Get only non-primary smtp and X500 from proxyAddresses. If we get the primary
                # the CSV mapping domain logic will break as SMTP should be external for contacts
                $ProxyArray = [System.Collections.ArrayList]::new()
                $Proxy = $i.EmailAddresses
                foreach ($email in $Proxy)
                {
                    if ($email -clike 'smtp:*' -or $email -like 'x500:*' -and $email -notlike '*.onmicrosoft.com')
                    {
                        [void]$ProxyArray.Add($email)
                    }
                }

                # Join proxyAddresses using ";"
                $ProxyToString = $ProxyArray -Join ";"

                # Map through the CSV which source domain will become which target domain
                Foreach ($Domain in $MappingCSV)
                {
                    # Add @ before the domain to avoid issues with subdomains
                    $SourceDomain = $Domain.Source.Insert(0,"@")
                    $TargetDomain = $Domain.Target.Insert(0,"@")

                    if ($ProxyToString -match $Domain.source)
                    {
                        $ProxyToString = $ProxyToString.Replace($SourceDomain,$TargetDomain)
                    }
                }

                [void]$object.Add("EmailAddresses",$ProxyToString)

                # Connect to AD exported module only if this machine has not AD Module installed and
                # filtering based on what "Include" was passed to avoid dump too many unnecessary stuff
                if ($LocalMachineIsNotExchange.IsPresent -and $LocalAD -eq '')
                {
                    $ADUser = Get-RemoteADObject -Identity $i.DistinguishedName -Server $PreferredDC -Properties $ADProperties
                    # dump those "-Include" attributes only if we
                    # found more stuff than those junk properties
                    if ($ADProperties.Count -gt 3)
                    {
                        [void](Export-ADPersonalAttribute)
                    }
                }
                else
                {
                    $ADUser = Get-ADObject -Identity $i.DistinguishedName -Server $PreferredDC -Properties $ADProperties
                    # dump those "-Include" attributes only if we
                    # found more stuff than those junk properties
                    if ($ADProperties.Count -gt 3)
                    {
                        [void](Export-ADPersonalAttribute)
                    }
                }
                
                # Create PSObject from hashtable
                # and add PSObject to ArrayList
                $outPSObject = New-Object -TypeName PSObject -Property $object
                [void]$outArray.Add($outPSObject)
            }

            if ($outArray.Count -gt 0)
            {
                Write-PSFMessage -Level Output -Message "Saving CSV on $($outfile)"
                $outArray | Export-CSV $outfile -notypeinformation
            }
        }

        Import {
            # region local variables
            [int]$counter = 0
            $ContactsCount = ($ImportContactList | Measure-Object).count
            $CheckContactManager = ($ImportContactList[0].psobject.Properties).Where({$_.Name -eq "Manager"})

            # region iterate contacts. Variable kept as $user cause
            # the Import-ADPersonalAttribute relies on that value
            ForEach ($user in $ImportContactList)
            {
                $counter++
                Write-Progress -Activity "Creating MEU objects and importing attributes from CSV" -Status "Working on $($i.DisplayName)" -PercentComplete ($counter * 100 / $ContactsCount)
                $Replace = @{}
                $tmpContact = $null

                # If OU was passed through param, honor it.
                # Otherwise create the MEU without OU specification
                if ($OUContacts)
                {
                    $tmpContact = New-MailContact -ExternalEmailAddress $user.ExternalEmailAddress -PrimarySmtpAddress `
                    $user.PrimarySMTPAddress -FirstName $user.FirstName -LastName $user.LastName -Alias $user.alias -Name `
                    $user.Name -DisplayName $user.DisplayName -OrganizationalUnit $OUContacts
                }
                else
                {
                    $tmpContact = New-MailContact -ExternalEmailAddress $user.ExternalEmailAddress -PrimarySmtpAddress `
                    $user.PrimarySMTPAddress -FirstName $user.FirstName -LastName $user.LastName -Alias $user.alias -Name `
                    $user.Name -DisplayName $user.DisplayName
                }
                
                # we must resolve the GUID in order to
                # use Set-ADObject cmdlet down the road
                $ResolvedGUID = Get-MailContact -Identity $user.Alias | Select-Object GUID

                # Convert legacyDN to X500 and add all EmailAddresses to array
                $x500 = "x500:" + $user.legacyExchangeDN
                $proxy = $user.EmailAddresses.Replace(";",",")
                $ProxyArray = @()
                $ProxyArray = $proxy.Split(",") + $x500

                # region import old LegacyDN as X500 and CustomAttribute
                Set-MailContact -Identity $user.Alias -EmailAddresses @{Add=$ProxyArray}

                # region import "-Include" values
                if ($CheckContactManager -or $CheckGeneral -or $CheckAddress -or $CheckPhones -or $CheckOrganization -or $CheckCustomAttributes)
                {
                    [void](Import-ADPersonalAttribute)
                }

                # region set $replace hashtable to ADUser
                if ($LocalMachineIsNotExchange.IsPresent -and $null -eq $LocalAD -and $Replace.Count -gt 0)
                {
                    Set-RemoteADObject -Identity $ResolvedGUID.Guid -Server $PreferredDC -Replace $Replace
                }
                elseif ($Replace.Count -gt 0)
                {
                    Set-ADObject -Identity $ResolvedGUID.Guid -Server $PreferredDC -Replace $Replace
                }
            }
        }
    }
}

Function Export-T2TAttributes {
    <#
    .SYNOPSIS
        This script will dump all necessary attributes that cross-tenant MRS migration requires.
        No changes will be performed by this code.
     
    .DESCRIPTION
        This script will dump all necessary attributes that cross-tenant MRS migration requires.
        No changes will be performed by this code.
 
    .PARAMETER AdminUPN
        Optional parameter used to connect to to Exchange Online. Only the UPN is
        stored to avoid token expiration during the session, no password is stored.
 
    .PARAMETER CustomAttributeNumber
        Mandatory parameter used to inform the code which custom
        attributes will be used to scope the search. Valid range: 1-15.
 
    .PARAMETER CustomAttributeValue
        Mandatory parameter used to inform the code which value will be used to scope the search.
 
    .PARAMETER DomainMappingCSV
        Enter the CSV path which you mapped the source and target domains.
        You file header should have 2 columns and be: 'Source','Target'
 
    .PARAMETER IncludeContacts
        Switch to get mail contacts. Mail contact dump
        also relies on the Custom Attibute filter.
 
    .PARAMETER IncludeSIP
        Switch to get SIP values from proxyAddresses. Without
        this switch the function returns only SMTP and X500.
 
    .PARAMETER IncludeManager
        Switch to get values from Manager attribute. Be sure to
        scope users and managers if this switch will be used.
 
    .PARAMETER IncludeGeneral
        Switch which indicates that the function must export attributes from the General
        AD tab such as: description, physicalDeliveryOfficeName, wWWHomePage and url.
 
    .PARAMETER IncludeAddress
        Switch which indicates that the function must export
        all attributes in the "Address" tab such as: street,
        P.O. Box, City, State/Province, ZIP Code and Country
 
    .PARAMETER IncludePhones
        Switch which indicates that the function must
        export all attributes in the phone tab such as
        Home, Pager, Mobile, Fax, IP Phone and Notes.
 
    .PARAMETER IncludeOrganization
        Switch which indicates that the function must export
        the Job Title, Department and Company attributes.
 
    .PARAMETER IncludeCustomAttributes
        Switch which indicates that the function
        must export the all custom attributes.
 
    .PARAMETER BypassAutoExpandingArchiveCheck
        The script will check if you have Auto-Expanding archive enable on organization
        level, if yes each mailbox will be check if there is an Auto-Expanding archive mailbox
        This check might increase the script duration. You can opt-out using this switch
 
    .PARAMETER LocalMachineIsNotExchange
        Optional parameter used to inform that you are running the script from a
        non-Exchange Server machine. This parameter will require the -ExchangeHostname.
 
    .PARAMETER ExchangeHostname
        Mandatory parameter if the switch -LocalMachineIsNotExchange was used.
        Used to inform the Exchange Server FQDN that the script will connect.
 
    .PARAMETER PreferredDC
        Preferred domain controller to connect with. Consider using this parameter
        to avoid replication issues in environments with too many domain controllers.
 
    .PARAMETER FolderPath
        Optional parameter used to inform which path will be used to save the
        CSV. If no path is chosen, the script will save on the Desktop path.
 
    .EXAMPLE
        PS C:\> Export-T2TAttributes -CustomAttributeNumber 10 -CustomAttributeValue "T2T" -DomainMappingCSV sourcetargetmap.csv -FolderPath C:\LoggingPath
        The function will export all users matching the value "T2T" on the CustomAttribute 10, and based on all the users found, we will
        mapping source and target domains according to the CSV provided. All changes and CSV files will be generated in "C:\LoggingPath" folder.
 
    .EXAMPLE
        PS C:\> Export-T2TAttributes -CustomAttributeNumber 10 -CustomAttributeValue "T2T" -DomainMappingCSV sourcetargetmap.csv -LocalMachineIsNotExchange -ExchangeHostname ExServer1
        The function will connect to the onprem Exchange Server "ExServer1" and export all users matching the value
        "T2T" on the CustomAttribute 10, and based on all the users found, we will mapping source and target domains
        according to the CSV provided. All changes and CSV files will be generated in "C:\LoggingPath" folder.
 
    .NOTES
        Title: Export-T2TAttributes.ps1
        Version: 2.1.6
        Date: 2021.02.04
        Authors: Denis Vilaca Signorelli (denis.signorelli@microsoft.com)
        Contributors: Agustin Gallegos (agustin.gallegos@microsoft.com)
 
    REQUIREMENT
        1.ExchangeOnlineManagement module (EXO v2)
        2.PSFramework module
        3.To make things easier, run this script from Exchange On-Premises machine powershell,
        the script will automatically import the Exchange On-Prem module. If you don't want
        to run the script from an Exchange machine, use the switch -LocalMachineIsNotExchange
        and enter the Exchange Server hostname.
 
    #########################################################################
    # This sample script is provided AS IS without warranty of any kind and #
    # not supported under any Microsoft standard support program or service #
    #########################################################################
    #>


    # Bypass PSReviewUnusedParameter as $IncludeManager is reconized as unused
    # actually it's used but in the Export-ADPersonalAttribute. TODO: Fix this
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter','')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly','')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns','')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars','')]
    [CmdletBinding(DefaultParameterSetName="Default")]
    Param(
        [Parameter(Mandatory=$False,
        HelpMessage="Enter the user admin to connect to Exchange Online")]
        [string]$AdminUPN,
        
        [Parameter(Mandatory=$true,
        HelpMessage="Enter the custom attribute number. Valid range: 1-15")]
        [ValidateRange(1,15)]
        [Int]$CustomAttributeNumber,
        
        [Parameter(Mandatory=$true,
        HelpMessage="Enter the custom attribute value that will be used")]
        [string]$CustomAttributeValue,
        
        [Parameter(Mandatory=$true,
        HelpMessage="Enter the CSV name where you mapped the source and target domains")]
        [string]$DomainMappingCSV,

        [Parameter(Mandatory=$False,
        HelpMessage="SwitchParameter to get mail contacts. Mail contact dump
        also relies on the Custom Attibute filter"
)]
        [switch]$IncludeContacts,

        [Parameter(Mandatory=$False,
        HelpMessage="SwitchParameter to get SIP values from proxyAddresses.
        Only SMTP and X500 are dumped by default"
)]
        [switch]$IncludeSIP,

        [Parameter(Mandatory=$false,
        HelpMessage="SwitchParameter to dump the manager AD attribute value")]
        [switch]$IncludeManager,

        [Parameter(Mandatory=$false,
        HelpMessage="Switch which indicates that the function must export attributes from the
        General AD tab such as: description, physicalDeliveryOfficeName, wWWHomePage and url."
)]
        [switch]$IncludeGeneral,

        [Parameter(Mandatory=$false,
        HelpMessage="SwitchParameter which indicates that the function
        must export all attributes in the Address tab such as: street,
        P.O. Box, City, State/Province, ZIP Code and Country
            "
)]
        [switch]$IncludeAddress,

        [Parameter(Mandatory=$false,
        HelpMessage="SwitchParameter which indicates that the function
        must export all attributes in the phone tab such as Home, Pager,
        Mobile, Fax, IP Phone and Notes."
)]
        [switch]$IncludePhones,

        [Parameter(Mandatory=$false,
        HelpMessage="SwitchParameter which indicates that the function
        must export the Job Title, Department and Company attributes"
)]
        [switch]$IncludeOrganization,

        [Parameter(Mandatory=$false,
        HelpMessage="SwitchParameter which indicates that the function
        must export the Custom Attributes 1-15 that contain any value"
)]
        [switch]$IncludeCustomAttributes,

        [Parameter(Mandatory=$false,
        HelpMessage="SwitchParameter used to bypass the Auto-Expanding
        Archive check as it can increase the function duration"
)]
        [switch]$BypassAutoExpandingArchiveCheck,

        [Parameter(ParameterSetName="RemoteExchange",Mandatory=$false,
        HelpMessage="SwitchParameter to indicate that the
        machine running the function is not an Exchange Server"
)]
        [switch]$LocalMachineIsNotExchange,
        
        [Parameter(ParameterSetName="RemoteExchange",Mandatory=$true,
        HelpMessage="Enter the remote exchange hostname")]
        [string]$ExchangeHostname,

        [Parameter(Mandatory=$false,
        HelpMessage="Enter the preferred domain controller FQDN to connect with")]
        [string]$PreferredDC,

        [Parameter(Mandatory=$false,
        HelpMessage="Enter the file path used to save the
        function output. Default value is the Desktop path"
)]
        [string]$FolderPath
    )

    Set-PSFConfig -FullName PSFramework.Logging.FileSystem.ModernLog -Value $True
    Write-PSFMessage  -Level Output -Message "Starting export script. All logs are being saved in: $((Get-PSFConfig PSFramework.Logging.FileSystem.LogPath).Value)"

    # region local variables
    [int]$counter = 0
    $outArray = [System.Collections.ArrayList]::new()
    # we always get these 3 junk properties. The others depends on which include has been passed
    $ADProperties = [System.Collections.Generic.List[string]]@('msExchSafeSendersHash','msExchSafeRecipientsHash','msExchBlockedSendersHash')
    if ($IncludeAddress.IsPresent)
    {
        $IncludeAddressArr = [System.Collections.Generic.List[string]]@('streetAddress','postOfficeBox','l','postalCode','c','co','countryCode','st')
        $ADProperties.AddRange($IncludeAddressArr)
    }

    if ($IncludePhones.IsPresent)
    {
        $IncludePhonesArr = [System.Collections.Generic.List[string]]@('telephoneNumber','otherTelephone','homePhone','otherHomePhone','pager',
            'otherPager','mobile','otherMobile','facsimileTelephoneNumber','otherFacsimileTelephoneNumber','ipPhone','otherIpPhone','info')
        $ADProperties.AddRange($IncludePhonesArr)
    }

    if ($IncludeGeneral.IsPresent)
    {
        $IncludeGeneralArr = [System.Collections.Generic.List[string]]@('physicalDeliveryOfficeName','wWWHomePage','url','Description')
        $ADProperties.AddRange($IncludeGeneralArr)
    }

    if ($IncludeOrganization.IsPresent)
    {
        $IncludeOrganizationArr = [System.Collections.Generic.List[string]]@('title','department','company')
        $ADProperties.AddRange($IncludeOrganizationArr)
    }

    if ($IncludeCustomAttributes.IsPresent)
    {
        $IncludeCustomAttributesArr = [System.Collections.Generic.List[string]]@('extensionAttribute1','extensionAttribute2','extensionAttribute3',
            'extensionAttribute4','extensionAttribute5','extensionAttribute6','extensionAttribute7','extensionAttribute8','extensionAttribute9',
            'extensionAttribute10','extensionAttribute11','extensionAttribute12','extensionAttribute13','extensionAttribute14','extensionAttribute15')
        $ADProperties.AddRange($IncludeCustomAttributesArr)
    }

    if ($FolderPath)
    {
        $outFile = "$FolderPath\UserListToImport.csv"
        $AUXFile = "$FolderPath\AUXUsers.txt"
    }
    else
    {
        $outFile = "$home\desktop\UserListToImport.csv"
        $AUXFile = "$home\desktop\AUXUsers.txt"
    }

    # region global variables
    $Global:CustomAttribute = "CustomAttribute$CustomAttributeNumber"
    $Global:CustomAttributeValue = $Global:CustomAttributeValue
    $Global:DomainMappingCSV

    # Check if $DomainMappingCSV is valid
    $CSVMappingExist =  Get-CSVStatus -MappingFile
    if ( $CSVMappingExist -eq 0 ) { Break }
    $Global:MappingCSV = Import-CSV -Path $DomainMappingCSV

    # region connections
    if ( $LocalMachineIsNotExchange.IsPresent )
    {
        $ServicesToConnect = Assert-ServiceConnection -Services EXO, ExchangeRemote, AD
        # Connect to services if ServicesToConnect is not empty
        if ( $ServicesToConnect.Count )
        {
            Connect-OnlineServices -AdminUPN $AdminUPN -Services $ServicesToConnect -ExchangeHostname $ExchangeHostname
        }
    }
    else
    {
        $ServicesToConnect = Assert-ServiceConnection -Services EXO, ExchangeLocal
        # Connect to services if ServicesToConnect is not empty
        if ( $ServicesToConnect.Count )
        {
            Connect-OnlineServices -AdminUPN $AdminUPN -Services $ServicesToConnect
        }
    }

    # view entire forest and set the preferred DC. If no preferred DC was set, use
    # the same that dclocator is already connected. PS: I coudn't pass this to a
    # different function cause there is a bug preventing the option to hidden the
    # Disconnect-ExchangeOnline banner when it's performed by a different function
    if ( $PreferredDC )
    {
        try

        {
            Set-AdServerSettings -ViewEntireForest $true -PreferredServer $PreferredDC -ErrorAction Stop
        }
        catch
        {
            # if no valid DC is used break and clean up sessions
            Write-PSFMessage -Level Output -Message "Error: DC was not found. Please run the function again providing a valid Domain Controller FQDN. For example: 'DC01.contoso.com'"
            Disconnect-ExchangeOnline -Confirm:$false -ErrorAction SilentlyContinue
            Get-PSSession | Remove-PSSession
            Remove-Variable * -ErrorAction SilentlyContinue
            Break
        }
    }
    else
    {
        $PreferredDC = $env:LogOnServer.Replace("\\","")
    }
    
    # region dump contacts if -IncludeContacts is present
    if ( $IncludeContacts.IsPresent )
    {
        Move-Contact -Sync Export
    }

    # region dump all properties from MEU object to variable
    $RemoteMailboxes = (Get-RemoteMailbox -ResultSize Unlimited).Where({$_.$CustomAttribute -like $CustomAttributeValue})
    $UsersCount = ($RemoteMailboxes | Measure-Object).count
    Write-PSFMessage -Level Output -Message "$($UsersCount) mailboxes with $($CustomAttribute) as $($CustomAttributeValue) were returned"

    # dump AUX org status if bypass switch is not present
    if ($BypassAutoExpandingArchiveCheck.IsPresent)
    {
        Write-PSFMessage -Level Output -Message "Bypassing Auto-Expand archive check"
    }
    else
    {
        $OrgAUXStatus = Get-EXOrganizationConfig | Select-Object AutoExpandingArchiveEnabled

        if ($OrgAUXStatus.AutoExpandingArchiveEnabled -eq '$True')
        {
            Write-PSFMessage -Level Output -Message "Auto-Expand archive is enabled at organization level"
        }
        else
        {
            Write-PSFMessage -Level Output -Message "Auto-Expand archive is not enabled at organization level, but we will check each mailbox"
        }
    }
    
    Write-PSFMessage -Level Output -Message "Getting EXO mailboxes necessary attributes. This may take some time..."

    # region iterate objects
    ForEach ($i in $RemoteMailboxes)
    {
        $counter++
        Write-Progress -Activity "Exporting mailbox attributes to CSV" -Status "Working on $($i.DisplayName)" -PercentComplete ($counter * 100 / $UsersCount)
        
        $user = Get-Recipient $i.alias
        $object = [ordered]@{
            alias=$i.alias
            FirstName=$User.FirstName
            LastName=$User.LastName
            DisplayName=$User.DisplayName
            Name=$i.Name
            SamAccountName=$i.SamAccountName
            legacyExchangeDN=$i.legacyExchangeDN
        }

        if ($BypassAutoExpandingArchiveCheck.IsPresent)
        {
            try
            {
                # Save necessary properties from EXO object to variable avoiding AUX check
                $EXOMailbox = Get-EXOMailbox -Identity $i.Alias -PropertySets Retention,Hold,Archive,StatisticsSeed -ErrorAction Stop
            }
            catch
            {
                # If no EXO mailbox exist, continue the iteration
                Write-PSFMessage -Level Output -Message "Error: The user $($i.Alias) doesn't have an EXO mailbox."
                Continue
            }
        }
        else
        {
            if ($OrgAUXStatus.AutoExpandingArchiveEnabled -eq '$True')
            {
                try
                {
                    # If AUX is enable at org side, doesn't metter if the mailbox has it explicitly enabled
                    $EXOMailbox = Get-EXOMailbox -Identity $i.Alias -Properties ExchangeGuid,MailboxLocations,LitigationHoldEnabled,SingleItemRecoveryEnabled,ArchiveDatabase,ArchiveGuid -ErrorAction Stop
                }
                catch
                {
                    # If no EXO mailbox exist, continue the iteration
                    Write-PSFMessage -Level Output -Message "Error: The user $($i.Alias) doesn't have an EXO mailbox."
                    Continue
                }
            }
            else
            {
                try
                {
                    # If AUX isn't enable at org side, we check if the mailbox has it explicitly enabled
                    $EXOMailbox = Get-EXOMailbox -Identity $i.Alias -Properties ExchangeGuid,MailboxLocations,LitigationHoldEnabled,SingleItemRecoveryEnabled,ArchiveDatabase,ArchiveGuid,AutoExpandingArchiveEnabled -ErrorAction Stop
                }
                catch
                {
                    # If no EXO mailbox exist, continue the iteration
                    Write-PSFMessage -Level Output -Message "Error: The user $($i.Alias) doesn't have an EXO mailbox."
                    Continue
                }
            }
        }

        if ($BypassAutoExpandingArchiveCheck.IsPresent)
        {
            # Save necessary properties from EXO object to variable avoiding AUX check
            Write-PSFMessage -Level Output -Message "Bypassing $($i.Alias) MailboxLocation check for Auto-Expanding archive"
        }
        else
        {
            # AUX enabled doesn't mean that the mailbox indeed have AUX
            # archive. We need to check the MailboxLocation to be sure
            if (($OrgAUXStatus.AutoExpandingArchiveEnabled -eq '$True' -and $EXOMailbox.MailboxLocations -like '*;AuxArchive;*') -or
            ($OrgAUXStatus.AutoExpandingArchiveEnabled -eq '$False' -and $EXOMailbox.AutoExpandingArchiveEnabled -eq '$True' -and
            $EXOMailbox.MailboxLocations -like '*;AuxArchive;*'))
            {
                $AuxMessage = "[$(Get-Date -format "HH:mm:ss")] User $($i.Alias) has an auxiliar Auto-Expanding archive mailbox. Be aware that any auxiliar archive mailbox will not be migrated"
                $AuxMessage | Out-File -FilePath $AUXFile -Append
                Write-PSFHostColor -String $AuxMessage -DefaultColor Cyan
            }
        }

        # Get mailbox guid from EXO because if the mailbox was created from scratch
        # on EXO the ExchangeGuid would not be write-backed to On-Premises
        [void]$object.Add("ExchangeGuid",$EXOMailbox.ExchangeGuid)

        # Get mailbox ELC value
        $ELCValue = 0
        if ($EXOMailbox.LitigationHoldEnabled)
        {
            $ELCValue = $ELCValue + 8
        }
        if ($EXOMailbox.SingleItemRecoveryEnabled)
        {
            $ELCValue = $ELCValue + 16
        }
        if ($ELCValue -ge 0)
        {
            [void]$object.Add("ELCValue",$ELCValue)
        }
        
        # Get the ArchiveGuid from EXO if it exist. The reason that we don't rely on
        # "-ArchiveStatus" parameter is that may not be trustable in certain scenarios
        # https://docs.microsoft.com/en-us/office365/troubleshoot/archive-mailboxes/archivestatus-set-none
        if ($EXOMailbox.ArchiveDatabase -ne '' -and
            $EXOMailbox.ArchiveGuid -ne "00000000-0000-0000-0000-000000000000")
        {
            [void]$object.Add("ArchiveGuid",$EXOMailbox.ArchiveGuid)
        }
        else
        {
            [void]$object.Add("ArchiveGuid",$Null)
        }

        # Get only SMTP, X500 and SIP if the switch is present
        # from proxyAddresses and define the targetAddress
        $ProxyArray = [System.Collections.ArrayList]::new()
        $Proxy = $i.EmailAddresses
        ForEach ($email in $Proxy)
        {
            if ($email -like 'SMTP:*' -or $email -like 'X500:*')
            {
                [void]$ProxyArray.Add($email)
            }
            if ($IncludeSIP.IsPresent -and $email -like 'SIP:*')
            {
                [void]$ProxyArray.Add($email)
            }
            if ($email -like 'SMTP:*' -and $email -like '*.onmicrosoft.com')
            {
                [string]$TargetString = $email -replace "smtp:",""
            }
        }

        # Join it using ";" and replace the old domain (source) to the new one (target)
        $ProxyToString = $ProxyArray -Join ";" -Replace "SMTP","smtp"

        # Declare PrymarySMTPAddress
        $PrimarySMTP = $i.PrimarySmtpAddress

        # Map from the CSV which source domain will become which target domain
        ForEach ($Domain in $MappingCSV)
        {
            # Add @ before the domain to avoid issues with subdomains
            $SourceDomain = $Domain.Source.Insert(0,"@")
            $TargetDomain = $Domain.Target.Insert(0,"@")

            if ($ProxyToString -match $Domain.source)
            {
                $ProxyToString = $ProxyToString -replace $SourceDomain,$TargetDomain
            }

            if ($PrimarySMTP -match $Domain.source)
            {
                $PrimarySMTP = $PrimarySMTP -replace $SourceDomain,$TargetDomain
            }

            if ($TargetString -match $Domain.source)
            {
                $TargetPostMigration = $TargetString -replace $SourceDomain,$TargetDomain
            }
        }

        [void]$object.Add("EmailAddresses",$ProxyToString)
        [void]$object.Add("PrimarySMTPAddress",$PrimarySMTP)

        # Get ProxyAddress only for *.mail.onmicrosoft to define in the target AD the
        # targetAddress value. This value should not be converted through the domain mapping CSV.
        [void]$object.Add("ExternalEmailAddress",$TargetString)

        # Get only the proxyAddress which matches *.mail.onmicrosoft.com to define in the target AD the
        # targetAddress value post-migration. This value should be converted through the domain mapping CSV.
        [void]$object.Add("ExternalEmailAddressPostMove",$TargetPostMigration)

        # Connect to AD exported module only if this machine has not AD Module installed and
        # filtering based on what "Include" was passed to avoid dump too many unnecessary stuff
        if ($LocalMachineIsNotExchange.IsPresent -and $LocalAD -eq '')
        {
            $ADUser = Get-RemoteADUser -Identity $i.SamAccountName -Server $PreferredDC -Properties $ADProperties
            # dump those "-Include" attributes only if we
            # found more stuff than those junk properties
            if ($ADProperties.Count -gt 3)
            {
                [void](Export-ADPersonalAttribute)
            }
        }
        else
        {
            $ADUser = Get-ADUser -Identity $i.SamAccountName -Server $PreferredDC -Properties $ADProperties
            # dump those "-Include" attributes only if we
            # found more stuff than those junk properties
            if ($ADProperties.Count -gt 3)
            {
                [void](Export-ADPersonalAttribute)
            }
        }

        # Get Junk hashes, these are SHA-265 write-backed from EXO. Check if the user
        # has any hash, if yes we convert the HEX to String removing the dash "-"
        if ($ADUser.msExchSafeSendersHash.Length -gt 0)
        {
            $SafeSender = [System.BitConverter]::ToString($ADUser.msExchSafeSendersHash)
            [void]$object.Add("SafeSender",$SafeSender.Replace("-",""))
        }
        else
        {
            [void]$object.Add("SafeSender",$Null)
        }

        if ($ADUser.msExchSafeRecipientsHash.Length -gt 0)
        {
            $SafeRecipient = [System.BitConverter]::ToString($ADUser.msExchSafeRecipientsHash)
            [void]$object.Add("SafeRecipient",$SafeRecipient.Replace("-",""))
        }
        else
        {
            [void]$object.Add("SafeRecipient",$Null)
        }

        if ($ADUser.msExchBlockedSendersHash.Length -gt 0)
        {
            $BlockedSender = [System.BitConverter]::ToString($ADUser.msExchBlockedSendersHash)
            [void]$object.Add("BlockedSender",$BlockedSender.Replace("-",""))
        }
        else
        {
            [void]$object.Add("BlockedSender",$Null)
        }

        # Create PSObject from hashtable
        # and add PSObject to ArrayList
        $outPSObject = New-Object -TypeName PSObject -Property $object
        [void]$outArray.Add($outPSObject)

    }

    # region export CSV
    if ($outArray.Count -gt 0)
    {
        try
        {
            $outArray | Export-CSV $outFile -NoTypeInformation -ErrorAction Stop
            if ($?)
            {
                Write-PSFMessage -Level Output -Message "Saving CSV on $($outFile)"
            }
        }
        catch
        {
            Write-PSFMessage -Level Output -Message "The path $($outfile) could not be found. The file will be saved on $($home)\desktop\UserListToImport.csv"
            $outArray | Export-CSV "$home\desktop\UserListToImport.csv" -NoTypeInformation -ErrorAction Stop
        }
    }
    if ($AuxMessage)
    {
        Write-PSFMessage -Level Output -Message "Saving TXT on $($AUXFile)"
    }
    
    # region clean up variables and sessions
    Disconnect-ExchangeOnline -Confirm:$false -InformationAction Ignore -ErrorAction SilentlyContinue
    Get-PSSession | Remove-PSSession
    Remove-Variable * -ErrorAction SilentlyContinue

}

function Export-T2TLogs
{
    <#
    .SYNOPSIS
    This function will export current PSFramework logs.
     
    .DESCRIPTION
    This function will export current PSFramework logs based on the amount of days old define in the 'DaysOld' parameter.
    It will allow to export to CSV file and/or display in powershell GridView.
    Output will have the following header: "ComputerName","Username","Timestamp","Level","Message","Type","FunctionName","ModuleName","File","Line","Tags","TargetObject","Runspace","Callstack"
     
    .PARAMETER FilePath
    Defines the path file to export the CSV file.
    Default value is the user's Desktop with a file name like "yyyy-MM-dd HH_mm_ss" - T2T logs.csv"
     
    .PARAMETER OutputType
    Defines the output types available. Can be a single output or combined.
    Current available options are CSV, GridView.
 
    .PARAMETER DaysOld
    Defines how old we will go to fetch the logs. Valid range is between 1 through 7 days old. Default Value is 1
     
    .EXAMPLE
    PS C:\> Export-T2Tlogs -OutputType CSV
    In this example, the script will fetch all logs within the last 24 hrs (by default), and export to CSV to default location at the Desktop.
     
    .EXAMPLE
    PS C:\> Export-T2Tlogs -OutputType GridView -DaysOld 3
    In this example, the script will fetch all logs within the last 3 days, and displays them in powershell's GridView.
 
    .EXAMPLE
    PS C:\> Export-T2Tlogs -OutputType CSV,GridView -DaysOld 5
    In this example, the script will fetch all logs within the last 5 days, export to CSV to default location at the Desktop and also displays in powershell's GridView.
 
    .EXAMPLE
    PS C:\> Export-T2Tlogs -OutputType CSV,GridView -DaysOld 7 -FilePath "C:\Temp\newLog.csv"
    In this example, the script will fetch all logs within the last 7 days, export to CSV to path "C:\Temp\newLog.csv" and also displays them in powershell's GridView.
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "")]
    [CmdletBinding()]
    Param (
        [String]$FilePath = "$home\Desktop\$(get-date -Format "yyyy-MM-dd HH_mm_ss") - T2T logs.csv",
        
        [ValidateSet('CSV','GridView')]
        [string[]]$OutputType = "GridView",

        [ValidateRange(1,7)]
        [int]$DaysOld = 1
    )
    Import-module PSFramework
    $loggingpath = (Get-PSFConfig PSFramework.Logging.FileSystem.LogPath).Value
    $logFiles = Get-ChildItem -Path $loggingpath | Where-Object LastwriteTime -gt (Get-Date).adddays(-1 * $DaysOld)
    $csv = Import-Csv -Path $logFiles.FullName
    Switch ( $OutputType)
    {
        CSV { $csv | Select-Object @{N="Date";E={($_.timestamp -split " ")[0]}},@{N="Time";E={ Get-Date ($_.timestamp.Substring($_.timestamp.IndexOf(" ")).trim()) -Format HH:mm:ss }},`
        "ComputerName","Username","Level","Message","Type","FunctionName","ModuleName","File","Line","Tags","TargetObject","Runspace","Callstack" | Sort-Object Date -Descending | export-csv -Path $FilePath -NoTypeInformation }
        GridView { $csv | Select-Object @{N="Date";E={($_.timestamp -split " ")[0]}},@{N="Time";E={Get-Date ($_.timestamp.Substring($_.timestamp.IndexOf(" ")).trim()) -Format HH:mm:ss }},`
        "ComputerName","Username","Level","Message","Type","FunctionName","ModuleName","File","Line","Tags","TargetObject","Runspace","Callstack" | Sort-Object Date -Descending | Out-GridView }
    }
}


Function Import-T2TAttributes {
    <#
    .SYNOPSIS
        The script will create on the target AD On-Prem the MEU objects getting all
        attribute values from the CSV generated by Export-T2TAttributes command.
     
    .DESCRIPTION
        The script will create on the target AD On-Prem the MEU objects getting all
        attribute values from the CSV generated by Export-T2TAttributes command.
 
    .PARAMETER UPNSuffix
        Mandatory parameter used to inform which is the UPN domain for the MEU object e.g: contoso.com.
 
    .PARAMETER Password
        Optional parameter if you want to choose a password for all new MEU objects
 
    .PARAMETER ResetPassword
        Optional parameter if you want to require users to reset password in the first sign-in
 
    .PARAMETER OU
        Optional parameter if you want to create MEU objects in a specific OU. Valid values are
        name, Canonical name, Distinguished name (DN) or GUID. If not defined, the user object
        will be created on Users container.
 
    .PARAMETER OUContacts
        Optional parameter if you want to create Mail Contacts objects in a specific OU.
        Valid values are name, Canonical name, Distinguished name (DN) or GUID. If not
        defined, the user object will be created on Users container.
 
    .PARAMETER UserListToImport
        Optional parameter used to inform which path will be used import the CSV. If no path
        is chosen, the script will search for UserListToImport.csv file on desktop path.
 
    .PARAMETER ContactListToImport
        Optional parameter used to inform which path will be used import the CSV. If no path
        is chosen, the script will search for ContactListToImport.csv file on desktop path.
 
    .PARAMETER LocalMachineIsNotExchange
        Optional parameter used to inform that you are running the script from a
        non-Exchange Server machine. This parameter will require the -ExchangeHostname.
 
    .PARAMETER ExchangeHostname
        Mandatory parameter if the switch -LocalMachineIsNotExchange was used.
        Used to inform the Exchange Server FQDN that the script will connect.
 
    .PARAMETER PreferredDC
        Preferred domain controller to connect with. Consider using this parameter
        to avoid replication issues in environments with too many domain controllers.
 
    .EXAMPLE
        PS C:\> Import-T2TAttributes -UPNSuffix "fabrikam.com" -ResetPassword
        The function will import all users from the file "C:\temp\UserListToImport.csv", create the new MailUsers
        with the new UPNSuffix of "fabrikam.com", and enable the check mark to "Reset the password on next logon".
 
    .EXAMPLE
        PS C:\> Import-T2TAttributes -UPNSuffix "fabrikam.com" -LocalMachineIsNotExchange -ExchangeHostname "ExServer2"
        The function will connect to the onprem Exchange Server "ExServer2" and import all users
        from the file "C:\temp\UserListToImport.csv", create the new MailUsers with the new UPNSuffix
        of "fabrikam.com", and enable the check mark to "Reset the password on next logon".
 
    .NOTES
        Title: Import-T2TAttributes.ps1
        Version: 2.1.6
        Date: 2021.01.03
        Author: Denis Vilaca Signorelli (denis.signorelli@microsoft.com)
        Contributors: Agustin Gallegos (agustin.gallegos@microsoft.com)
 
    REQUIREMENT
        1.To make things easier, run this script from Exchange On-Premises machine powershell,
        the script will automatically import the Exchange On-Prem module. If you don't want
        to run the script from an Exchange machine, use the switch -LocalMachineIsNotExchange
        and enter the Exchange Server hostname.
        2.The script encourage you to stop the Azure AD Sync cycle before the execution.
        The script can disable the sync for you as long as you provide the AzureADConnect
        hostname. Otherwiser, you can disable by your self manually and then re-run the script.
 
    #########################################################################
    # This sample script is provided AS IS without warranty of any kind and #
    # not supported under any Microsoft standard support program or service #
    #########################################################################
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidGlobalVars", "")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "")]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "")]
    [CmdletBinding(DefaultParameterSetName="Default")]
    Param(
        [Parameter(Mandatory=$true,
        HelpMessage="Enter UPN suffix of your domain E.g. contoso.com")]
        [string]$UPNSuffix,
        
        [Parameter(Mandatory=$false,
        HelpMessage="Enter the password for the new MEU objects. If no password
        is chosen, the script will define '?r4mdon-_p@ss0rd!' as password"
)]
        [string]$Password,
        
        [Parameter(Mandatory=$false,
        HelpMessage=" SwitchParameter Require password change on first user access")]
        [switch]$ResetPassword,
        
        [Parameter(Mandatory=$false,
        HelpMessage="Enter the organization unit that MEU objects will be created.
        The input is accepted as Name, Canonical name, Distinguished name (DN) or GUID"
)]
        [string]$OU,

        [Parameter(Mandatory=$false,
        HelpMessage="Enter the organization unit that mail objects will be created.
        The input is accepted as Name, Canonical name, Distinguished name (DN) or GUID"
)]
        [string]$OUContacts,
        
        [Parameter(Mandatory=$false,
        HelpMessage="Enter a custom import path for the csv. if no value is defined
        the script will search on Desktop path for the UserListToImport.csv"
)]
        [string]$UserListToImport,

        [Parameter(Mandatory=$false,
        HelpMessage="Enter a custom import path for the csv. if no value is defined
        the script will search on Desktop path for the ContactListToImport.csv"
)]
        [string]$ContactListToImport,

        [Parameter(ParameterSetName="RemoteExchange",Mandatory=$false,
        HelpMessage="SwitchParameter to indicate that the
        machine running the function is not an Exchange Server"
)]
        [switch]$LocalMachineIsNotExchange,
        
        [Parameter(ParameterSetName="RemoteExchange",Mandatory=$true,
        HelpMessage="Enter the remote exchange hostname")]
        [string]$ExchangeHostname,

        [Parameter(Mandatory=$false,
        HelpMessage="Enter the preferred domain controller FQDN to connect with")]
        [string]$PreferredDC
    )

    Set-PSFConfig -FullName PSFramework.Logging.FileSystem.ModernLog -Value $True
    Write-PSFMessage  -Level Output -Message "Starting script. All logs are being saved in: $((Get-PSFConfig PSFramework.Logging.FileSystem.LogPath).Value)"

    # region global variables
    $Global:UserListToImport | Out-Null
    $Global:ContactListToImport | Out-Null
    $Global:ImportUserList | Out-Null
    $Global:ImportContactList | Out-Null
    $Global:OUContacts | Out-Null

    # region requirements check
    $UserListToImportCheck = Get-CSVStatus -User
    if ( $UserListToImportCheck -eq 0 ) {Break}
    
    $ContactListToImportCheck = Get-CSVStatus -Contact
    
    $AADCStopped = Get-Requirement -Requirements AADConnect
    if ( $AADCStopped -eq 0 ) {Break}


    # region local variables
    [int]$counter = 0
    [string]$UPNSuffix = "@$UPNSuffix"
    $pw = New-Object "System.Security.SecureString";
    # check headers to decide if we need to import those "-Include" properties
    $CheckGeneral = ($ImportUserList[0].psobject.Properties).Where({$_.Name -eq "description"})
    $CheckAddress = ($ImportUserList[0].psobject.Properties).Where({$_.Name -eq "streetAddress"})
    $CheckPhones = ($ImportUserList[0].psobject.Properties).Where({$_.Name -eq "telephoneNumber"})
    $CheckOrganization = ($ImportUserList[0].psobject.Properties).Where({$_.Name -eq "title"})
    $CheckManager = ($ImportUserList[0].psobject.Properties).Where({$_.Name -eq "Manager"})
    $CheckCustomAttributes = ($ImportUserList[0].psobject.Properties).Where({$_.Name -eq "extensionAttribute1"})

    if ($Password)
    {
        $pwstr = $Password
    }
    else
    {
        $pwstr = "?r4mdon-_p@ss0rd!"
    }
    
    if ($ResetPassword.IsPresent)
    {
        [bool]$resetpwrd = $True
    }
    else
    {
        [bool]$resetpwrd = $False
    }

    # region connections
    if ($LocalMachineIsNotExchange)
    {
        $Global:LocalMachineIsNotExchange = $LocalMachineIsNotExchange
        $ServicesToConnect = Assert-ServiceConnection -Services AD, ExchangeRemote
        # Connect to services if ServicesToConnect is not empty
        if ($ServicesToConnect.Count)
        {
            Connect-OnlineServices -Services $ServicesToConnect -ExchangeHostname $ExchangeHostname
        }
    }
    else
    {
        $ServicesToConnect = Assert-ServiceConnection -Services ExchangeLocal
        # Connect to services if ServicesToConnect is not empty
        if ($ServicesToConnect.Count)
        {
            Connect-OnlineServices -Services $ServicesToConnect
        }
    }

    # view entire forest and set the preferred DC. If no preferred
    # DC was set, use the same that dclocator is already connected
    if ($PreferredDC)
    {
        try
        {
            Set-AdServerSettings -ViewEntireForest $true -PreferredServer $PreferredDC -ErrorAction Stop
        }
        catch
        {
            # if no valid DC is used break and clean up sessions
            Write-PSFMessage -Level Output -Message "Error: DC was not found. Please run the function again providing a valid Domain Controller FQDN. For example: 'DC01.contoso.com'"
            Get-PSSession | Remove-PSSession
            Remove-Variable * -ErrorAction SilentlyContinue
            Break
        }
    }
    else
    {
        $PreferredDC = $env:LogOnServer.Replace("\\","")
    }

    # region iterate password
    For ($i=0; $i -lt $pwstr.Length; $i++)
    {
        $pw.AppendChar($pwstr[$i])
    }

    # region iterate MEU
    $UsersCount = ($ImportUserList | Measure-Object).count
    ForEach ($user in $ImportUserList)
    {
        $counter++
        Write-Progress -Activity "Creating MEU objects and importing attributes from CSV" -Status "Working on $($user.DisplayName)" -PercentComplete ($counter * 100 / $UsersCount)
        
        $Replace = @{}
        $tmpUser = $null
        $UPN = $user.Alias+$UPNSuffix
        
        # If OU was passed through param, honor it.
        # Otherwise create the MEU without OU specification
        if ($OU)
        {
            $tmpUser = New-MailUser -UserPrincipalName $upn -ExternalEmailAddress $user.ExternalEmailAddress  `
            -FirstName $user.FirstName -LastName $user.LastName -SamAccountName $user.SamAccountName -Alias `
            $user.alias -PrimarySmtpAddress $upn -Name $User.Name -DisplayName $user.DisplayName -Password `
            $pw -ResetPasswordOnNextLogon $resetpwrd -OrganizationalUnit $OU
        }
        else
        {
            $tmpUser = New-MailUser -UserPrincipalName $upn -ExternalEmailAddress $user.ExternalEmailAddress -FirstName `
            $user.FirstName -LastName $user.LastName -SamAccountName $user.SamAccountName -Alias $user.alias -PrimarySmtpAddress `
            $upn -Name $User.Name -DisplayName $user.DisplayName -Password $pw -ResetPasswordOnNextLogon $resetpwrd
        }

        # Convert legacyDN to X500 and add all proxyAddresses to array
        $x500 = "x500:" + $user.legacyExchangeDN
        $proxy = $user.EmailAddresses.Replace(";",",")
        $ProxyArray = @()
        $ProxyArray = $proxy.Split(",") + $x500

        # Set ArchiveGuid if user has source cloud archive. If not
        # proceed with ExchangeGuid and all proxyAddresses values
        if ($null -ne $user.ArchiveGuid -and $user.ArchiveGuid -ne '')
        {
            $tmpUser | Set-MailUser -ExchangeGuid $user.ExchangeGuid -EmailAddresses @{Add=$ProxyArray} -ArchiveGuid $user.ArchiveGuid
        }
        else
        {
            $tmpUser | Set-MailUser -ExchangeGuid $user.ExchangeGuid -EmailAddresses @{Add=$ProxyArray}
        }

        # Add ELC value to hashtable
        [void]$Replace.Add("msExchELCMailboxFlags",$user.ELCValue)

        # region junk hashes. If the user has Junk hash, convert the
        # HEX string to byte array and add to the $replace hashtable
        if ($user.SafeSender)
        {
            $BytelistSafeSender = New-Object -TypeName System.Collections.Generic.List[System.Byte]
            $HexStringSafeSender = $user.SafeSender
            for ($i = 0; $i -lt $HexStringSafeSender.Length; $i += 2)
            {
                $HexByteSafeSender = [System.Convert]::ToByte($HexStringsafeSender.Substring($i, 2), 16)
                $BytelistSafeSender.Add($HexByteSafeSender)
            }

            [void]$Replace.Add("msExchSafeSendersHash",$BytelistSafeSender.ToArray())
        }

        if ($user.SafeRecipient)
        {
            $BytelistSafeRecipient = New-Object -TypeName System.Collections.Generic.List[System.Byte]
            $HexStringSafeRecipient = $user.SafeRecipient
            for ($i = 0; $i -lt $HexStringSafeRecipient.Length; $i += 2)
            {
                $HexByteSafeRecipient = [System.Convert]::ToByte($HexStringSafeRecipient.Substring($i, 2), 16)
                $BytelistSafeRecipient.Add($HexByteSafeRecipient)
            }

            [void]$Replace.Add("msExchSafeRecipientsHash",$BytelistSafeRecipient.ToArray())
        }

        if ($user.BlockedSender)
        {
            $BytelistBlockedSender = New-Object -TypeName System.Collections.Generic.List[System.Byte]
            $HexStringBlockedSender = $user.BlockedSender
            for ($i = 0; $i -lt $HexStringBlockedSender.Length; $i += 2)
            {
                $HexByteBlockedSender = [System.Convert]::ToByte($HexStringBlockedSender.Substring($i, 2), 16)
                $BytelistBlockedSender.Add($HexByteBlockedSender)
            }

            [void]$Replace.Add("msExchBlockedSendersHash",$BytelistBlockedSender.ToArray())
        }

        # region set "-Include" values calling
        # Import-ADPersonalAttribute function.
        if ($CheckGeneral -or $CheckAddress -or $CheckPhones -or $CheckOrganization -or $CheckCustomAttributes)
        {
            [void](Import-ADPersonalAttribute)
        }

        # region set $replace hashtable to ADUser
        if ($Replace.Count -gt 0 -and $LocalMachineIsNotExchange.IsPresent -and $Null -eq $LocalAD)
        {
            Set-RemoteADUser -Identity $user.SamAccountName -Server $PreferredDC -Replace $Replace
        }
        elseif ($Replace.Count -gt 0)
        {
            Set-ADUser -Identity $user.SamAccountName -Server $PreferredDC -Replace $Replace
        }

        Write-PSFMessage -Level InternalComment -Message "$($user.alias) MailUser successfully created."
    }

    # region import Manager value if the CSV contains the manager header
    if ($CheckManager)
    {
        Import-Manager -ObjType MEU
    }

    # region export Mail Contacts
    if ($ContactListToImportCheck -eq 1)
    {
        Move-Contact -Sync Import
    }

    Write-PSFMessage -Level Output -Message "The import is completed. Please confirm that all users are correctly created before enable the Azure AD Sync Cycle."
    Write-PSFMessage -Level Output -Message "You can re-enable Azure AD Connect using the following cmdlet: 'Set-ADSyncScheduler -SyncCycleEnabled 1'"

    # region clean up variables and sessions
    Get-PSSession | Remove-PSSession
    Remove-Variable * -ErrorAction SilentlyContinue
}

Function Update-T2TPostMigration {
    <#
    .SYNOPSIS
        Function developed to update objects post-moverequest.
 
    .DESCRIPTION
        The Update-T2TPostMigration function is intended to update all necessary attributes
        in the source and target environment once the migration batch is finished. The function
        is divided between two switch parameters called -Source and -Destination.
 
    .PARAMETER Destination
        Required switch parameter to update the objects in the destination environment
 
    .PARAMETER AdminUPN
        Enter the user admin to connect to the target Exchange Online
 
    .PARAMETER UserListToImport
        Custom path to import the UserListToImport.csv. if no value
        is defined the function will try to get it from the Desktop.
 
    .PARAMETER MigratedUsersOutputPath
        Enter the file path used to save the MigratedUsers.csv. If
        no value is defined, default value will be the Desktop path
 
    .PARAMETER Source
        Required switch parameter to update the objects in the source environment.
 
    .PARAMETER UseMOERATargetAddress
        Switch param to indicate that ExternalEmailAddress will be the
        destination MOERA (destination.mail.onmicrosoft.com) address.
        If not used, the default value is the target PrimarySMTPAddress
 
    .PARAMETER KeepOldPrimarySMTPAddress
        Switch to indicate that PrimarySMTPAddress will be kept as the
        source domain value. If not used, the primary address will be the
        same as the ExternalEmailAddress value pointing to destination.
 
    .PARAMETER SnapshotToXML
        Switch to dump RemoteMailbox attributes and export to an XML
        file before the conversion from RemoteMailbox to MailUser.
 
    .PARAMETER SnapshotPath
        Define the folder path such as: C:\Temp\Export. If param isn't defined
        and -SnapshotToXML is used, the XML files will be saved on desktop.
 
    .PARAMETER MigratedUsers
        Custom path to import the MigratedUsers.csv. if no value is
        defined the function will try to get it from the Desktop.
 
    .PARAMETER UsePrimarySMTPAsTargetAddress
        Switch to indicate if the targetAddress (ExternalEmailAddress) will be
        the PrimarySMTPAddress. If not used, the default value is the MOERA domain
 
    .PARAMETER LocalMachineIsNotExchange
        Optional parameter used to inform that you are running the script from a
        non-Exchange Server machine. This parameter will require the -ExchangeHostname.
 
    .PARAMETER ExchangeHostname
        Mandatory parameter if the switch -LocalMachineIsNotExchange was used.
        Used to inform the Exchange Server FQDN that the script will connect.
 
    .PARAMETER PreferredDC
        Preferred domain controller to connect with. Consider using this parameter
        to avoid replication issues in environments with too many domain controllers.
 
    .PARAMETER Confirm
        If this switch is enabled, you will be prompted for confirmation
        before executing any operations that change state.
 
    .PARAMETER WhatIf
        If this switch is enabled, no actions are performed but informational messages
        will be displayed that explain what would happen if the command were to run.
 
    .EXAMPLE
        PS C:\> Update-T2TPostMigration -Destination -EXOAdmin admin@contoso.com
        Running from an Exchange Server in the destionaron environment.
 
    .EXAMPLE
        PS C:\> Update-T2TPostMigration -Source -AdminUPN admin@contoso.com -SnapshotToXML -SnapshotPath "C:\Snapshot\" -LocalMachineIsNotExchange -ExchangeHostname "Exchange02" -PreferredDC "DC02"
        The function will connect to the onprem Exchange Server "ExchangeServer02" and the Domain Controller "DC02". Then every remote
        mailboxes present in MigratedUsers.csv with "Completed" value as MoveRequestStatus will be converted to MEU with the new destination
        ExternalEmailAddress. Besides, for each converted MEU, an XML will be created including all attributes values before the convertion.
 
    .EXAMPLE
        PS C:\> Update-T2TPostMigration -Source -AdminUPN admin@contoso.com -KeepOldPrimarySMTPAddress
        The function will connect to the locally Exchange Server. Then every remote mailboxes present in MigratedUsers.csv
        with "Completed" value as MoveRequestStatus will be converted to MEU with the new destination ExternalEmailAddress.
        Besides, the old primary SMTP address will be kept and the MEU will not be updated by the EmailAddressPolicy.
 
    .NOTES
        Title: Update-T2TPostMigration.ps1
        Version: 2.1.6
        Date: 2021.04.21
        Author: Denis Vilaca Signorelli (denis.signorelli@microsoft.com)
 
    REQUIREMENT
        1.ExchangeOnlineManagement module (EXO v2)
        2.PSFramework module
        3.To make things easier, run this script from Exchange On-Premises machine powershell,
        the script will automatically import the Exchange On-Prem module. If you don't want
        to run the script from an Exchange machine, use the switch -LocalMachineIsNotExchange
        and enter the Exchange Server hostname.
 
    #########################################################################
    # This sample script is provided AS IS without warranty of any kind and #
    # not supported under any Microsoft standard support program or service #
    #########################################################################
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidGlobalVars", "")]
    [CmdletBinding(SupportsShouldProcess = $True, ConfirmImpact = 'Low')]
    [CmdletBinding(DefaultParameterSetName="Default")]
    Param(
        [Parameter(ParameterSetName="Destination",Mandatory=$true,
        HelpMessage="Switch Parameter to indicate the destination tenant to connect")]
        [switch]$Destination,

        [Parameter(ParameterSetName="Source",Mandatory=$true,
        HelpMessage="SwitchParameter to indicate that the
        machine running the function is not an Exchange Server"
)]
        [switch]$Source,

        [Parameter(ParameterSetName="Destination",Mandatory=$true,
        HelpMessage="Enter the user admin to connect to Exchange Online")]
        [string]$AdminUPN,

        [Parameter(ParameterSetName="Destination",Mandatory=$false,
        HelpMessage="Enter a custom import path for the csv. if no value is defined
        the script will search on Desktop path for the UserListToImport.csv"
)]
        [string]$UserListToImport,

        [Parameter(ParameterSetName="Destination",Mandatory=$false,
        HelpMessage="Enter the file path used to save the MigratedUsers.csv.
        If no value is defined, default value will be the Desktop path"
)]
        [string]$MigratedUsersOutputPath,

        [Parameter(ParameterSetName="Source",Mandatory=$false,
        HelpMessage="Switch to indicate that ExternalEmailAddress will be
        the destination MOERA (destination.mail.onmicrosoft.com) address.
        If not used, the default value is the target PrimarySMTPAddress"
)]
        [switch]$UseMOERATargetAddress,

        [Parameter(ParameterSetName="Source",Mandatory=$false,
        HelpMessage="Switch to indicate that PrimarySMTPAddress will be kept
        as the source domain value. If not used, the primary address will be
        the same as the ExternalEmailAddress value pointing to destination"
)]
        [switch]$KeepOldPrimarySMTPAddress,

        [Parameter(ParameterSetName="Source",Mandatory=$false,
        HelpMessage="Switch to Dump RemoteMailbox attributes and export to an XML file")]
        [switch]$SnapshotToXML,

        [Parameter(ParameterSetName="Source",Mandatory=$false,
        HelpMessage="Define the folder path such as: C:\Temp\Export if param is not
        defined and -SnapshotToXML is used, the XML files will be saved on desktop"
)]
        [string]$SnapshotPath,

        [Parameter(ParameterSetName="Source",Mandatory=$false,
        HelpMessage="Enter a custom import path for the csv. if no value is defined
        the script will search on Desktop path for the MigratedUsers.csv"
)]
        [string]$MigratedUsers,

        [Parameter(Mandatory=$false,HelpMessage="SwitchParameter to indicate
        that the machine running the function is not an Exchange Server"
)]
        [switch]$LocalMachineIsNotExchange,

        [Parameter(Mandatory=$false,HelpMessage="Enter the remote exchange hostname")]
        [string]$ExchangeHostname,

        [Parameter(Mandatory=$false,
        HelpMessage="Enter the preferred domain controller FQDN to connect with")]
        [string]$PreferredDC
    )

    Set-PSFConfig -FullName PSFramework.Logging.FileSystem.ModernLog -Value $True
    Write-PSFMessage  -Level Output -Message "Starting script. All logs are being saved in: $((Get-PSFConfig PSFramework.Logging.FileSystem.LogPath).Value)"

    if ( $LocalMachineIsNotExchange.IsPresent -and $ExchangeHostname -like '' )
    {
        $ExchangeHostname = Read-Host "$(Get-Date -Format "HH:mm:ss") - Please enter the Exchange Server hostname"
    }

    # region global variables
    if ($MigratedUsers) { $Global:MigratedUsers | Out-Null }
    if ($MigratedUsersOutputPath) { $Global:MigratedUsersOutputPath  | Out-Null }
    if ($SnapshotToXML) { $Global:SnapshotToXML | Out-Null }
    if ($SnapshotPath) { $Global:SnapshotPath | Out-Null }
    if ($UseMOERATargetAddress) { $Global:UseMOERATargetAddress | Out-Null }
    if ($KeepOldPrimarySMTPAddress) { $Global:KeepOldPrimarySMTPAddress | Out-Null }
    if ($LocalMachineIsNotExchange) { $Global:LocalMachineIsNotExchange | Out-Null }
    if ($PreferredDC) { $Global:PreferredDC | Out-Null }

    # region connections
    if ($LocalMachineIsNotExchange.IsPresent -and $Destination.IsPresent)
    {
        $ServicesToConnect = Assert-ServiceConnection -Services EXO, ExchangeRemote
        # Connect to services if ServicesToConnect is not empty
        if ($ServicesToConnect.Count)
        {
            Connect-OnlineServices -AdminUPN $AdminUPN -Services $ServicesToConnect -ExchangeHostname $ExchangeHostname
        }
    }
    elseif ($Destination.IsPresent)
    {
        $ServicesToConnect = Assert-ServiceConnection -Services EXO, ExchangeLocal
        # Connect to services if ServicesToConnect is not empty
        if ($ServicesToConnect.Count)
        {
            Connect-OnlineServices -AdminUPN $AdminUPN -Services $ServicesToConnect
        }
    }
    elseif ($LocalMachineIsNotExchange.IsPresent -and $Source.IsPresent)
    {
        $ServicesToConnect = Assert-ServiceConnection -Services ExchangeRemote, AD
        # Connect to services if ServicesToConnect is not empty
        if ($ServicesToConnect.Count)
        {
            Connect-OnlineServices -AdminUPN $AdminUPN -Services $ServicesToConnect -ExchangeHostname $ExchangeHostname
        }
    }
    elseif ($Source.IsPresent)
    {
        $ServicesToConnect = Assert-ServiceConnection -Services ExchangeLocal
        # Connect to services if ServicesToConnect is not empty
        if ($ServicesToConnect.Count)
        {
            Connect-OnlineServices -AdminUPN $AdminUPN -Services $ServicesToConnect
        }
    }

    # view entire forest and set the preferred DC. If no preferred
    # DC was set, use the same that dclocator is already connected
    if ($PreferredDC)
    {
        try
        {
            Set-AdServerSettings -ViewEntireForest $true -PreferredServer $PreferredDC -ErrorAction Stop
        }
        catch
        {
            # if no valid DC is used, break and clean up sessions. This will
            # avoid EXO throttling limit with appended sessions down the road
            Write-PSFMessage -Level Output -Message "Error: DC was not found. Please run the function again providing a valid Domain Controller FQDN. For example: 'DC01.contoso.com'"
            if ($Destination.IsPresent)
            {
                Disconnect-ExchangeOnline -Confirm:$false -ErrorAction SilentlyContinue
            }
            Get-PSSession | Remove-PSSession
            Remove-Variable * -ErrorAction SilentlyContinue
            Break
        }
    }
    else
    {
        $PreferredDC = $env:LogOnServer.Replace("\\","")
    }

    # region call target internal function
    if ($Destination.IsPresent)
    {
        Convert-Target
    }

    # region call source internal function
    if ($Source.IsPresent)
    {
        Convert-Source
    }

}

<#
This is an example configuration file
 
By default, it is enough to have a single one of them,
however if you have enough configuration settings to justify having multiple copies of it,
feel totally free to split them into multiple files.
#>


<#
# Example Configuration
Set-PSFConfig -Module 'T2Tscripts' -Name 'Example.Setting' -Value 10 -Initialize -Validation 'integer' -Handler { } -Description "Example configuration setting. Your module can then use the setting using 'Get-PSFConfigValue'"
#>


Set-PSFConfig -Module 'T2Tscripts' -Name 'Import.DoDotSource' -Value $false -Initialize -Validation 'bool' -Description "Whether the module files should be dotsourced on import. By default, the files of this module are read as string value and invoked, which is faster but worse on debugging."
Set-PSFConfig -Module 'T2Tscripts' -Name 'Import.IndividualFiles' -Value $false -Initialize -Validation 'bool' -Description "Whether the module files should be imported individually. During the module build, all module code is compiled into few files, which are imported instead by default. Loading the compiled versions is faster, using the individual files is easier for debugging and testing out adjustments."

<#
Stored scriptblocks are available in [PsfValidateScript()] attributes.
This makes it easier to centrally provide the same scriptblock multiple times,
without having to maintain it in separate locations.
 
It also prevents lengthy validation scriptblocks from making your parameter block
hard to read.
 
Set-PSFScriptblock -Name 'T2Tscripts.ScriptBlockName' -Scriptblock {
     
}
#>


<#
# Example:
Register-PSFTeppScriptblock -Name "T2Tscripts.alcohol" -ScriptBlock { 'Beer','Mead','Whiskey','Wine','Vodka','Rum (3y)', 'Rum (5y)', 'Rum (7y)' }
#>


<#
# Example:
Register-PSFTeppArgumentCompleter -Command Get-Alcohol -Parameter Type -Name T2Tscripts.alcohol
#>


New-PSFLicense -Product 'T2Tscripts' -Manufacturer 'designor' -ProductVersion $script:ModuleVersion -ProductType Module -Name MIT -Version "1.0.0.0" -Date (Get-Date "2021-02-04") -Text @"
Copyright (c) 2021 designor
 
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
 
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
 
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"@

#endregion Load compiled code