Files
IntuneManagement/UI/Avalonia/Extensions/AuthenticationDeferredAvalonia.ps1
T

128 lines
4.4 KiB
PowerShell

# Avalonia authentication actions that must start after their initiating control
# event returns. Authentication itself remains synchronous and on the UI thread;
# only its start is deferred by one dispatcher turn so Avalonia can release the
# initiating button's pointer capture before a cancellable wait begins.
$script:AvaloniaDeferredActions = [System.Collections.Generic.Queue[object]]::new()
# Schedule module code for the next dispatcher turn. Arguments are stored
# explicitly because NewBoundScriptBlock intentionally does not retain
# function-local captures.
function Invoke-AvaloniaDeferredAction {
[CmdletBinding()]
param(
[Parameter(Mandatory)][scriptblock]$Action,
$Argument
)
$boundAction = ConvertTo-AvaloniaEventScriptBlock $Action
$runner = (ConvertTo-AvaloniaEventScriptBlock {
Invoke-NextAvaloniaDeferredAction
}) -as [Action]
if(-not $runner) { throw 'Could not create deferred Avalonia dispatcher action.' }
$script:AvaloniaDeferredActions.Enqueue([PSCustomObject]@{
Action = $boundAction
Argument = $Argument
})
[Avalonia.Threading.Dispatcher]::UIThread.Post($runner)
}
function Invoke-NextAvaloniaDeferredAction {
if(-not $script:AvaloniaDeferredActions -or $script:AvaloniaDeferredActions.Count -eq 0) { return }
$work = $script:AvaloniaDeferredActions.Dequeue()
try { & $work.Action $work.Argument }
catch { Write-LogError 'Deferred Avalonia action failed' $_.Exception }
}
function Invoke-AvaloniaInteractiveSignIn {
param([string]$Cloud)
if($Cloud) { Write-Status "Signing in to $Cloud cloud..." }
else { Write-Status 'Signing in...' }
try {
$tokenInfo = if($Cloud) {
Invoke-AuthProviderInteractiveLogin -Cloud $Cloud
} else {
Invoke-AuthProviderInteractiveLogin
}
# Successful providers register the token and synchronously fire
# AuthenticatedNewToken. Avalonia's handler owns profile enrichment and the
# auth-info redraw; repeating Get-MSALUserInfo here would issue /me and photo
# requests twice for every MSAL sign-in.
if (-not $tokenInfo) {
Write-Log 'Sign-in returned nothing (user cancelled or auth failed)' 2
}
}
catch {
Write-LogError 'Deferred sign-in failed' $_.Exception
}
finally {
Write-Status ''
}
}
function Invoke-AvaloniaConsentPrompt {
try {
if (Get-Command Start-MSALConsentPrompt -ErrorAction SilentlyContinue) {
Start-MSALConsentPrompt
}
}
catch {
Write-LogError 'Start-MSALConsentPrompt failed' $_.Exception
}
}
function Invoke-AvaloniaProfileRefresh {
Write-Status 'Refreshing the token'
try {
$providerNow = $null
try { $providerNow = Get-AuthProvider } catch { }
$refreshed = $false
if ($providerNow -and $providerNow.UsesBuiltInConnectPath) {
$refreshed = [bool](Connect-EntraEnvironment -ForceRefresh -TokenId (Get-DefaultAuthTokenId))
} elseif ($providerNow) {
# The real default token id, not a literal 0 - see the same call in
# UI/WPF/Extensions/MSGraphAuthenticationUIWPF.ps1: ids start at 1, so 0
# matched nothing in OAuth's token table and refresh was a silent no-op.
$refreshed = [bool]$providerNow.Refresh((Get-DefaultAuthTokenId))
}
if (-not $refreshed) {
$script:UIProvider.HidePopup()
} else {
try { Get-MSALUserInfo } catch { Write-LogError 'Get-MSALUserInfo failed after refresh' $_.Exception }
if (Get-Command Show-ViewMenu -ErrorAction SilentlyContinue) { Show-ViewMenu }
}
}
catch {
Write-LogError 'Token refresh failed' $_.Exception
}
finally {
Write-Status ''
}
}
function Invoke-AvaloniaTenantSwitch {
param($Tenant)
if(-not $Tenant) { return }
Write-Status "Logging in to $($Tenant.DisplayName)"
try {
$userName = $script:CurrentUser.userPrincipalName
if ($userName) {
Connect-EntraEnvironment -User $userName -TenantId $Tenant.tenantId -DefaultToken | Out-Null
} else {
Connect-EntraEnvironment -TenantId $Tenant.tenantId -DefaultToken | Out-Null
}
}
catch {
Write-LogError 'Tenant switch failed' $_.Exception
}
finally {
Write-Status ''
}
}