Public/Connect-Avd.ps1

function Connect-Avd {
    <#
    .SYNOPSIS
        Get an access token using either authorization code flow or device code flow, that can be used to authenticate and authorize against resources in Azure.
    .DESCRIPTION
        Get an access token using either authorization code flow or device code flow, that can be used to authenticate and authorize against resources in Azure.
    .PARAMETER TenantID
        Specify the tenant name or ID, e.g. tenant.onmicrosoft.com or <GUID>.
    .PARAMETER ClientID
        Application ID (Client ID) for an Azure AD service principal. Uses by default the 'Microsoft Azure PowerShell' service principal Application ID.
    .PARAMETER ClientSecret
        Application secret is using a custom Azure AD service principal.
    .PARAMETER RedirectUri
        Specify the Redirect URI (also known as Reply URL) of the custom Azure AD service principal.
    .PARAMETER DeviceCode
        Specify delegated login using devicecode flow, you will be prompted to navigate to https://microsoft.com/devicelogin
    .PARAMETER SubscriptionId
        Specify a subscription ID to set the Azure context. Can be changed later using Set-AvdContext.
    .PARAMETER Refresh
        Specify to refresh an existing access token.
    .EXAMPLE
        Connect-Avd -TenantID $Tenantid -SubscriptionId $SubscriptionId -DeviceCode
    .EXAMPLE
        Connect-Avd -ClientID xxxx -ClientSecret "xxxxx" -TenantID $Tenantid -SubscriptionId $SubscriptionId
    #>

    [CmdletBinding(DefaultParameterSetName = "ClientSecret")]
    param(
        [parameter(Mandatory, ParameterSetName = "DeviceCode")]
        [parameter(ParameterSetName = "ClientSecret")]
        [parameter(ParameterSetName = "Refresh")]
        [ValidateNotNullOrEmpty()]
        [string]$TenantID,

        [parameter(ParameterSetName = "ClientSecret", HelpMessage = "Application ID (Client ID) for an Azure AD service principal. Uses by default the 'Microsoft Azure PowerShell' service principal Application ID.")]
        [parameter(ParameterSetName = "DeviceCode")]
        [ValidateNotNullOrEmpty()]
        [string]$ClientID = "1950a258-227b-4e31-a9cf-717495945fc2",

        [parameter(ParameterSetName = "ClientSecret", HelpMessage = "Specify the Redirect URI (also known as Reply URL) of the custom Azure AD service principal.")]
        [parameter(ParameterSetName = "DeviceCode")]
        [ValidateNotNullOrEmpty()]
        [string]$RedirectUri = [string]::Empty,

        [parameter(Mandatory, HelpMessage = "Specify the subscription ID to connect to")]
        [ValidateNotNullOrEmpty()]
        [string]$SubscriptionId,

        [parameter(ParameterSetName = "ClientSecret", HelpMessage = "Specify the client secret of your registered application")]
        [string]$ClientSecret,

        [parameter(ParameterSetName = "ClientSecret", HelpMessage = "Specify the subscription ID to connect to")]
        [parameter(ParameterSetName = "DeviceCode", HelpMessage = "Specify the subscription ID to connect to")]
        [ValidateNotNullOrEmpty()]
        [string]$Scope = "https://management.azure.com//.default",

        [parameter(Mandatory, ParameterSetName = "DeviceCode", HelpMessage = "Specify to do delegated login using devicecode flow, you will be prompted to navigate to https://microsoft.com/devicelogin")]
        [switch]$DeviceCode,

        [parameter(ParameterSetName = "Refresh", HelpMessage = "Specify to refresh an existing access token.")]
        [string]$RefreshToken,

        [parameter(ParameterSetName = "AccessToken", HelpMessage = "Provide an access token to use for authentication.")]
        [string]$AccessToken
    )
    Begin {
        $global:TenantId = $TenantID
        if ($SubscriptionId) {
            Write-Verbose "Subscription ID provided, setting context to $SubscriptionId"
            $global:subscriptionId = $SubscriptionId
        }
        # Determine the correct RedirectUri (also known as Reply URL) to use with MSAL.PS
        if ($ClientID -eq "1950a258-227b-4e31-a9cf-717495945fc2") {
            $RedirectUri = "urn:ietf:wg:oauth:2.0:oob"
        }
        else {
            if (-not([string]::IsNullOrEmpty($ClientID))) {
                Write-Verbose -Message "Using custom Azure AD service principal specified with Application ID: $($ClientID)"

                # Adjust RedirectUri parameter input in case non was passed on command line
                if ([string]::IsNullOrEmpty($RedirectUri)) {
                    switch -Wildcard ($PSVersionTable["PSVersion"]) {
                        "5.*" {
                            $RedirectUri = "https://login.microsoftonline.com/common/oauth2/nativeclient"
                        }
                        "7.*" {
                            $RedirectUri = "http://localhost"
                        }
                    }
                }
            }
        }
        Write-Verbose -Message "Using RedirectUri with value: $($RedirectUri)"

        # Set default error action preference configuration
        $ErrorActionPreference = "Stop"
    }
    Process {
        Write-Verbose -Message "Using authentication flow: $($PSCmdlet.ParameterSetName)"
        try {
            switch ($PSCmdlet.ParameterSetName) {
                "DeviceCode" {
                    if ($null -ne $global:tokenRequest -and ($global:tokenRequest.scope -eq 'user_impersonation')){
                        throw "You allready are logged in with user_impersonation, disconnect first if you want to reauthenticate. Use Disconnect-Avd"
                    }
                    $clientBody = @{
                        client_id = $ClientId
                        tenant    = $TenantID
                        scope     = $Scope
                    }
                    $requestUrl = Invoke-WebRequest -Method POST -Uri "https://login.microsoftonline.com/$($TenantID)/oauth2/v2.0/devicecode" -Body $clientBody
                    $content = ($requestUrl.Content | ConvertFrom-Json)
                    if ([string]::IsNullOrEmpty($global:tokenRequest.access_token)) {
                        Write-Information "`n$($content.message)" -InformationAction Continue
                    }
                    # Get OAuth Token
                    $tokenBody = @{
                        grant_type = "urn:ietf:params:oauth:grant-type:device_code"
                        code       = $content.device_code
                        client_id  = $ClientId
                    }
                    while ([string]::IsNullOrEmpty($global:tokenRequest.access_token)) {
                        $global:tokenRequest = try {
                            Invoke-RestMethod -Method POST -Uri "https://login.microsoftonline.com/$TenantID/oauth2/token" -Body $tokenBody
                        }
                        catch {
                            $errorMessage = $_.ErrorDetails.Message | ConvertFrom-Json
                            # If not waiting for auth, throw error
                            if ($errorMessage.error -ne "authorization_pending") {
                                throw "Authorization is pending."
                            }
                        }
                    }
                }
                "ClientSecret" {
                    if ($null -ne $global:tokenRequest -and (!($global:tokenRequest.scope))){
                        throw "You allready are logged in with a service principal, disconnect first if you want to reauthenticate. Use Disconnect-Avd"
                    }
                    # Must be this URL. https://management.azure.com is not working while using the resource object. The scope object is ignored when providing management.azure.com
                    $Scope = 'https://management.core.windows.net/'
                    $tokenBody = @{
                        grant_type    = "client_credentials"
                        client_id     = $ClientID
                        client_secret = $ClientSecret
                        resource      = $Scope
                    }
                    $contentType = "application/x-www-form-urlencoded"
                    $global:tokenRequest = try {
                        Invoke-RestMethod -Method POST -Uri "https://login.microsoftonline.com/$TenantID/oauth2/token" -Body $tokenBody -ContentType $contentType
                    }
                    catch {
                        $errorMessage = $_.ErrorDetails.Message | ConvertFrom-Json
                        # If not waiting for auth, throw error
                        if ($errorMessage.error -ne "authorization_pending") {
                            throw "Authorization is pending."
                        }
                    }
                }
                "Refresh" {
                    Write-Verbose "Refreshing token"
                    $tokenBody = @{
                        grant_type    = "refresh_token"
                        client_id     = $ClientId
                        refresh_token = $RefreshToken
                        scope         = $global:tokenRequest.scope
                    }
                    $global:tokenRequest = try {
                        Invoke-RestMethod -Method POST -Uri "https://login.microsoftonline.com/$TenantID/oauth2/token" -Body $tokenBody
                    }
                    catch {
                        $errorMessage = $_.ErrorDetails.Message | ConvertFrom-Json
                        # If not waiting for auth, throw error
                    }
                }
                "AccessToken" {
                    Write-Verbose "Using provided access token, that is ease for me. Thank you for that."
                    $global:tokenRequest = [PSCustomObject]@{
                        access_token = $AccessToken
                    }
                }
            }
            Write-Verbose "Token is $($global:tokenRequest)"
            $global:authHeader = @{
                'Content-Type' = 'application/json'
                Authorization  = "Bearer {0}" -f $global:tokenRequest.access_token
            }
            Write-Information "Succesfully connected to scope $scope"
            return $global:authHeader
        }
        catch [System.Exception] {
            Write-Error -Message "An error occurred while constructing parameter input for access token retrieval. Error message: $($PSItem.Exception.Message)"
        }
    }
}