Views/PimRoleActivation.ps1
|
function Show-InTUIPimScreen { [CmdletBinding()] param( [Parameter(Mandatory)] [string[]]$BreadcrumbPath ) Clear-Host Show-InTUIHeader Show-InTUIBreadcrumb -Path $BreadcrumbPath } function Test-InTUIPimInteractiveConnection { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$ActionVerb, [Parameter(Mandatory)] [string]$ActionNoun ) if (-not $script:Connected) { Show-InTUIWarning "Connect to Microsoft Graph before $ActionVerb PIM roles." Read-InTUIKey return $false } if (-not (Test-InTUIPimDelegatedContext)) { Show-InTUIError "PIM role $ActionNoun requires an interactive delegated user connection. Service principal connections are not supported." Read-InTUIKey return $false } return $true } function Invoke-InTUIPimDataLoadWithReconnect { [CmdletBinding()] param( [Parameter(Mandatory)] [scriptblock]$LoadData, [Parameter(Mandatory)] [string[]]$BreadcrumbPath ) for ($attempt = 0; $attempt -lt 2; $attempt++) { $data = & $LoadData if (-not $data.PermissionError) { return $data } Show-InTUIPimPermissionWarning if (-not (Show-InTUIConfirm -Message "[yellow]Reconnect with PIM permissions now?[/]")) { Read-InTUIKey return $null } if (-not (Connect-InTUIPimPermissions)) { Show-InTUIError 'Reconnect with PIM permissions failed.' Read-InTUIKey return $null } Show-InTUIPimScreen -BreadcrumbPath $BreadcrumbPath } return $data } function Show-InTUIPimRoleActivation { [CmdletBinding()] param() $breadcrumbPath = @('Home', 'Security', 'Entra ID PIM Role Activation') Show-InTUIPimScreen -BreadcrumbPath $breadcrumbPath if (-not (Test-InTUIPimInteractiveConnection -ActionVerb 'activating' -ActionNoun 'activation')) { return } $data = Invoke-InTUIPimDataLoadWithReconnect -LoadData { Get-InTUIPimRoleActivationData } -BreadcrumbPath $breadcrumbPath if ($null -eq $data -or $data.PermissionError) { Show-InTUIPimPermissionWarning Read-InTUIKey return } $eligibleRoles = @($data.Eligible) $activeRoles = @($data.Active) $availableRoles = @($eligibleRoles) if ($activeRoles.Count -gt 0) { Show-InTUIInfo "$($activeRoles.Count) active role assignment(s) found. Eligible roles are still shown for activation and labeled Active." Write-InTUIText (Get-InTUIPimActiveRoleSummary -Roles $activeRoles) } if ($availableRoles.Count -eq 0) { Show-InTUIWarning "No eligible Entra ID directory roles found for this account." Write-InTUIText "[grey]- You may not have direct eligible PIM assignments.[/]" Write-InTUIText "[grey]- Group-based PIM eligibility is not included in this view.[/]" Write-InTUIText "[grey]- The current connection may lack PIM permissions.[/]" Read-InTUIKey return } $roleChoices = @(Get-InTUIPimActivationRoleChoices -EligibleRoles $availableRoles -ActiveRoles $activeRoles) $choiceMap = Get-InTUIChoiceMap -Choices $roleChoices $selectedChoices = @(Show-InTUIMultiSelect -Title "[red]Select PIM roles to activate[/]" -Choices $choiceMap.Choices -PageSize 15) if ($selectedChoices.Count -eq 0) { return } $selectedRoles = @(Resolve-InTUIPimSelectedRole -SelectedChoices $selectedChoices -ChoiceMap $choiceMap -AvailableRoles $availableRoles) if ($selectedRoles.Count -eq 0) { return } $hours = Read-InTUIPimDurationInput -MaximumHours 8 if ($null -eq $hours) { return } $reason = Read-InTUIPimReasonInput if (-not (Test-InTUIPimReason -Reason $reason)) { return } if (-not (Confirm-InTUIPimActivation -Roles $selectedRoles -Hours $hours -Reason $reason)) { return } $results = Show-InTUILoading -Title "[red]Submitting PIM activation request(s)...[/]" -ScriptBlock { Invoke-InTUIPimRoleActivation -Roles $selectedRoles -Hours $hours -Reason $reason } Start-Sleep -Seconds 2 $refreshedActive = Show-InTUILoading -Title "[red]Refreshing activation status...[/]" -ScriptBlock { @(Get-InTUIPimActiveDirectoryRole) } Update-InTUIPimActivationResultsFromActiveRoles -Results $results -ActiveRoles $refreshedActive $hasActivatedResults = @($results | Where-Object { $_.Status -eq 'Activated' }).Count -gt 0 if ($hasActivatedResults) { if (Complete-InTUIPimActivationReauth -Results $results) { Show-InTUIInfo 'Refreshed Microsoft Graph authentication after successful PIM activation.' } else { Show-InTUIWarning 'PIM activation succeeded, but Graph token refresh failed. Reconnect manually if Intune access still shows stale authorization.' } } Show-InTUIPimActivationResults -Results $results Read-InTUIKey } function Show-InTUIPimRoleDeactivation { [CmdletBinding()] param() $breadcrumbPath = @('Home', 'Security', 'Entra ID PIM Role Deactivation') Show-InTUIPimScreen -BreadcrumbPath $breadcrumbPath if (-not (Test-InTUIPimInteractiveConnection -ActionVerb 'deactivating' -ActionNoun 'deactivation')) { return } $data = Invoke-InTUIPimDataLoadWithReconnect -LoadData { Get-InTUIPimActiveRoleData } -BreadcrumbPath $breadcrumbPath if ($null -eq $data -or $data.PermissionError) { Show-InTUIPimPermissionWarning Read-InTUIKey return } $activeRoles = @($data.Active) if ($activeRoles.Count -eq 0) { Show-InTUIWarning "No active Entra ID PIM directory roles found for this account." Write-InTUIText "[grey]- Activate a role first, or wait for Graph to reflect the active assignment.[/]" Write-InTUIText "[grey]- Group-based PIM activation is not included in this view.[/]" Read-InTUIKey return } $roleChoices = @() foreach ($role in $activeRoles) { $scope = Get-InTUIPimScopeLabel -DirectoryScopeId $role.DirectoryScopeId $roleChoices += "[white]$(ConvertTo-InTUISafeMarkup -Text $role.DisplayName)[/] [grey]| Scope: $scope[/]" } $choiceMap = Get-InTUIChoiceMap -Choices $roleChoices $selectedChoices = @(Show-InTUIMultiSelect -Title "[red]Select active PIM roles to deactivate[/]" -Choices $choiceMap.Choices -PageSize 15) if ($selectedChoices.Count -eq 0) { return } $selectedRoles = @(Resolve-InTUIPimSelectedRole -SelectedChoices $selectedChoices -ChoiceMap $choiceMap -AvailableRoles $activeRoles) if ($selectedRoles.Count -eq 0) { return } $reason = Read-InTUIPimOptionalReasonInput if (-not (Confirm-InTUIPimDeactivation -Roles $selectedRoles -Reason $reason)) { return } $results = Show-InTUILoading -Title "[red]Submitting PIM deactivation request(s)...[/]" -ScriptBlock { Invoke-InTUIPimRoleDeactivation -Roles $selectedRoles -Reason $reason } Start-Sleep -Seconds 2 $refreshedActive = Show-InTUILoading -Title "[red]Refreshing active role status...[/]" -ScriptBlock { @(Get-InTUIPimActiveDirectoryRole) } Update-InTUIPimDeactivationResultsFromActiveRoles -Results $results -ActiveRoles $refreshedActive Show-InTUIPimActivationResults -Title 'PIM Deactivation Results' -Results $results Read-InTUIKey } function Get-InTUIPimRoleActivationData { [CmdletBinding()] param() Show-InTUILoading -Title "[red]Loading eligible PIM roles...[/]" -ScriptBlock { $script:LastGraphError = $null $eligible = @(Get-InTUIPimEligibleDirectoryRole) if (Test-InTUIPimPermissionError -ErrorInfo $script:LastGraphError) { return @{ PermissionError = $true; Eligible = @(); Active = @() } } $active = @(Get-InTUIPimActiveDirectoryRole) if (Test-InTUIPimPermissionError -ErrorInfo $script:LastGraphError) { return @{ PermissionError = $true; Eligible = @(); Active = @() } } @{ PermissionError = $false Eligible = $eligible Active = $active } } } function Get-InTUIPimActiveRoleData { [CmdletBinding()] param() Show-InTUILoading -Title "[red]Loading active PIM roles...[/]" -ScriptBlock { $script:LastGraphError = $null $active = @(Get-InTUIPimActiveDirectoryRole) if (Test-InTUIPimPermissionError -ErrorInfo $script:LastGraphError) { return @{ PermissionError = $true; Active = @() } } @{ PermissionError = $false Active = $active } } } function Connect-InTUIPimPermissions { [CmdletBinding()] param() $tenantId = $script:TenantId $environment = if ($script:CloudEnvironment) { $script:CloudEnvironment } else { 'Global' } Reconnect-InTUIGraph -TenantId $tenantId -Environment $environment -Scopes (Get-InTUIPimConnectionScopes) -UseDeviceCode:$script:UseDeviceCode } function Get-InTUIPimRoleChoiceLabel { [CmdletBinding()] param( [Parameter(Mandatory)] [object]$Role, [Parameter()] [hashtable]$ActiveRoleKeys = @{} ) $scope = Get-InTUIPimScopeLabel -DirectoryScopeId $Role.DirectoryScopeId $displayName = ConvertTo-InTUISafeMarkup -Text $Role.DisplayName $activeLabel = if ($ActiveRoleKeys.ContainsKey((Get-InTUIPimRoleKey -Role $Role))) { ' [green]| Active[/]' } else { '' } return "[white]$displayName[/]$activeLabel [grey]| Scope: $scope[/]" } function Get-InTUIPimActivationRoleChoices { [CmdletBinding()] param( [Parameter()] [object[]]$EligibleRoles = @(), [Parameter()] [object[]]$ActiveRoles = @() ) $activeRoleKeys = @{} foreach ($role in @($ActiveRoles)) { $activeRoleKeys[(Get-InTUIPimRoleKey -Role $role)] = $true } $roleChoices = @() foreach ($role in @($EligibleRoles)) { $roleChoices += Get-InTUIPimRoleChoiceLabel -Role $role -ActiveRoleKeys $activeRoleKeys } return $roleChoices } function Format-InTUIPimExpirationDisplay { [CmdletBinding()] param( [Parameter()] [string]$EndDateTime ) if ([string]::IsNullOrWhiteSpace($EndDateTime)) { return $null } try { $expiry = [System.DateTimeOffset]::Parse($EndDateTime) $localExpiry = $expiry.ToLocalTime().ToString('yyyy-MM-dd HH:mm') $remaining = $expiry - [System.DateTimeOffset]::UtcNow if ($remaining.TotalMinutes -le 0) { return "$localExpiry (expired)" } if ($remaining.TotalHours -lt 1) { return "$localExpiry ($([math]::Ceiling($remaining.TotalMinutes))m left)" } if ($remaining.TotalDays -lt 1) { return "$localExpiry ($([math]::Floor($remaining.TotalHours))h left)" } return "$localExpiry ($([math]::Floor($remaining.TotalDays))d left)" } catch { return $EndDateTime } } function Get-InTUIPimActiveRoleSummary { [CmdletBinding()] param( [Parameter()] [object[]]$Roles = @() ) if ($Roles.Count -eq 0) { return '[grey]Active now:[/] [grey]None[/]' } $roleLabels = @($Roles | Sort-Object DisplayName, DirectoryScopeId | ForEach-Object { $scope = Get-InTUIPimScopeLabel -DirectoryScopeId $_.DirectoryScopeId $expiration = Format-InTUIPimExpirationDisplay -EndDateTime $_.EndDateTime $details = if ([string]::IsNullOrWhiteSpace($expiration)) { $scope } else { "$scope | Expires: $expiration" } " [green]$(ConvertTo-InTUISafeMarkup -Text $_.DisplayName)[/] [grey]($details)[/]" }) return "[grey]Active now:[/]`n$($roleLabels -join "`n")" } function Complete-InTUIPimActivationReauth { [CmdletBinding()] param( [Parameter()] [object[]]$Results = @() ) $activatedResults = @($Results | Where-Object { $_.Status -eq 'Activated' }) if ($activatedResults.Count -eq 0) { return $false } $roleNames = @($activatedResults | ForEach-Object { $_.RoleName } | Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_) }) Write-InTUILog -Message 'Refreshing Graph token after PIM activation' -Context @{ ActivatedCount = $activatedResults.Count Roles = ($roleNames -join ',') } $context = Get-MgContext -ErrorAction SilentlyContinue $reauthScopes = Get-InTUIPimReauthScopes -Scopes $context.Scopes return (Show-InTUILoading -Title "[red]Refreshing Graph token after PIM activation...[/]" -ScriptBlock { Reconnect-InTUIGraph -Scopes $reauthScopes -UseDeviceCode:$script:UseDeviceCode }) } function Resolve-InTUIPimSelectedRole { [CmdletBinding()] param( [Parameter(Mandatory)] [string[]]$SelectedChoices, [Parameter(Mandatory)] [hashtable]$ChoiceMap, [Parameter(Mandatory)] [object[]]$AvailableRoles ) $selectedRoles = @() foreach ($choice in $SelectedChoices) { $idx = $ChoiceMap.IndexMap[$choice] if ($null -ne $idx -and $idx -lt $AvailableRoles.Count) { $selectedRoles += $AvailableRoles[$idx] } } return $selectedRoles } function Read-InTUIPimDurationInput { [CmdletBinding()] param( [Parameter()] [int]$MaximumHours = 8 ) while ($true) { $value = Read-InTUITextInput -Message "[red]Duration in hours[/]" -DefaultAnswer '1' if ([string]::IsNullOrWhiteSpace($value)) { return $null } $hours = 0 if ([int]::TryParse($value, [ref]$hours) -and $hours -ge 1 -and $hours -le $MaximumHours) { return $hours } Show-InTUIWarning "Enter a whole number from 1 to $MaximumHours." } } function Read-InTUIPimReasonInput { [CmdletBinding()] param() while ($true) { $reason = Read-InTUITextInput -Message "[red]Reason for activation[/]" if (Test-InTUIPimReason -Reason $reason) { return $reason.Trim() } Show-InTUIWarning 'Activation reason is required.' } } function Read-InTUIPimOptionalReasonInput { [CmdletBinding()] param() $reason = Read-InTUITextInput -Message "[red]Reason for deactivation[/] [grey](optional, press Enter to skip)[/]" if ([string]::IsNullOrWhiteSpace($reason)) { return '' } return $reason.Trim() } function Confirm-InTUIPimActivation { [CmdletBinding()] param( [Parameter(Mandatory)] [object[]]$Roles, [Parameter(Mandatory)] [int]$Hours, [Parameter(Mandatory)] [string]$Reason ) $roleLines = @($Roles | ForEach-Object { $scope = Get-InTUIPimScopeLabel -DirectoryScopeId $_.DirectoryScopeId "- $($_.DisplayName) ($scope)" }) $content = @" [bold white]Selected roles:[/] $($roleLines -join "`n") [grey]Duration:[/] $Hours hour(s) [grey]Reason:[/] $Reason "@ Show-InTUIPanel -Title "[red]Review PIM Activation[/]" -Content $content -BorderColor Red return (Show-InTUIConfirm -Message "[yellow]Submit activation request(s)?[/]") } function Confirm-InTUIPimDeactivation { [CmdletBinding()] param( [Parameter(Mandatory)] [object[]]$Roles, [Parameter()] [string]$Reason ) $roleLines = @($Roles | ForEach-Object { $scope = Get-InTUIPimScopeLabel -DirectoryScopeId $_.DirectoryScopeId "- $($_.DisplayName) ($scope)" }) $reasonLine = if ([string]::IsNullOrWhiteSpace($Reason)) { 'N/A' } else { $Reason } $content = @" [bold white]Selected active roles:[/] $($roleLines -join "`n") [grey]Reason:[/] $reasonLine "@ Show-InTUIPanel -Title "[red]Review PIM Deactivation[/]" -Content $content -BorderColor Red return (Show-InTUIConfirm -Message "[yellow]Submit deactivation request(s)?[/]") } function Update-InTUIPimActivationResultsFromActiveRoles { [CmdletBinding()] param( [Parameter()] [object[]]$Results = @(), [Parameter()] [object[]]$ActiveRoles = @() ) $activeKeys = @{} foreach ($role in @($ActiveRoles)) { $activeKeys[(Get-InTUIPimRoleKey -Role $role)] = $true } foreach ($result in @($Results)) { if ($result.Status -eq 'Failed' -or $null -eq $result.Role) { continue } if ($activeKeys.ContainsKey((Get-InTUIPimRoleKey -Role $result.Role))) { $result.Status = 'Activated' } } } function Update-InTUIPimDeactivationResultsFromActiveRoles { [CmdletBinding()] param( [Parameter()] [object[]]$Results = @(), [Parameter()] [object[]]$ActiveRoles = @() ) $activeKeys = @{} foreach ($role in @($ActiveRoles)) { $activeKeys[(Get-InTUIPimRoleKey -Role $role)] = $true } foreach ($result in @($Results)) { if ($result.Status -eq 'Failed' -or $null -eq $result.Role) { continue } if (-not $activeKeys.ContainsKey((Get-InTUIPimRoleKey -Role $result.Role))) { $result.Status = 'Deactivated' } } } function Show-InTUIPimActivationResults { [CmdletBinding()] param( [Parameter()] [string]$Title = 'PIM Activation Results', [Parameter()] [object[]]$Results = @() ) $rows = @() foreach ($result in @($Results)) { $statusColor = switch -Regex ($result.Status) { 'Activated|Granted|Provisioned' { 'green' } 'Pending|Approval' { 'yellow' } 'Failed|Denied' { 'red' } default { 'blue' } } $detail = if ($result.Error) { $result.Error } elseif ($result.RequestId) { $result.RequestId } else { 'N/A' } $rows += , @( ($result.RoleName ?? 'Unknown role'), "[$statusColor]$($result.Status)[/]", $detail ) } Show-InTUITable -Title $Title -Columns @('Role', 'Status', 'Request/Error') -Rows $rows -BorderColor Red } function Show-InTUIPimPermissionWarning { [CmdletBinding()] param() $scopes = (Get-InTUIPimRequiredScopes) -join ', ' Show-InTUIWarning "PIM role activation requires delegated Graph permissions: $scopes." } |