Workloads/MicrosoftGraph.ps1

function Connect-MSCloudLoginMicrosoftGraph
{
    [CmdletBinding()]
    param()

    $ProgressPreference = 'SilentlyContinue'
    $source = 'Connect-MSCloudLoginMicrosoftGraph'

    if (Test-MSCloudLoginConnectionReusable -WorkloadProfile $Script:MSCloudLoginConnectionProfile.MicrosoftGraph `
            -ProbeScript { Get-MgContext } `
            -Source $source)
    {
        return
    }

    if ($Script:CustomEnvConfig.CustomEnvironment)
    {
        $customEnv = Get-MgEnvironment | Where-Object { $_.Name -eq 'Custom' }
        if ($null -eq $customEnv)
        {
            Add-MgEnvironment -Name 'Custom' -GraphEndpoint $Script:CustomEnvConfig.CustomGraphResourceUrl -AzureADEndPoint $Script:CustomEnvConfig.CustomGraphTokenUrl
        }
    }

    if ($Script:MSCloudLoginConnectionProfile.MicrosoftGraph.AuthenticationType -eq 'CredentialsWithApplicationId' -or
        $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.AuthenticationType -eq 'Credentials')
    {
        Add-MSCloudLoginAssistantEvent -Message 'Will try connecting with user credentials' -Source $source
        Connect-MSCloudLoginMSGraphWithUser
    }
    elseif ($Script:MSCloudLoginConnectionProfile.MicrosoftGraph.AuthenticationType -eq 'CredentialsWithTenantId')
    {
        Add-MSCloudLoginAssistantEvent -Message 'Will try connecting with user credentials and Tenant Id' -Source $source
        Connect-MSCloudLoginMSGraphWithUser -TenantId $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.TenantId
    }
    elseif ($Script:MSCloudLoginConnectionProfile.MicrosoftGraph.AuthenticationType -eq 'Identity')
    {
        Add-MSCloudLoginAssistantEvent -Message 'Connecting with managed identity' -Source $source

        $resourceEndpoint = $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.ResourceUrl.TrimEnd('/')
        $accessToken = Get-AuthToken -Resource $resourceEndpoint -Identity

        $accessToken = $accessToken | ConvertTo-SecureString -AsPlainText -Force
        Connect-MgGraph -AccessToken $accessToken -Environment $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.GraphEnvironment -NoWelcome
        $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.CompleteConnection()
        $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.TenantId = (Get-MgContext).TenantId
    }
    else
    {
        try
        {
            if ($Script:MSCloudLoginConnectionProfile.MicrosoftGraph.AuthenticationType -eq 'ServicePrincipalWithThumbprint')
            {
                if (($Script:CustomEnvConfig.CustomEnvironment -or $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.GraphEnvironment -eq 'Custom') -and `
                    $null -ne $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.Scope -and `
                    $null -ne $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.TokenUrl)
                {
                    $accessToken = Get-MSCloudLoginAccessToken -ConnectionUri $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.Scope `
                        -AzureADAuthorizationEndpointUri $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.TokenUrl `
                        -ApplicationId $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.ApplicationId `
                        -TenantId $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.TenantId `
                        -CertificateThumbprint $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.CertificateThumbprint
                    $accessToken = ConvertTo-SecureString $accessToken -AsPlainText -Force
                    Connect-MgGraph -AccessToken $accessToken -NoWelcome -Environment $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.GraphEnvironment
                    Add-MSCloudLoginAssistantEvent -Message 'Successfully connected to the Microsoft Graph API using Certificate Thumbprint' -Source $source
                }
                else
                {
                    Add-MSCloudLoginAssistantEvent -Message 'Connecting by Environment Name' -Source $source
                    $cert = Get-MSCloudLoginCertificate -CertificateThumbprint $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.CertificateThumbprint
                    Connect-MgGraph -ClientId $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.ApplicationId `
                        -TenantId $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.TenantId `
                        -Environment $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.GraphEnvironment `
                        -Certificate $cert `
                        -NoWelcome `
                        -ErrorAction Stop
                }

                $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.CompleteConnection()
            }
            elseif ($Script:MSCloudLoginConnectionProfile.MicrosoftGraph.AuthenticationType -eq 'ServicePrincipalWithSecret')
            {
                Add-MSCloudLoginAssistantEvent -Message 'Connecting to Microsoft Graph with ApplicationSecret' -Source $source
                $secStringPassword = ConvertTo-SecureString -String $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.ApplicationSecret -AsPlainText -Force
                $userName = $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.ApplicationId
                [pscredential]$credObject = New-Object System.Management.Automation.PSCredential ($userName, $secStringPassword)
                Connect-MgGraph -TenantId $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.TenantId `
                    -ClientSecretCredential $credObject `
                    -Environment $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.GraphEnvironment `
                    -NoWelcome
                $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.CompleteConnection()
            }
            elseif ($Script:MSCloudLoginConnectionProfile.MicrosoftGraph.AuthenticationType -eq 'ServicePrincipalWithPath')
            {
                Add-MSCloudLoginAssistantEvent -Message 'Connecting to Microsoft Graph with Certificate Path' -Source $source
                $certificate = Get-MSCloudLoginCertificate -CertificatePath $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.CertificatePath `
                    -CertificatePassword $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.CertificatePassword
                Connect-MgGraph -TenantId $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.TenantId `
                    -ClientId $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.ApplicationId `
                    -Certificate $certificate `
                    -Environment $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.GraphEnvironment `
                    -NoWelcome
                $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.CompleteConnection()
            }
            elseif ($Script:MSCloudLoginConnectionProfile.MicrosoftGraph.AuthenticationType -eq 'AccessTokens')
            {
                Add-MSCloudLoginAssistantEvent -Message 'Connecting to Microsoft Graph with AccessToken' -Source $source
                $secStringAccessToken = ConvertTo-SecureString -String (Get-MSCloudLoginAccessTokenValue -Token $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.AccessTokens[0]) -AsPlainText -Force
                Connect-MgGraph -Environment $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.GraphEnvironment `
                    -AccessToken $secStringAccessToken `
                    -NoWelcome
                $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.CompleteConnection()
            }
            else
            {
                throw "Authentication type '$($Script:MSCloudLoginConnectionProfile.MicrosoftGraph.AuthenticationType)' is not supported for workload 'MicrosoftGraph'."
            }
            Add-MSCloudLoginAssistantEvent -Message 'Connected' -Source $source
        }
        catch
        {
            $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.Connected = $false
            Add-MSCloudLoginAssistantEvent -Message "Failed to connect to Microsoft Graph: $($_.Exception.Message)" -Source $source -EntryType 'Error'
            throw
        }
    }
}

function Connect-MSCloudLoginMSGraphWithUser
{
    [CmdletBinding()]
    param(
        [Parameter()]
        [System.String]
        $TenantId
    )

    if ($Script:CustomEnvConfig.CustomEnvironment)
    {
        $customEnv = Get-MgEnvironment | Where-Object { $_.Name -eq 'Custom' }
        if ($null -eq $customEnv)
        {
            Add-MgEnvironment -Name 'Custom' -GraphEndpoint $Script:CustomEnvConfig.CustomGraphResourceUrl -AzureADEndPoint $Script:CustomEnvConfig.CustomGraphTokenUrl
        }
    }

    $source = 'Connect-MSCloudLoginMSGraphWithUser'

    if ($Script:MSCloudLoginConnectionProfile.MicrosoftGraph.Credentials.UserName -ne (Get-MgContext).Account)
    {
        Add-MSCloudLoginAssistantEvent -Message "The currently connected account doesn't match the one we're trying to authenticate with. Disconnecting from Graph." -Source $source
        try
        {
            Disconnect-MgGraph -ErrorAction Stop | Out-Null
        }
        catch
        {
            Add-MSCloudLoginAssistantEvent -Message "Disconnecting from Microsoft Graph failed (likely no connection existed): $($_.Exception.Message)" -Source $source
        }
    }

    if ([System.String]::IsNullOrEmpty($Script:MSCloudLoginConnectionProfile.MicrosoftGraph.ApplicationId))
    {
        $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.ApplicationId = '14d82eec-204b-4c2f-b7e8-296a70dab67e'
    }

    Add-MSCloudLoginAssistantEvent -Message 'Requesting Access Token for Microsoft Graph' -Source $source

    try
    {
        try
        {
            $request = Get-AuthToken -AuthorizationUrl $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.AuthorizationUrl `
                -Credential $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.Credentials `
                -ClientId $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.ApplicationId `
                -Scope $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.Scope `
                -TenantId (Get-MSCloudLoginTenantDomainFromCredentials -Credentials $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.Credentials)

            $AccessToken = ConvertTo-SecureString -String $request.access_token -AsPlainText -Force

            Add-MSCloudLoginAssistantEvent -Message "Connecting to Microsoft Graph - Environment {$($Script:MSCloudLoginConnectionProfile.MicrosoftGraph.GraphEnvironment)}" -Source $source

            # Domain.Read.All permission Scope is required to get the domain name for the SPO Admin Center.
            Connect-MgGraph -AccessToken $AccessToken `
                    -Environment $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.GraphEnvironment `
                    -NoWelcome
            $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.CompleteConnection()
            $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.AccessTokens = @($request.access_token)
        }
        catch
        {
            if ((Test-MSCloudLoginMFARequiredError -ErrorRecord $_))
            {
                Add-MSCloudLoginAssistantEvent -Message 'Account used requires MFA' -Source $source
                Connect-MSCloudLoginMSGraphWithUserMFA
            }
            else
            {
                # Rethrow into the outer catch, which handles the fallback logic.
                throw
            }
        }
    }
    catch
    {
        if ($_.Exception.Message -like 'System.Net.WebException: The remote server returned an error: (400) Bad Request.*' -and `
            (Assert-IsNonInteractiveShell) -eq $true)
        {
            $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.Connected = $false
            throw "Unable to retrieve AccessToken. Have you registered the 'Microsoft Graph PowerShell' application already? Please run 'Connect-MgGraph -Scopes Domain.Read.All' and logon using '$($Script:MSCloudLoginConnectionProfile.MicrosoftGraph.Credentials.Username)'"
        }

        try
        {
            Add-MSCloudLoginAssistantEvent -Message 'Attempting to connect without specifying the Environment' -Source $source
            Connect-MgGraph -AccessToken $AccessToken -NoWelcome -ErrorAction Stop
            $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.CompleteConnection()
            $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.AccessTokens = @($request.access_token)
        }
        catch
        {
            Add-MSCloudLoginAssistantEvent -Message "Error connecting - $_" -Source $source -EntryType 'Error'

            if (Assert-IsNonInteractiveShell)
            {
                $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.Connected = $false
                Add-MSCloudLoginAssistantEvent -Message 'Unable to connect to Microsoft Graph and interactive fallback is not possible in a non-interactive session.' -Source $source -EntryType 'Error'
                throw
            }

            Add-MSCloudLoginAssistantEvent -Message 'Connecting to Microsoft Graph interactively' -Source $source
            try
            {
                Connect-MgGraph -Environment $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.GraphEnvironment `
                    -TenantId $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.TenantId `
                    -ClientId $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.ApplicationId `
                    -Scopes 'Domain.Read.All' -ErrorAction 'Stop' `
                    -NoWelcome
                $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.CompleteConnection()
            }
            catch
            {
                $err = $_
                if ($err.ToString() -like '*\.graph\GraphContext.json*')
                {
                    $pathStart = $err.ToString().IndexOf("to file at '", 0) + 12
                    $pathEnd = $err.ToString().IndexOf("'", $pathStart)
                    $path = $err.ToString().Substring($pathStart, $pathEnd - $pathStart)

                    New-Item $path -Force | Out-Null
                    Connect-MgGraph -Environment $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.GraphEnvironment `
                        -TenantId $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.TenantId `
                        -ClientId $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.ApplicationId `
                        -Scopes 'Domain.Read.All' `
                        -NoWelcome
                    $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.CompleteConnection()
                }
                elseif ($err.Exception.Message -eq 'Device code terminal timed-out after 120 seconds. Please try again.')
                {
                    $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.Connected = $false
                    throw 'Unable to connect to the Microsoft Graph. Please make sure the app permissions are setup correctly. Please run Update-M365DSCAllowedGraphScopes.'
                }
                else
                {
                    $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.Connected = $false
                    Add-MSCloudLoginAssistantEvent -Message "Failed to connect to Microsoft Graph interactively: $($err.Exception.Message)" -Source $source -EntryType 'Error'
                    throw
                }
            }
        }
    }
}

function Connect-MSCloudLoginMSGraphWithUserMFA
{
    [CmdletBinding()]
    param()

    $source = 'Connect-MSCloudLoginMSGraphWithUserMFA'
    if ([System.String]::IsNullOrEmpty($Script:MSCloudLoginConnectionProfile.MicrosoftGraph.TenantId))
    {
        $tenantId = Get-MSCloudLoginTenantDomainFromCredentials -Credentials $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.Credentials
    }
    else
    {
        $tenantId = $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.TenantId
    }

    Add-MSCloudLoginAssistantEvent -Message 'Getting access token from Microsoft Graph using device code' -Source $source

    $request = Get-AuthToken -AuthorizationUrl $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.AuthorizationUrl `
        -Credentials $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.Credentials `
        -TenantId $tenantId `
        -ClientId $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.ApplicationId `
        -Scope $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.Scope `
        -DeviceCode

    $AccessToken = ConvertTo-SecureString -String $request.access_token -AsPlainText -Force

    Add-MSCloudLoginAssistantEvent -Message "Connecting to Microsoft Graph with MFA - Environment {$($Script:MSCloudLoginConnectionProfile.MicrosoftGraph.GraphEnvironment)}" -Source $source
    Connect-MgGraph -AccessToken $AccessToken `
        -Environment $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.GraphEnvironment `
        -NoWelcome

    Add-MSCloudLoginAssistantEvent -Message 'Successfully connected to Microsoft Graph with MFA' -Source $source

    $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.AccessTokens = @($request.access_token)
    $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.CompleteConnection($true)
}

function Disconnect-MSCloudLoginMicrosoftGraph
{
    [CmdletBinding()]
    param()

    $source = 'Disconnect-MSCloudLoginMicrosoftGraph'

    if ($Script:MSCloudLoginConnectionProfile.MicrosoftGraph.Connected)
    {
        Add-MSCloudLoginAssistantEvent -Message 'Attempting to disconnect from Microsoft Graph' -Source $source
        Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null
        $Script:MSCloudLoginConnectionProfile.MicrosoftGraph.Connected = $false
        Add-MSCloudLoginAssistantEvent -Message 'Successfully disconnected from Microsoft Graph' -Source $source
    }
    else
    {
        Add-MSCloudLoginAssistantEvent -Message 'No connections to Microsoft Graph were found' -Source $source
    }
}