source/Private/TenantOnboardingOperations.ps1
|
function Set-RJTenantServicePrincipal { [CmdletBinding(SupportsShouldProcess)] [OutputType([System.Collections.Hashtable])] param( [Parameter(Mandatory)] [PSCustomObject]$FeatureResolution ) Write-Verbose "Creating/updating service principals for features: $($FeatureResolution.FeatureNames -join ', ')" $mandatoryCount = (Get-RJMandatoryFeatureList).Count $totalFeatures = $FeatureResolution.FeatureNames.Count $optionalFeatures = $totalFeatures - $mandatoryCount Write-Verbose "Feature statistics: $totalFeatures features ($mandatoryCount mandatory, $optionalFeatures optional), $($FeatureResolution.AllPermissionsCount) total permissions" if ($PSCmdlet.ShouldProcess("Tenant Service Principals", "Configure permissions for $($FeatureResolution.FeatureNames.Count) features")) { # Use shared analysis logic to determine what changes are needed $analysis = Get-RJTenantPermissionAnalysis -FeatureResolution $FeatureResolution $msGraphSp = $analysis.MsGraphServicePrincipal $processedApps = @() foreach ($featureAnalysis in $analysis.FeatureAnalysis) { $appId = $featureAnalysis.AppId Write-Information "Processing feature: $($featureAnalysis.FeatureName) (AppId: $appId)" Write-Verbose "Permissions for feature $($featureAnalysis.FeatureName): $($featureAnalysis.DesiredPermissions -join ', ')" # If no changes are needed, log and continue if ($featureAnalysis.FeatureChangeType -eq "NoChanges") { Write-Information "Feature: $($featureAnalysis.FeatureName) - No changes required" $processedApps += @{ FeatureName = $featureAnalysis.FeatureName AppId = $appId ServicePrincipal = @{ displayName = $featureAnalysis.ServicePrincipalName; id = $featureAnalysis.ServicePrincipalId } PermissionCount = $featureAnalysis.DesiredPermissions.Count Success = $true ChangeType = $featureAnalysis.FeatureChangeType } continue } Write-Verbose "Changes needed for feature $($featureAnalysis.FeatureName): Add=$($featureAnalysis.FeaturePermissionsToAdd.Count), Remove=$($featureAnalysis.FeaturePermissionsToRemove.Count)" # Convert feature permissions to app role assignments $assignmentsToAdd = @() foreach ($permission in $featureAnalysis.FeaturePermissionsToAdd) { $appRole = $msGraphSp.appRoles | Where-Object { $_.value -eq $permission } if ($appRole) { $assignmentsToAdd += @{ ResourceId = $msGraphSp.id AppRoleId = $appRole.id PermissionName = $permission } } } $assignmentsToRemove = @() # Use the permission details with assignment IDs from the analysis foreach ($permissionDetail in $featureAnalysis.FeaturePermissionDetailsToRemove) { $assignmentsToRemove += @{ ResourceId = $permissionDetail.ResourceId AppRoleId = $permissionDetail.AppRoleId PermissionName = $permissionDetail.PermissionName PrincipalId = $permissionDetail.PrincipalId Id = $permissionDetail.AppRoleAssignmentId } } # Execute the actual work Write-Verbose "Creating/updating service principal and assigning permissions for feature: $($featureAnalysis.FeatureName)" $servicePrincipal = Set-RJServicePrincipal -AppId $appId -AssignmentsToAdd $assignmentsToAdd -AssignmentsToRemove $assignmentsToRemove Write-Information "Feature $($featureAnalysis.FeatureName) configured: $($assignmentsToAdd.Count) permissions assigned, $($assignmentsToRemove.Count) permissions removed" $processedApps += @{ FeatureName = $featureAnalysis.FeatureName AppId = $appId ServicePrincipal = $servicePrincipal PermissionCount = $assignmentsToAdd.Count Success = $true ChangeType = $featureAnalysis.FeatureChangeType } } # Process orphaned service principals if ($analysis.OrphanedServicePrincipals -and $analysis.OrphanedServicePrincipals.Count -gt 0) { Write-Information "Processing $($analysis.OrphanedServicePrincipals.Count) orphaned service principals" foreach ($orphanedSP in $analysis.OrphanedServicePrincipals) { Write-Information "Removing orphaned service principal: $($orphanedSP.ServicePrincipalName)" Remove-ServicePrincipal -Id $orphanedSP.ServicePrincipalId Write-Information "Successfully removed orphaned service principal: $($orphanedSP.ServicePrincipalName)" $processedApps += @{ FeatureName = "Orphaned Service Principal" AppId = $orphanedSP.AppId ServicePrincipal = @{ displayName = $orphanedSP.ServicePrincipalName; id = $orphanedSP.ServicePrincipalId } PermissionCount = 0 Success = $true ChangeType = "RemoveServicePrincipal" } } } return @{ FeatureResolution = $FeatureResolution ProcessedApps = $processedApps MsGraphServicePrincipal = $msGraphSp Success = $true } } else { # WhatIf mode - return empty result return @{ FeatureResolution = $FeatureResolution ProcessedApps = @() MsGraphServicePrincipal = $null Success = $true } } } function Remove-RJLegacyApplication { [CmdletBinding(SupportsShouldProcess)] [OutputType([System.Collections.Hashtable])] param() Write-Verbose "Removing legacy applications" $legacyApps = Get-RJLegacyAppList if (-not $legacyApps -or $legacyApps.Count -eq 0) { Write-Information "No legacy applications configured for cleanup." return @{ ProcessedApps = @() Success = $true } } Write-Information "Checking for $($legacyApps.Count) legacy applications to clean up" if ($PSCmdlet.ShouldProcess("Legacy Applications", "Remove $($legacyApps.Count) legacy applications")) { $processedApps = @() foreach ($legacyApp in $legacyApps) { Write-Verbose "Processing legacy app: $($legacyApp.DisplayName) (AppId: $($legacyApp.AppId))" # Use shared analysis logic to determine what action is needed $legacyDiff = Get-RJLegacyAppDiff -LegacyApp $legacyApp # Create the result object based on the analysis $result = @{ AppId = $legacyDiff.AppId DisplayName = $legacyDiff.DisplayName ObjectId = $legacyDiff.ServicePrincipalId Action = $legacyDiff.Action Success = $true Error = $legacyDiff.Error } # Handle each action type switch ($legacyDiff.Action) { "Remove" { Write-Information "Found legacy app: $($legacyDiff.ServicePrincipalName) (ObjectId: $($legacyDiff.ServicePrincipalId))" Write-Verbose "Removing legacy service principal: $($legacyDiff.ServicePrincipalName)" Remove-ServicePrincipal -Id $legacyDiff.ServicePrincipalId Write-Information "Successfully removed legacy app: $($legacyDiff.ServicePrincipalName)" $result.Action = 'Removed' $result.DisplayName = $legacyDiff.ServicePrincipalName # Use actual name from service principal } "NotFound" { Write-Verbose "Legacy app not found (already removed): $($legacyApp.DisplayName)" $result.Action = 'NotFound' } "Error" { Write-Warning "Error checking legacy app $($legacyApp.AppId): $($legacyDiff.Error)" $result.Success = $false } } $processedApps += , @($result) } return @{ ProcessedApps = $processedApps Success = $true } } else { # WhatIf mode - return empty result return @{ ProcessedApps = @() Success = $true } } } function Get-RJLegacyAppList { [CmdletBinding()] [OutputType([System.Object[]])] param() $config = Get-RJConfiguration # Use comma operator to prevent PowerShell from unwrapping single-element arrays return , $config.LegacyApps } function Open-RJPortalIfNewServicePrincipal { [CmdletBinding()] param( [Parameter(Mandatory)] [array]$ProcessedApps ) $realmJoinPortalAppId = 'b0130885-16be-4c6f-83de-5b1042b5d2e3' # Check if RealmJoin Portal service principal was newly created $realmJoinApp = $ProcessedApps | Where-Object { $_.AppId -eq $realmJoinPortalAppId -and $_.ChangeType -eq 'CreateServicePrincipal' } if ($realmJoinApp) { Write-Information "Opening RealmJoin Portal in browser for newly created service principal" try { $portalUrl = Get-RJPortalUrl Start-Process $portalUrl Write-Information "RealmJoin Portal opened: $portalUrl" } catch { Write-Warning "Failed to open RealmJoin Portal in browser: $($_.Exception.Message)" } } else { Write-Verbose "RealmJoin Portal service principal was not newly created - skipping browser launch" } } function Invoke-RJTenantRefresh { [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Token, [Parameter()] [string[]]$OptionalFeatures = @() ) $body = @{ token = $Token optionalFeatures = $OptionalFeatures } | ConvertTo-Json try { Write-Information "Refreshing RealmJoin tenant configuration. This may take a few moments" Start-Sleep -Seconds 10 # Short delay to ensure previous changes are settled Invoke-RestMethod -Uri (Get-RJCustomerApiUrl) -Method Post -Body $body -ContentType "application/json" Write-Information "Tenant refresh completed successfully" Write-Information "Waiting for API to complete processing..." Start-Sleep -Seconds 5 } catch { $statusCode = $null $statusDescription = $null if ($_.Exception.Response) { $statusCode = $_.Exception.Response.StatusCode.Value__ $statusDescription = $_.Exception.Response.StatusDescription } if ($statusCode -eq 401) { Write-Warning "Authentication failed (401). The token may be invalid or expired." Show-RJManualTokenInput -OptionalFeatures $OptionalFeatures } else { Write-Warning ("StatusCode: {0}" -f $statusCode) Write-Warning ("StatusDescription: {0}" -f $statusDescription) Write-Warning ("ErrorMessage: {0}" -f $_.Exception.Message) } } } function Show-RJManualTokenInput { [CmdletBinding()] param( [Parameter(Mandatory)] [string[]]$OptionalFeatures ) Write-Host "Press any key to continue..." -ForegroundColor Gray $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") $choice = Show-RJGenericMenu -Title "Token Authentication Failed" -Prompt "What would you like to do?" -Options $TokenRetryOptions switch ($choice) { 'Manual' { $newToken = Read-Host "Enter the new token" if (-not [string]::IsNullOrWhiteSpace($newToken)) { Invoke-RJTenantRefresh -Token $newToken -OptionalFeatures $OptionalFeatures } else { Write-Host "No token entered. Skipping token verification." -ForegroundColor Yellow } } 'Portal' { try { $portalFeaturesUrl = Get-RJPortalFeaturesUrl Start-Process $portalFeaturesUrl Write-Host "Portal opened: $portalFeaturesUrl" -ForegroundColor Green } catch { Write-Host "Failed to open portal: $($_.Exception.Message)" -ForegroundColor Red } $newToken = Read-Host "Enter the token from the portal" if (-not [string]::IsNullOrWhiteSpace($newToken)) { Invoke-RJTenantRefresh -Token $newToken -OptionalFeatures $OptionalFeatures } else { Write-Host "No token entered. Skipping token verification." -ForegroundColor Yellow } } 'Skip' { Write-Host "Skipping token verification as requested." -ForegroundColor Gray } default { Write-Host "Skipping token verification." -ForegroundColor Gray } } } |