Private/PimRoleChange.ps1
|
function New-InTUIPimRoleChangeResult { [CmdletBinding()] param( [Parameter(Mandatory)] [object]$Role, [Parameter(Mandatory)] [string]$Status, [Parameter()] [string]$RequestId, [Parameter()] [string]$ErrorMessage, [Parameter()] [object]$RawResponse, [Parameter()] [switch]$Submitted ) [pscustomobject]@{ Role = $Role RoleName = $Role.DisplayName Status = $Status RequestId = $RequestId Error = $ErrorMessage RawResponse = $RawResponse Submitted = $Submitted.IsPresent } } function Get-InTUIPimRoleChangeOperation { [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateSet('Activation', 'Deactivation')] [string]$Operation ) switch ($Operation) { 'Activation' { $browserSuccessContent = New-InTUIBrowserAuthPageContent ` -Title 'PIM Activation Successful - InTUI' ` -Heading 'PIM Activation Successful' ` -Message 'Your elevated session was refreshed. You can close this window and return to InTUI.' return [pscustomobject]@{ Operation = 'Activation' CompletedStatus = 'Activated' BlockedWarning = 'Some selected PIM roles are already active and will not be activated again.' ResultsTitle = 'PIM Activation Results' SubmitTitle = '[red]Submitting PIM activation request(s)...[/]' RefreshTitle = '[red]Refreshing activation status...[/]' SelectionLabel = 'Selected roles' UsesDuration = $true RequestLogMessage = 'PIM activation requested' FailureLogMessage = 'PIM activation failed' ResponseLogMessage = 'PIM activation response received' BlockLogMessage = 'PIM activation skipped because role is already active' ReauthLoadingTitle = '[red]Refreshing Graph token after PIM activation...[/]' ReauthSuccessMessage = 'Refreshed Microsoft Graph authentication after successful PIM activation.' ReauthFailureMessage = 'PIM activation succeeded, but Graph token refresh failed. Reconnect manually if Intune access still shows stale authorization.' BrowserSuccessContent = $browserSuccessContent ValidateRequest = { param( [Parameter()] [int]$Hours, [Parameter()] [string]$Reason ) if ($Hours -lt 1) { throw 'Activation duration is required.' } if ($Hours -gt 24) { throw 'Activation duration cannot exceed 24 hours.' } if (-not (Test-InTUIPimReason -Reason $Reason)) { throw 'Activation reason is required.' } } GetBlockedMessage = { param( [Parameter()] [object]$Role, [Parameter()] [hashtable]$ActiveRoleKeys = @{} ) if ($ActiveRoleKeys.ContainsKey((Get-InTUIPimRoleKey -Role $Role))) { return 'PIM role is already active. Deactivate it first or wait for it to expire before activating again.' } return $null } NewRequestBody = { param( [Parameter()] [object]$Role, [Parameter()] [int]$Hours, [Parameter()] [string]$Reason ) New-InTUIPimActivationRequestBody -Role $Role -Hours $Hours -Reason $Reason } GetLogFields = { param( [Parameter()] [object]$Role ) $null = $Role @{} } } } 'Deactivation' { $browserSuccessContent = New-InTUIBrowserAuthPageContent ` -Title 'PIM Deactivation Successful - InTUI' ` -Heading 'PIM Deactivation Successful' ` -Message 'Your reduced-privilege session was refreshed. You can close this window and return to InTUI.' return [pscustomobject]@{ Operation = 'Deactivation' CompletedStatus = 'Deactivated' BlockedWarning = 'Some selected PIM roles were activated less than 5 minutes ago and cannot be deactivated yet.' ResultsTitle = 'PIM Deactivation Results' SubmitTitle = '[red]Submitting PIM deactivation request(s)...[/]' RefreshTitle = '[red]Refreshing active role status...[/]' SelectionLabel = 'Selected active roles' UsesDuration = $false RequestLogMessage = 'PIM deactivation requested' FailureLogMessage = 'PIM deactivation failed' ResponseLogMessage = 'PIM deactivation response received' BlockLogMessage = 'PIM deactivation skipped because role was activated too recently' ReauthLoadingTitle = '[red]Refreshing Graph token after PIM deactivation...[/]' ReauthSuccessMessage = 'Refreshed Microsoft Graph authentication after successful PIM deactivation.' ReauthFailureMessage = 'PIM deactivation succeeded, but Graph token refresh failed. Reconnect manually if authorization still shows stale privileges.' BrowserSuccessContent = $browserSuccessContent ValidateRequest = { param( [Parameter()] [int]$Hours, [Parameter()] [string]$Reason ) $null = $Hours $null = $Reason } GetBlockedMessage = { param( [Parameter()] [object]$Role, [Parameter()] [hashtable]$ActiveRoleKeys = @{} ) $null = $ActiveRoleKeys Get-InTUIPimDeactivationMinimumAgeMessage -Role $Role } NewRequestBody = { param( [Parameter()] [object]$Role, [Parameter()] [int]$Hours, [Parameter()] [string]$Reason ) $null = $Hours New-InTUIPimDeactivationRequestBody -Role $Role -Reason $Reason } GetLogFields = { param( [Parameter()] [object]$Role ) if ($Role.Id) { return @{ TargetScheduleId = $Role.Id } } return @{} } } } } } function Get-InTUIPimRoleChangeOperationByCompletedStatus { [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateSet('Activated', 'Deactivated')] [string]$CompletedStatus ) if ($CompletedStatus -eq 'Activated') { return (Get-InTUIPimRoleChangeOperation -Operation Activation) } return (Get-InTUIPimRoleChangeOperation -Operation Deactivation) } function New-InTUIPimRoleChangePlan { [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateSet('Activation', 'Deactivation')] [string]$Operation, [Parameter(Mandatory)] [object[]]$Roles, [Parameter()] [object[]]$ActiveRoles = @() ) $operationInfo = Get-InTUIPimRoleChangeOperation -Operation $Operation $allowedRoles = [System.Collections.Generic.List[object]]::new() $blockedResults = [System.Collections.Generic.List[object]]::new() $activeRoleKeys = New-InTUIPimRoleKeySet -Roles $ActiveRoles foreach ($role in @($Roles)) { $blockMessage = & ($operationInfo.GetBlockedMessage) -Role $role -ActiveRoleKeys $activeRoleKeys if ($blockMessage) { $blockedResults.Add((New-InTUIPimRoleChangeResult -Role $role -Status 'Blocked' -ErrorMessage $blockMessage)) } else { $allowedRoles.Add($role) } } [pscustomobject]@{ Operation = $operationInfo.Operation AllowedRoles = $allowedRoles.ToArray() BlockedResults = $blockedResults.ToArray() } } function New-InTUIPimActivationPlan { [CmdletBinding()] param( [Parameter(Mandatory)] [object[]]$Roles, [Parameter()] [object[]]$ActiveRoles = @() ) New-InTUIPimRoleChangePlan -Operation Activation -Roles $Roles -ActiveRoles $ActiveRoles } function New-InTUIPimDeactivationPlan { [CmdletBinding()] param( [Parameter(Mandatory)] [object[]]$Roles ) New-InTUIPimRoleChangePlan -Operation Deactivation -Roles $Roles } function New-InTUIPimRoleChangeLogContext { [CmdletBinding()] param( [Parameter(Mandatory)] [object]$Role, [Parameter()] [object]$OperationInfo, [Parameter()] [string]$Reason, [Parameter()] [int]$Hours, [Parameter()] [string]$Status, [Parameter()] [string]$RequestId, [Parameter()] [string]$ErrorMessage ) $context = @{ RoleName = $Role.DisplayName RoleDefinitionId = $Role.RoleDefinitionId DirectoryScopeId = $Role.DirectoryScopeId } if ($OperationInfo -and $OperationInfo.GetLogFields) { $extraContext = & ($OperationInfo.GetLogFields) -Role $Role foreach ($key in @($extraContext.Keys)) { $context[$key] = $extraContext[$key] } } if ($Hours -gt 0) { $context['Hours'] = $Hours } if ($Reason) { $context['Reason'] = $Reason } if ($Status) { $context['Status'] = $Status } if ($RequestId) { $context['RequestId'] = $RequestId } if ($ErrorMessage) { $context['Error'] = $ErrorMessage } return $context } function Invoke-InTUIPimRoleChange { [CmdletBinding()] param( [Parameter(Mandatory)] [ValidateSet('Activation', 'Deactivation')] [string]$Operation, [Parameter()] [object[]]$Roles, [Parameter()] [int]$Hours, [Parameter()] [string]$Reason, [Parameter()] [object]$Plan ) if ($null -eq $Plan -and $null -eq $Roles) { throw "PIM $($Operation.ToLowerInvariant()) requires roles or a role-change plan." } $operationInfo = Get-InTUIPimRoleChangeOperation -Operation $Operation & ($operationInfo.ValidateRequest) -Hours $Hours -Reason $Reason $rolePlan = if ($Plan) { $Plan } else { New-InTUIPimRoleChangePlan -Operation $Operation -Roles $Roles } if ($rolePlan.Operation -and $rolePlan.Operation -ne $Operation) { throw "PIM $($Operation.ToLowerInvariant()) received a $($rolePlan.Operation.ToLowerInvariant()) plan." } $results = [System.Collections.Generic.List[object]]::new() $redactedReason = ConvertTo-InTUIPimRedactedReason -Reason $Reason foreach ($blockedResult in @($rolePlan.BlockedResults)) { $role = $blockedResult.Role Write-InTUILog -Level 'WARN' -Message $operationInfo.BlockLogMessage -Context (New-InTUIPimRoleChangeLogContext -Role $role -OperationInfo $operationInfo -ErrorMessage $blockedResult.Error) $results.Add($blockedResult) } foreach ($role in @($rolePlan.AllowedRoles)) { $body = & ($operationInfo.NewRequestBody) -Role $role -Hours $Hours -Reason $Reason Write-InTUILog -Message $operationInfo.RequestLogMessage -Context (New-InTUIPimRoleChangeLogContext -Role $role -OperationInfo $operationInfo -Hours $Hours -Reason $redactedReason) $response = Invoke-InTUIGraphRequest -Uri '/roleManagement/directory/roleAssignmentScheduleRequests' -Method POST -Body $body -Beta if ($null -eq $response) { $errorMessage = $script:LastGraphError.Message ?? 'Graph request failed' Write-InTUILog -Level 'ERROR' -Message $operationInfo.FailureLogMessage -Context (New-InTUIPimRoleChangeLogContext -Role $role -OperationInfo $operationInfo -Reason $redactedReason -ErrorMessage $errorMessage) $results.Add((New-InTUIPimRoleChangeResult -Role $role -Status 'Failed' -ErrorMessage $errorMessage -Submitted)) continue } $status = $response.status ?? 'Submitted' Write-InTUILog -Message $operationInfo.ResponseLogMessage -Context (New-InTUIPimRoleChangeLogContext -Role $role -OperationInfo $operationInfo -Status $status -RequestId $response.id -Reason $redactedReason) $results.Add((New-InTUIPimRoleChangeResult -Role $role -Status $status -RequestId $response.id -RawResponse $response -Submitted)) } return $results.ToArray() } function Invoke-InTUIPimRoleActivation { [CmdletBinding()] param( [Parameter()] [object[]]$Roles, [Parameter()] [int]$Hours, [Parameter()] [string]$Reason, [Parameter()] [object]$Plan ) Invoke-InTUIPimRoleChange -Operation Activation -Roles $Roles -Plan $Plan -Hours $Hours -Reason $Reason } function Invoke-InTUIPimRoleDeactivation { [CmdletBinding()] param( [Parameter()] [object[]]$Roles, [Parameter()] [string]$Reason, [Parameter()] [object]$Plan ) Invoke-InTUIPimRoleChange -Operation Deactivation -Roles $Roles -Plan $Plan -Reason $Reason } function Complete-InTUIPimRoleChangeReauth { [CmdletBinding()] param( [Parameter()] [object[]]$Results = @(), [Parameter(Mandatory)] [ValidateSet('Activated', 'Deactivated')] [string]$CompletedStatus ) $operationInfo = Get-InTUIPimRoleChangeOperationByCompletedStatus -CompletedStatus $CompletedStatus $completedResults = @($Results | Where-Object { $_.Status -eq $CompletedStatus }) if ($completedResults.Count -eq 0) { return $false } $roleNames = @($completedResults | ForEach-Object { $_.RoleName } | Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_) }) Write-InTUILog -Message 'Refreshing Graph token after PIM role change' -Context @{ CompletedStatus = $CompletedStatus CompletedCount = $completedResults.Count Roles = ($roleNames -join ',') } $reauthScopes = Get-InTUIPimPostRoleChangeRefreshScopes $refreshed = Show-InTUILoading -Title $operationInfo.ReauthLoadingTitle -ScriptBlock { Reconnect-InTUIGraph ` -Scopes $reauthScopes ` -BrowserSuccessContent $operationInfo.BrowserSuccessContent } if ($refreshed) { Show-InTUIInfo $operationInfo.ReauthSuccessMessage } else { Show-InTUIWarning $operationInfo.ReauthFailureMessage } return $refreshed } function Update-InTUIPimRoleChangeResultsFromActiveRoles { [CmdletBinding()] param( [Parameter()] [object[]]$Results = @(), [Parameter()] [object[]]$ActiveRoles = @(), [Parameter(Mandatory)] [object]$OperationInfo ) $activeKeys = New-InTUIPimRoleKeySet -Roles $ActiveRoles foreach ($result in @($Results)) { $submittedProperty = $result.PSObject.Properties['Submitted'] $isSubmitted = ($null -eq $submittedProperty -or $submittedProperty.Value) if (-not $isSubmitted -or $result.Status -eq 'Failed' -or $null -eq $result.Role) { continue } $isActive = $activeKeys.ContainsKey((Get-InTUIPimRoleKey -Role $result.Role)) if (($OperationInfo.CompletedStatus -eq 'Activated' -and $isActive) -or ($OperationInfo.CompletedStatus -eq 'Deactivated' -and -not $isActive)) { $result.Status = $OperationInfo.CompletedStatus } } } function Update-InTUIPimActivationResultsFromActiveRoles { [CmdletBinding()] param( [Parameter()] [object[]]$Results = @(), [Parameter()] [object[]]$ActiveRoles = @() ) $operationInfo = Get-InTUIPimRoleChangeOperation -Operation Activation Update-InTUIPimRoleChangeResultsFromActiveRoles -Results $Results -ActiveRoles $ActiveRoles -OperationInfo $operationInfo } function Update-InTUIPimDeactivationResultsFromActiveRoles { [CmdletBinding()] param( [Parameter()] [object[]]$Results = @(), [Parameter()] [object[]]$ActiveRoles = @() ) $operationInfo = Get-InTUIPimRoleChangeOperation -Operation Deactivation Update-InTUIPimRoleChangeResultsFromActiveRoles -Results $Results -ActiveRoles $ActiveRoles -OperationInfo $operationInfo } |