functions/Update-T2TPostMigration.ps1

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

}