Workloads/ExchangeOnline.psm1

function Connect-MSCloudLoginExchangeOnline
{
    [CmdletBinding()]
    param(
        [Parameter()]
        [System.String]$Prefix
    )
    if ($null -eq $Global:o365Credential)
    {
        $Global:o365Credential = Get-Credential -Message "Cloud Credential"
    }
    $VerbosePreference = 'SilentlyContinue'
    $WarningPreference = "Continue"
    $clientid = "a0c73c16-a7e3-4564-9a95-2bdf47383716";
    $ResourceURI = "https://outlook.office365.com";
    $RedirectURI = "urn:ietf:wg:oauth:2.0:oob";
    $ClosedOrBrokenSessions = Get-PSSession -ErrorAction SilentlyContinue | Where-Object -FilterScript { $_.State -ne 'Opened' }
    if ($ClosedOrBrokenSessions)
    {
        Write-Verbose -Message "Found Existing Unusable Session(s)."
        foreach ($SessionToBeClosed in $ClosedOrBrokenSessions)
        {
            Write-Verbose -Message "Closing Session: $(($SessionToBeClosed).InstanceId)"
            $SessionToBeClosed | Remove-PSSession
        }
    }

    $Global:OpenExchangeSession = Get-PSSession -Name 'ExchangeOnline' `
        -ErrorAction SilentlyContinue | `
        Where-Object -FilterScript { $_.State -eq 'Opened' }
    if ($null -eq $Global:OpenExchangeSession)
    {
        try
        {
            $PowerShellConnections = Get-NetTCPConnection | `
                Where-Object -FilterScript { `
                    $_.RemotePort -eq '443' -and $_.State -ne 'Established' `
            }

            while ($PowerShellConnections)
            {
                Write-Verbose -Message "This process is using the following connections in a non-Established state: $($PowerShellConnections | Out-String)"
                Write-Verbose -Message "Waiting for closing connections to close..."
                Get-PSSession -Name 'ExchangeOnline' -ErrorAction SilentlyContinue | Remove-PSSession
                Start-Sleep -seconds 1
                $CheckConnectionsWithoutKillingWhileLoop = Get-NetTCPConnection | Where-Object -FilterScript { $_.OwningProcess -eq $PID -and $_.RemotePort -eq '443' -and $_.State -ne 'Established' }
                if (-not $CheckConnectionsWithoutKillingWhileLoop)
                {
                    Write-Verbose -Message "Connections have closed. Waiting 5 more seconds..."
                    Start-Sleep -seconds 5
                    $PowerShellConnections = Get-NetTCPConnection | Where-Object -FilterScript { $_.OwningProcess -eq $PID -and $_.RemotePort -eq '443' -and $_.State -ne 'Established' }
                }
            }

            if ($Global:ExchangeOnlineSession.State -eq "Closed")
            {
                Remove-PSSession $Global:ExchangeOnlineSession
                $Global:ExchangeOnlineSession = $null
            }

            while ($null -eq $Global:ExchangeOnlineSession)
            {
                Write-Verbose -Message "Creating new EXO Session"
                $TenantName = $Global:o365Credential.UserName.split("@")[1]
                $TenantInfo = Get-TenantLoginEndPoint -TenantName $TenantName

                if ($TenantInfo -like '*login.microsoftonline.us*')
                {
                    $Global:CloudEnvironment = 'USGovernment'
                    $ResourceURI = 'https://outlook.office365.us'
                }
                elseif ($TenantInfo -like '*login.microsoftonline.com*')
                {
                    $Global:CloudEnvironment = 'Public'
                    $ResourceURI = 'https://outlook.office365.com'
                }
                elseif ($TenantInfo -like '*login.microsoftonline.de*')
                {
                    $Global:CloudEnvironment = 'Germany'
                    $ResourceURI = 'https://outlook.office.de'
                }

                try
                {
                    $Global:ExchangeOnlineSession = New-PSSession -Name 'ExchangeOnline' -ConfigurationName Microsoft.Exchange -ConnectionUri "$ResourceURI/powershell-liveid/" -Credential $O365Credential -Authentication Basic -AllowRedirection -ErrorAction Stop
                    $Global:IsMFAAuth = $false
                }
                catch
                {
                    try
                    {
                        $AuthHeader = Get-AuthHeader -UserPrincipalName $Global:o365Credential.UserName -RessourceURI $ResourceURI -clientID $clientID -RedirectURI $RedirectURI
                        $Password = ConvertTo-SecureString -AsPlainText $AuthHeader -Force
                        $Ctoken = New-Object System.Management.Automation.PSCredential -ArgumentList $Global:o365Credential.UserName, $Password
                        $Global:ExchangeOnlineSession = New-PSSession -ConfigurationName Microsoft.Exchange `
                            -ConnectionUri "$ResourceURI/PowerShell-LiveId?BasicAuthToOAuthConversion=true" `
                            -Credential $Ctoken `
                            -Authentication Basic `
                            -ErrorAction Stop `
                            -AllowRedirection
                        $Global:UseModernAuth = $True
                        $Global:IsMFAAuth = $True
                    }
                    catch
                    {
                        if ($_ -like '*Connecting to remote server *Access is denied.*')
                        {
                            Throw "The provided account doesn't have admin access to Exchange Online."
                        }
                    }
                }
            }
            if ($null -eq $Global:ExchangeOnlineModules)
            {
                Write-Verbose -Message "Importing all commands into the EXO Session"
                $WarningPreference = 'SilentlyContinue'
                $Global:ExchangeOnlineModules = Import-PSSession $Global:ExchangeOnlineSession -AllowClobber
                $IPMOParameters = @{}
                if ($PSBoundParameters.containskey("Prefix"))
                {
                    $IPMOParameters.add("Prefix",$prefix)
                }
                Import-Module $Global:ExchangeOnlineModules -Global @IPMOParameters | Out-Null
            }
        }
        catch
        {
            $ExceptionMessage = $_.Exception
            $Error.Clear()
            $VerbosePreference = 'SilentlyContinue'
            if ($ExceptionMessage -imatch 'Please wait for [0-9]* seconds')
            {
                Write-Verbose -Message "Waiting for available runspace..."
                [regex]$WaitTimePattern = 'Please wait for [0-9]* seconds'

                $WaitTimePatternMatch = (($WaitTimePattern.Match($ExceptionMessage)).Value | `
                        Select-String -Pattern '[0-9]*' -AllMatches)

                $WaitTimeInSeconds = ($WaitTimePatternMatch | ForEach-Object { $_.Matches } | Where-Object -FilterScript { $_.Value -NotLike $null }).Value
                Write-Verbose -Message "Waiting for requested $WaitTimeInSeconds seconds..."
                Start-Sleep -Seconds ($WaitTimeInSeconds + 1)
                try
                {
                    Test-MSCloudLogin -Platform 'ExchangeOnline' -CloudCredential $Global:o365Credential
                }
                catch
                {
                    $VerbosePreference = 'SilentlyContinue'
                    $WarningPreference = "SilentlyContinue"
                    $Global:ExchangeOnlineSession = $null
                    Close-SessionsAndReturnError -ExceptionMessage $_.Exception
                    $Message = "Can't open Exchange Online session from Connect-ExchangeOnline"
                    New-Office365DSCLogEntry -Error $_ -Message $Message
                }
            }
            else
            {
                Write-Verbose $_.Exception
                $VerbosePreference = 'SilentlyContinue'
                throw $_
            }
        }
    }
    else
    {
        Write-Verbose -Message "Using Existing ExchangeOnline Session."
        $Global:OpenExchangeSession = Get-PSSession -Name 'ExchangeOnline' -ErrorAction SilentlyContinue | Where-Object -FilterScript { $_.State -eq 'Opened' }
        $VerbosePreference = 'SilentlyContinue'
        $WarningPreference = "SilentlyContinue"
    }
    return
}