Public/Add-SafePath.ps1

function Add-SafePath {
    <#
    .SYNOPSIS
        Adds a new path entry safely, with validation and optimal positioning.
         
    .DESCRIPTION
        Adds a path to the PATH environment variable after verifying:
        - The path exists on the filesystem
        - The path is not already in PATH
        - Inserts at the optimal position based on priority category
         
    .PARAMETER Path
        The path to add to the PATH environment variable.
         
    .PARAMETER Target
        Which PATH to modify: User or Machine. Default is User.
         
    .PARAMETER Force
        Skips confirmation prompt.
         
    .PARAMETER WhatIf
        Shows what would happen without making changes.
         
    .EXAMPLE
        Add-SafePath -Path "C:\MyApp\bin"
         
    .EXAMPLE
        Add-SafePath -Path "C:\Tools" -Target Machine
         
    .EXAMPLE
        Add-SafePath -Path "C:\NewTool\bin" -WhatIf
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory, Position = 0)]
        [string]$Path,
        
        [ValidateSet('User', 'Machine')]
        [string]$Target = 'User',
        
        [switch]$Force
    )

    # Normalize the path
    $Path = $Path.TrimEnd('\')
    
    # Check for admin rights if targeting Machine
    if ($Target -eq 'Machine') {
        $isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
        if (-not $isAdmin) {
            Write-Error "Administrator privileges required to modify the Machine PATH. Please run PowerShell as Administrator."
            return
        }
    }

    Write-Host
    Write-Host " Add Path to [$Target] PATH" -ForegroundColor Cyan
    Write-Host " ═══════════════════════════════════════" -ForegroundColor DarkGray
    Write-Host
    Write-Host " Path to add: " -NoNewline -ForegroundColor White
    Write-Host $Path -ForegroundColor Yellow
    Write-Host

    # Security Check: Validate path for security issues
    $securityCheck = Test-PathSecurity -Path $Path
    if (-not $securityCheck.IsValid) {
        Write-Host " ✗ SECURITY: Path failed validation" -ForegroundColor Red
        foreach ($issue in $securityCheck.Issues) {
            Write-Host " ⚠ $issue" -ForegroundColor Red
        }
        Write-Host
        return [PSCustomObject]@{
            Success        = $false
            Reason         = 'Security validation failed'
            SecurityIssues = $securityCheck.Issues
            Severity       = $securityCheck.Severity
            Path           = $Path
        }
    }
    elseif ($securityCheck.Issues.Count -gt 0) {
        # Has warnings but not critical
        Write-Host " ⚠ Security warnings:" -ForegroundColor Yellow
        foreach ($issue in $securityCheck.Issues) {
            Write-Host " • $issue" -ForegroundColor Yellow
        }
        Write-Host
    }
    else {
        Write-Host " ✓ Security validation passed" -ForegroundColor Green
    }

    # Check 1: Does the path exist?
    $expandedPath = [Environment]::ExpandEnvironmentVariables($Path)
    if (-not (Test-Path -LiteralPath $expandedPath -PathType Container)) {
        Write-Host " ✗ Path does not exist: $expandedPath" -ForegroundColor Red
        Write-Host
        return [PSCustomObject]@{
            Success = $false
            Reason  = 'Path does not exist'
            Path    = $Path
        }
    }
    Write-Host " ✓ Path exists" -ForegroundColor Green

    # Check 2: Is it already in PATH?
    $currentPath = [Environment]::GetEnvironmentVariable('PATH', $Target)
    $currentPaths = $currentPath -split ';' | Where-Object { $_ -ne '' }
    
    $normalizedNew = $expandedPath.TrimEnd('\').ToLowerInvariant()
    $alreadyExists = $false
    
    foreach ($existing in $currentPaths) {
        $normalizedExisting = [Environment]::ExpandEnvironmentVariables($existing).TrimEnd('\').ToLowerInvariant()
        if ($normalizedExisting -eq $normalizedNew) {
            $alreadyExists = $true
            Write-Host " ✗ Path already exists in $Target PATH" -ForegroundColor Red
            Write-Host " Existing entry: $existing" -ForegroundColor DarkGray
            Write-Host
            return [PSCustomObject]@{
                Success       = $false
                Reason        = 'Path already exists'
                Path          = $Path
                ExistingEntry = $existing
            }
        }
    }
    Write-Host " ✓ Path is not a duplicate" -ForegroundColor Green

    # Check 3: If targeting User, check if path already exists in Machine PATH (redundant)
    if ($Target -eq 'User') {
        $machinePath = [Environment]::GetEnvironmentVariable('PATH', 'Machine')
        $machinePaths = $machinePath -split ';' | Where-Object { $_ -ne '' }
        
        foreach ($existing in $machinePaths) {
            $normalizedExisting = [Environment]::ExpandEnvironmentVariables($existing).TrimEnd('\').ToLowerInvariant()
            if ($normalizedExisting -eq $normalizedNew) {
                Write-Host " ✗ Path already exists in Machine PATH (would be redundant)" -ForegroundColor Red
                Write-Host " Machine PATH entry: $existing" -ForegroundColor DarkGray
                Write-Host " Adding it to User PATH is unnecessary since Machine PATH is always loaded." -ForegroundColor Yellow
                Write-Host
                return [PSCustomObject]@{
                    Success          = $false
                    Reason           = 'Path exists in Machine PATH (redundant)'
                    Path             = $Path
                    MachinePathEntry = $existing
                }
            }
        }
        Write-Host " ✓ Path is not redundant (not in Machine PATH)" -ForegroundColor Green
    }

    # Determine priority category
    $priority = Get-PathPriority -Path $Path
    Write-Host " ✓ Category detected: $($priority.Category) (Priority: $($priority.Priority))" -ForegroundColor Green
    Write-Host

    # Find optimal insertion position
    $insertPosition = 0
    for ($i = 0; $i -lt $currentPaths.Count; $i++) {
        $existingPriority = Get-PathPriority -Path $currentPaths[$i]
        if ($existingPriority.Priority -gt $priority.Priority) {
            $insertPosition = $i
            break
        }
        $insertPosition = $i + 1
    }

    # Build new PATH
    $newPaths = [System.Collections.ArrayList]::new()
    $newPaths.AddRange($currentPaths)
    $newPaths.Insert($insertPosition, $Path)
    $newPath = $newPaths -join ';'

    # Show what will happen
    Write-Host " Insertion position: $($insertPosition + 1) of $($currentPaths.Count + 1)" -ForegroundColor White
    if ($insertPosition -gt 0) {
        Write-Host " After: $($currentPaths[$insertPosition - 1])" -ForegroundColor DarkGray
    }
    if ($insertPosition -lt $currentPaths.Count) {
        Write-Host " Before: $($currentPaths[$insertPosition])" -ForegroundColor DarkGray
    }
    Write-Host

    # WhatIf handling
    if ($WhatIfPreference) {
        Write-Host " [WhatIf] Would add path to $Target PATH at position $($insertPosition + 1)" -ForegroundColor Magenta
        return [PSCustomObject]@{
            Success  = $true
            WhatIf   = $true
            Path     = $Path
            Position = $insertPosition + 1
            Category = $priority.Category
        }
    }

    # Confirmation
    if (-not $Force) {
        $confirm = Read-Host " Add this path? (Y/N)"
        if ($confirm -ne 'Y' -and $confirm -ne 'y') {
            Write-Host " Operation cancelled." -ForegroundColor Gray
            return [PSCustomObject]@{
                Success   = $false
                Cancelled = $true
            }
        }
    }

    # Create backup
    $backup = Backup-Path -Target $Target
    Write-Host " ✓ Backup created: $($backup.BackupFile)" -ForegroundColor Green

    # Apply the change
    try {
        [Environment]::SetEnvironmentVariable('PATH', $newPath, $Target)
        Write-Host
        Write-Host " ✓ Path added successfully!" -ForegroundColor Green
        Write-Host " Note: Restart your terminal for changes to take effect." -ForegroundColor DarkGray
        Write-Host

        return [PSCustomObject]@{
            Success    = $true
            Path       = $Path
            Target     = $Target
            Position   = $insertPosition + 1
            Category   = $priority.Category
            BackupFile = $backup.BackupFile
        }
    }
    catch {
        Write-Error "Failed to update PATH: $_"
        return [PSCustomObject]@{
            Success = $false
            Error   = $_.Exception.Message
        }
    }
}