Private/Get-PatConfigurationPath.ps1

function Get-PatConfigurationPath {
    <#
    .SYNOPSIS
        Gets the configuration file path for PlexAutomationToolkit.
 
    .DESCRIPTION
        Determines the best location for the configuration file with fallback options.
        Prefers OneDrive-synced location for cross-machine sync on Windows.
        Uses ~/.config/PlexAutomationToolkit on Linux/macOS.
 
    .OUTPUTS
        String
        Returns the full path to the configuration file
    #>

    [CmdletBinding()]
    param ()

    $isWindowsPlatform = $PSVersionTable.PSVersion.Major -lt 6 -or $IsWindows

    if ($isWindowsPlatform) {
        # Try OneDrive location first (syncs across machines)
        if ($env:OneDrive) {
            # Validate OneDrive path doesn't contain path traversal sequences
            $oneDrivePath = $env:OneDrive
            if ($oneDrivePath -match '\.\.' -or $oneDrivePath -match '[\x00-\x1F]') {
                Write-Debug "OneDrive path contains suspicious characters, using fallback"
            }
            else {
                $configurationDirectory = Join-Path $oneDrivePath 'Documents\PlexAutomationToolkit'
                # Resolve to absolute path and verify it's under user profile
                try {
                    $resolvedDir = [System.IO.Path]::GetFullPath($configurationDirectory)
                    $userProfile = [System.IO.Path]::GetFullPath($env:USERPROFILE)

                    # Ensure resolved path is under user profile (security check)
                    if (-not $resolvedDir.StartsWith($userProfile, [System.StringComparison]::OrdinalIgnoreCase)) {
                        Write-Debug "OneDrive configuration path escapes user profile, using fallback"
                    }
                    else {
                        $configurationPath = Join-Path $resolvedDir 'servers.json'

                        # Test if OneDrive location is writable
                        try {
                            # Create directory if needed (Force handles race condition)
                            $null = New-Item -Path $resolvedDir -ItemType Directory -Force -ErrorAction Stop

                            # Test write access
                            $testFile = Join-Path $resolvedDir '.test'
                            [IO.File]::WriteAllText($testFile, 'test')
                            Remove-Item $testFile -Force
                            return $configurationPath
                        }
                        catch [System.IO.IOException] {
                            Write-Debug "OneDrive path not accessible (IOException), using fallback"
                        }
                        catch {
                            Write-Debug "OneDrive path not accessible ($($_.Exception.GetType().Name)), using fallback"
                        }
                    }
                }
                catch {
                    Write-Debug "Failed to resolve OneDrive path ($($_.Exception.GetType().Name)), using fallback"
                }
            }
        }

        # Fallback to user Documents
        if ($env:USERPROFILE) {
            # Validate USERPROFILE doesn't contain path traversal sequences
            $userProfilePath = $env:USERPROFILE
            if ($userProfilePath -notmatch '\.\.' -and $userProfilePath -notmatch '[\x00-\x1F]') {
                $configurationDirectory = Join-Path $userProfilePath 'Documents\PlexAutomationToolkit'
                $configurationPath = Join-Path $configurationDirectory 'servers.json'

                try {
                    # Create directory if needed (Force handles race condition)
                    $null = New-Item -Path $configurationDirectory -ItemType Directory -Force -ErrorAction Stop
                    return $configurationPath
                }
                catch {
                    Write-Debug "Documents folder not accessible ($($_.Exception.GetType().Name)), trying LocalAppData"
                }
            }
            else {
                Write-Debug "USERPROFILE path contains suspicious characters, using fallback"
            }
        }

        # Last resort: LocalAppData
        if ($env:LOCALAPPDATA) {
            # Validate LOCALAPPDATA doesn't contain path traversal sequences
            $localAppDataPath = $env:LOCALAPPDATA
            if ($localAppDataPath -notmatch '\.\.' -and $localAppDataPath -notmatch '[\x00-\x1F]') {
                $configurationDirectory = Join-Path $localAppDataPath 'PlexAutomationToolkit'
                # Create directory if needed (Force handles race condition)
                $null = New-Item -Path $configurationDirectory -ItemType Directory -Force -ErrorAction Stop
                return Join-Path $configurationDirectory 'servers.json'
            }
            else {
                Write-Debug "LOCALAPPDATA path contains suspicious characters"
            }
        }
    }

    # Linux/macOS: use ~/.config/PlexAutomationToolkit
    $homeDir = $env:HOME
    if (-not $homeDir) {
        $homeDir = [Environment]::GetFolderPath('UserProfile')
    }

    # Validate home directory doesn't contain path traversal sequences
    if ($homeDir -match '\.\.' -or $homeDir -match '[\x00-\x1F]') {
        throw "HOME path contains invalid characters - cannot determine safe configuration path"
    }

    $configurationDirectory = Join-Path $homeDir '.config/PlexAutomationToolkit'
    $configurationPath = Join-Path $configurationDirectory 'servers.json'

    # Create directory if needed
    $null = New-Item -Path $configurationDirectory -ItemType Directory -Force -ErrorAction SilentlyContinue

    return $configurationPath
}