Private/Flow/Get-MsalDeviceCodeCallback.ps1

<#
.SYNOPSIS
    Builds the device code callback and the queue it fills.
.DESCRIPTION
    MSAL invokes the callback on a thread-pool thread, where no runspace exists, so it is an
    expression tree, not a scriptblock. It only queues the message; Invoke-MsalRequest reads the
    queue on the PowerShell thread and writes the message with Write-MsalDeviceCodeMessage.
 
    The result has Callback, for Get-MsalDeviceCodeRequest, and Queue, for Invoke-MsalRequest.
.EXAMPLE
    $DeviceCode = Get-MsalDeviceCodeCallback
    Creates the callback and its queue.
.OUTPUTS
    System.Management.Automation.PSCustomObject
#>

function Get-MsalDeviceCodeCallback {
    [CmdletBinding()]
    [OutputType([psobject])]
    param ()

    ## MSAL invokes the device-code callback on a thread-pool thread, where no runspace exists, so it
    ## is built as an expression tree, not a scriptblock. It only queues the message; the caller
    ## reads the queue on its own thread and shows the message.
    $Queue = New-Object 'System.Collections.Concurrent.ConcurrentQueue[string]'
    $Expression = [System.Linq.Expressions.Expression]
    $ResultParameter = $Expression::Parameter([Microsoft.Identity.Client.DeviceCodeResult], 'result')
    $Enqueue = $Expression::Call($Expression::Constant($Queue), $Queue.GetType().GetMethod('Enqueue'), $Expression::Property($ResultParameter, 'Message'))
    $Completed = $Expression::Property($null, [System.Threading.Tasks.Task].GetProperty('CompletedTask'))
    $Body = $Expression::Block([System.Threading.Tasks.Task], [System.Linq.Expressions.Expression[]] @($Enqueue, $Completed))
    $Callback = $Expression::Lambda([System.Func[Microsoft.Identity.Client.DeviceCodeResult, System.Threading.Tasks.Task]], $Body, [System.Linq.Expressions.ParameterExpression[]] @($ResultParameter)).Compile()

    [PSCustomObject] @{
        Callback = $Callback
        Queue    = $Queue
    }
}