source/Public/Update-RJTenant.ps1

function Update-RJTenant {
    <#
    .SYNOPSIS
    Updates an existing RealmJoin tenant by updating service principal permissions and removing legacy applications.
 
    .DESCRIPTION
    This cmdlet updates an existing RealmJoin tenant by:
    1. Installing mandatory features if not present (same as New-RJTenant)
    2. Adding/removing permissions to align with current configuration
    3. Removing legacy applications that are no longer needed
 
    This is essentially the same as New-RJTenant but includes cleanup of legacy applications.
 
    .PARAMETER Features
    Optional array of feature names to enable. Available features can be viewed using Show-RJFeatureInfo.
    Mandatory features are automatically included and don't need to be specified.
    Default: @('IntuneLAPS', 'ShowSignin', 'Autopilot', 'DeviceIntuneActions', 'DeviceHealthScript', 'BitLockerRecoveryKeys', 'WindowsDeviceUpdateEnrollment', 'IntuneLicenseCount')
    Use @() to enable only mandatory features.
    Cannot be used together with AddFeatures or RemoveFeatures.
 
    .PARAMETER AddFeatures
    Optional array of feature names to add to the current configuration.
    Existing features remain unchanged. Available features can be viewed using Show-RJFeatureInfo.
    Cannot be used together with Features or All.
 
    .PARAMETER RemoveFeatures
    Optional array of feature names to remove from the current configuration.
    Existing features (except those being removed) remain unchanged.
    Cannot be used together with Features or All.
    Mandatory features cannot be removed.
 
    .PARAMETER All
    When specified, enables all available features (both mandatory and optional).
    Cannot be used together with AddFeatures or RemoveFeatures.
 
    .PARAMETER ReadOnly
    When specified, assigns read-only permissions instead of full permissions where available.
    For example, Group.ReadWrite.All becomes Group.Read.All.
    In incremental mode (AddFeatures/RemoveFeatures), only applies to new features being added.
 
    .PARAMETER Token
    Optional token for RealmJoin customer API integration. When provided, sends the configured
    optional features to the RealmJoin customer API after successful tenant update.
 
    .PARAMETER WhatIf
    Shows what would be done without actually performing the tenant update.
 
    .EXAMPLE
    Update-RJTenant
    Updates the tenant with mandatory features plus default optional features and removes legacy apps.
 
    .EXAMPLE
    Update-RJTenant -AddFeatures @('Client')
    Adds the Client feature to the current tenant configuration while keeping existing features unchanged.
 
    .EXAMPLE
    Update-RJTenant -RemoveFeatures @('DeviceHealthScript')
    Removes the DeviceHealthScript feature from the current tenant configuration while keeping other features.
 
    .EXAMPLE
    Update-RJTenant -AddFeatures @('Client') -RemoveFeatures @('ShowSignin')
    Adds Client feature and removes ShowSignin feature in a single operation.
 
    .EXAMPLE
    Update-RJTenant -AddFeatures @('Client') -ReadOnly
    Adds the Client feature with read-only permissions while keeping existing features unchanged.
 
    .EXAMPLE
    Update-RJTenant -Features @()
    Updates the tenant with only mandatory features and removes legacy apps.
 
    .EXAMPLE
    Update-RJTenant -Features @('DeviceHealthScript')
    Updates the tenant with mandatory features plus only DeviceHealthScript and removes legacy apps.
 
    .EXAMPLE
    Update-RJTenant -All
    Updates the tenant with all available features enabled and removes legacy apps.
 
    .EXAMPLE
    Update-RJTenant -ReadOnly
    Updates the tenant with default features using read-only permissions and removes legacy apps.
 
    .EXAMPLE
    Update-RJTenant -All -ReadOnly
    Updates the tenant with all available features using read-only permissions and removes legacy apps.
 
    .EXAMPLE
    Update-RJTenant -WhatIf
    Shows what permissions would be updated and what legacy apps would be removed.
 
    .EXAMPLE
    Update-RJTenant -Features @('DeviceHealthScript') -Verbose
    Updates tenant with detailed verbose output showing the update process.
 
    .EXAMPLE
    Update-RJTenant -Token "abc123"
    Updates the tenant with default features and sends configuration to RealmJoin customer API.
 
    .EXAMPLE
    Complete-RJTenantOnboarding -Features @('DeviceHealthScript')
    Same as Update-RJTenant - completes tenant onboarding using the alias.
 
    .EXAMPLE
    Complete-RJTenantOnboarding -All -ReadOnly
    Completes tenant onboarding with all available features using read-only permissions using the alias.
 
    .NOTES
    This cmdlet requires appropriate Azure AD permissions to create/update service principals,
    assign app roles, and delete service principals.
    Mandatory features are always enabled regardless of the Features parameter.
    Use either -Features/-All or -AddFeatures/-RemoveFeatures, but not both.
    The -ReadOnly switch applies to all selected features in traditional mode, or only to new features in incremental mode.
 
    The alias Complete-RJTenantOnboarding can be used interchangeably with Update-RJTenant.
    #>


    [Alias('Complete-RJTenantOnboarding')]
    [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Features')]
    param(
        [Parameter(ParameterSetName = 'Features')]
        [string[]]$Features,

        [Parameter(ParameterSetName = 'Incremental')]
        [string[]]$AddFeatures = @(),

        [Parameter(ParameterSetName = 'Incremental')]
        [string[]]$RemoveFeatures = @(),

        [Parameter(ParameterSetName = 'All')]
        [switch]$All,

        [switch]$ReadOnly,

        [string]$Token
    )

    begin {
        Write-Verbose "Updating RealmJoin tenant"
    }

    process {
        Write-Information "Initializing required modules"
        if (-not (Initialize-RequiredModule)) {
            # Modules are not ready - exit gracefully without throwing
            Write-Verbose "Module initialization failed - function cannot proceed"
            return
        }
        Write-Information "All required modules are initialized"

        Write-Information "Logging in"
        $null = Invoke-GraphLogin

        Write-Information "Getting tenant information"
        $tenantInfo = Get-TenantInfo
        Write-Information "Tenant: $($tenantInfo.DisplayName) ($($tenantInfo.TenantId))"

        # Validate parameter combinations and determine mode
        switch ($PSCmdlet.ParameterSetName) {
            'Incremental' {
                # Validate that at least one incremental parameter is provided
                if ($AddFeatures.Count -eq 0 -and $RemoveFeatures.Count -eq 0) {
                    throw "At least one of AddFeatures or RemoveFeatures must be specified in incremental mode"
                }

                Write-Verbose "Using incremental mode: Add=$($AddFeatures.Count), Remove=$($RemoveFeatures.Count), ReadOnly=$($ReadOnly.IsPresent)"

                # Get current configuration
                $currentConfig = Get-RJCurrentConfiguration
                $featureResolution = Resolve-RJFeature -CurrentConfig $currentConfig -FeaturesToAdd $AddFeatures -FeaturesToRemove $RemoveFeatures -ReadOnly:$ReadOnly

                $operationParts = @()
                if ($AddFeatures.Count -gt 0) { $operationParts += "adding $($AddFeatures.Count) feature$(if($AddFeatures.Count -ne 1){'s'})" }
                if ($RemoveFeatures.Count -gt 0) { $operationParts += "removing $($RemoveFeatures.Count) feature$(if($RemoveFeatures.Count -ne 1){'s'})" }
                $permissionMode = if ($ReadOnly) { "read-only for new features" } else { "full for new features" }
                Write-Information "Incremental update: $($operationParts -join ', ') with $permissionMode"
            }
            default {
                # Traditional mode: determine selected features based on parameter set
                $selectedFeatures = switch ($PSCmdlet.ParameterSetName) {
                    'All' {
                        if ($All) {
                            # No action needed, just for clarity
                        }
                        Write-Verbose "Using -All switch: enabling all available features"
                        (Get-RJFeatureDefinitionList).Name
                    }
                    'Features' {
                        if($Features.Count -eq 0){
                            $Features = $DefaultSelectedFeatures
                            Write-Verbose "Parameter 'Features' specified but empty, using default features"
                        }
                        Write-Verbose "Using specified features: $($Features -join ', ')"
                        $Features
                    }
                    default {
                        Write-Verbose "No parameters specified, using default features"
                        $DefaultSelectedFeatures
                    }
                }

                $featureResolution = Resolve-RJFeature -SelectedFeatures $selectedFeatures -ReadOnly:$ReadOnly
                $permissionMode = if ($ReadOnly) { "read-only" } else { "full" }
                Write-Information "Selected features: $($featureResolution.FeatureNames -join ', ') with $permissionMode permissions"
            }
        }

        # Get analysis
        $analysis = Get-RJTenantPermissionAnalysis -FeatureResolution $featureResolution

        # Determine which command was used for ShouldProcess message
        $commandUsed = if ($MyInvocation.InvocationName -eq 'Complete-RJTenantOnboarding') {
            'Complete-RJTenantOnboarding'
        } else {
            'Update-RJTenant'
        }

        if ($PSCmdlet.ShouldProcess("Tenant: $($tenantInfo.DisplayName) ($($tenantInfo.TenantId))", $commandUsed)) {
            # Step 1: Service Principal Operations
            $permissionResults = Set-RJTenantServicePrincipal -FeatureResolution $featureResolution
            if (-not $permissionResults.Success) {
                throw "Failed to configure service principals"
            }

            # Step 2: Legacy Applications
            Write-Information ""
            Write-Information "=== Cleaning Up Legacy Applications ==="

            $legacyApps = Get-RJLegacyAppList

            if (-not $legacyApps -or $legacyApps.Count -eq 0) {
                Write-Information "No legacy applications configured for cleanup."
                $cleanupResults = @{ ProcessedApps = @(); Success = $true }
            }
            else {
                $cleanupResults = Remove-RJLegacyApplication
                if (-not $cleanupResults.Success) {
                    Write-Warning "Legacy application cleanup encountered issues but continuing"
                }
            }

            # Send token to customer API if provided
            if ($Token) {
                $optionalFeatures = $permissionResults.FeatureResolution.FeatureNames | Where-Object { $_ -notin (Get-RJMandatoryFeatureList).Name }
                Invoke-RJTenantRefresh -Token $Token -OptionalFeatures $optionalFeatures
            }

            # Open RealmJoin Portal if new service principal was created
            Open-RJPortalIfNewServicePrincipal -ProcessedApps $permissionResults.ProcessedApps

            # Final summary
            Write-Information ""
            Write-Information "=== Update Summary ==="

            $cleanupApps = $cleanupResults.ProcessedApps | Where-Object { $_.Action -eq 'Removed' }
            Write-Information "Legacy apps processed: $($cleanupResults.ProcessedApps.Count) checked, $($cleanupApps.Count) removed"

            Write-Information ""
            if ($featureResolution.IsIncrementalMode) {
                $permissionMode = if ($ReadOnly) { "read-only for new features" } else { "full for new features" }
                Write-Information "Successfully updated RealmJoin tenant using incremental mode with $permissionMode"
                Write-Information "Final features: $($permissionResults.FeatureResolution.FeatureNames -join ', ')"

                if ($AddFeatures -and $AddFeatures.Count -gt 0) {
                    Write-Information "Added features: $($AddFeatures -join ', ')"
                }
                if ($RemoveFeatures -and $RemoveFeatures.Count -gt 0) {
                    Write-Information "Removed features: $($RemoveFeatures -join ', ')"
                }
            } else {
                $permissionMode = if ($ReadOnly) { "read-only" } else { "full" }
                Write-Information "Successfully updated RealmJoin tenant with $($permissionResults.FeatureResolution.FeatureNames.Count) features using $permissionMode permissions"
                Write-Information "Enabled features: $($permissionResults.FeatureResolution.FeatureNames -join ', ')"
            }

            if ($cleanupApps.Count -gt 0) {
                Write-Information "Removed legacy applications: $($cleanupApps.DisplayName -join ', ')"
            }
        }
        else {
            # Show analysis for all service principals (WhatIf mode)
            Write-Verbose "Showing analysis in WhatIf mode"
            Show-RJFeatureAnalysis -FeatureAnalysis $analysis.FeatureAnalysis -OrphanedServicePrincipals $analysis.OrphanedServicePrincipals

            # Show analysis for all legacy apps (WhatIf mode)
            $legacyApps = Get-RJLegacyAppList

            if ($legacyApps -and $legacyApps.Count -gt 0) {
                Write-Verbose "Getting analysis for all legacy applications using shared diff logic"
                $legacyDiffs = @()

                foreach ($legacyApp in $legacyApps) {
                    $legacyDiffs += Get-RJLegacyAppDiff -LegacyApp $legacyApp
                }

                Show-RJLegacyAppAnalysis -Analysis $legacyDiffs
            }
        }
    }

    end {
        Write-Verbose "RealmJoin tenant update completed"
    }
}