Public/Start-QuickPath.ps1

using namespace System.Management.Automation

<#
.SYNOPSIS
    Initializes and configures quick directory navigation system for PowerShell.
 
.DESCRIPTION
    Start-QuickPath establishes a quick navigation system enabling rapid directory
    switching using short aliases. It supports persistent configuration through
    PowerShell profiles and provides cross-session availability.
 
    The function creates and manages an XML configuration file storing path aliases,
    implements tab completion for enhanced usability, and supports multiple user scopes.
 
.PARAMETER ConfigPath
    Specifies the location for the XML configuration file.
    Default: "$env:USERPROFILE\QuickSoft\quickpaths.xml"
 
.PARAMETER Scope
    Determines the persistence scope of the configuration:
    - CurrentSession: Active only in current PowerShell session
    - CurrentUser: Persists across all sessions for current user
    - AllUsers: System-wide persistence for all users
    Default: CurrentSession
 
.PARAMETER NoAlias
    Disables creation of the 'q' shorthand alias for quick access.
 
.EXAMPLE
    Start-QuickPath -Scope CurrentUser
    # Initializes Quick-Paths with persistence for current user
 
.EXAMPLE
    Start-QuickPath -ConfigPath "D:\Config\paths.xml" -Scope AllUsers
    # Sets up system-wide Quick-Paths with custom config location
.LINK
    https://github.com/AutomateSilent/QuickSoft
     
.NOTES
    Name: Start-QuickPath
    Author: AutomateSilent
    Version: 1.0.0
    Required Modules: None
#>

function Start-QuickPath {
    [CmdletBinding()]
    param (
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$ConfigPath = "$env:USERPROFILE\QuickSoft\quickpaths.xml",

        [Parameter()]
        [ValidateSet('CurrentSession', 'CurrentUser', 'AllUsers')]
        [string]$Scope = 'CurrentSession',

        [Parameter()]
        [switch]$NoAlias
    )

    begin {
        Write-Verbose "Initializing Quick-Paths with config path: $ConfigPath"
        $ErrorActionPreference = 'Stop'

        #region Helper Functions
        function Initialize-Configuration {
            [CmdletBinding()]
            param (
                [Parameter(Mandatory)]
                [ValidateNotNullOrEmpty()]
                [string]$Path
            )

            try {
                $configDir = Split-Path -Path $Path -Parent
                if (-not (Test-Path -Path $configDir)) {
                    $null = New-Item -Path $configDir -ItemType Directory -Force
                    Write-Verbose "Created directory: $configDir"
                }

                if (-not (Test-Path -Path $Path)) {
                    $defaultXml = @"
<?xml version="1.0" encoding="UTF-8"?>
<QuickPaths>
    <Configuration>
        <Source>$Path</Source>
        <LastUpdated>$((Get-Date).ToString('yyyy-MM-dd HH:mm:ss'))</LastUpdated>
    </Configuration>
    <Paths>
        <Path alias="desktop" location="$env:USERPROFILE\Desktop" />
        <Path alias="docs" location="$env:USERPROFILE\Documents" />
    </Paths>
</QuickPaths>
"@

                    $defaultXml | Out-File -FilePath $Path -Encoding UTF8 -Force
                    Write-Verbose "Created config file: $Path"
                }
            }
            catch {
                throw "Failed to initialize configuration: $_"
            }
        }

        function script:Backup-QuickPathsConfig {
            [CmdletBinding()]
            param (
                [Parameter(Mandatory)]
                [ValidateScript({ Test-Path $_ })]
                [string]$Path
            )
        
            try {
                $backupPath = "$Path.backup_$(Get-Date -Format 'yyyyMMdd_HHmmss')"
                Copy-Item -Path $Path -Destination $backupPath -Force
                Write-Verbose "Created backup at: $backupPath"
                return $backupPath
            }
            catch {
                Write-Error "Backup failed: $_"
                return $null
            }
        }

        function script:Import-QuickPathsConfig {
            [CmdletBinding()]
            param(
                [Parameter(Mandatory)]
                [ValidateScript({
                    if (-not (Test-Path -Path $_)) {
                        throw "File not found: $_"
                    }
                    if (-not ($_ -match '\.xml$')) {
                        throw "File must be XML: $_"
                    }
                    try {
                        [xml]$content = Get-Content -Path $_ -ErrorAction Stop
                        if (-not ($content.QuickPaths -and $content.QuickPaths.Paths)) {
                            throw "Invalid Quick-Paths format"
                        }
                        return $true
                    }
                    catch {
                        throw "XML validation failed: $_"
                    }
                })]
                [string]$Path,
        
                [Parameter()]
                [ValidateSet('Merge', 'Replace', 'Skip')]
                [string]$Strategy = 'Skip'
            )
        
            try {
                Write-Verbose "Starting import process..."
                Write-Verbose "Source: $Path"
                Write-Verbose "Target: $script:QuickPathsFile"
                Write-Verbose "Strategy: $Strategy"
        
                # Create backup
                $backupPath = script:Backup-QuickPathsConfig -Path $script:QuickPathsFile
                if (-not $backupPath) {
                    throw "Backup creation failed"
                }
                Write-Verbose "Created backup at: $backupPath"
        
                # Load configurations
                [xml]$sourceConfig = Get-Content -Path $Path -Raw
                [xml]$targetConfig = Get-Content -Path $script:QuickPathsFile -Raw
        
                # Track changes
                $changes = @{
                    Added = 0
                    Replaced = 0
                    Skipped = 0
                    Merged = 0
                }
        
                # Process paths
                foreach ($sourcePath in $sourceConfig.QuickPaths.Paths.Path) {
                    $alias = $sourcePath.alias
                    $location = $sourcePath.location
                    Write-Verbose "Processing: $alias -> $location"
        
                    $existingPath = $targetConfig.QuickPaths.Paths.Path | 
                        Where-Object { $_.alias -eq $alias }
        
                    if ($existingPath) {
                        switch ($Strategy) {
                            'Replace' {
                                $existingPath.location = $location
                                $changes.Replaced++
                                Write-Verbose "Replaced: $alias"
                            }
                            'Merge' {
                                if ($existingPath.location -ne $location) {
                                    $newAlias = "${alias}_imported"
                                    $newPath = $targetConfig.CreateElement("Path")
                                    $newPath.SetAttribute("alias", $newAlias)
                                    $newPath.SetAttribute("location", $location)
                                    $targetConfig.QuickPaths.Paths.AppendChild($newPath)
                                    $changes.Merged++
                                    Write-Verbose "Merged as: $newAlias"
                                }
                            }
                            'Skip' {
                                $changes.Skipped++
                                Write-Verbose "Skipped: $alias"
                            }
                        }
                    }
                    else {
                        $newPath = $targetConfig.CreateElement("Path")
                        $newPath.SetAttribute("alias", $alias)
                        $newPath.SetAttribute("location", $location)
                        $targetConfig.QuickPaths.Paths.AppendChild($newPath)
                        $changes.Added++
                        Write-Verbose "Added: $alias"
                    }
                }
        
                # Save changes
                $targetConfig.Save($script:QuickPathsFile)
        
                # Display summary
                Write-Host "`nImport Summary:" -ForegroundColor Cyan
                Write-Host "Added: $($changes.Added)" -ForegroundColor Green
                Write-Host "Replaced: $($changes.Replaced)" -ForegroundColor Yellow
                Write-Host "Merged: $($changes.Merged)" -ForegroundColor Green
                Write-Host "Skipped: $($changes.Skipped)" -ForegroundColor Yellow
                Write-Host "`nBackup created at: $backupPath" -ForegroundColor Cyan
        
                return $true
            }
            catch {
                Write-Error "Import failed: $_"
                if ($backupPath -and (Test-Path $backupPath)) {
                    Copy-Item -Path $backupPath -Destination $script:QuickPathsFile -Force
                    Write-Warning "Restored from backup: $backupPath"
                }
                return $false
            }
        }
        
       

        function Set-ProfileIntegration {
            [CmdletBinding()]
            [OutputType([bool])]
            param (
                [Parameter(Mandatory)]
                [ValidateSet('CurrentUser', 'AllUsers')]
                [string]$Scope,

                [Parameter(Mandatory)]
                [ValidateNotNullOrEmpty()]
                [string]$ConfigPath
            )

            try {
                # Get appropriate profile path
                $profilePath = switch ($Scope) {
                    'CurrentUser' { $PROFILE.CurrentUserAllHosts }
                    'AllUsers' { $PROFILE.AllUsersAllHosts }
                }

                # Ensure profile directory exists
                $profileDir = Split-Path -Path $profilePath -Parent
                if (-not (Test-Path -Path $profileDir)) {
                    $null = New-Item -Path $profileDir -ItemType Directory -Force
                }

                # Generate profile content
                $profileContent = @"
                Clear-Host
# Quick-Paths Configuration
# Added: $([DateTime]::Now.ToString('yyyy-MM-dd HH:mm:ss'))
`$script:QuickPathsFile = '$ConfigPath'
`$script:QuickPathsProfile = '$profilePath'
Write-Host "Profile loaded: " -NoNewline -ForegroundColor Gray; Write-Host "$profilePath" -ForegroundColor Cyan ;
Write-Host " " -NoNewline; Write-Host "Computer: " -NoNewline -ForegroundColor Gray; Write-Host "$ENV:computername" -ForegroundColor Cyan;
Write-Host " " -ForegroundColor Gray -NoNewline; Write-Host "$([DateTime]::Now.ToString('hh:mm tt'))" -ForegroundColor White;
# Initialize Quick-Paths
if (Get-Module -ListAvailable -Name QuickSoft) {
    Import-Module QuickSoft
    Show-QuickSoftBanner
    Start-QuickPath
    New-Alias -Name q -Value qp -Scope Global -Force
}
"@


                # Update or create profile
                if (Test-Path -Path $profilePath) {
                    $existing = Get-Content -Path $profilePath -Raw
                    if ($existing -notlike "*Quick-Paths Configuration*") {
                        Add-Content -Path $profilePath -Value "`n$profileContent" -Force
                    }
                }
                else {
                    Set-Content -Path $profilePath -Value $profileContent -Force
                }

                Write-Verbose "Profile updated: $profilePath"
                return $true
            }
            catch {
                Write-Error "Profile integration failed: $_"
                return $false
            }
        }
        #endregion Helper Functions
    }

    process {
        try {
            # Initialize core configuration
            Initialize-Configuration -Path $ConfigPath

            # Configure profile integration
            if ($Scope -ne 'CurrentSession') {
                $profileSuccess = Set-ProfileIntegration -Scope $Scope -ConfigPath $ConfigPath
                if (-not $profileSuccess) {
                    Write-Warning "Profile integration incomplete. Some features may be limited."
                }
            }

            # Set global configuration path
            $script:QuickPathsFile = $ConfigPath

            # Create session alias
            if (-not $NoAlias) {
                New-Alias -Name q -Value qp -Scope Global -Force
                Write-Verbose "Created 'q' alias"
            }

            # Display status
            Write-Host " " -NoNewline; Write-Host "Quick-Paths initialized successfully!"  -ForegroundColor Green; 
            Write-Host " " -NoNewline; Write-Host "Configuration: " -NoNewline -ForegroundColor Gray; Write-Host "$ConfigPath" -ForegroundColor Cyan ;
            if ($Scope -ne 'CurrentSession') {
                Write-Host "Profile configured for $Scope" -ForegroundColor Green
                Write-Host " " -NoNewline; Write-Host "Profile path: " -NoNewline -ForegroundColor Gray; Write-Host "$($PROFILE.$($Scope+'AllHosts'))" -ForegroundColor Cyan ;
            }
            Write-Host "Type" -NoNewline -ForegroundColor Green; Write-Host " 'q help' " -ForegroundColor Yellow -NoNewline; Write-Host "for usage information" -ForegroundColor Green;
        }
        catch {
            Write-Error -Exception $_.Exception -Message "Quick-Paths initialization failed: $_"
            return
        }
    }
}

function global:qp {
    [CmdletBinding()]
    param(
        [Parameter(Position = 0)]
        [string]$Command,

        [Parameter(Position = 1)]
        [string]$Arg1,

        [Parameter(Position = 2)]
        [string]$Arg2,

        # Parameters for import command
        [Parameter()]
        [switch]$Merge,

        [Parameter()]
        [switch]$Replace,

        [Parameter()]
        [switch]$Skip
    )

    try {
        if (-not (Test-Path -Path $script:QuickPathsFile)) {
            throw "Configuration file not found at: $script:QuickPathsFile"
        }

        [xml]$config = Get-Content -Path $script:QuickPathsFile

        switch -Regex ($Command) {
            '^(add)$' {
                if (-not $Arg1 -or -not $Arg2) {
                    throw "Usage: q add <alias> <path>"
                }
                if (Test-Path -Path $Arg2) {
                    $Arg2 = (Resolve-Path -Path $Arg2).Path
                }
                $newPath = $config.CreateElement("Path")
                $newPath.SetAttribute("alias", $Arg1)
                $newPath.SetAttribute("location", $Arg2)
                $config.QuickPaths.Paths.AppendChild($newPath) | Out-Null
                $config.Save($script:QuickPathsFile)
                Write-Host "Added: $Arg1 -> $Arg2" -ForegroundColor Green
            }
            '^(rm|remove)$' {
                if (-not $Arg1) {
                    throw "Usage: q rm <alias>"
                }
                $pathToRemove = $config.QuickPaths.Paths.Path | 
                    Where-Object { $_.alias -eq $Arg1 }
                if ($pathToRemove) {
                    $config.QuickPaths.Paths.RemoveChild($pathToRemove) | Out-Null
                    $config.Save($script:QuickPathsFile)
                    Write-Host "Removed: $Arg1" -ForegroundColor Yellow
                }
            }
            '^(ls|list)$' {
                Write-Host "`nQuick Paths:" -ForegroundColor Cyan
                $config.QuickPaths.Paths.Path | Format-Table @{
                    Label = "Alias"
                    Expression = { $_.alias }
                    Width = 15
                }, @{
                    Label = "Location"
                    Expression = { $_.location }
                }
            }
            '^(open)$' {
                # Implementation of 'q open' command
                try {
                    $currentPath = Get-Location
                    Write-Verbose "Attempting to open File Explorer at location: $currentPath"
                    
                    # Validate current path exists
                    if (-not (Test-Path -Path $currentPath)) {
                        throw "Current path does not exist: $currentPath"
                    }
                    
                    # Start File Explorer process
                    $process = Start-Process -FilePath "explorer" -ArgumentList "." -ErrorAction Stop
                    
                    # Provide success feedback
                    Write-Host "Opened File Explorer in: $currentPath" -ForegroundColor Green
                    Write-Verbose "Process started successfully with ID: $($process.Id)"
                }
                catch {
                    # Detailed error handling
                    $errorMessage = "Failed to open File Explorer: $($_.Exception.Message)"
                    Write-Error -Message $errorMessage -Category OperationStopped
                    Write-Verbose "Stack Trace: $($_.Exception.StackTrace)"
                    
                    # Provide resolution steps
                    Write-Warning "Please ensure you have sufficient permissions and Explorer.exe is accessible"
                }
            }
           '^(backup)$' {
            $backupPath = script:Backup-QuickPathsConfig -Path $script:QuickPathsFile
                if ($backupPath) {
                    Write-Host "Backup created: $backupPath" -ForegroundColor Green
                }
            }
           '^(import)$' {
                # Parameter validation
                if (-not $Arg1) {
                    throw "Usage: q import <path> [-merge|-replace|-skip]"
                }

                # Resolve full path
                $importPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Arg1)
                Write-Verbose "Resolved import path: $importPath"

                # File validation
                if (-not (Test-Path -Path $importPath -PathType Leaf)) {
                    throw "Import file not found: $importPath"
                }

                # Strategy determination based on switches
                $strategy = 'Skip' # Default strategy
                if ($Merge) { $strategy = 'Merge' }
                elseif ($Replace) { $strategy = 'Replace' }

                Write-Verbose "Using merge strategy: $strategy"

                # Perform import
                $result = Import-QuickPathsConfig -Path $importPath -Strategy $strategy
                if (-not $result) {
                    throw "Import failed - check previous errors"
                }
            }

                        '^(help|\?)$' {

                Write-Host "Quick-Paths Commands:" -ForegroundColor Yellow
    
                # Navigation and Basic Commands
                Write-Host " " -NoNewline; Write-Host "q " -NoNewline -ForegroundColor White; Write-Host "<alias>" -ForegroundColor Cyan -NoNewline; Write-Host " " -NoNewline; Write-Host "->" -ForegroundColor Yellow -NoNewline; Write-Host " Jump to location" -ForegroundColor Green
                Write-Host " " -NoNewline; Write-Host "q add" -ForegroundColor White -NoNewline; Write-Host " " -NoNewline; Write-Host "<alias> <path>" -ForegroundColor Cyan -NoNewline; Write-Host " " -NoNewline; Write-Host "->" -ForegroundColor Yellow -NoNewline; Write-Host " Add new location" -ForegroundColor Green
                Write-Host " " -NoNewline; Write-Host "q rm" -ForegroundColor White -NoNewline; Write-Host " " -NoNewline; Write-Host "<alias>" -ForegroundColor Cyan -NoNewline; Write-Host " " -NoNewline; Write-Host "->" -ForegroundColor Yellow -NoNewline; Write-Host " Remove location" -ForegroundColor Green
                Write-Host " " -NoNewline; Write-Host "q ls" -ForegroundColor White -NoNewline; Write-Host " " -NoNewline; Write-Host "->" -ForegroundColor Yellow -NoNewline; Write-Host " List locations" -ForegroundColor Green
                Write-Host " " -NoNewline; Write-Host "q open" -ForegroundColor White -NoNewline; Write-Host " " -NoNewline; Write-Host "->" -ForegroundColor Yellow -NoNewline; Write-Host " Open File Explorer in current directory" -ForegroundColor Green

                # Configuration Management
                Write-Host "`nConfig Management:" -ForegroundColor Yellow
                Write-Host " " -NoNewline; Write-Host "q import" -ForegroundColor White -NoNewline; Write-Host " " -NoNewline; Write-Host "<path>" -ForegroundColor Cyan -NoNewline; Write-Host " " -NoNewline; Write-Host "->" -ForegroundColor Yellow -NoNewline; Write-Host " Import paths (skip duplicates)" -ForegroundColor Green
                Write-Host " " -NoNewline; Write-Host "q import" -ForegroundColor White -NoNewline; Write-Host " " -NoNewline; Write-Host "<path> -merge" -ForegroundColor Cyan -NoNewline; Write-Host " " -NoNewline; Write-Host "->" -ForegroundColor Yellow -NoNewline; Write-Host " Import and rename duplicates" -ForegroundColor Green
                Write-Host " " -NoNewline; Write-Host "q import" -ForegroundColor White -NoNewline; Write-Host " " -NoNewline; Write-Host "<path> -replace" -ForegroundColor Cyan -NoNewline; Write-Host " " -NoNewline; Write-Host "->" -ForegroundColor Yellow -NoNewline; Write-Host " Import and replace duplicates" -ForegroundColor Green
                Write-Host " " -NoNewline; Write-Host "q backup" -ForegroundColor White -NoNewline; Write-Host " " -NoNewline; Write-Host "->" -ForegroundColor Yellow -NoNewline; Write-Host " Create configuration backup" -ForegroundColor Green
                
                # Help and Information
                Write-Host " " -NoNewline; Write-Host "q help" -ForegroundColor White -NoNewline; Write-Host " " -NoNewline; Write-Host "->" -ForegroundColor Yellow -NoNewline; Write-Host " Show this help`n" -ForegroundColor Green
                
                Write-Host "Examples:" -ForegroundColor Yellow
                Write-Host " " -NoNewline; Write-Host "q add" -ForegroundColor White -NoNewline; Write-Host " " -NoNewline; Write-Host "dev ." -ForegroundColor Cyan -NoNewline; Write-Host " " -NoNewline; Write-Host "->" -ForegroundColor Yellow -NoNewline; Write-Host " Add current directory" -ForegroundColor Green
                Write-Host " " -NoNewline; Write-Host "q" -ForegroundColor White -NoNewline; Write-Host " " -NoNewline; Write-Host "dev" -ForegroundColor Cyan -NoNewline; Write-Host " " -NoNewline; Write-Host "->" -ForegroundColor Yellow -NoNewline; Write-Host " Jump to dev location" -ForegroundColor Green
                Write-Host " " -NoNewline; Write-Host "q import" -ForegroundColor White -NoNewline; Write-Host " " -NoNewline; Write-Host "common-paths.xml -merge" -ForegroundColor Cyan -NoNewline; Write-Host " " -NoNewline; Write-Host "->" -ForegroundColor Yellow -NoNewline; Write-Host " Import configuration" -ForegroundColor Green
                Write-Host " " -NoNewline; Write-Host "q backup" -ForegroundColor White -NoNewline; Write-Host " " -NoNewline; Write-Host "->" -ForegroundColor Yellow -NoNewline; Write-Host " Create backup before changes`n" -ForegroundColor Green
            }
            default {
                if ([string]::IsNullOrWhiteSpace($Command)) {
                    $config.QuickPaths.Paths.Path | Format-Table @{
                        Label = "Alias"; Expression = { $_.alias }; Width = 15
                    }, @{
                        Label = "Location"; Expression = { $_.location }
                    }
                    return
                }

                $targetPath = $config.QuickPaths.Paths.Path | 
                    Where-Object { $_.alias -eq $Command }
                if ($targetPath) {
                    Set-Location -Path $targetPath.location
                    Write-Host "-> $($targetPath.location)" -ForegroundColor Green
                }
                else {
                    throw "Unknown alias '$Command'. Use 'q ls' to see available paths."
                }
            }
        }
    }
    catch {
        Write-Error $_
    }
}

# Only update the tab completion part - no other changes needed
# Tab completion registration for Quick-Paths
Register-ArgumentCompleter -CommandName qp -ParameterName Command -ScriptBlock {
    param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
    
    try {
        # Get aliases from configuration
        [xml]$config = Get-Content -Path $script:QuickPathsFile
        
        # Define base commands including new 'open' command
        $commands = @(
            'add',    # Add new path
            'rm',     # Remove path
            'ls',     # List paths
            'list',   # Alternate list command
            'help',   # Show help
            'import', # Import configuration
            'backup', # Backup configuration
            'open'    # New command for opening Explorer
        )
        
        # Combine commands and aliases
        $completions = @($commands) + @($config.QuickPaths.Paths.Path.alias)
        
        # Handle import parameter completions
        if ($commandAst.CommandElements.Count -gt 1 -and 
            $commandAst.CommandElements[1].Value -eq 'import' -and
            $wordToComplete -like '-*') {
            
            @('-merge', '-replace', '-skip') | 
                Where-Object { $_ -like "$wordToComplete*" } | 
                ForEach-Object {
                    [CompletionResult]::new($_, $_, 'ParameterValue', $_)
                }
        }
        
        # Return matching completions
        $completions | 
            Where-Object { $_ -like "$wordToComplete*" } | 
            ForEach-Object {
                [CompletionResult]::new($_, $_, 'ParameterValue', $_)
            }
    }
    catch {
        Write-Verbose "Tab completion error: $_"
        # Silently fail for tab completion
    }
}