mirror of
https://github.com/Micke-K/IntuneManagement.git
synced 2026-09-28 19:05:38 +02:00
1018 lines
48 KiB
PowerShell
1018 lines
48 KiB
PowerShell
Add-AppEventHandler "AppInitialized" "Invoke-MSALUIAppInitialized"
|
|
|
|
# Invoke-AuthProviderInteractiveLogin moved to Internal/MSGraphAuthentication.ps1
|
|
# (shared by both UI trees — no WPF dependency).
|
|
|
|
function Show-CloudPickerMenu
|
|
{
|
|
<#
|
|
.SYNOPSIS
|
|
Modal dialog to choose a target cloud before sign-in.
|
|
.DESCRIPTION
|
|
Single-combo picker over the flat $script:Clouds taxonomy (Public / USGov / USGovDOD / China).
|
|
Returns the selected Cloud value (string) on OK, $null on Cancel.
|
|
|
|
Defaults the selection to -Default if provided, otherwise to the current
|
|
DefaultCloud setting. Pass -Persist to save the choice back to DefaultCloud
|
|
(so subsequent zero-config sign-ins use it); without -Persist the choice is
|
|
one-off and used only for the immediate sign-in.
|
|
#>
|
|
param(
|
|
[string]$Default,
|
|
[switch]$Persist
|
|
)
|
|
|
|
$script:loginMenuForm = Initialize-Window ($script:AppUIRootFolder + "\Xaml\CloudPickerMenu.xaml")
|
|
if(-not $script:loginMenuForm) { return $null }
|
|
|
|
$script:UIProvider.SetXamlProperty($script:loginMenuForm, "cbCloud", "ItemsSource", $script:Clouds)
|
|
|
|
if(-not $Default) { $Default = Get-DefaultCloud }
|
|
$script:UIProvider.SetXamlProperty($script:loginMenuForm, "cbCloud", "SelectedValue", $Default)
|
|
|
|
$script:UIProvider.AddXamlEvent($script:loginMenuForm, "btnLogin", "Add_Click", {
|
|
$script:loginMenuForm.DialogResult = $true
|
|
$script:loginMenuForm.Close()
|
|
})
|
|
|
|
$script:UIProvider.AddXamlEvent($script:loginMenuForm, "btnCancel", "Add_Click", {
|
|
$script:loginMenuForm.DialogResult = $false
|
|
$script:loginMenuForm.Close()
|
|
})
|
|
|
|
$script:loginMenuForm.Owner = $script:window
|
|
$script:loginMenuForm.Icon = $script:Window.Icon
|
|
$ok = $script:loginMenuForm.ShowDialog()
|
|
if(-not $ok) { return $null }
|
|
|
|
$cloudValue = $script:UIProvider.GetXamlProperty($script:loginMenuForm, "cbCloud", "SelectedValue")
|
|
if(-not $cloudValue) { $cloudValue = "Public" }
|
|
if($Persist) {
|
|
Set-DefaultCloud $cloudValue
|
|
}
|
|
return [string]$cloudValue
|
|
}
|
|
|
|
function Get-MSALUserProfile
|
|
{
|
|
param($Size = 32, $FontSize = 20, $Color = "Blue", [Switch]$Popup)
|
|
|
|
Write-LogDebug "Create Profile Ellipse"
|
|
|
|
# Refresh $script:MSALAccounts from the persistent MSAL cache whenever the popup is
|
|
# built. Without this, BYO/Confidential sessions (which never go through the interactive
|
|
# MSAL path) show an empty "cached users" list even when the on-disk cache has
|
|
# interactive accounts from previous sessions. Cheap — Get-AccountsAsync reads the
|
|
# already-deserialized cache, no network call.
|
|
# Lazy-MSAL gate: this runs on every startup draw (window Loaded ->
|
|
# Show-AuthenticationInfo), including signed-out sessions. When the MSAL runtime
|
|
# was never loaded this session, skip the refresh instead of forcing the DLL
|
|
# load - the login-click handler re-enumerates via GetCachedAccounts (which
|
|
# ensure-loads), so the popup list is fully populated on first click.
|
|
if($Popup -and ($script:MSALApps.Count -gt 0 -or $script:MSALPrereqLoaded)) {
|
|
try {
|
|
$publicClientApp = $script:MSALApps | Select-Object -First 1
|
|
if(-not $publicClientApp) {
|
|
# No public-client app exists yet (pure BYO/CC session). Create one so
|
|
# the file cache deserializes; this does NOT trigger any auth.
|
|
$publicClientApp = New-MSALApp
|
|
}
|
|
if($publicClientApp) {
|
|
$script:MSALAccounts = $publicClientApp.GetAccountsAsync().GetAwaiter().GetResult()
|
|
}
|
|
}
|
|
catch {
|
|
Write-LogDebug "Failed to refresh cached account list: $($_.Exception.Message)"
|
|
}
|
|
}
|
|
|
|
$grdUserProfile = $null
|
|
|
|
# Treat an expired default token the same as "not signed in" so the title-bar
|
|
# reverts to the Sign-in icon (which prompts interactive login on click) instead
|
|
# of showing a stale logged-in avatar. Test-DefaultTokenExpired is provider-
|
|
# agnostic and returns $false when expiry is unknown/SDK-managed.
|
|
$tokenExpired = $false
|
|
if(Get-Command Test-DefaultTokenExpired -ErrorAction SilentlyContinue) {
|
|
try { $tokenExpired = Test-DefaultTokenExpired } catch { }
|
|
}
|
|
|
|
# Signed-in state is provider-agnostic: $script:CurrentUser is set by
|
|
# Sync-AuthContextFromProvider for EVERY provider (MSAL/OAuth/MgGraph). Rich popup
|
|
# fields below come from the provider's GetUserInfo() and the photo from
|
|
# $script:CurrentProfilePhoto - this function reads no provider-specific globals.
|
|
if(-not $script:CurrentUser -or $tokenExpired)
|
|
{
|
|
#########################################################################################################
|
|
### Build login button when no user is logged on (or the token has expired)
|
|
#########################################################################################################
|
|
|
|
Write-LogDebug "Add login button"
|
|
$grdUserProfile = [System.Windows.Controls.Border]::new()
|
|
$icon = $script:UIProvider.GetXamlObject(($script:AppUIRootFolder + "\Xaml\Icons\Logon.xaml"))
|
|
$icon.Width = $Size
|
|
$icon.Height = $Size
|
|
$grdUserProfile.Background = "#01000000"
|
|
$grdUserProfile.Child = $icon
|
|
|
|
$lnkButton = [System.Windows.Controls.Button]::new()
|
|
$lnkButton.Content = $grdUserProfile
|
|
$lnkButton.Cursor = "Hand"
|
|
$lnkButton.Style = $script:window.TryFindResource("ContentButton")
|
|
$lnkButton.add_Click({
|
|
Write-Log "Login icon clicked"
|
|
try {
|
|
# Cached-account list comes from the active provider so providers that don't
|
|
# surface cached identities (MgGraph) just show an empty list and the user
|
|
# is taken straight to the interactive flow.
|
|
$authProviderClick = $null
|
|
try { $authProviderClick = Get-AuthProvider } catch { Write-LogError "Get-AuthProvider failed in login-click handler" $_.Exception }
|
|
|
|
Write-Log "Login-click: active provider = '$(if($authProviderClick){$authProviderClick.Id}else{'<none>'})'"
|
|
|
|
$cachedRows = @()
|
|
if($authProviderClick -and $authProviderClick.SupportsCachedUsers) {
|
|
try { $cachedRows = @($authProviderClick.GetCachedAccounts()) } catch { $cachedRows = @() }
|
|
}
|
|
|
|
if($cachedRows.Count -eq 0)
|
|
{
|
|
# No cached users for the active provider — go straight to interactive
|
|
# auth via whichever provider is active.
|
|
Write-Status "Signing in..."
|
|
Write-Log "Login-click: calling Invoke-AuthProviderInteractiveLogin"
|
|
$tokenInfo = Invoke-AuthProviderInteractiveLogin
|
|
if($tokenInfo) {
|
|
Write-Log "Login-click: interactive login returned a token, refreshing UI"
|
|
try { Get-MSALUserInfo } catch { Write-LogError "Get-MSALUserInfo failed after interactive login" $_.Exception }
|
|
try { Show-AuthenticationInfo } catch { Write-LogError "Show-AuthenticationInfo failed after interactive login" $_.Exception }
|
|
try { Set-EnvironmentInfo } catch { Write-LogError "Set-EnvironmentInfo failed after interactive login" $_.Exception }
|
|
}
|
|
else {
|
|
Write-Log "Login-click: interactive login returned nothing (user cancelled or auth failed)" 2
|
|
}
|
|
Write-Status ""
|
|
}
|
|
else
|
|
{
|
|
# Add list of cached users + a 'Sign in with a different account' option
|
|
|
|
$xaml = Get-Content ($script:AppUIRootFolder + "\Xaml\LoginPanel.Xaml") -Encoding UTF8
|
|
$loginPanel = [Windows.Markup.XamlReader]::Parse($xaml)
|
|
$otherLogins = $loginPanel.FindName("grdAccounts")
|
|
foreach($row in $cachedRows)
|
|
{
|
|
# Filter Microsoft personal accounts. .Native is the underlying
|
|
# provider-specific object (MSAL IAccount for the MSAL provider) —
|
|
# Add-CachedUser reads its UserName / HomeAccountId fields.
|
|
if($row.Native -and (Test-IsPersonalMSAAccount $row.Native)) { continue }
|
|
Add-CachedUser $row.Native $otherLogins
|
|
}
|
|
|
|
#########################################################################################################
|
|
### Add login button
|
|
#########################################################################################################
|
|
$grdAccount = [System.Windows.Controls.Grid]::new()
|
|
$cd = [System.Windows.Controls.ColumnDefinition]::new()
|
|
$grdAccount.ColumnDefinitions.Add($cd)
|
|
$cd = [System.Windows.Controls.ColumnDefinition]::new()
|
|
$cd.Width = [double]::NaN
|
|
$grdAccount.ColumnDefinitions.Add($cd)
|
|
|
|
$icon = $script:UIProvider.GetXamlObject(($script:AppUIRootFolder + "\Xaml\Icons\Logon.xaml"))
|
|
$icon.Width = 24
|
|
$icon.Height = 24
|
|
$icon.Margin = "0,0,5,0"
|
|
$grdAccount.Children.Add($icon) | Out-Null
|
|
|
|
$lbObj = [Windows.Markup.XamlReader]::Parse("<TextBlock $script:WPFNS>Sign in with a different account</TextBlock>")
|
|
$lbObj.SetValue([System.Windows.Controls.Grid]::ColumnProperty,1)
|
|
#$lbObj.Style = $script:window.TryFindResource("HoverUnderlineStyle")
|
|
$grdAccount.Children.Add($lbObj) | Out-Null
|
|
|
|
$lnkButton = [System.Windows.Controls.Button]::new()
|
|
$lnkButton.Content = $grdAccount
|
|
$lnkButton.Style = $script:window.TryFindResource("LinkButton")
|
|
$lnkButton.Margin = "0,5,0,0"
|
|
$lnkButton.Cursor = "Hand"
|
|
$lnkButton.add_Click({
|
|
Write-Status "Logging in..."
|
|
$script:UIProvider.HidePopup()
|
|
if((Invoke-AuthProviderInteractiveLogin))
|
|
{
|
|
## ToDo: Show graph objects
|
|
}
|
|
Write-Status ""
|
|
})
|
|
|
|
Add-GridObject $otherLogins $lnkButton
|
|
|
|
$loginPanel.Tag = $this.Content
|
|
|
|
$loginPanel.Add_Loaded({param($Obj, $E)
|
|
$point = $Obj.Tag.TransformToAncestor($script:window).Transform([System.Windows.Point]::new(0,0));
|
|
[System.Windows.Controls.Canvas]::SetLeft($Obj,($point.X - $Obj.ActualWidth + $Obj.Tag.ActualWidth))
|
|
[System.Windows.Controls.Canvas]::SetTop($Obj,($point.Y + $Obj.Tag.ActualHeight))
|
|
})
|
|
|
|
$script:UIProvider.ShowPopup($loginPanel)
|
|
}
|
|
}
|
|
catch {
|
|
Write-LogError "Login-click handler threw" $_.Exception
|
|
}
|
|
})
|
|
|
|
return $lnkButton
|
|
}
|
|
|
|
#########################################################################################################
|
|
### Build the ellipse image for the Profile Info
|
|
#########################################################################################################
|
|
|
|
# Provider-agnostic user view. Drives org / app / auth / expiry / app-login below.
|
|
# Identity name, initials and photo still come from $script:CurrentUser /
|
|
# $script:CurrentProfilePhoto. No MSAL globals here, so OAuth/MgGraph render too.
|
|
$provider = $null
|
|
try { $provider = Get-AuthProvider } catch { }
|
|
$userInfo = $null
|
|
if($provider) { try { $userInfo = $provider.GetUserInfo((Get-DefaultAuthTokenId)) } catch { } }
|
|
$isAppLogin = [bool]($userInfo -and $userInfo.AuthType -in @('Confidential','ClientCredential','ManagedIdentity','WorkloadFederation'))
|
|
|
|
if($script:CurrentUser.givenName -and $script:CurrentUser.surname)
|
|
{
|
|
$initials = "$($script:CurrentUser.givenName[0])$($script:CurrentUser.surname[0])".ToUpper()
|
|
}
|
|
elseif($script:CurrentUser.userPrincipalName)
|
|
{
|
|
$initials = "$($script:CurrentUser.userPrincipalName[0])".ToUpper()
|
|
}
|
|
elseif($isAppLogin)
|
|
{
|
|
$initials = "APP"
|
|
}
|
|
|
|
$grdUserProfile = Get-MSALUserPhotoEllips -size $Size -fontSize $FontSize -Color $Color
|
|
|
|
if($Popup)
|
|
{
|
|
# Hide the popup when mouse button is clicked anywhere
|
|
$grdUserProfile.add_MouseLeftButtonDown(({param($Obj, $E)
|
|
if(-not $script:grdProfileInfo) { return }
|
|
$script:UIProvider.ShowPopup($script:grdProfileInfo)
|
|
}))
|
|
|
|
try
|
|
{
|
|
#########################################################################################################
|
|
### Build Profile Info for current user
|
|
#########################################################################################################
|
|
|
|
$script:grdProfileInfo = $null
|
|
$xaml = Get-Content ($script:AppUIRootFolder + "\Xaml\ProfileInfo.Xaml") -Encoding UTF8
|
|
$script:grdProfileInfo = [Windows.Markup.XamlReader]::Parse($xaml)
|
|
$script:grdProfileInfo.Tag = $grdUserProfile
|
|
$grdUserProfile.Tag = $script:grdProfileInfo
|
|
# Org name from the provider's tenant name, falling back to the neutral
|
|
# $script:OrganizationName (both set for every provider).
|
|
$orgNameForPopup = if($userInfo -and $userInfo.TenantName) { $userInfo.TenantName } else { $script:OrganizationName }
|
|
$script:UIProvider.SetXamlProperty($script:grdProfileInfo, "txtOrganization", "Text", ([string]$orgNameForPopup))
|
|
if($isAppLogin)
|
|
{
|
|
$script:UIProvider.SetXamlProperty($script:grdProfileInfo, "txtUsername", "Text", "App Login")
|
|
}
|
|
else
|
|
{
|
|
$script:UIProvider.SetXamlProperty($script:grdProfileInfo, "txtUsername", "Text", $script:CurrentUser.displayName)
|
|
$script:UIProvider.SetXamlProperty($script:grdProfileInfo, "txtLogonName", "Text", $script:CurrentUser.userPrincipalName)
|
|
}
|
|
|
|
# App info from the provider-agnostic user view (GetUserInfo resolves it
|
|
# from the JWT app claims / EntraApp per provider).
|
|
$appName = if($userInfo) { $userInfo.AppName } else { $null }
|
|
$appId = if($userInfo) { $userInfo.AppId } else { $null }
|
|
|
|
Write-LogDebug "Profile Info app=$appName id=$appId"
|
|
$script:UIProvider.SetXamlProperty($script:grdProfileInfo, "txtAppName", "Text", $appName)
|
|
$script:UIProvider.SetXamlProperty($script:grdProfileInfo, "txtAppId", "Text", $appId)
|
|
|
|
# Auth method + expiry from the provider-agnostic user view.
|
|
$authLabel = $null
|
|
$expiryLabel = $null
|
|
if($userInfo) {
|
|
$authTypeRaw = if($userInfo.AuthType) { $userInfo.AuthType } else { "Interactive" }
|
|
$modeName = switch ($authTypeRaw) {
|
|
"BYO" { "Bring-your-own token" }
|
|
"Confidential" { "Client credentials" }
|
|
"ClientCredential" { "Client credentials" }
|
|
"ManagedIdentity" { "Managed identity" }
|
|
default { "Interactive" }
|
|
}
|
|
$providerDisplay = if($provider) { $provider.DisplayName } else { "" }
|
|
$authLabel = "$providerDisplay - $modeName"
|
|
if($userInfo.ExpiresOn) { $expiryLabel = "Token expires $($userInfo.ExpiresOn.ToString('g'))" }
|
|
}
|
|
$script:UIProvider.SetXamlProperty($script:grdProfileInfo, "txtAuthMethod", "Text", $authLabel)
|
|
$script:UIProvider.SetXamlProperty($script:grdProfileInfo, "txtAuthMethod", "Visibility", "Visible")
|
|
if($expiryLabel) {
|
|
$script:UIProvider.SetXamlProperty($script:grdProfileInfo, "txtAuthExpiry", "Text", $expiryLabel)
|
|
$script:UIProvider.SetXamlProperty($script:grdProfileInfo, "txtAuthExpiry", "Visibility", "Visible")
|
|
}
|
|
|
|
$script:UIProvider.AddXamlEvent($script:grdProfileInfo, "btnProfileClose", "add_click", { $script:UIProvider.HidePopup() })
|
|
|
|
# Get the elips with only the photo in a larger size for the popup info
|
|
$tmpObj = Get-MSALUserPhotoEllips -size 64 -fontSize 32
|
|
|
|
$profileGrid = $script:grdProfileInfo.FindName("ProfileInfo")
|
|
if($tmpObj -and $profileGrid)
|
|
{
|
|
$tmpObj.SetValue([System.Windows.Controls.Grid]::RowProperty,1)
|
|
$tmpObj.SetValue([System.Windows.Controls.Grid]::RowSpanProperty,2)
|
|
}
|
|
|
|
if($tmpObj)
|
|
{
|
|
$profileGrid.Children.Add($tmpObj) | Out-Null
|
|
}
|
|
|
|
if($isAppLogin)
|
|
{
|
|
$tmpObj.Visibility = "Collapsed"
|
|
}
|
|
|
|
$script:grdProfileInfo.Add_Loaded({param($Obj, $E)
|
|
$point = $Obj.Tag.TransformToAncestor($script:window).Transform([System.Windows.Point]::new(0,0));
|
|
[System.Windows.Controls.Canvas]::SetLeft($Obj,($point.X - $Obj.ActualWidth + $Obj.Tag.ActualWidth))
|
|
[System.Windows.Controls.Canvas]::SetTop($Obj,($point.Y + $Obj.Tag.ActualHeight))
|
|
})
|
|
|
|
# Consent prompt is delegated-only - for app-only tokens (client-credential
|
|
# / certificate / managed identity) there is no interactive user to walk
|
|
# through the consent UI, so the link is hidden. $isAppLogin derives from the
|
|
# provider-agnostic GetUserInfo().AuthType.
|
|
if(-not $isAppLogin)
|
|
{
|
|
#########################################################################################################
|
|
### Show / Hide consent button
|
|
#########################################################################################################
|
|
$script:userEllipsGrid = $tmpObj
|
|
if(($script:missingPermissions | Measure-Object).Count -eq 0)
|
|
{
|
|
$script:UIProvider.SetXamlProperty($script:userEllipsGrid, "lnkRequestConsent", "Visibility", "Collapsed")
|
|
}
|
|
else
|
|
{
|
|
$script:UIProvider.SetXamlProperty($script:userEllipsGrid, "lnkRequestConsent", "Visibility", "Visible")
|
|
}
|
|
$script:UIProvider.AddXamlEvent($script:userEllipsGrid, "lnkRequestConsent", "add_Click", {
|
|
Start-MSALConsentPrompt
|
|
})
|
|
|
|
$otherLogins = $script:grdProfileInfo.FindName("grdCachedAccounts")
|
|
|
|
#########################################################################################################
|
|
### Add cached users
|
|
#########################################################################################################
|
|
# Cached-account list comes from the active provider. Providers that don't
|
|
# surface cached identities (MgGraph today, SupportsCachedUsers=$false)
|
|
# return an empty array so the section stays empty — no leftover MSAL
|
|
# users shown in MgGraph mode.
|
|
$cachedRows = @()
|
|
if($activeProvider -and $activeProvider.SupportsCachedUsers) {
|
|
try { $cachedRows = @($activeProvider.GetCachedAccounts()) } catch { $cachedRows = @() }
|
|
}
|
|
if((Get-SettingValue "SortAccountList") -eq $true) {
|
|
$cachedRows = @($cachedRows | Sort-Object -Property Username)
|
|
}
|
|
|
|
foreach($row in $cachedRows)
|
|
{
|
|
if(-not $row.Native) { continue }
|
|
$Account = $row.Native
|
|
|
|
# Skip current logged on user (matched on UPN / user id from the
|
|
# provider-agnostic user view).
|
|
if($script:CurrentUser.userPrincipalName -eq $Account.Username -or
|
|
($userInfo -and $userInfo.UserId -eq $Account.HomeAccountId.ObjectId)) { continue }
|
|
|
|
# Skip Microsoft personal (MSA) accounts — not usable for Intune/Graph
|
|
if(Test-IsPersonalMSAAccount $Account) { continue }
|
|
|
|
Add-CachedUser $Account $otherLogins
|
|
}
|
|
|
|
#########################################################################################################
|
|
### Add login with another user
|
|
#########################################################################################################
|
|
$grdAccount = [System.Windows.Controls.Grid]::new()
|
|
$cd = [System.Windows.Controls.ColumnDefinition]::new()
|
|
$grdAccount.ColumnDefinitions.Add($cd)
|
|
$cd = [System.Windows.Controls.ColumnDefinition]::new()
|
|
$cd.Width = [double]::NaN
|
|
$grdAccount.ColumnDefinitions.Add($cd)
|
|
|
|
$icon = $script:UIProvider.GetXamlObject(($script:AppUIRootFolder + "\Xaml\Icons\Logon.xaml"))
|
|
$icon.Width = 24
|
|
$icon.Height = 24
|
|
$icon.Margin = "0,0,5,0"
|
|
$grdAccount.Children.Add($icon) | Out-Null
|
|
|
|
$lbObj = [Windows.Markup.XamlReader]::Parse("<TextBlock $script:WPFNS>Sign in with a different account</TextBlock>")
|
|
$lbObj.SetValue([System.Windows.Controls.Grid]::ColumnProperty,1)
|
|
#$lbObj.Style = $script:window.TryFindResource("HoverUnderlineStyle")
|
|
$grdAccount.Children.Add($lbObj) | Out-Null
|
|
|
|
$lnkButton = [System.Windows.Controls.Button]::new()
|
|
$lnkButton.Content = $grdAccount
|
|
$lnkButton.Style = $script:window.TryFindResource("LinkButton")
|
|
$lnkButton.Margin = "0,5,0,0"
|
|
$lnkButton.Cursor = "Hand"
|
|
$lnkButton.Tag = $Account
|
|
$lnkButton.add_Click({
|
|
Write-Status "Logging in..."
|
|
$script:UIProvider.HidePopup()
|
|
if((Invoke-AuthProviderInteractiveLogin))
|
|
{
|
|
# ToDo Show-GraphObjects
|
|
}
|
|
Write-Status ""
|
|
})
|
|
|
|
$otherLogins = $script:grdProfileInfo.FindName("grdLoginAccount")
|
|
|
|
Add-GridObject $otherLogins $lnkButton
|
|
|
|
#########################################################################################################
|
|
### Add "Sign in to a different cloud..." (opt-in picker; replaces the old EntraLoginMenu gate)
|
|
#########################################################################################################
|
|
$grdCloud = [System.Windows.Controls.Grid]::new()
|
|
$cd = [System.Windows.Controls.ColumnDefinition]::new()
|
|
$grdCloud.ColumnDefinitions.Add($cd)
|
|
$cd = [System.Windows.Controls.ColumnDefinition]::new()
|
|
$cd.Width = [double]::NaN
|
|
$grdCloud.ColumnDefinitions.Add($cd)
|
|
|
|
# Reuse the Logon icon so the row visually pairs with "Sign in with a
|
|
# different account" above it; the text on the right is what distinguishes them.
|
|
$iconCloud = $script:UIProvider.GetXamlObject(($script:AppUIRootFolder + "\Xaml\Icons\Logon.xaml"))
|
|
$iconCloud.Width = 24
|
|
$iconCloud.Height = 24
|
|
$iconCloud.Margin = "0,0,5,0"
|
|
$grdCloud.Children.Add($iconCloud) | Out-Null
|
|
|
|
$lbCloud = [Windows.Markup.XamlReader]::Parse("<TextBlock $script:WPFNS>Sign in to a different cloud...</TextBlock>")
|
|
$lbCloud.SetValue([System.Windows.Controls.Grid]::ColumnProperty, 1)
|
|
$grdCloud.Children.Add($lbCloud) | Out-Null
|
|
|
|
$btnCloud = [System.Windows.Controls.Button]::new()
|
|
$btnCloud.Content = $grdCloud
|
|
$btnCloud.Style = $script:window.TryFindResource("LinkButton")
|
|
$btnCloud.Margin = "0,5,0,0"
|
|
$btnCloud.Cursor = "Hand"
|
|
$btnCloud.ToolTip = "Sign in to US Government (GCC High / DoD) or China cloud. To make a choice permanent, change Default cloud in Settings."
|
|
$btnCloud.add_Click({
|
|
$script:UIProvider.HidePopup()
|
|
$picked = Show-CloudPickerMenu
|
|
if($picked) {
|
|
Write-Status "Signing in to $picked cloud..."
|
|
if((Invoke-AuthProviderInteractiveLogin -Cloud $picked)) {
|
|
# ToDo Show-GraphObjects
|
|
}
|
|
Write-Status ""
|
|
}
|
|
})
|
|
Add-GridObject $otherLogins $btnCloud
|
|
|
|
$otherLogins = $script:grdProfileInfo.FindName("grdTenantAccounts")
|
|
|
|
if(($script:AccessibleTenants | Measure-Object).Count -gt 1)
|
|
{
|
|
#########################################################################################################
|
|
### Add switch to another tenant
|
|
#########################################################################################################
|
|
$lbObj = [Windows.Markup.XamlReader]::Parse("<TextBlock $script:WPFNS><Bold>Tenants:</Bold></TextBlock>")
|
|
$lbObj.Margin = "0,5,0,0"
|
|
|
|
if((Get-SettingValue "SortTenantList") -eq $true)
|
|
{
|
|
$tenants = $script:AccessibleTenants | Sort-Object -Property DisplayName
|
|
}
|
|
else
|
|
{
|
|
$tenants = $script:AccessibleTenants
|
|
}
|
|
|
|
Add-GridObject $otherLogins $lbObj
|
|
foreach($tenant in $tenants)
|
|
{
|
|
try
|
|
{
|
|
$lbObj = [Windows.Markup.XamlReader]::Parse("<TextBlock $script:WPFNS HorizontalAlignment=`"Stretch`"><Bold>$($tenant.DisplayName)</Bold><LineBreak/>$($tenant.defaultDomain)<LineBreak/>$($tenant.tenantId)</TextBlock>")
|
|
|
|
if($tenant.tenantId -ne $userInfo.TenantId)
|
|
{
|
|
$lbObj.Style = $script:window.TryFindResource("HoverUnderlineStyleWithBackground")
|
|
$lbObj.HorizontalAlignment = "Stretch"
|
|
$lnkButton = [System.Windows.Controls.Button]::new()
|
|
$lnkButton.Content = $lbObj
|
|
$lnkButton.HorizontalAlignment = "Stretch"
|
|
$lnkButton.Style = $script:window.TryFindResource("ContentButton")
|
|
$lnkButton.Margin = "0,5,0,0"
|
|
$lnkButton.Cursor = "Hand"
|
|
$lnkButton.Tag = $tenant
|
|
$lnkButton.add_Click({
|
|
Write-Status "Logging in to $($this.Tag.DisplayName)"
|
|
# Set authority to selected tenant
|
|
$script:UIProvider.HidePopup()
|
|
if((Connect-EntraEnvironment -User ($script:MSALAccounts | Where-Object UserName -eq $script:CurrentUser.userPrincipalName).Username -TenantId $this.Tag.tenantId -DefaultToken))
|
|
{
|
|
# ToDo: Show-GraphObjects
|
|
}
|
|
Write-Status ""
|
|
})
|
|
Add-GridObject $otherLogins $lnkButton
|
|
}
|
|
else
|
|
{
|
|
$lbObj.Background = $script:window.TryFindResource("SelectedRowBackgroundColor")
|
|
$lbObj.Margin = "0,5,0,0"
|
|
Add-GridObject $otherLogins $lbObj
|
|
}
|
|
}
|
|
catch {}
|
|
}
|
|
}
|
|
}
|
|
|
|
#########################################################################################################
|
|
### Add event handling
|
|
#########################################################################################################
|
|
# Token-info button. For MSAL, shows AuthenticationResult fields. For
|
|
# other providers, shows whatever the provider's session exposes (e.g.
|
|
# Get-MgContext for MgGraph).
|
|
$script:UIProvider.AddXamlEvent($tmpObj, "lnkTokeninfo", "add_Click", {
|
|
$tokenArr = @()
|
|
$providerNow = $null
|
|
try { $providerNow = Get-AuthProvider } catch { }
|
|
|
|
# Provider-agnostic: each provider returns its own native session rows
|
|
# (MSAL AuthenticationResult fields, MgGraph Get-MgContext, ...).
|
|
if($providerNow) { $tokenArr = @($providerNow.GetSessionInfoRows()) }
|
|
|
|
$dg = [System.Windows.Controls.DataGrid]::new()
|
|
Set-GridPixelScrolling $dg
|
|
$dg.ItemsSource = ($tokenArr | Select-Object Name, Value)
|
|
$script:UIProvider.ShowModalForm("Session Info", $dg)
|
|
})
|
|
|
|
$script:UIProvider.AddXamlEvent($tmpObj, "lnkAccessTokenInfo", "add_Click", {
|
|
# Decode the active provider's access token on demand (works for any
|
|
# provider whose access token is a JWT - MSAL, OAuth).
|
|
$providerNow = $null
|
|
try { $providerNow = Get-AuthProvider } catch { }
|
|
if($providerNow) {
|
|
$raw = $providerNow.GetAccessToken(0, "https://$(Get-GraphDomain)")
|
|
if($raw) {
|
|
$jwt = Get-JWTtoken $raw
|
|
Show-MSALDecodedToken $jwt "Access Token Info"
|
|
}
|
|
else {
|
|
$script:UIProvider.ShowMessageBox("No access token available from provider '$($providerNow.Id)'.", "Access Token Info", "OK", "Information") | Out-Null
|
|
}
|
|
}
|
|
})
|
|
|
|
$script:UIProvider.AddXamlEvent($tmpObj, "lnkIdTokenInfo", "add_Click", {
|
|
# ID token decode via the provider (only providers that surface an id
|
|
# token return one; the button is hidden otherwise).
|
|
$providerNow = $null
|
|
try { $providerNow = Get-AuthProvider } catch { }
|
|
$idJwt = if($providerNow) { $providerNow.GetIdTokenJwt((Get-DefaultAuthTokenId)) } else { $null }
|
|
if($idJwt) {
|
|
Show-MSALDecodedToken $idJwt "Id Token Info"
|
|
}
|
|
else {
|
|
$script:UIProvider.ShowMessageBox("ID token is not available from the active provider.", "Id Token Info", "OK", "Information") | Out-Null
|
|
}
|
|
})
|
|
|
|
# Permissions: token scopes x Intune role, per policy type (EffectivePermissionsUIWPF.ps1).
|
|
$script:UIProvider.AddXamlEvent($tmpObj, "lnkEffectivePermissions", "add_Click", { Show-EffectivePermissionsDialog })
|
|
|
|
# Phase 3: capability-flag-aware buttons. Disable buttons the active
|
|
# provider doesn't support, so the user gets clear feedback instead of
|
|
# silent no-ops.
|
|
#
|
|
# Refresh:
|
|
# * Required: provider.SupportsRefresh AND it's not a BYO token (BYO is
|
|
# externally-supplied; even on a provider that supports refresh, BYO
|
|
# can't be refreshed).
|
|
if($provider) {
|
|
$isByo = [bool]($userInfo -and $userInfo.AuthType -eq "BYO")
|
|
if(-not $provider.SupportsRefresh) {
|
|
$script:UIProvider.SetXamlProperty($tmpObj, "lnkForceRefresh", "IsEnabled", $false)
|
|
$script:UIProvider.SetXamlProperty($tmpObj, "lnkForceRefresh", "ToolTip", "$($provider.DisplayName) does not support manual refresh.")
|
|
}
|
|
elseif($isByo) {
|
|
$script:UIProvider.SetXamlProperty($tmpObj, "lnkForceRefresh", "IsEnabled", $false)
|
|
$script:UIProvider.SetXamlProperty($tmpObj, "lnkForceRefresh", "ToolTip", "BYO tokens cannot be refreshed. Re-run Connect-IntuneManagement with a new token.")
|
|
}
|
|
|
|
# Inspector buttons are shown only when the provider actually has that
|
|
# data (self-describing, provider-agnostic):
|
|
# * Session Info: only when the provider returns session rows (OAuth has none).
|
|
# * Access Token: always - decoded on demand from GetAccessToken.
|
|
# * Id Token: only when the provider surfaces an id token (MSAL).
|
|
if(@($provider.GetSessionInfoRows()).Count -eq 0) {
|
|
$script:UIProvider.SetXamlProperty($tmpObj, "lnkTokeninfo", "Visibility", "Collapsed")
|
|
}
|
|
if($null -eq $provider.GetIdTokenJwt((Get-DefaultAuthTokenId))) {
|
|
$script:UIProvider.SetXamlProperty($tmpObj, "lnkIdTokenInfo", "Visibility", "Collapsed")
|
|
}
|
|
}
|
|
|
|
$script:UIProvider.AddXamlEvent($tmpObj, "lnkForceRefresh", "add_Click", {
|
|
Write-Status "Refreshing the token"
|
|
|
|
$providerNow = $null
|
|
try { $providerNow = Get-AuthProvider } catch { }
|
|
|
|
$refreshed = $false
|
|
if($providerNow -and $providerNow.UsesBuiltInConnectPath) {
|
|
# Providers on the built-in Connect path refresh a specific token id
|
|
# via Connect-EntraEnvironment -ForceRefresh.
|
|
$refreshed = [bool](Connect-EntraEnvironment -ForceRefresh -TokenId (Get-DefaultAuthTokenId))
|
|
}
|
|
elseif($providerNow) {
|
|
# Generic provider Refresh (e.g. MgGraph re-runs Connect-MgGraph).
|
|
# Pass the REAL default token id: ids are allocated from 1, so the
|
|
# literal 0 this used to send matched nothing in OAuth's token table
|
|
# and its Refresh returned $false before trying anything - the button
|
|
# just closed the popup. MgGraph ignores the id, so it is unaffected.
|
|
$refreshed = [bool]$providerNow.Refresh((Get-DefaultAuthTokenId))
|
|
}
|
|
|
|
if(-not $refreshed)
|
|
{
|
|
$script:UIProvider.HidePopup()
|
|
}
|
|
else
|
|
{
|
|
# Refresh title-bar profile and view menu after a successful refresh
|
|
# so any new role assignments / scope changes are reflected.
|
|
try { Get-MSALUserInfo } catch { Write-LogError "Get-MSALUserInfo failed after refresh" $_.Exception }
|
|
if(Get-Command Show-ViewMenu -ErrorAction SilentlyContinue) { Show-ViewMenu }
|
|
}
|
|
Write-Status ""
|
|
})
|
|
|
|
$script:UIProvider.AddXamlEvent($tmpObj, "lnkLogout", "add_Click", {
|
|
$script:UIProvider.HidePopup()
|
|
|
|
if(Disconnect-EntraEnvironment)
|
|
{
|
|
# ToDo: Show-GraphObjects
|
|
}
|
|
})
|
|
|
|
if($isAppLogin)
|
|
{
|
|
$script:UIProvider.SetXamlProperty($tmpObj, "lnkLogout", "Visibility", "Collapsed")
|
|
}
|
|
}
|
|
catch {
|
|
Write-LogError "Failed to create profile information object. Error: " $_.Exception
|
|
}
|
|
}
|
|
|
|
$grdUserProfile
|
|
}
|
|
|
|
# Filters out Microsoft personal / consumer (MSA) accounts. Those accounts live on the
|
|
# well-known tenant 9188040d-6c67-4c5b-b112-36a304b66dad and cannot be used to sign into
|
|
# Entra-tenant resources (Intune / Graph) — surfacing them in the picker only confuses
|
|
# the user. Keep work/school accounts plus accounts where tenant info isn't available.
|
|
function Test-IsPersonalMSAAccount {
|
|
param($Account)
|
|
if(-not $Account -or -not $Account.HomeAccountId) { return $false }
|
|
$tid = $Account.HomeAccountId.TenantId
|
|
return ($tid -eq "9188040d-6c67-4c5b-b112-36a304b66dad")
|
|
}
|
|
|
|
function Add-CachedUser
|
|
{
|
|
param($Account, $ParentObj)
|
|
|
|
try
|
|
{
|
|
$grdAccount = [System.Windows.Controls.Grid]::new()
|
|
|
|
$cd = [System.Windows.Controls.ColumnDefinition]::new()
|
|
$grdAccount.ColumnDefinitions.Add($cd) # Login
|
|
|
|
$cd = [System.Windows.Controls.ColumnDefinition]::new()
|
|
$cd.Width = [double]::NaN
|
|
$grdAccount.ColumnDefinitions.Add($cd) # Forget
|
|
|
|
$grdLogin = [System.Windows.Controls.Grid]::new()
|
|
$cd = [System.Windows.Controls.ColumnDefinition]::new()
|
|
$grdLogin.ColumnDefinitions.Add($cd)
|
|
$cd = [System.Windows.Controls.ColumnDefinition]::new()
|
|
$cd.Width = [double]::NaN
|
|
$grdLogin.ColumnDefinitions.Add($cd)
|
|
|
|
$icon = $script:UIProvider.GetXamlObject(($script:AppUIRootFolder + "\Xaml\Icons\LoggedOnUser.xaml"))
|
|
$icon.Width = 24
|
|
$icon.Height = 24
|
|
$icon.Margin = "0,0,5,0"
|
|
$grdLogin.Children.Add($icon) | Out-Null
|
|
|
|
$tenantName = Get-SettingStoreValue $Account.HomeAccountId.TenantId "_Name" $Account.HomeAccountId.TenantId
|
|
|
|
$lbObj = [Windows.Markup.XamlReader]::Parse("<TextBlock $script:WPFNS>$($Account.UserName)<LineBreak/>$($tenantName)</TextBlock>")
|
|
$lbObj.SetValue([System.Windows.Controls.Grid]::ColumnProperty,1)
|
|
$grdLogin.Children.Add($lbObj) | Out-Null
|
|
|
|
$lnkButton = [System.Windows.Controls.Button]::new()
|
|
$lnkButton.Content = $grdLogin
|
|
$lnkButton.Style = $script:window.TryFindResource("LinkButton")
|
|
$lnkButton.Margin = "0,5,0,0"
|
|
$lnkButton.Cursor = "Hand"
|
|
$lnkButton.Tag = $Account
|
|
$lnkButton.add_Click({
|
|
Write-Status "Logging in with $($this.Tag.UserName)"
|
|
$script:UIProvider.HidePopup()
|
|
|
|
if((Connect-EntraEnvironment -User $this.Tag.UserName -DefaultToken))
|
|
{
|
|
## ToDo: Show-GraphObjects
|
|
}
|
|
Write-Status ""
|
|
})
|
|
|
|
$grdAccount.Children.Add($lnkButton) | Out-Null
|
|
|
|
# Add Forget user icon
|
|
$icon = $script:UIProvider.GetXamlObject(($script:AppUIRootFolder + "\Xaml\Icons\Bin.xaml"))
|
|
$icon.Width = 16
|
|
$icon.Height = 16
|
|
$icon.Margin = "5,5,0,0"
|
|
|
|
$lnkButton = [System.Windows.Controls.Button]::new()
|
|
$lnkButton.ToolTip = "Forget"
|
|
$lnkButton.Content = $icon
|
|
$lnkButton.Style = $script:window.TryFindResource("LinkButton")
|
|
$lnkButton.Margin = "0,5,0,0"
|
|
$lnkButton.Cursor = "Hand"
|
|
$lnkButton.Tag = $Account
|
|
$lnkButton.SetValue([System.Windows.Controls.Grid]::ColumnProperty,1)
|
|
$lnkButton.add_Click({
|
|
# Truly evict the MSAL account: removes from in-memory MSALTokens (if signed in
|
|
# under this account) AND from the persistent on-disk cache so it stops showing
|
|
# up in future sessions. The old code called Disconnect-EntraEnvironment with an
|
|
# Account object, which coerced to int 0 (default token) — never the intended one.
|
|
Write-Status "Removing $($this.Tag.UserName)"
|
|
try {
|
|
# If this account is currently logged in, disconnect properly first so we
|
|
# promote a new default and fire the event.
|
|
$matchingTokens = @($script:MSALTokens.Values | Where-Object {
|
|
$_.Token -and $_.Token.Account -and
|
|
$_.Token.Account.HomeAccountId.Identifier -eq $this.Tag.HomeAccountId.Identifier
|
|
})
|
|
foreach($tok in $matchingTokens) { Disconnect-EntraEnvironment -TokenID $tok.Id }
|
|
|
|
# Always also evict from the persistent MSAL cache (covers cached-but-not-signed-in).
|
|
Remove-MSALAccount -Account $this.Tag
|
|
|
|
$this.Parent.Parent.Children.Remove($this.Parent)
|
|
}
|
|
catch {
|
|
Write-LogError "Failed to forget account $($this.Tag.UserName)" $_.Exception
|
|
}
|
|
Write-Status ""
|
|
})
|
|
|
|
$grdAccount.Children.Add($lnkButton) | Out-Null
|
|
|
|
Add-GridObject $ParentObj $grdAccount
|
|
}
|
|
catch {}
|
|
}
|
|
|
|
function Get-MSALUserPhotoEllips
|
|
{
|
|
param($Size = 32, $FontSize = 20, $Color = "Blue")
|
|
|
|
$grdUserPhoto = [System.Windows.Controls.Grid]::new()
|
|
|
|
$ellipse = [System.Windows.Shapes.Ellipse]::new()
|
|
$ellipse.Width = $Size
|
|
$ellipse.Height = $Size
|
|
$ellipse.Fill = $Color
|
|
$ellipse.Stroke = "#FFFF00FF"
|
|
$ellipse.StrokeThickness = "0"
|
|
|
|
$grdUserPhoto.Children.Add($ellipse) | Out-Null
|
|
|
|
$tb = [System.Windows.Controls.TextBlock]::new()
|
|
$tb.FontSize = $FontSize
|
|
$tb.Foreground = "White"
|
|
#$tb.FontFamily=""
|
|
$tb.FontWeight = "Bold"
|
|
#$tb.TextLineBounds="Tight"
|
|
$tb.VerticalAlignment="Center"
|
|
$tb.HorizontalAlignment="Center"
|
|
#$tb.IsTextScaleFactorEnabled="False"
|
|
$tb.Text = $initials
|
|
|
|
$grdUserPhoto.Children.Add($tb) | Out-Null
|
|
|
|
if($script:CurrentProfilePhoto -and [IO.File]::Exists($script:CurrentProfilePhoto))
|
|
{
|
|
Write-LogDebug "Create image"
|
|
$img = [System.Windows.Media.Imaging.BitmapImage]::new()
|
|
$img.BeginInit()
|
|
$img.CacheOption = [System.Windows.Media.Imaging.BitmapCacheOption]::OnLoad
|
|
$img.UriSource = [System.Uri]::new($script:CurrentProfilePhoto)
|
|
$img.EndInit()
|
|
$ib = [System.Windows.Media.ImageBrush]::new()
|
|
$ib.ImageSource = $img
|
|
|
|
$ellipse = [System.Windows.Shapes.Ellipse]::new()
|
|
$ellipse.Width = $Size
|
|
$ellipse.Height = $Size
|
|
$ellipse.FlowDirection="LeftToRight"
|
|
$ellipse.Fill = $ib
|
|
$grdUserPhoto.Children.Add($ellipse) | Out-Null
|
|
}
|
|
|
|
$grdUserPhoto
|
|
}
|
|
|
|
function Show-MSALDecodedToken {
|
|
param (
|
|
$TokenData,
|
|
$Title
|
|
)
|
|
|
|
if(-not $TokenData.Header) { return }
|
|
|
|
$tokenArr = @()
|
|
foreach($prop in ($TokenData.Header | Get-Member | Where-Object MemberType -eq NoteProperty))
|
|
{
|
|
$tokenArr += New-Object PSObject -Property @{
|
|
Name=$prop.Name
|
|
Value=$TokenData.Header."$($prop.Name)"
|
|
}
|
|
}
|
|
|
|
# Each claim yields one or more GRID ROWS rather than one row holding a
|
|
# multi-line string. Newline-joining a long list makes a single row several
|
|
# times taller than the rest (8 permissions measured 147px against 33px in
|
|
# the Avalonia grid), and a virtualizing DataGrid estimates its scroll extent
|
|
# from the rows it has realized - so that one outlier makes the extent swing
|
|
# as it scrolls in and out of view, giving a jumpy scrollbar and a mouse
|
|
# wheel that appears to run through the table several times. One value per
|
|
# row keeps every row a uniform height and stays fully readable.
|
|
foreach($prop in ($TokenData.Payload | Get-Member | Where-Object MemberType -eq NoteProperty))
|
|
{
|
|
$raw = $TokenData.Payload."$($prop.Name)"
|
|
|
|
if($prop.Name -in @("exp","iat","nbf","xms_tcdt"))
|
|
{
|
|
$values = @([datetime]::new(1970, 1, 1, 0, 0, 0, 0, [System.DateTimeKind]::Utc).AddSeconds($raw).ToLocalTime())
|
|
}
|
|
elseif($prop.Name -in @("acrs","amr"))
|
|
{
|
|
# Short arrays; a single ";"-joined line stays well inside one row.
|
|
$values = @($raw -join ";")
|
|
}
|
|
elseif($prop.Name -in @("wids"))
|
|
{
|
|
if(-not $script:AADRoles)
|
|
{
|
|
# This will fail if RoleManagement.Read.Directory permission is not granted. Use -NoError to hide any problems
|
|
$script:AADRoles = (Invoke-MSGraphAPI -url "/directoryRoles?`$select=roleTemplateId,displayName" -ODataMetadata "minimal" -Noerror).value
|
|
}
|
|
$values = @()
|
|
foreach($wid in $raw)
|
|
{
|
|
$text = $wid
|
|
$role = ($script:AADRoles | Where-Object roleTemplateId -eq $wid)
|
|
if($role)
|
|
{
|
|
$text = ($text + " ($($role.displayName))")
|
|
}
|
|
$values += $text
|
|
}
|
|
}
|
|
elseif($prop.Name -eq "scp")
|
|
{
|
|
# Delegated tokens pack the scopes into one space-separated string.
|
|
$values = @(([string]$raw).Split(" ") | Where-Object { $_ })
|
|
}
|
|
else
|
|
{
|
|
# Covers "roles" (an array on app-only tokens) and any other array
|
|
# claim: one row each, so nothing ever renders as "System.Object[]".
|
|
$values = @($raw)
|
|
}
|
|
|
|
foreach($v in $values)
|
|
{
|
|
$tokenArr += New-Object PSObject -Property @{
|
|
Name=$prop.Name
|
|
Value=$v
|
|
}
|
|
}
|
|
}
|
|
$dg = [System.Windows.Controls.DataGrid]::new()
|
|
Set-GridPixelScrolling $dg
|
|
$dg.ItemsSource = ($tokenArr | Select-Object Name, Value)
|
|
$script:UIProvider.ShowModalForm($Title, $dg)
|
|
}
|
|
|
|
# Get-MSALUserInfo moved to Internal/MSGraphAuthentication.ps1.
|
|
|
|
#region Event functions
|
|
|
|
function Invoke-MSALUIEventNewAuthentication
|
|
{
|
|
[CmdLetbinding()]
|
|
param($TokenInfo)
|
|
|
|
# Provider-neutral tenant/user sync and the ScopeTags/AssignmentFilters
|
|
# dependency-cache preload used to happen here. Both moved to
|
|
# Invoke-AuthCoreOnNewToken in Internal/AuthenticationCore.ps1, which
|
|
# subscribes to the same "AuthenticatedNewToken" event and runs regardless
|
|
# of UI mode -- headless flows (OAuth AppId+Secret in Azure Automation)
|
|
# get the same state updates that were previously UI-exclusive.
|
|
#
|
|
# This handler now does only UI-specific work: MSAL profile enrichment
|
|
# (Graph /me, profile photo, JWT-derived app name) and the auth-info bar
|
|
# refresh. Update-MSALUserProfile self-guards on $script:MSALDefaultToken
|
|
# so it's a no-op for non-MSAL providers.
|
|
if($TokenInfo.IsDefault) {
|
|
Update-MSALUserProfile
|
|
Show-AuthenticationInfo
|
|
}
|
|
}
|
|
|
|
function Invoke-MSALUIEventTokenRefreshed
|
|
{
|
|
[CmdLetbinding()]
|
|
param($TokenInfo)
|
|
|
|
# A silent renewal keeps the same user/tenant, so skip the heavy profile
|
|
# enrichment (Graph /me + photo) and full object reload that a NEW token
|
|
# triggers - just redraw the auth-info bar so the shown token expiry reflects
|
|
# the renewed token. Only the default token drives the visible profile.
|
|
if($TokenInfo.IsDefault) {
|
|
try { Show-AuthenticationInfo }
|
|
catch { Write-LogError "Show-AuthenticationInfo failed on token refresh" $_.Exception }
|
|
}
|
|
}
|
|
|
|
function Invoke-MSALUIEventUserDisconnected
|
|
{
|
|
[CmdLetbinding()]
|
|
param($TokenInfo)
|
|
|
|
# Wipe every cache entry tagged for the disconnected tenant so a future sign-in to
|
|
# a different tenant doesn't see stale ScopeTags / Filters / AAD / baseline templates.
|
|
if($TokenInfo -and $TokenInfo.TenantID) {
|
|
try { Clear-TenantCache -TenantId $TokenInfo.TenantID }
|
|
catch { Write-LogError "Failed to clear tenant cache on disconnect" $_.Exception }
|
|
}
|
|
|
|
Get-MSALUserInfo
|
|
Show-AuthenticationInfo
|
|
}
|
|
|
|
function Invoke-MSALUIEventAuthenticationFailed
|
|
{
|
|
# Profile enrichment is best-effort and must NEVER stop the auth-info bar from
|
|
# redrawing - the redraw is the whole point of this handler: on a hard failure
|
|
# (expired token / no session) Get-MSALUserProfile's expired/no-token branch
|
|
# flips the avatar back to the Sign-in icon. Guard so a throw in Get-MSALUserInfo
|
|
# can't skip Show-AuthenticationInfo.
|
|
try { Get-MSALUserInfo } catch { Write-LogError "Get-MSALUserInfo failed on AuthenticationFailed" $_.Exception }
|
|
Show-AuthenticationInfo
|
|
# Also refresh the environment/tenant badge so it hides when the session is gone
|
|
# (expired token still returns GetUserInfo, so Set-EnvironmentInfo's own expiry
|
|
# guard does the hiding). Explicit here so it doesn't depend on the enrichment
|
|
# above reaching its own Set-EnvironmentInfo call.
|
|
try { Set-EnvironmentInfo } catch { Write-LogError "Set-EnvironmentInfo failed on AuthenticationFailed" $_.Exception }
|
|
}
|
|
|
|
function Invoke-MSALUIAppInitialized
|
|
{
|
|
Add-AppEventHandler "AuthenticatedNewToken" "Invoke-MSALUIEventNewAuthentication"
|
|
Add-AppEventHandler "AuthenticationTokenRefresh" "Invoke-MSALUIEventTokenRefreshed"
|
|
Add-AppEventHandler "AuthenticationUserDisconnected" "Invoke-MSALUIEventUserDisconnected"
|
|
Add-AppEventHandler "AuthenticationFailed" "Invoke-MSALUIEventAuthenticationFailed"
|
|
}
|
|
|
|
#Endregion
|