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.
    #>

    [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 -EXO
    Connects to Exchange Online.
     
    #>

    [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 {}
            } -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 {}
            } -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 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.
    #>


    [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-Requirements {
    <#
    .SYNOPSIS
    Checks requirements
     
    .DESCRIPTION
    Checks requirements
     
    .PARAMETER Requirements
    Lists the available Services requirements to be checked. Currently Available is 'AADConnect'.
     
    .EXAMPLE
    PS C:\> Get-Requirements -Requirements PSFramework, EXO
    Checks if PSFramework and EXO v2 modure is installed. If not, we install it.
    #>


    [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-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 ( $LocalMachineIsNotExchange.IsPresent -and $i.Manager ) {

                    Try
                    {

                        Set-RemoteADUser -Identity $i.SamAccountName -Manager $i.Manager -ErrorAction Stop

                    }
                    catch
                    {

                        Write-PSFMessage -Level Output -Message "Failed to add the user's $($i.DisplayName) manager attribute"

                    }
                }
                elseif ( $i.Manager ) {

                    Try
                    {

                        Set-ADUser -Identity $i.SamAccountName -Manager $i.Manager -ErrorAction Stop

                    }
                    catch
                    {

                        Write-PSFMessage -Level Output -Message "Failed to add the user's $($i.DisplayName) manager attribute"

                    }
                }
            }
        }

        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-Contacts {
    <#
    .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.
    #>


    [CmdletBinding()]
    param (
        [ValidateSet('Export','Import')]
        [String[]]
        $Sync
    )

    Switch ( $Sync ) {

        Export {

            # region variables
            $outArray = @()
            $outFile = "$home\desktop\ContactListToImport.csv"
            $ContactCustomAttribute = $CustomAttribute
            $ContactCustomAttributeValue = $CustomAttributeValue


            # Get Contacts filtering by custom attribute
            $Contacts = Get-MailContact -resultsize unlimited | Where-Object { $_.$ContactCustomAttribute -like $ContactCustomAttributeValue }
            Write-PSFMessage -Level Output -Message "$($Contacts.Count) Mail Contacts with $($ContactCustomAttribute) as $($ContactCustomAttributeValue) were returned"

            [int]$counter = 0
            $ContactCount = ($Contacts | Measure-Object).count
            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 = New-Object System.Object
                $object | Add-Member -type NoteProperty -name PrimarySMTPAddress -value $i.PrimarySMTPAddress
                $object | Add-Member -type NoteProperty -name alias -value $i.alias
                $object | Add-Member -type NoteProperty -name FirstName -value $User.FirstName
                $object | Add-Member -type NoteProperty -name LastName -value $User.LastName
                $object | Add-Member -type NoteProperty -name DisplayName -value $User.DisplayName
                $object | Add-Member -type NoteProperty -name Name -value $i.Name
                $object | Add-Member -type NoteProperty -name legacyExchangeDN -value $i.legacyExchangeDN
                $object | Add-Member -type NoteProperty -name CustomAttribute -value $ContactCustomAttribute
                $object | Add-Member -type NoteProperty -name CustomAttributeValue -value $ContactCustomAttributeValue

                # ExternalEmailAddress should contains "SMTP:" depending
                # on the deserialization, just try a replace to avoid IF
                $j = $i.ExternalEmailAddress -replace "SMTP:",""
                $object | Add-Member -type NoteProperty -name ExternalEmailAddress -value $j

                # Get manager property and resolve CN to alias
                if ( $IncludeManager.IsPresent -and $user.Manager -ne $Null ) {

                    $Manager = ( Get-Recipient $user.Manager ).Alias
                    $object | Add-Member -type NoteProperty -name Manager -value $Manager

                }
                if ( $IncludeManager.IsPresent -and $user.Manager -eq $Null ) {

                    $object | Add-Member -type NoteProperty -name Manager -value $Null

                }

                # 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 = @()
                $Proxy = $i.EmailAddresses
                foreach ($email in $Proxy)
                {
                    if ( $email -clike 'smtp:*' -or $email -like 'x500:*' -and $email -notlike '*.onmicrosoft.com' )
                    {

                        $ProxyArray = $ProxyArray += $email

                    }
                }

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

                # 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

                    }
                }

                $object | Add-Member -type NoteProperty -name EmailAddresses -value $ProxyToString
                $outArray += $object
            
            }

            Write-PSFMessage -Level Output -Message "Saving CSV on $($outfile)"
            $outArray | Export-CSV $outfile -notypeinformation

        }

        Import {

            [int]$counter = 0
            $ContactsCount = ( $ImportContactList | Measure-Object ).count
            foreach ( $i in $ImportContactList )
            {
                $counter++
                Write-Progress -Activity "Creating MEU objects and importing attributes from CSV" -Status "Working on $($i.DisplayName)" -PercentComplete ( $counter * 100 / $ContactsCount )
                $tmpContact = $null

                # If OU was passed through param, honor it.
                # Otherwise create the MEU without OU specification
                if ( $OUContacts )
                {
                    $tmpContact = New-MailContact -ExternalEmailAddress $i.ExternalEmailAddress -PrimarySmtpAddress `
                    $i.PrimarySMTPAddress -FirstName $i.FirstName -LastName $i.LastName -Alias $i.alias -Name `
                    $i.Name -DisplayName $i.DisplayName -OrganizationalUnit $OUContacts

                } else {

                    $tmpContact = New-MailContact -ExternalEmailAddress $i.ExternalEmailAddress -PrimarySmtpAddress `
                    $i.PrimarySMTPAddress -FirstName $i.FirstName -LastName $i.LastName -Alias $i.alias -Name `
                    $i.Name -DisplayName $i.DisplayName

                }

                # Convert legacyDN to X500, replace back to ","
                $x500 = "x500:" + $i.legacyExchangeDN
                $proxy = $i.EmailAddresses.Replace(";",",")
                $ProxyArray = @()
                $ProxyArray = $Proxy -split ","
                $ProxyArray = $ProxyArray + $x500

                # Matching the variable's name to the parameter's name
                $CustomAttributeParam = @{ $i.CustomAttribute=$i.CustomAttributeValue }

                # Set previou LegacyDN as X500 and CustomAttribute
                Set-MailContact -Identity $i.Alias -EmailAddresses @{ Add=$ProxyArray } @CustomAttributeParam

            }

            # Import Manager value if the CSV contains the manager header
            $IncludeManager = $ImportContactList[0].psobject.Properties | Where { $_.Name -eq "Manager" }
            if ( $IncludeManager ) { Import-Manager -ObjType Contact }

        }
    }
}

Function Update-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:\> Update-Source
        The following example run this function.
    #>


    # region local variables
    $MigratedUsersImportCheck = Get-CSVStatus -UsersMigrated
    if ( $MigratedUsersImportCheck -eq 0 ) { Break }
    $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 variable before disable remote mailbox
            try
            {
                $user = Get-RemoteMailbox -Identity $i.Alias -ErrorAction Stop
                if ( $LocalMachineIsNotExchange.IsPresent -and $LocalAD -eq '' ) {

                    $aduser = Get-RemoteADUser $i.Alias -Properties * | Select-Object -Property $Properties -ErrorAction Stop

                } else {

                    $aduser = Get-ADUser $i.Alias -Properties * | Select-Object -Property $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 -Confirm:$false
                if ( $? ) {
                    
                    Write-PSFMessage  -Level Output -Message "RemoteMailbox $($i.PrimarySMTPAddress) successfully converted to MailUser."

                    # if -UsePrimarySMTPAsTargetAddress is preset,
                    # honor it, otherwise set MOERA as targetAddress
                    if ( $UsePrimarySMTPAsTargetAddress.IsPresent ) {

                        Enable-MailUser -Identity $i.Alias -ExternalEmailAddress $i.PrimarySMTPAddress | Out-Null

                    } else {

                        Enable-MailUser -Identity $i.Alias -ExternalEmailAddress $i.ExternalEmailAddress | Out-Null

                    }

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

                    # if there were custom attribues before, re-add them
                    $Replace = @{}
                    foreach ( $element in $Properties ) {
                        if ( $aduser.$element ) {
                            $Replace.Add( $element, $aduser.$element )
                            }
                        }
                    if ( $Replace ) { Set-ADUser -Identity $i.Alias -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 Update-Target {
    <#
    .SYNOPSIS
        Function to update the targetAddress and
        object type from MailUser to RemoteMailbox
 
    .DESCRIPTION
        This function is called by Update-T2TPostMigration
        function through the parameter -Destination
 
    .EXAMPLE
        PS C:\> Update-Target
        The following example run this function.
    #>


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

    # region local variables
    if ( $FolderPath ) { $outFile = "$FolderPath\MigratedUsers.csv" }
    else { $outFile = "$home\desktop\MigratedUsers.csv" }
    $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 -ne $Null ) {

        foreach ( $i in $MEU ) {

            Write-Progress -Activity "Verifying move request status" -Status "Verifying user $($i)"

            # Reset variables
            $MoveRequest = $Null
            $object = New-Object System.Object

            # 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."
                $object | Add-Member -type NoteProperty -name ExternalEmailAddress -value $i
                $object | Add-Member -type NoteProperty -name MoveRequestStatus -value MoveRequestNotExist
                $object | Add-Member -type NoteProperty -name PrimarySMTPAddress -value $Null
                $object | Add-Member -type NoteProperty -name Alias -value $Null
                $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 Update-Source if UsePrimarySMTPAsTargetAddress is present.
                    try
                    {
                        $user = Get-MailUser -identity $i -ErrorAction Stop
                        if ( $? ) {

                            $object | Add-Member -type NoteProperty -name ExternalEmailAddress -value $i
                            $object | Add-Member -type NoteProperty -name PrimarySMTPAddress -value $user.PrimarySMTPAddress
                            $object | Add-Member -type NoteProperty -name Alias -value $user.Alias
                            Enable-RemoteMailbox -identity $user.Alias -RemoteRoutingAddress $i -ErrorAction Stop | Out-Null
                            if ( $? ) {

                                Write-PSFMessage  -Level Output -Message "Converted MailUser $($i) to RemoteMailbox and changed ExternalEmailAddress successfully."
                                $object | Add-Member -type NoteProperty -name MoveRequestStatus -value 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."
                            $object | Add-Member -type NoteProperty -name ExternalEmailAddress -value $i
                            $object | Add-Member -type NoteProperty -name MoveRequestStatus -value IsAlreadyRemoteMailbox
                            $object | Add-Member -type NoteProperty -name PrimarySMTPAddress -value $Null
                            $object | Add-Member -type NoteProperty -name Alias -value $Null
                            $BreakLoop.Remove($i)

                        } Else {

                            Write-PSFMessage  -Level Output -Message "MEUNotFound: The MailUser $($i) was not found."
                            $object | Add-Member -type NoteProperty -name ExternalEmailAddress -value $i
                            $object | Add-Member -type NoteProperty -name MoveRequestStatus -value MEUNotFound
                            $object | Add-Member -type NoteProperty -name PrimarySMTPAddress -value $Null
                            $object | Add-Member -type NoteProperty -name Alias -value $Null
                            $BreakLoop.Remove($i)

                        }
                    }
                }

                if ( $MoveRequest -like "Failed" ) {

                    Write-PSFMessage  -Level Output -Message "MoveRequestFailed: The $($i) move request was failed"
                    $object | Add-Member -type NoteProperty -name ExternalEmailAddress -value $i
                    $object | Add-Member -type NoteProperty -name MoveRequestStatus -value MoveRequestFailed
                    $object | Add-Member -type NoteProperty -name PrimarySMTPAddress -value $Null
                    $object | Add-Member -type NoteProperty -name Alias -value $Null
                    $BreakLoop.Remove($i)

                }
            }
            
            # add object to array only if there is MoveRequestStatus
            # to avoid empty lines while the move does not finish
            if ( $object.MoveRequestStatus ) { $outArray += $object }

        }

        # Remove from the $MEU array all objects that have MoveRequestStatus value
        # it means that the object should not go through the foreach 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)"
    $outArray | Export-CSV $outfile -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-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 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 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.
 
    .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 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.
 
    .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.0.1
        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 #
    #########################################################################
    #>


    [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "")]
    [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="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 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
    $outArray = @()
    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 }
    
    }
    
    # Dump contacts. TO DO: Run this function in parallel. Job is
    # not an option as this function relies on global variables
    if ( $IncludeContacts.IsPresent ) { Move-Contacts -Sync Export }

    # Save all properties from MEU object to variable
    $RemoteMailboxes = Get-RemoteMailbox -resultsize unlimited | Where-Object {$_.$CustomAttribute -like $CustomAttributeValue}
    Write-PSFMessage -Level Output -Message "$($RemoteMailboxes.Count) mailboxes with $($CustomAttribute) as $($CustomAttributeValue) were returned"
    
    # Saving 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..."

    [int]$counter = 0
    $UsersCount = ($RemoteMailboxes | Measure-Object).count
    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 = New-Object System.Object
        $object | Add-Member -type NoteProperty -name alias -value $i.alias
        $object | Add-Member -type NoteProperty -name FirstName -value $User.FirstName
        $object | Add-Member -type NoteProperty -name LastName -value $User.LastName
        $object | Add-Member -type NoteProperty -name DisplayName -value $User.DisplayName
        $object | Add-Member -type NoteProperty -name Name -value $i.Name
        $object | Add-Member -type NoteProperty -name SamAccountName -value $i.SamAccountName
        $object | Add-Member -type NoteProperty -name legacyExchangeDN -value $i.legacyExchangeDN
        $object | Add-Member -type NoteProperty -name RemoteRecipientType -value $i.RemoteRecipientType
        $object | Add-Member -type NoteProperty -name CustomAttribute -value $CustomAttribute
        $object | Add-Member -type NoteProperty -name CustomAttributeValue -value $CustomAttributeValue
        
        # We must resolve the manager's CN to alias
        if ( $IncludeManager.IsPresent -and $user.Manager -ne $Null ) {

            $Manager = ( Get-Recipient $user.Manager ).Alias
            $object | Add-Member -type NoteProperty -name Manager -value $Manager

        }
        if ( $IncludeManager.IsPresent -and $user.Manager -eq $Null ) {

            $object | Add-Member -type NoteProperty -name Manager -value $Null

        }

        if ( $BypassAutoExpandingArchiveCheck.IsPresent ) {
        
            # Save necessary properties from EXO object to variable avoiding AUX check
            $EXOMailbox = Get-EXOMailbox -Identity $i.Alias -PropertySets Retention,Hold,Archive,StatisticsSeed
        
        } else {

            if ($OrgAUXStatus.AutoExpandingArchiveEnabled -eq '$True') {

                # 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

            } else {

                # 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
            
            }

        }

        if ( $BypassAutoExpandingArchiveCheck.IsPresent ) {
        
            # Save necessary properties from EXO object to variable avoiding AUX check
            Write-PSFMessage -Level Output -Message "Bypassing 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
        $object | Add-Member -type NoteProperty -name ExchangeGuid -value $EXOMailbox.ExchangeGuid

        # Get mailbox ELC value
        $ELCValue = 0
        if ($EXOMailbox.LitigationHoldEnabled) {$ELCValue = $ELCValue + 8}
        if ($EXOMailbox.SingleItemRecoveryEnabled) {$ELCValue = $ELCValue + 16}
        if ($ELCValue -ge 0) { $object | Add-Member -type NoteProperty -name ELCValue -value $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" )
        {

            $object | Add-Member -type NoteProperty -name ArchiveGuid -value $EXOMailbox.ArchiveGuid

        } else {

            $object | Add-Member -type NoteProperty -name ArchiveGuid -value $Null

        }

        # Get only SMTP, X500 and SIP if the switch is present
        # from proxyAddresses and define the targetAddress
        $ProxyArray = @()
        $Proxy = $i.EmailAddresses
        foreach ($email in $Proxy)
        {
            if ($email -like 'SMTP:*' -or $email -like 'X500:*')
            {

                $ProxyArray = $ProxyArray += $email

            }
            if ($IncludeSIP.IsPresent -and $email -like 'SIP:*')
            {

                $ProxyArray = $ProxyArray += $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"

        $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

            }
        }

        $object | Add-Member -type NoteProperty -name EmailAddresses -value $ProxyToString
        $object | Add-Member -type NoteProperty -name PrimarySMTPAddress -value $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.
        $object | Add-Member -type NoteProperty -name ExternalEmailAddress -value $TargetString

        # Get proxyAddress only for *.mail.onmicrosoft to define in the target AD the targetAddress
        # value post-migration. This value should be converted through the domain mapping CSV.
        $object | Add-Member -type NoteProperty -name ExternalEmailAddressPostMove -value $TargetPostMigration

        # Connect to AD exported module only if this machine has not AD Module installed
        if ( $LocalMachineIsNotExchange.IsPresent -and $LocalAD -eq '' )
        {

            $Junk = Get-RemoteADUser -Identity $i.SamAccountName -Properties *

        } else {

            $Junk = Get-ADUser -Identity $i.SamAccountName -Properties *

        }

        # 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 "-"
        if ( $junk.msExchSafeSendersHash.Length -gt 0 )
        {
        
            $SafeSender = [System.BitConverter]::ToString($junk.msExchSafeSendersHash)
            $Safesender = $SafeSender.Replace("-","")
            $object | Add-Member -type NoteProperty -name SafeSender -value $SafeSender
        
        } else {

            $object | Add-Member -type NoteProperty -name SafeSender $Null
        
        }
        
        if ( $junk.msExchSafeRecipientsHash.Length -gt 0 )
        {
            
            $SafeRecipient = [System.BitConverter]::ToString($junk.msExchSafeRecipientsHash)
            $SafeRecipient = $SafeRecipient.Replace("-","")
            $object | Add-Member -type NoteProperty -name SafeRecipient -value $SafeRecipient

        }  else {

            $object | Add-Member -type NoteProperty -name SafeRecipient -value $Null

        }

        if ( $junk.msExchBlockedSendersHash.Length -gt 0 )
        {
            
            $BlockedSender = [System.BitConverter]::ToString($junk.msExchBlockedSendersHash)
            $BlockedSender = $BlockedSender.Replace("-","")
            $object | Add-Member -type NoteProperty -name BlockedSender -value $BlockedSender
        
        } else {

            $object | Add-Member -type NoteProperty -name BlockedSender -value $Null

        }

        $outArray += $object

    }

    # region export the CSV
    if ( $AuxMessage ) {

        Write-PSFMessage -Level Output -Message "Saving CSV on $($outfile)"
        Write-PSFMessage -Level Output -Message "Saving TXT on $($AUXFile)"

    } else {

        Write-PSFMessage -Level Output -Message "Saving CSV on $($outfile)"

    }

    # region clean up variables and sessions
    $outArray | Export-CSV $outfile -notypeinformation
    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.
 
    .EXAMPLE
        PS C:\> Import-T2TAttributes -UPNSuffix "fabrikam.com" -ResetPassword -FilePath "C:\temp\UserListToImport.csv"
        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" -ResetPassword -FilePath "C:\temp\UserListToImport.csv" -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.0.1
        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 Azure AD Connect
        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("PSAvoidUsingPlaintextForPassword", "")]
    [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
    )

    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 local variables
    if ( $Password ) { $pwstr = $Password }
    else { $pwstr = "?r4mdon-_p@ss0rd!" }

    if ( $ResetPassword.IsPresent ) { [bool]$resetpwrd = $True }
    else { [bool]$resetpwrd = $False }

    $UPNSuffix = "@$UPNSuffix"
    $pw = New-Object "System.Security.SecureString";

    # 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-Requirements -Requirements AADConnect
    if ( $AADCStopped -eq 0 ) { Break }

    #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 }
    }


    for ( $i=0; $i -lt $pwstr.Length; $i++ )  { $pw.AppendChar($pwstr[$i]) }

    [int]$counter = 0
    $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 )
        
        $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, replace back to ","
        $x500 = "x500:" + $user.legacyExchangeDN
        $proxy = $user.EmailAddresses.Replace(";",",")
        $ProxyArray = @()
        $ProxyArray = $Proxy -split ","
        $ProxyArray = $ProxyArray + $x500
        
        # Matching the variable's name to the parameter's name
        $CustomAttributeParam = @{ $User.CustomAttribute = $user.CustomAttributeValue }
        
        # Set ExchangeGuid, old LegacyDN as X500 and CustomAttribute
        $tmpUser | Set-MailUser -ExchangeGuid $user.ExchangeGuid @CustomAttributeParam -EmailAddresses @{ Add=$ProxyArray }

        # Set ELC value
        if ( $LocalMachineIsNotExchange.IsPresent -and $null -eq $LocalAD )
        {
            
            Set-RemoteADUser -Identity $user.SamAccountName -Replace @{ msExchELCMailboxFlags = $user.ELCValue }
                
        } else {

            Set-ADUser -Identity $user.SamAccountName -Replace @{ msExchELCMailboxFlags=$user.ELCValue }

        }

        # Set ArchiveGuid if user has source cloud archive. We don't really care if the
        # archive will be moved, it's up to the batch to decide, we just import the attribute
        if ( $null -ne $user.ArchiveGuid -and $user.ArchiveGuid -ne '' )
        {
            
            $tmpUser | Set-MailUser -ArchiveGuid $user.ArchiveGuid
        
        }

        # If the user has Junk hash, convert the HEX string to byte array
        if ( $null -ne $user.SafeSender -and $user.SafeSender -ne '' )
        {
        
            $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)
                }
            
            $BytelistSafeSenderArray = $BytelistSafeSender.ToArray()
            
                if ( $LocalMachineIsNotExchange.IsPresent -and $null -eq $LocalAD )
                {
                    
                    Set-RemoteADUser -Identity $user.SamAccountName -Replace @{ msExchSafeSendersHash = $BytelistSafeSenderArray }

                } else {

                    Set-ADUser -Identity $user.SamAccountName -Replace @{ msExchSafeSendersHash = $BytelistSafeSenderArray }

                }
            
        }

        if ( $null -ne $user.SafeRecipient -and $user.SafeRecipient -ne '' )
        {
        
            $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)
                }
            
            $BytelistSafeRecipientArray = $BytelistSafeRecipient.ToArray()
            
                if ( $LocalMachineIsNotExchange.IsPresent -and $null -eq $LocalAD )
                {
                    
                    Set-RemoteADUser -Identity $user.SamAccountName -Replace @{ msExchSafeRecipientsHash = $BytelistSafeRecipientArray }

                } else {

                    Set-ADUser -Identity $user.SamAccountName -Replace @{ msExchSafeRecipientsHash = $BytelistSafeRecipientArray }

                }
        
        }

        if ( $null -ne $user.BlockedSender -and $user.BlockedSender -ne '' )
        {
        
            $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)
                }
            
            $BytelistBlockedSenderArray = $BytelistBlockedSender.ToArray()
            
                if ( $LocalMachineIsNotExchange.IsPresent -and $null -eq $LocalAD )
                {
                    
                    Set-RemoteADUser -Identity $user.SamAccountName -Replace @{ msExchBlockedSendersHash = $BytelistBlockedSenderArray }

                } else {

                    Set-ADUser -Identity $user.SamAccountName -Replace @{ msExchBlockedSendersHash = $BytelistBlockedSenderArray }

                }
            }

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

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

    # Import Manager value if the CSV contains the manager header
    $IncludeManager = $ImportUserList[0].psobject.Properties | Where { $_.Name -eq "Manager" }
    if ( $IncludeManager ) { Import-Manager -ObjType MEU }

    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'"
    Get-PSSession | Remove-PSSession
    Remove-Variable * -ErrorAction SilentlyContinue

}

Function Update-T2TPostMigration {
    <#
    .SYNOPSIS
        Function developed to update objects once 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 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.
 
 
    .EXAMPLE
        PS C:\> Update-T2TPostMigration -Destination -EXOAdmin admin@contoso.com
        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:\> Update-T2TPostMigration -Source -EXOAdmin admin@contoso.com -UsePrimarySMTPAsTargetAddress
        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: Update-T2TPostMigration.ps1
        Version: 2.0.1
        Date: 2021.04.21
        Authors: Denis Vila�a 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("PSUseSingularNouns", "")]
    [CmdletBinding(DefaultParameterSetName="Default")]
    Param(
        [Parameter(ParameterSetName="Destination",Mandatory=$true,
        HelpMessage="Switch Parameter to indicate the destination tenant to connect")]
        [switch]$Destination,

        [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=$true,
        HelpMessage="SwitchParameter to indicate that the
        machine running the function is not an Exchange Server"
)]
        [switch]$Source,

        [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(ParameterSetName="Source",Mandatory=$false,
        HelpMessage="Switch to indicate if the targetAddress (ExternalEmailAddress) will
        be the PrimarySMTPAddress. If not used, the default value is the MOERA domain"
)]
        [switch]$UsePrimarySMTPAsTargetAddress,

        [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
    )

    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
    if ( $MigratedUsers ) { $Global:MigratedUsers | Out-Null }
    if ( $MigratedUsersOutputPath ) { $Global:FolderPath | Out-Null}
    if ( $UsePrimarySMTPAsTargetAddress ) { $Global:UsePrimarySMTPAsTargetAddress | Out-Null }
    if ( $LocalMachineIsNotExchange ) { $Global:LocalMachineIsNotExchange | 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
        # 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 }
    
    }
    

    # region target function
    if ( $Destination.IsPresent ) { Update-Target }

    # region source function
    if ( $Source.IsPresent ) { Update-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