MSCloudLoginAssistant.psm1

<#
.SYNOPSIS
    The Test-MSCloudLogin function is used to assist with logging in to various Microsoft Cloud services, such as Azure, SharePoint Online, and SharePoint PnP.
.EXAMPLE
    Test-MSCloudLogin -Platform AzureAD -Verbose
.EXAMPLE
    Test-MSCloudLogin -Platform PnP
.PARAMETER Platform
    The Platform parameter specifies which cloud service for which we are testing the login state. Possible values are Azure, AzureAD, SharePointOnline, ExchangeOnline, MSOnline, and PnP.
.NOTES
    Created & maintained by Brian Lalancette (@brianlala), 2019.
.LINK
    https://github.com/Microsoft/MSCloudLoginAssistant
#>


function Test-MSCloudLogin
{
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory=$true)]
        [ValidateSet("Azure","AzureAD","SharePointOnline","ExchangeOnline","SecurityComplianceCenter","MSOnline","PnP","MicrosoftTeams")]
        [System.String]
        $Platform,

        [Parameter()]
        [System.String]
        $ConnectionUrl,

        [Parameter()]
        [Alias("o365Credential")]
        [System.Management.Automation.PSCredential]
        $CloudCredential,

        [Parameter()]
        [Switch]
        $UseModernAuth
    )

    # If we specified the CloudCredential parameter then set the global o365Credential object to its value
    if ($null -ne $CloudCredential)
    {
        $Global:o365Credential = $CloudCredential
    }
    if($null -eq $Global:UseModernAuth){
        $Global:UseModernAuth = $UseModernAuth.IsPresent
    }
    switch ($Platform)
    {
        'Azure'
        {
            $testCmdlet = "Get-AzResource";
            $exceptionStringMFA = "AADSTS";
            $clientid = "1950a258-227b-4e31-a9cf-717495945fc2";
            $RessourceURI = "https://management.core.windows.net";
            $RedirectURI = "urn:ietf:wg:oauth:2.0:oob";
            $connectCmdlet = "Connect-AzAccount";
            $connectCmdletArgs = "-Credential `$Global:o365Credential";
            $connectCmdletMfaRetryArgs = "-AccessToken `$AuthToken -AccountId `$global:o365Credential.UserName";
            $variablePrefix = "az"
        }
        'AzureAD'
        {
            $testCmdlet = "Get-AzureADUser";
            $exceptionStringMFA = "AADSTS";
            $clientid = "1b730954-1685-4b74-9bfd-dac224a7b894";
            $RessourceURI = "https://graph.windows.net";
            $RedirectURI = "urn:ietf:wg:oauth:2.0:oob";
            $connectCmdlet = "Connect-AzureAD";
            $connectCmdletArgs = "-Credential `$Global:o365Credential";
            $connectCmdletMfaRetryArgs = "-AadAccessToken `$AuthToken -AccountId `$global:o365Credential.UserName"
            $variablePrefix = "aad"
        }
        'SharePointOnline'
        {
            if ([string]::IsNullOrEmpty($ConnectionUrl))
            {
                $Global:spoAdminUrl = Get-SPOAdminUrl;
            }
            else
            {
                $Global:spoAdminUrl = $ConnectionUrl
            }
            $testCmdlet = "Get-SPOSite";
            $exceptionStringMFA = "sign-in name or password does not match one in the Microsoft account system";
            $clientid = "9bc3ab49-b65d-410a-85ad-de819febfddc";
            $RessourceURI = $Global:spoAdminUrl;
            $RedirectURI = "urn:ietf:wg:oauth:2.0:oob";
            $connectCmdlet = "Connect-SPOService";
            $connectCmdletArgs = "-Url $Global:spoAdminUrl -Credential `$Global:o365Credential";
            $connectCmdletMfaRetryArgs = $connectCmdletArgs.Replace("-Credential `$Global:o365Credential","");
            $variablePrefix = "spo"
        }
        'ExchangeOnline'
        {  
            $VerbosePreference = 'SilentlyContinue'
            $WarningPreference = "Continue"
            $clientid = "a0c73c16-a7e3-4564-9a95-2bdf47383716";
            $RessourceURI = "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"

                        try
                        {
                            $Global:ExchangeOnlineSession = New-PSSession -Name 'ExchangeOnline' -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $O365Credential -Authentication Basic -AllowRedirection -ErrorAction Stop
                        }
                        catch
                        {
                            try {
                                $AuthHeader = Get-AuthHeader -UserPrincipalName $Global:o365Credential.UserName -RessourceURI $RessourceURI -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 https://outlook.office365.com/PowerShell-LiveId?BasicAuthToOAuthConversion=true `
                                -Credential $Ctoken `
                                -Authentication Basic `
                                -ErrorAction Stop `
                                -AllowRedirection
                                $Global:UseModernAuth = $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:ExchangeOnlineSession)
                        {
                            Write-Warning "Exceeded max number of connections. Waiting 60 seconds"
                            Start-Sleep 60
                        }
                    }
                    if ($null -eq $Global:ExchangeOnlineModules)
                    {
                        Write-Verbose -Message "Importing all commands into the EXO Session"
                        $WarningPreference = 'silentlycontinue'
                        $Global:ExchangeOnlineModules = Import-PSSession $Global:ExchangeOnlineSession -AllowClobber
                        Import-Module $Global:ExchangeOnlineModules -Global | 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
                        {
                            Write-Verbose -Message "Opening New ExchangeOnline Session."
                            $PowerShellConnections = Get-NetTCPConnection | Where-Object -FilterScript { $_.OwningProcess -eq $PID -and $_.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' }
                                }
                            }
                            $VerbosePreference = 'SilentlyContinue'
                            $Global:ExchangeOnlineSession = $null
                            while (-not $Global:ExchangeOnlineSession)
                            {
                                try {
                                    $Global:ExchangeOnlineSession = New-PSSession -Name 'ExchangeOnline' -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $O365Credential -Authentication Basic -AllowRedirection -ErrorAction Stop
                                }
                                catch {
                                    $AuthHeader = Get-AuthHeader -UserPrincipalName $Global:o365Credential.UserName -RessourceURI $RessourceURI -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 https://outlook.office365.com/PowerShell-LiveId?BasicAuthToOAuthConversion=true `
                                    -Credential $Ctoken `
                                    -Authentication Basic `
                                    -AllowRedirection `
                                    -ErrorAction SilentlyContinue
                                    $Global:UseModernAuth = $True
                                }
                                
                            }
                            $WarningPreference='silentlycontinue'
                            $Global:ExchangeOnlineModules = Import-PSSession $Global:ExchangeOnlineSession -AllowClobber -ErrorAction SilentlyContinue
                            $ExchangeOnlineModuleImport = Import-Module $ExchangeOnlineModules -Global -ErrorAction SilentlyContinue
                        }
                        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'
                        Get-PSSession -Name 'ExchangeOnline' -ErrorAction SilentlyContinue | Remove-PSSession
                        Write-Verbose -Message "Exchange Online connection failed."
                        Write-Verbose -Message "Waiting 60 seconds..."
                        Start-Sleep -Seconds 60
                        try
                        {
                            Write-Verbose -Message "Opening New ExchangeOnline Session."
                            $VerbosePreference = 'SilentlyContinue'
                            Get-PSSession -Name 'ExchangeOnline' -ErrorAction SilentlyContinue | Remove-PSSession -ErrorAction SilentlyContinue
                            $WarningPreference='silentlycontinue'
                            $Global:ExchangeOnlineSession = New-PSSession -Name 'ExchangeOnline' -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $O365Credential -Authentication Basic -AllowRedirection
                            $Global:ExchangeOnlineModules = Import-PSSession $Global:ExchangeOnlineSession -AllowClobber -ErrorAction SilentlyContinue
                            $ExchangeOnlineModuleImport = Import-Module $ExchangeOnlineModules -Global -ErrorAction SilentlyContinue
                        }
                        catch
                        {
                            $VerbosePreference = 'SilentlyContinue'
                            $WarningPreference = "SilentlyContinue"
                            $Global:ExchangeOnlineSession = $null
                            Close-SessionsAndReturnError -ExceptionMessage $_.Exception
                        }
                    }
                }
            }
            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
        }
        'SecurityComplianceCenter'
        {
            $WarningPreference='silentlycontinue'
            $Global:SessionSecurityCompliance = Get-PSSession | Where-Object{$_.ComputerName -like "*.ps.compliance.protection.outlook.com" -and $_.State -eq "Opened"}
            #Try Catch doesn't work even with $Global:ErrorActionPreference = "stop"
            if ($null -eq $Global:SessionSecurityCompliance)
            {
                try
                {
                    Write-Verbose -Message "Session to Security & Compliance no working session found, creating a new one"
                    $Global:SessionSecurityCompliance = New-PSSession -ConfigurationName "Microsoft.Exchange" `
                    -ConnectionUri https://ps.compliance.protection.outlook.com/powershell-liveid/ `
                    -Credential $O365Credential `
                    -Authentication Basic `
                    -ErrorAction Stop `
                    -AllowRedirection               
                }
                catch
                {
                    $clientid = "a0c73c16-a7e3-4564-9a95-2bdf47383716";
                    $RessourceURI = "https://ps.compliance.protection.outlook.com";
                    $RedirectURI = "urn:ietf:wg:oauth:2.0:oob";
                    $AuthHeader = Get-AuthHeader -UserPrincipalName $Global:o365Credential.UserName -RessourceURI $RessourceURI -clientID $clientID -RedirectURI $RedirectURI
                    $Password = ConvertTo-SecureString -AsPlainText $AuthHeader -Force
                    $Ctoken = New-Object System.Management.Automation.PSCredential -ArgumentList $Global:o365Credential.UserName, $Password
                    $Global:SessionSecurityCompliance = New-PSSession -ConfigurationName Microsoft.Exchange `
                    -ConnectionUri https://ps.compliance.protection.outlook.com/powershell-liveid?BasicAuthToOAuthConversion=true `
                    -Credential $Ctoken `
                    -Authentication Basic `
                    -AllowRedirection
                    $Global:UseModernAuth = $True
                }
            }
            else 
            {
                Write-Verbose -Message "Session to Security & Compliance already exists, re-using existing session"
            }
            $WarningPreference='silentlycontinue'
            if ($null -eq $Global:SCModule)
            {
                $Global:SCModule = Import-PSSession $Global:SessionSecurityCompliance  `
                        -ErrorAction SilentlyContinue `
                        -AllowClobber

                Import-Module $Global:SCModule -Global | Out-Null
            }
            return
        }
        'MSOnline'
        {
            $testCmdlet = "Get-MsolUser";
            $exceptionStringMFA = "AADSTS";
            $clientid = "1b730954-1685-4b74-9bfd-dac224a7b894";
            $RessourceURI = "https://graph.windows.net";
            $RedirectURI = "urn:ietf:wg:oauth:2.0:oob";
            $connectCmdlet = "Connect-MsolService";
            $connectCmdletArgs = "-Credential `$Global:o365Credential";
            $connectCmdletMfaRetryArgs = "-AdGraphAccessToken `$AuthToken";
            $variablePrefix = "msol"
        }
        'PnP'
        {
            if ([string]::IsNullOrEmpty($ConnectionUrl))
            {
                # If we haven't specified a ConnectionUrl, just make the connection URL central admin
                $Global:spoAdminUrl = Get-SPOAdminUrl;
                $Global:ConnectionUrl = $Global:spoAdminUrl
            }
            else
            {
                $Global:ConnectionUrl = $ConnectionUrl
            }
            Write-Verbose -Message "`$Global:ConnectionUrl is $Global:ConnectionUrl."
            $testCmdlet = "Get-PnPSite";
            $exceptionStringMFA = "sign-in name or password does not match one in the Microsoft account system";
            $clientid = "9bc3ab49-b65d-410a-85ad-de819febfddc";
            $RessourceURI = $Global:ConnectionUrl;
            $RedirectURI = "https://oauth.spops.microsoft.com/";
            $connectCmdlet = "Connect-PnPOnline";
            $connectCmdletArgs = "-Url `$Global:ConnectionUrl -Credentials `$Global:o365Credential";
            $connectCmdletMfaRetryArgs = "-AccessToken `$AuthToken";
            $variablePrefix = "pnp"
        }
        'MicrosoftTeams'
        {
            # Need to force-import this for some reason as of 1.0.0
            Import-Module MicrosoftTeams -Force
            $testCmdlet = "Get-Team";
            $exceptionStringMFA = "AADSTS";
            $clientid = "12128f48-ec9e-42f0-b203-ea49fb6af367";
            $RessourceURI = "https://graph.windows.net";
            $RedirectURI = "https://teamscmdlet.microsoft.com";
            $connectCmdlet = "Connect-MicrosoftTeams";
            $connectCmdletArgs = "-Credential `$Global:o365Credential";
            #$connectCmdletMfaRetryArgs = "-AadAccessToken `$AuthToken -AccountId `$Global:o365Credential.UserName";
            $connectCmdletMfaRetryArgs = "-AccountId `$Global:o365Credential.UserName";
            $variablePrefix = "teams"
        }
    }

    New-Variable -Name $variablePrefix"LoginSucceeded" -Value $false -Scope Global -Option AllScope -Force
    Write-Debug -Message `$$variablePrefix"LoginSucceeded is '$(Get-Variable -Name $($variablePrefix+"LoginSucceeded") -ValueOnly -Scope Global -ErrorAction SilentlyContinue)'."
    try
    {
        Write-Verbose -Message "Checking $Platform login..."
        # Run a simple command to check if we are logged in
        Write-Debug -Message "Running '$testCmdlet -ErrorAction Stop -WarningAction SilentlyContinue | Out-Null'"
        Invoke-Expression -Command "$testCmdlet -ErrorAction Stop -WarningAction SilentlyContinue | Out-Null"
        if ($? -eq $false)
        {
            throw
        }
        elseif ($Platform -eq "PnP")
        {
            $CurrentPnPConnection = (Get-PnPConnection).Url
            if ($ConnectionUrl -ne $CurrentPnPConnection)
            {
                throw "PnP requires you to reconnect to new location using $connectCmdlet"
            }
            else
            {
                Write-Verbose -Message "You are already logged in to $Platform."
            }
        }
        else
        {
            Write-Debug -Message "'$testCmdlet -ErrorAction Stop -WarningAction SilentlyContinue | Out-Null' succeeded."
            Write-Verbose -Message "You are already logged in to $Platform."
        }
    }
    catch
    {
        if ($_.Exception -like "*$connectCmdlet*" -or $_.Exception -like "*The access token expiry*")
        {
            Write-Debug -Message "Running '$testCmdlet' failed on initial attempt."
            try
            {
                # Prompt for Windows-style credentials if we don't already have a credential object
                if ($null -eq $Global:o365Credential)
                {
                    Write-Host -ForegroundColor Cyan " - Prompting for Microsoft Online credentials..."
                    $Global:o365Credential = Get-Credential -Message "Please enter your credentials for MS Online Services:"
                    if ($null -eq $Global:o365Credential)
                    {
                        throw "Microsoft Online credentials must be supplied."
                    }
                    Write-Verbose -Message "Will now attempt to use credential for '$($Global:o365Credential.UserName)'..."
                }
                if($_.Exception -like "*The access token expiry*")
                {
                    throw
                }
                if($Global:UseModernAuth -eq $True)
                {
                    throw
                }
                Write-Verbose -Message "Running '$connectCmdlet -ErrorAction Stop $connectCmdletArgs -ErrorVariable `$err | Out-Null'"
                Invoke-Expression -Command "$connectCmdlet -ErrorAction Stop $connectCmdletArgs -ErrorVariable `$err | Out-Null"
                if ($? -eq $false -or $err)
                {
                    throw
                }
                else
                {
                    New-Variable -Name $variablePrefix"LoginSucceeded" -Value $true -Scope Global -Option AllScope -Force
                    Write-Debug -Message `$$variablePrefix"LoginSucceeded is now '$(Get-Variable -Name $($variablePrefix+"LoginSucceeded") -ValueOnly -Scope Global -ErrorAction SilentlyContinue)'."
                }
            }
            catch
            {
                Write-Debug -Message "Login using '$connectCmdlet' and '$connectCmdletArgs' failed on initial attempt."
                if ($_.Exception -like "*User canceled authentication*")
                {
                    throw "User canceled authentication"
                }
                elseif ($_.Exception -like "*The user name or password is incorrect*" -or $_.Exception -like "*ID3242*")
                {
                    throw  "Bad credentials were supplied"
                }
                elseif ($_.Exception -like "*$exceptionStringMFA*" -or `
                        $_.Exception -like "*Sequence contains no elements*" -or `
                        ($_.Exception -like "*System.Reflection.TargetInvocationException: Exception has been thrown*" -and $Platform -eq "PNP") -or `
                        ($_.Exception -like "*or the web site does not support SharePoint Online credentials*" -and $Platform -eq "SharePointOnline") -or `
                        ($_.Exception -like "*The access token expiry*" -and $Platform -eq "Azure") -or `
                        $Global:UseModernAuth -eq $True)
                {
                    Write-Verbose -Message "The specified account is configured for Multi-Factor Authentication. Please re-enter your credentials."
                    Write-Host -ForegroundColor Green " - Prompting for credentials with MFA for $Platform"
                    try
                    {
                        Write-Debug -Message "Replacing connection parameters '$connectCmdletArgs' with '$connectCmdletMfaRetryArgs'..."
                        if($Platform -ne "SharePointOnline" -and $Platform -ne "MicrosoftTeams"){
                            $AuthHeader = Get-AuthHeader -UserPrincipalName $Global:o365Credential.UserName -RessourceURI $RessourceURI -clientID $clientID -RedirectURI $RedirectURI
                            $AuthToken = $AuthHeader.split(" ")[1]
                        }
                        Invoke-Expression -Command "$connectCmdlet -ErrorAction Stop $connectCmdletMfaRetryArgs | Out-Null"
                        if ($? -eq $false)
                        {
                            throw
                        }
                        else
                        {
                            New-Variable -Name $variablePrefix"LoginSucceeded" -Value $true -Scope Global -Option AllScope -Force
                            Write-Debug $variablePrefix"LoginSucceeded is now '$(Get-Variable -Name $($variablePrefix+"LoginSucceeded") -ValueOnly -Scope Global -ErrorAction SilentlyContinue)'."
                            $Global:UseModernAuth = $True
                        }
                    }
                    catch
                    {
                        Write-Debug -Message "Login using '$connectCmdlet' and '$connectCmdletMfaRetryArgs' failed."
                        Write-Host -ForegroundColor Red $_.Exception
                        throw $_
                    }
                }
                else
                {
                    Write-Host -ForegroundColor Red $_.Exception
                    throw $_
                }
            }
        }
        elseif ($_.Exception -like "*Unable to acquire token for tenant*")
        {
           Write-Host -ForegroundColor Red $_.Exception
        }
        elseif ($_.Exception -like "*null array*")
        {
            # Do nothing
        }
        elseif ($_.Exception -like "*$testCmdlet*")
        {
            # If the exception contains the name of the cmdlet we're trying to run, we probably don't have the required module installed yet
            Write-Error -Message "It appears you don't have the '$Platform' module installed, or it isn't loaded. Please install/load the module and try again."
        }
        elseif ($_.Exception -like "*this.Client.SubscriptionId*" -and $Platform -eq "Azure")
        {
            throw "It appears there are no Azure subscriptions associated with the account '$($Global:o365Credential.UserName)'."
        }
        else
        {
            Write-Host -ForegroundColor Red $_.Exception
        }
    }
    finally
    {
        if (Get-Variable -Name $variablePrefix"LoginSucceeded" -ValueOnly -Scope "Global")
        {
            Write-Verbose -Message " - Successfully logged in to $Platform."
            # Extra step needed if we're logging into Azure - in case we have multiple subs we need to prompt for one
            if ($Platform -eq "Azure")
            {
                [array]$subscriptions = Get-AzSubscription -WarningAction Continue
                # Prompt for a subscription in case we have more than one
                if ($subscriptions.Count -gt 1)
                {
                    Write-Host -ForegroundColor Cyan " - Prompting for Azure subscription..."
                    $Global:subscriptionDetails = Get-AzSubscription -WarningAction SilentlyContinue | Sort-Object Name | Out-GridView -Title "Select ONE subscription..." -PassThru
                    if ($null -eq $subscriptionDetails)
                    {
                        throw " - A subscription must be selected."
                    }
                    elseif ($subscriptionDetails.Count -gt 1)
                    {
                        throw " - Please select *only one* subscription."
                    }
                    Write-Host -ForegroundColor White " - Setting active subscription to '$($Global:subscriptionDetails.Name)'..."
                    Set-AzContext -Subscription $Global:subscriptionDetails.Id
                }
            }

        }
    }
}

function Get-SPOAdminUrl
{
    [CmdletBinding()]
    [OutputType([System.String])]
    param
    (
    )

    Write-Verbose -Message "Connection to Azure AD is required to automatically determine SharePoint Online admin URL..."
    Test-MSCloudLogin -Platform AzureAD
    Write-Verbose -Message "Getting SharePoint Online admin URL..."
    $defaultDomain = Get-AzureADDomain | Where-Object {$_.Name -like "*.onmicrosoft.com" -and $_.IsInitial -eq $true} # We don't use IsDefault here because the default could be a custom domain
    $tenantName = $defaultDomain[0].Name -replace ".onmicrosoft.com",""
    $spoAdminUrl = "https://$tenantName-admin.sharepoint.com"
    Write-Verbose -Message "SharePoint Online admin URL is $spoAdminUrl"
    return $spoAdminUrl
}

function Get-AzureADDLL
{
    [CmdletBinding()]
    [OutputType([System.String])]
    param(
    )
    [array]$AzureADModules = Get-Module -ListAvailable | Where-Object{$_.name -eq "AzureAD" -or $_.name -eq "AzureADPreview"}
    if($AzureADModules.count -eq 0)
    {
        Throw "Can't find Azure AD DLL. Install the module manually 'Install-Module AzureAD'"
    }
    else
    {
        $AzureDLL = Join-Path (($AzureADModules | Sort-Object version -Descending | Select-Object -first 1).Path | split-Path) Microsoft.IdentityModel.Clients.ActiveDirectory.dll
        Return $AzureDLL
    }

}

function Get-TenantLoginEndPoint
{
    [CmdletBinding()]
    [OutputType([System.String])]
    Param(
        [Parameter(Mandatory = $True)]
        [System.String]
        $TenantName,
        [Parameter(Mandatory = $false)]
        [System.String]
        [ValidateSet('MicrosoftOnline','EvoSTS')]
        $LoginSource = "EvoSTS"
    )
    $TenantInfo = @{}
    if($LoginSource -eq "EvoSTS")
    {
        $webrequest = Invoke-WebRequest -Uri https://login.windows.net/$($TenantName)/.well-known/openid-configuration -UseBasicParsing
    }
    else {
        $webrequest = Invoke-WebRequest -Uri https://login.microsoftonline.com/$($TenantName)/.well-known/openid-configuration -UseBasicParsing
    }
    if($webrequest.StatusCode -eq 200){
        $webrequest.content.replace("{","").replace("}","").split(",") | ForEach-Object { if($_ -like '*:*'){ $TenantInfo[(($_.split(":")[0]).replace('"',''))]= ($_.substring($($_.split(":")[0]).length +1)).replace('"','') } }
    }
    Return $TenantInfo
}

function New-ADALServiceInfo
{
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $True)]
        [System.String]
        $TenantName,
        [Parameter(Mandatory = $True)]
        [System.String]
        $UserPrincipalName,
        [Parameter(Mandatory = $false)]
        [System.String]
        [ValidateSet('MicrosoftOnline','EvoSTS')]
        $LoginSource = "EvoSTS"
    )
    $AzureADDLL = Get-AzureADDLL
    if([string]::IsNullOrEmpty($AzureADDLL))
    {
        Throw "Can't find Azure AD DLL"
        Exit
    }
    else
    {
        $tMod = [System.Reflection.Assembly]::LoadFrom($AzureADDLL)
    }
    $TenantInfo = Get-TenantLoginEndPoint -TenantName $TenantName
    if([string]::IsNullOrEmpty($TenantInfo))
    {
        Throw "Can't find Tenant Login Endpoint"
        Exit
    }
    else
    {
        [string] $authority = $($TenantInfo.get_item("authorization_endpoint"))
    }
    $PromptBehavior = [Microsoft.IdentityModel.Clients.ActiveDirectory.PromptBehavior]::Auto
    $Service=@{}
    $Service["authContext"] = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority   
    $Service["platformParam"] = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.PlatformParameters" -ArgumentList $PromptBehavior
    $Service["userId"] = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifier" -ArgumentList $UserPrincipalName, "OptionalDisplayableId"
    Return $Service
}

function Get-AuthHeader
{
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $True)]
        [System.String]
        $UserPrincipalName,
        [Parameter(Mandatory = $True)]
        $RessourceURI,
        [Parameter(Mandatory = $True)]
        $clientId,
        [Parameter(Mandatory = $True)]
        [System.String]
        $RedirectURI
    )
    if($null -eq $Global:ADALServicePoint)
    {
        $TenantName = $UserPrincipalName.split("@")[1]
        $Global:ADALServicePoint = New-ADALServiceInfo -TenantName $TenantName -UserPrincipalName $UserPrincipalName
    }
    
    try{
        Write-Debug "Looking for a refresh token"
        $authResult = $Global:ADALServicePoint.authContext.AcquireTokenSilentAsync($RessourceURI, $clientId)
        if($null -eq $authResult.result)
        {
            Write-Debug "Creating a new Token"
            $authResult = $Global:ADALServicePoint.authContext.AcquireTokenAsync($RessourceURI, $clientId, $RedirectURI, $Global:ADALServicePoint.platformParam, $Global:ADALServicePoint.userId)
        }
        $AuthHeader=$authResult.result.CreateAuthorizationHeader()
        }
    catch{
        Throw "Can't create Authorization header"
    }
    Return $AuthHeader
}