Private/Get-WindowsDeviceLinkDeviceCodeToken.ps1

function Get-WindowsDeviceLinkDeviceCodeToken {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$TenantId,

        [ValidateNotNullOrEmpty()]
        [string]$ClientId = '14d82eec-204b-4c2f-b7e8-296a70dab67e',

        [ValidateNotNullOrEmpty()]
        [string]$Scope = 'https://graph.microsoft.com/DeviceManagementServiceConfig.ReadWrite.All offline_access openid profile'
    )

    [Net.ServicePointManager]::SecurityProtocol =
        [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12

    $authority = "https://login.microsoftonline.com/$TenantId/oauth2/v2.0"

    $deviceCodeResponse = Invoke-RestMethod `
        -Method POST `
        -Uri "$authority/devicecode" `
        -ContentType 'application/x-www-form-urlencoded' `
        -Body @{
            client_id = $ClientId
            scope     = $Scope
        } `
        -ErrorAction Stop

    $verificationUri = if ($deviceCodeResponse.verification_uri) { $deviceCodeResponse.verification_uri } else { $deviceCodeResponse.verification_url }

    Write-Information -InformationAction Continue -MessageData (
        "To sign in, open {0} and enter code {1}." -f $verificationUri, $deviceCodeResponse.user_code
    )

    $interval = if ($deviceCodeResponse.interval) { [int]$deviceCodeResponse.interval } else { 5 }
    $expiresAt = [DateTime]::UtcNow.AddSeconds([int]$deviceCodeResponse.expires_in)
    $lastStatusAt = [DateTime]::MinValue

    while ([DateTime]::UtcNow -lt $expiresAt) {
        Start-Sleep -Seconds $interval
        $remaining = $expiresAt - [DateTime]::UtcNow
        if (($remaining.TotalSeconds -gt 0) -and (([DateTime]::UtcNow - $lastStatusAt).TotalSeconds -ge 30)) {
            Write-Information -InformationAction Continue -MessageData ('Waiting for authentication... {0:mm\:ss} remaining.' -f $remaining)
            $lastStatusAt = [DateTime]::UtcNow
        }

        try {
            $tokenResponse = Invoke-RestMethod -Method POST -Uri "$authority/token" -ContentType 'application/x-www-form-urlencoded' -Body @{
                grant_type  = 'urn:ietf:params:oauth:grant-type:device_code'
                client_id   = $ClientId
                device_code = $deviceCodeResponse.device_code
            } -ErrorAction Stop

            if ($tokenResponse.access_token) {
                Write-Information -InformationAction Continue -MessageData 'Device code authentication succeeded.'
                return [pscustomobject]@{
                    AccessToken = $tokenResponse.access_token
                    TokenType   = $tokenResponse.token_type
                    ExpiresIn   = $tokenResponse.expires_in
                    Scope       = $tokenResponse.scope
                    ClientId    = $ClientId
                    TenantId    = $TenantId
                }
            }
        }
        catch {
            $errorCode = $null
            $errorDescription = $null
            $rawBody = if ($_.ErrorDetails -and $_.ErrorDetails.Message) { [string]$_.ErrorDetails.Message } else { $null }
            if ($rawBody) {
                try {
                    $errorJson = $rawBody | ConvertFrom-Json
                    $errorCode = [string]$errorJson.error
                    $errorDescription = [string]$errorJson.error_description
                }
                catch {
                    if ($rawBody -match 'authorization_pending|AADSTS70016') { $errorCode = 'authorization_pending' }
                    elseif ($rawBody -match 'slow_down') { $errorCode = 'slow_down' }
                    elseif ($rawBody -match 'authorization_declined|access_denied') { $errorCode = 'authorization_declined' }
                    elseif ($rawBody -match 'expired_token') { $errorCode = 'expired_token' }
                }
            }
            if (-not $errorCode) {
                $combinedErrorText = @([string]$_.Exception.Message,[string]$_,[string]$_.FullyQualifiedErrorId) -join ' '
                if ($combinedErrorText -match 'authorization_pending|AADSTS70016') { $errorCode = 'authorization_pending' }
                elseif ($combinedErrorText -match 'slow_down') { $errorCode = 'slow_down' }
                elseif ($combinedErrorText -match 'authorization_declined|access_denied') { $errorCode = 'authorization_declined' }
                elseif ($combinedErrorText -match 'expired_token') { $errorCode = 'expired_token' }
            }
            switch ($errorCode) {
                'authorization_pending' { continue }
                'slow_down' { $interval += 5; continue }
                'authorization_declined' { throw 'Device code authentication was declined by the user.' }
                'access_denied' { throw 'Device code authentication was denied.' }
                'expired_token' { throw 'The device code expired before authentication completed.' }
                default { if ($errorDescription) { throw "Device code authentication failed: $errorDescription" }; throw }
            }
        }
    }

    throw 'The device code expired before authentication completed.'
}