Private/Utility/Repair-PowerShellEnvironment.ps1

function Repair-PowerShellEnvironment {
    <#
        .SYNOPSIS
        Attempts to repair issues identified by Test-PowerShellEnvironment.
 
        .DESCRIPTION
        Automatically or interactively fixes PowerShell environment issues including:
        - Sets UTF-8 encoding for current session
        - Optionally adds UTF-8 to profile for persistence
        - Installs and imports missing modules
        - Provides guidance for non-fixable issues (OS, PS version, Terminal)
         
        This function is designed to work with the output from Test-PowerShellEnvironment,
        but can also run independently by performing its own environment test first.
         
        Fixable issues:
        - UTF-8 Encoding (current session and profile)
        - Missing Modules (installation and import)
         
        Non-fixable issues (guidance provided):
        - Operating System (not Windows)
        - OS Version (Windows 10 1607+/Server 2016+ required)
        - PowerShell Version (5.1 or 7.4+ required)
        - Windows Terminal (user must install manually)
 
        .PARAMETER EnvironmentTest
        The hashtable returned by Test-PowerShellEnvironment containing diagnostic results.
        If not provided, Test-PowerShellEnvironment will be run automatically.
 
        .PARAMETER Force
        Bypasses all interactive prompts and applies all fixes automatically.
        Useful for automation scenarios and non-interactive execution.
 
        .PARAMETER SkipProfileUpdate
        Skips adding UTF-8 encoding to the PowerShell profile.
        Encoding will still be set for the current session.
 
        .PARAMETER SkipModuleInstall
        Skips installing missing modules.
        Will still attempt to import modules that are already available.
 
        .INPUTS
        System.Collections.Hashtable
        You can pipe the output from Test-PowerShellEnvironment to this function.
 
        .OUTPUTS
        PSCustomObject
        Returns a custom object with the following properties:
        - EncodingRepaired: Boolean - UTF-8 encoding was set for current session
        - ProfileUpdated: Boolean - UTF-8 was added to PowerShell profile
        - ModulesInstalled: String[] - Array of module names that were installed
        - ModulesImported: String[] - Array of module names that were imported
        - RemainingIssues: String[] - Array of issues that could not be fixed
        - Success: Boolean - Overall success status
 
        .EXAMPLE
        Repair-PowerShellEnvironment
        Runs environment test and interactively repairs issues with user prompts.
 
        .EXAMPLE
        Test-PowerShellEnvironment | Repair-PowerShellEnvironment
        Pipes test results directly to repair function for targeted fixes.
 
        .EXAMPLE
        Repair-PowerShellEnvironment -Force
        Automatically repairs all fixable issues without prompting.
 
        .EXAMPLE
        Repair-PowerShellEnvironment -SkipProfileUpdate
        Repairs issues but doesn't modify the PowerShell profile.
 
        .EXAMPLE
        $result = Repair-PowerShellEnvironment -Verbose
        if ($result.Success) {
            Write-Host "Environment repaired successfully"
        }
        Stores repair results and checks overall success status.
 
        .NOTES
        This function uses ShouldProcess for operations that modify the system.
        Use -WhatIf to preview changes without applying them.
        Use -Confirm to be prompted for each change.
         
        Critical environment issues (OS, OS version, PowerShell version) cannot be
        automatically fixed and will result in guidance messages only.
 
        .LINK
        Test-PowerShellEnvironment
    #>

    [CmdletBinding(SupportsShouldProcess)]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(ValueFromPipeline)]
        [hashtable]$EnvironmentTest,
        
        [Parameter()]
        [switch]$Force,
        
        [Parameter()]
        [switch]$SkipProfileUpdate,
        
        [Parameter()]
        [switch]$SkipModuleInstall
    )

    #requires -Version 5.1

    begin {
        Write-Verbose "Starting PowerShell environment repair..."
        
        # Initialize result tracking
        $encodingRepaired = $false
        $profileUpdated = $false
        $modulesInstalled = @()
        $modulesImported = @()
        $remainingIssues = @()
    }

    process {
        try {
            # Run environment test if not provided
            if (-not $EnvironmentTest) {
                Write-Verbose "No environment test provided. Running Test-PowerShellEnvironment..."
                $EnvironmentTest = Test-PowerShellEnvironment
            }

            # Critical issues that cannot be fixed (provide guidance only)
            if (-not $EnvironmentTest.IsWindows) {
                $remainingIssues += "Operating System: Locksmith 2 requires Windows"
                Write-Warning "Locksmith 2 is only supported on Windows operating systems."
                Write-Warning "Current OS cannot be changed automatically."
            }

            if (-not $EnvironmentTest.IsSupportedOS) {
                $remainingIssues += "OS Version: Requires Windows 10 1607+/Server 2016+"
                Write-Warning "Locksmith 2 requires Windows 10 Anniversary Update (1607) or Windows Server 2016 or later."
                Write-Warning "Please upgrade your operating system."
            }

            if (-not $EnvironmentTest.IsSupportedPS) {
                $remainingIssues += "PowerShell Version: Requires 5.1 or 7.4+"
                Write-Warning "Locksmith 2 requires Windows PowerShell 5.1 or PowerShell 7.4+."
                Write-Warning "Download PowerShell 7.4+ from: https://aka.ms/powershell"
                Write-Warning "Or install via: winget install Microsoft.PowerShell"
            }

            # Fix 1: UTF-8 Encoding (current session)
            if (-not $EnvironmentTest.IsUtf8) {
                Write-Verbose "UTF-8 encoding is not set for current session."
                
                if ($Force -or $PSCmdlet.ShouldProcess("Console Output Encoding", "Set to UTF-8")) {
                    Update-OutputEncoding
                    $encodingRepaired = $true
                    Write-Verbose "UTF-8 encoding set for current session."
                }
            } else {
                Write-Verbose "UTF-8 encoding already configured for current session."
            }

            # Fix 2: UTF-8 Encoding (profile - persistent)
            if (-not $SkipProfileUpdate -and -not $EnvironmentTest.IsUtf8) {
                Write-Verbose "Checking if UTF-8 should be added to profile..."
                
                $updateProfile = $false
                if ($Force) {
                    $updateProfile = $true
                } else {
                    $choice = Read-Choice -Question "Add UTF-8 encoding to your PowerShell profile for future sessions?" -Options @('y', 'n') -Default 'y'
                    $updateProfile = ($choice -eq 'y')
                }
                
                if ($updateProfile) {
                    if ($PSCmdlet.ShouldProcess("PowerShell Profile ($PROFILE)", "Add UTF-8 encoding configuration")) {
                        Update-DollarSignProfile
                        $profileUpdated = $true
                        Write-Verbose "UTF-8 encoding added to PowerShell profile."
                    }
                } else {
                    Write-Verbose "Skipped profile update per user choice."
                }
            }

            # Fix 3: Missing Modules
            if (-not $EnvironmentTest.AllModulesLoaded) {
                Write-Verbose "Processing missing modules: $($EnvironmentTest.MissingModules -join ', ')"
                
                foreach ($moduleName in $EnvironmentTest.MissingModules) {
                    Write-Verbose "Processing module: $moduleName"
                    
                    # PSCertutil is mandatory for Locksmith 2
                    $isMandatory = ($moduleName -eq 'PSCertutil')
                    
                    # Check if module is available (installed but not loaded)
                    $isAvailable = Test-IsModuleAvailable -Name $moduleName
                    
                    if ($isAvailable) {
                        # Module is installed, just needs import
                        Write-Verbose "Module '$moduleName' is available. Importing..."
                        
                        if ($PSCmdlet.ShouldProcess($moduleName, "Import module")) {
                            try {
                                Import-Module -Name $moduleName -Global -ErrorAction Stop
                                $modulesImported += $moduleName
                                Write-Verbose "Module '$moduleName' imported successfully."
                            } catch {
                                if ($isMandatory) {
                                    $errorRecord = [System.Management.Automation.ErrorRecord]::new(
                                        [System.Exception]::new("Failed to import required module '$moduleName': $_"),
                                        'RequiredModuleImportFailed',
                                        [System.Management.Automation.ErrorCategory]::NotInstalled,
                                        $moduleName
                                    )
                                    $PSCmdlet.ThrowTerminatingError($errorRecord)
                                } else {
                                    Write-Warning "Failed to import module '$moduleName': $_"
                                    $remainingIssues += "Module Import: $moduleName failed"
                                }
                            }
                        }
                    } else {
                        # Module needs installation
                        if (-not $SkipModuleInstall) {
                            Write-Verbose "Module '$moduleName' is not available. Installation required..."
                            
                            $result = Install-NeededModule -Name $moduleName -Force:$Force -Mandatory:$isMandatory
                            
                            if ($result) {
                                $modulesInstalled += $moduleName
                                $modulesImported += $moduleName
                                Write-Verbose "Module '$moduleName' installed and imported successfully."
                            } else {
                                if ($isMandatory) {
                                    $errorRecord = [System.Management.Automation.ErrorRecord]::new(
                                        [System.Exception]::new("Required module '$moduleName' could not be installed."),
                                        'RequiredModuleInstallFailed',
                                        [System.Management.Automation.ErrorCategory]::NotInstalled,
                                        $moduleName
                                    )
                                    $PSCmdlet.ThrowTerminatingError($errorRecord)
                                } else {
                                    Write-Warning "Module '$moduleName' could not be installed."
                                    $remainingIssues += "Module Install: $moduleName failed"
                                }
                            }
                        } else {
                            if ($isMandatory) {
                                $errorRecord = [System.Management.Automation.ErrorRecord]::new(
                                    [System.Exception]::new("Required module '$moduleName' is missing and SkipModuleInstall is enabled."),
                                    'RequiredModuleMissing',
                                    [System.Management.Automation.ErrorCategory]::NotInstalled,
                                    $moduleName
                                )
                                $PSCmdlet.ThrowTerminatingError($errorRecord)
                            } else {
                                Write-Verbose "Skipping installation of '$moduleName' (SkipModuleInstall enabled)."
                                $remainingIssues += "Module Missing: $moduleName not installed"
                            }
                        }
                    }
                }
            } else {
                Write-Verbose "All required modules are already loaded."
            }

            # Guidance 1: Windows Terminal recommendation
            if (-not $EnvironmentTest.IsWindowsTerminal) {
                Write-Warning "For the best visual experience, install Windows Terminal:"
                Write-Host " - Microsoft Store: https://aka.ms/terminal" -ForegroundColor Cyan
                Write-Host " - Or run: winget install Microsoft.WindowsTerminal" -ForegroundColor Cyan
                $remainingIssues += "Terminal: Windows Terminal not detected (optional)"
            }

            # Guidance 2: PowerShell Core recommendation
            if (-not $EnvironmentTest.IsPowerShellCore -and $EnvironmentTest.IsSupportedPS) {
                Write-Warning "You're running Windows PowerShell 5.1. For full interactive features, upgrade to PowerShell 7.4+:"
                Write-Host " - Download: https://aka.ms/powershell" -ForegroundColor Cyan
                Write-Host " - Or run: winget install Microsoft.PowerShell" -ForegroundColor Cyan
                Write-Host " - Locksmith 2 will continue in headless mode." -ForegroundColor Yellow
                $remainingIssues += "PowerShell Edition: Desktop (5.1) - Interactive mode requires Core 7.4+"
            }

            # Build result object
            $success = ($remainingIssues.Count -eq 0) -or 
                       ($remainingIssues | Where-Object { $_ -notlike "*optional*" -and $_ -notlike "*PowerShell Edition*" }).Count -eq 0

            $result = [PSCustomObject]@{
                EncodingRepaired  = $encodingRepaired
                ProfileUpdated    = $profileUpdated
                ModulesInstalled  = $modulesInstalled
                ModulesImported   = $modulesImported
                RemainingIssues   = $remainingIssues
                Success           = $success
            }

            Write-Verbose "PowerShell environment repair complete."
            Write-Verbose "Success: $success"
            Write-Verbose "Encoding Repaired: $encodingRepaired"
            Write-Verbose "Profile Updated: $profileUpdated"
            Write-Verbose "Modules Installed: $($modulesInstalled.Count)"
            Write-Verbose "Modules Imported: $($modulesImported.Count)"
            Write-Verbose "Remaining Issues: $($remainingIssues.Count)"

            return $result

        } catch {
            $errorRecord = [System.Management.Automation.ErrorRecord]::new(
                $_.Exception,
                'EnvironmentRepairFailed',
                [System.Management.Automation.ErrorCategory]::NotSpecified,
                $EnvironmentTest
            )
            $PSCmdlet.ThrowTerminatingError($errorRecord)
        }
    }
}