Private/O365MFASupport/Connect-MDSEXOPSSession.ps1

function Connect-MDSEXOPSSession {
    <#
        .SYNOPSIS
            To connect in other Office 365 offerings, use the following settings:
             - Office 365 operated by 21Vianet: -ConnectionURI https://partner.outlook.cn/PowerShell-LiveID -AzureADAuthorizationEndpointUri https://login.chinacloudapi.cn/common
             - Office 365 Germany: -ConnectionURI https://outlook.office.de/PowerShell-LiveID -AzureADAuthorizationEndpointUri https://login.microsoftonline.de/common
         
            - PSSessionOption accept object created using New-PSSessionOption
 
            - EnableEXOTelemetry To collect telemetry on Exchange cmdlets. Default value is False.
 
            - TelemetryFilePath Telemetry records will be written to this file. Default value is %TMP%\EXOCmdletTelemetry\EXOCmdletTelemetry-yyyymmdd-hhmmss.csv
 
            - DoLogErrorMessage Switch to enable/disable error message logging in telemetry file. Default value is True.
 
        .DESCRIPTION
            This PowerShell module allows you to connect to Exchange Online service
        .LINK
            https://go.microsoft.com/fwlink/p/?linkid=837645
    #>

    [CmdletBinding()]
    param(
        # Connection Uri for the Remote PowerShell endpoint
        [string] $ConnectionUri = 'https://outlook.office365.com/PowerShell-LiveId',

        # Azure AD Authorization endpoint Uri that can issue the OAuth2 access tokens
        [string] $AzureADAuthorizationEndpointUri = 'https://login.windows.net/common',

        # PowerShell session options to be used when opening the Remote PowerShell session
        [System.Management.Automation.Remoting.PSSessionOption] $PSSessionOption = $null,

        # Switch to bypass use of mailbox anchoring hint.
        [switch] $BypassMailboxAnchoring = $false
    )
    DynamicParam {
        if (($isCloudShell = IsCloudShellEnvironment) -eq $false) {
            $attributes = New-Object System.Management.Automation.ParameterAttribute
            $attributes.Mandatory = $false

            $attributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
            $attributeCollection.Add($attributes)

            # User Principal Name or email address of the user
            $UserPrincipalName = New-Object System.Management.Automation.RuntimeDefinedParameter('UserPrincipalName', [string], $attributeCollection)
            $UserPrincipalName.Value = ''

            # User Credential to Logon
            $Credential = New-Object System.Management.Automation.RuntimeDefinedParameter('Credential', [System.Management.Automation.PSCredential], $attributeCollection)
            $Credential.Value = $null
            
            # Switch to collect telemetry on command execution.
            $EnableEXOTelemetry = New-Object System.Management.Automation.RuntimeDefinedParameter('EnableEXOTelemetry', [switch], $attributeCollection)
            $EnableEXOTelemetry.Value = $false
            
            # Where to store EXO command telemetry data. By default telemetry is stored in
            # %TMP%/EXOTelemetry/EXOCmdletTelemetry-yyyymmdd-hhmmss.csv.
            $TelemetryFilePath = New-Object System.Management.Automation.RuntimeDefinedParameter('TelemetryFilePath', [string], $attributeCollection)
            $TelemetryFilePath.Value = ''
            
            # Switch to Disable error message logging in telemetry file.
            $DoLogErrorMessage = New-Object System.Management.Automation.RuntimeDefinedParameter('DoLogErrorMessage', [switch], $attributeCollection)
            $DoLogErrorMessage.Value = $true
            
            $paramDictionary = New-object System.Management.Automation.RuntimeDefinedParameterDictionary
            $paramDictionary.Add('UserPrincipalName', $UserPrincipalName)
            $paramDictionary.Add('Credential', $Credential)
            $paramDictionary.Add('EnableEXOTelemetry', $EnableEXOTelemetry)
            $paramDictionary.Add('TelemetryFilePath', $TelemetryFilePath)
            $paramDictionary.Add('DoLogErrorMessage', $DoLogErrorMessage)
            return $paramDictionary
        }
        else {
            $attributes = New-Object System.Management.Automation.ParameterAttribute
            $attributes.Mandatory = $false

            $attributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
            $attributeCollection.Add($attributes)

            # Switch to MSI auth
            $Device = New-Object System.Management.Automation.RuntimeDefinedParameter('Device', [switch], $attributeCollection)
            $Device.Value = $false

            $paramDictionary = New-object System.Management.Automation.RuntimeDefinedParameterDictionary
            $paramDictionary.Add('Device', $Device)
            return $paramDictionary
        }
    }
    begin {
        $MFAExchangeModulePath = Get-MFAExchangeModulePath -ErrorAction Stop
        . "$MFAExchangeModulePath\CreateExoPSSession.ps1"
    }
    process {
        # Validate parameters
        if (-not (Test-Uri $ConnectionUri)) {
            throw "Invalid ConnectionUri parameter '$ConnectionUri'"
        }
        if (-not (Test-Uri $AzureADAuthorizationEndpointUri)) {
            throw "Invalid AzureADAuthorizationEndpointUri parameter '$AzureADAuthorizationEndpointUri'"
        }

        # Keep track of error count at beginning.
        $errorCountAtStart = $global:Error.Count
        
        try {
            # Cleanup old ps sessions
            $ComputerName = 'outlook.office365.com'
            If ((Get-PSSession).Where{$_.ComputerName -match $ComputerName}) {
                $WarningMsg = 'A previous connection to {0} has been removed.' -f $ComputerName
                Write-Warning $WarningMsg
            }

            $MFAExchangeModulePath = Get-MFAExchangeModulePath -ErrorAction Stop
            $ExoPowershellModule = "Microsoft.Exchange.Management.ExoPowershellModule.dll"
            $ModulePath = [System.IO.Path]::Combine($MFAExchangeModulePath, $ExoPowershellModule)

            $global:ConnectionUri = $ConnectionUri
            $global:AzureADAuthorizationEndpointUri = $AzureADAuthorizationEndpointUri
            $global:PSSessionOption = $PSSessionOption
            $global:BypassMailboxAnchoring = $BypassMailboxAnchoring

            if ($isCloudShell -eq $false) {
                $global:UserPrincipalName = $UserPrincipalName.Value
                $global:Credential = $Credential.Value
            }
            else {
                $global:Device = $Device.Value
            }

            Import-Module $ModulePath
            
            $newExoPSSessionSplat = @{
                BypassMailboxAnchoring          = $BypassMailboxAnchoring
                PSSessionOption                 = $PSSessionOption
                ConnectionUri                   = $ConnectionUri
                AzureADAuthorizationEndpointUri = $AzureADAuthorizationEndpointUri
            }

            if ($isCloudShell -eq $false) {
                $newExoPSSessionSplat.Add('UserPrincipalName', $UserPrincipalName.Value)
                $newExoPSSessionSplat.Add('Credential', $Credential.Value)
                $PSSession = New-ExoPSSession @newExoPSSessionSplat
            }
            else {
                $newExoPSSessionSplat.Add('Device', $Device.Value)
                $PSSession = New-ExoPSSession @newExoPSSessionSplat
            }

            if ($null -ne $PSSession) {
                $PSSessionModuleInfo = Import-PSSession $PSSession -AllowClobber
                UpdateImplicitRemotingHandler

                # If we are configured to collect telemetry, add telemetry wrappers.
                if ($EnableEXOTelemetry.Value -eq $true) {
                    $addEXOClientTelemetryWrapperSplat = @{
                        TelemetryFilePath   = $TelemetryFilePath.Value
                        DoLogErrorMessage   = $DoLogErrorMessage.Value
                        PSSessionModuleName = $PSSessionModuleInfo.Name
                        Organization        = (Get-OrgNameFromUPN -UPN $UserPrincipalName.Value)
                    }
                    $TelemetryFilePath.Value = Add-EXOClientTelemetryWrapper @addEXOClientTelemetryWrapperSplat
                }
            }
        }
        catch {
            throw $_
        }
        Finally {
            # If telemetry is enabled, log errors generated from this cmdlet also.
            if ($EnableEXOTelemetry.Value -eq $true) {
                $errorCountAtProcessEnd = $global:Error.Count 

                # If we have any errors during this cmdlet execution, log it.
                if ($errorCountAtProcessEnd -gt $errorCountAtStart) {
                    if (!$TelemetryFilePath.Value) {
                        $TelemetryFilePath.Value = New-EXOClientTelemetryFilePath
                    }

                    # Log errors which are encountered during Connect-EXOPSSession execution.
                    Write-Warning("Writing Connect-EXOPSSession errors to " + $TelemetryFilePath.Value)
                    
                    $pushEXOTelemetryRecordSplat = @{
                        CommandName            = 'Connect-EXOPSSession'
                        ScriptName             = $global:ExPSTelemetryScriptName
                        OrganizationName       = $global:ExPSTelemetryOrganization
                        ScriptExecutionGuid    = $global:ExPSTelemetryScriptExecutionGuid
                        TelemetryFilePath      = $TelemetryFilePath.Value
                        ErrorObject            = $global:Error
                        ErrorRecordsToConsider = ($errorCountAtProcessEnd - $errorCountAtStart)
                    }
                    Push-EXOTelemetryRecord @pushEXOTelemetryRecordSplat 
                }
            }
        }
    }
    end {}
}