Public/Remove-SafePath.ps1

function Remove-SafePath {
    <#
    .SYNOPSIS
        Removes a path entry safely, with validation and backup.
         
    .DESCRIPTION
        Removes a path from the PATH environment variable after verifying:
        - The path exists in PATH
        - Creates a backup before removing
         
    .PARAMETER Path
        The path to remove from 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
        Remove-SafePath -Path "C:\MyApp\bin"
         
    .EXAMPLE
        Remove-SafePath -Path "C:\Tools" -Target Machine
         
    .EXAMPLE
        Remove-SafePath -Path "C:\OldTool\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 " Remove Path from [$Target] PATH" -ForegroundColor Cyan
    Write-Host " ═══════════════════════════════════════" -ForegroundColor DarkGray
    Write-Host
    Write-Host " Path to remove: " -NoNewline -ForegroundColor White
    Write-Host $Path -ForegroundColor Yellow
    Write-Host

    # Get current PATH
    $currentPath = [Environment]::GetEnvironmentVariable('PATH', $Target)
    $currentPaths = @($currentPath -split ';' | Where-Object { $_ -ne '' })
    
    # Find the path to remove (case-insensitive comparison)
    $expandedPath = [Environment]::ExpandEnvironmentVariables($Path)
    $normalizedSearch = $expandedPath.TrimEnd('\').ToLowerInvariant()
    $foundIndex = -1
    $foundEntry = $null
    
    for ($i = 0; $i -lt $currentPaths.Count; $i++) {
        $normalizedExisting = [Environment]::ExpandEnvironmentVariables($currentPaths[$i]).TrimEnd('\').ToLowerInvariant()
        if ($normalizedExisting -eq $normalizedSearch) {
            $foundIndex = $i
            $foundEntry = $currentPaths[$i]
            break
        }
    }
    
    if ($foundIndex -eq -1) {
        Write-Host " ✗ Path not found in $Target PATH" -ForegroundColor Red
        Write-Host
        return [PSCustomObject]@{
            Success = $false
            Reason  = 'Path not found in PATH'
            Path    = $Path
            Target  = $Target
        }
    }
    
    Write-Host " ✓ Path found at position $($foundIndex + 1) of $($currentPaths.Count)" -ForegroundColor Green
    Write-Host " Entry: $foundEntry" -ForegroundColor DarkGray
    Write-Host

    # Check if path exists on filesystem
    $pathCheck = Test-PathEntry -Path $expandedPath
    if (-not $pathCheck.Exists) {
        if ($pathCheck.AccessDenied) {
            Write-Host " ⚠ Note: Path does not exist or no access (may require elevated privileges)" -ForegroundColor Yellow
        }
        else {
            Write-Host " ⚠ Note: Path does not exist on filesystem (already deleted or obsolete)" -ForegroundColor Yellow
        }
    }
    Write-Host

    # Build new PATH without the removed entry
    $newPaths = [System.Collections.ArrayList]::new()
    for ($i = 0; $i -lt $currentPaths.Count; $i++) {
        if ($i -ne $foundIndex) {
            $newPaths.Add($currentPaths[$i]) | Out-Null
        }
    }
    $newPath = $newPaths -join ';'

    # WhatIf handling
    if ($WhatIfPreference) {
        Write-Host " [WhatIf] Would remove path from $Target PATH at position $($foundIndex + 1)" -ForegroundColor Magenta
        Write-Host " [WhatIf] PATH entries would go from $($currentPaths.Count) to $($newPaths.Count)" -ForegroundColor Magenta
        return [PSCustomObject]@{
            Success  = $true
            WhatIf   = $true
            Path     = $Path
            Position = $foundIndex + 1
            Target   = $Target
        }
    }

    # Confirmation
    if (-not $Force) {
        $confirm = Read-Host " Remove 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
    if ($PSCmdlet.ShouldProcess("$Target PATH", "Remove path '$foundEntry'")) {
        try {
            [Environment]::SetEnvironmentVariable('PATH', $newPath, $Target)
            Write-Host
            Write-Host " ✓ Path removed successfully!" -ForegroundColor Green
            Write-Host " Note: Restart your terminal for changes to take effect." -ForegroundColor DarkGray
            Write-Host

            return [PSCustomObject]@{
                Success          = $true
                Path             = $Path
                RemovedEntry     = $foundEntry
                Target           = $Target
                Position         = $foundIndex + 1
                RemainingEntries = $newPaths.Count
                BackupFile       = $backup.BackupFile
            }
        }
        catch {
            Write-Error "Failed to update PATH: $_"
            return [PSCustomObject]@{
                Success = $false
                Error   = $_.Exception.Message
            }
        }
    }
    else {
        return [PSCustomObject]@{
            Success   = $false
            Cancelled = $true
        }
    }
}