public/Export-BrowserProfile.ps1

function Export-BrowserProfile {
    <#
    .SYNOPSIS
        Exports browser profiles to a destination path.
    
    .DESCRIPTION
        This function exports user profiles from supported browsers (Chrome, Edge, Firefox, Brave) to a specified destination path.
        It supports exporting from multiple browsers in parallel and can utilize rsync on non-Windows platforms for efficient copying.
    .PARAMETER Browser
        The browser to export profiles from. Valid values: Chrome, Edge, Firefox, Brave
    
    .PARAMETER DestinationPath
        The target directory where profile backups will be created.
    
    .PARAMETER All
        Export profiles from all supported browsers.
    
    .PARAMETER MaxParallelJobs
        Maximum number of parallel backup operations. Default: 4
    
    .PARAMETER Quiet
        Suppress verbose output.
    
    .EXAMPLE
        Export-BrowserProfile -Browser Brave -DestinationPath "/backup/browsers"
    
        Exports Brave browser profiles to the specified destination.
    .EXAMPLE
        Export-BrowserProfile -Browser Chrome -DestinationPath "C:\Backup\Chrome"
        
        Exports Chrome browser profiles to the specified destination.
    .EXAMPLE
        Export-BrowserProfile -All -DestinationPath "/backup/browsers" -MaxParallelJobs 8
        
        Exports profiles from all supported browsers to the specified destination using up to 8 parallel jobs.
    .EXAMPLE
        Export-BrowserProfile -All -DestinationPath "D:\BrowserBackups" -Quiet

        Exports profiles from all supported browsers to the specified destination with minimal output.
    .LINK
        https://github.com/Skatterbrainz/helium/blob/master/docs/Export-BrowserProfile.md
    #>

    [CmdletBinding(DefaultParameterSetName = 'Single')]
    param(
        [Parameter(Mandatory = $true, ParameterSetName = 'Single')]
        [ValidateSet('Chrome', 'Edge', 'Firefox', 'Brave')]
        [string]$Browser,
        
        [Parameter(Mandatory = $true, ParameterSetName = 'All')]
        [switch]$All,
        
        [Parameter(Mandatory = $true)]
        [string]$DestinationPath,
        
        [Parameter(Mandatory = $false)]
        [ValidateRange(1, 16)]
        [int]$MaxParallelJobs = 4,

        [parameter(Mandatory = $false)]
        [switch]$Quiet
    )
    
    try {
        # Detect OS for cross-platform compatibility
        $isWindowsOS = if ($PSVersionTable.PSVersion.Major -ge 6) { $IsWindows } else { $true }
        $useRsync = -not $isWindowsOS -and (Get-Command rsync -ErrorAction SilentlyContinue)
        
        # Determine which browsers to process
        $browsersToProcess = if ($All) {
            @('Chrome', 'Edge', 'Firefox', 'Brave')
        } else {
            @($Browser)
        }
        
        # Ensure destination path exists
        if (-not (Test-Path -Path $DestinationPath)) {
            New-Item -Path $DestinationPath -ItemType Directory -Force | Out-Null
            Write-Verbose "Created destination directory: $DestinationPath"
        }
        
        if (-not $Quiet) {
            Write-Host "Copy method: $(if ($useRsync) {'rsync'} else {'Copy-Item'})" -ForegroundColor Cyan
            Write-Host "Max parallel jobs: $MaxParallelJobs`n" -ForegroundColor Cyan
        }
        
        # Collect all profile tasks
        $allTasks = @()
        
        foreach ($browserName in $browsersToProcess) {
            if (-not $Quiet) {
                Write-Host "=== Querying $browserName profiles ===" -ForegroundColor Yellow
            }
            
            try {
                $browserProfiles = Get-BrowserProfile -Browser $browserName
                
                if (-not $browserProfiles) {
                    Write-Warning "No $browserName profiles found."
                    continue
                }
                
                # Create browser-specific subdirectory
                $browserDestination = Join-Path -Path $DestinationPath -ChildPath $browserName
                if (-not (Test-Path -Path $browserDestination)) {
                    New-Item -Path $browserDestination -ItemType Directory -Force | Out-Null
                }
                
                foreach ($browserProfile in $browserProfiles) {
                    $allTasks += [PSCustomObject]@{
                        Browser      = $browserName
                        ProfileName  = $browserProfile.Name
                        ProfilePath  = $browserProfile.Path
                        TargetFolder = Join-Path -Path $browserDestination -ChildPath $browserProfile.Name
                    }
                }
            } catch {
                if (-not $Quiet) {
                    Write-Warning "Failed to get profiles for $($browserName): $($_.Exception.Message)"
                }
            }
        }
        
        if ($allTasks.Count -eq 0) {
            if (-not $Quiet) {
                Write-Warning "No profiles found to export."
            }
            return
        }
        
        if (-not $Quiet) {
            Write-Host "`n=== Starting parallel export of $($allTasks.Count) profile(s) ===`n" -ForegroundColor Green
        }
        
        # Execute backups in parallel
        $results = $allTasks | ForEach-Object -ThrottleLimit $MaxParallelJobs -Parallel {
            $task = $_
            $useRsyncLocal = $using:useRsync
            
            $result = [PSCustomObject]@{
                Browser     = $task.Browser
                ProfileName = $task.ProfileName
                Success     = $false
                Message     = ""
            }
            
            try {
                # Resolve any tilde or relative paths to absolute paths
                $resolvedSourcePath = [System.IO.Path]::GetFullPath($task.ProfilePath.Replace('~', $HOME))
                $resolvedTargetPath = [System.IO.Path]::GetFullPath($task.TargetFolder.Replace('~', $HOME))
                
                if (-not (Test-Path -Path $resolvedSourcePath)) {
                    $result.Message = "Source path not found: $resolvedSourcePath"
                    return $result
                }
                
                if ($useRsyncLocal) {
                    # Use rsync on Linux/macOS (no -z for local operations)
                    $rsyncArgs = @(
                        '-av',
                        '--delete',
                        '--exclude=.DS_Store',
                        "$resolvedSourcePath/",
                        "$resolvedTargetPath/"
                    )
                    
                    $rsyncResult = & rsync @rsyncArgs 2>&1
                    
                    if ($LASTEXITCODE -eq 0) {
                        $result.Success = $true
                        $result.Message = "Exported via rsync"
                    } else {
                        $result.Message = "rsync failed: $rsyncResult"
                    }
                } else {
                    # Use Copy-Item on Windows
                    # Detect if destination is on a network drive
                    $targetDrive = Split-Path -Qualifier $resolvedTargetPath
                    $isNetworkDrive = $false
                    
                    if ($targetDrive) {
                        try {
                            $driveInfo = Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DeviceID='$targetDrive'" -ErrorAction SilentlyContinue
                            $isNetworkDrive = $driveInfo -and $driveInfo.DriveType -eq 4  # DriveType 4 = Network Drive
                        } catch {
                            # Fallback to UNC path detection if CIM query fails
                            $isNetworkDrive = $resolvedTargetPath -match '^\\\\'
                        }
                    } else {
                        # Handle UNC paths that don't have drive letters
                        $isNetworkDrive = $resolvedTargetPath -match '^\\\\'
                    }
                    
                    if ($isNetworkDrive) {
                        # For network destinations, create archive in temp and copy
                        $tempDir = Join-Path -Path $env:TEMP -ChildPath "BrowserBackup-$(Get-Date -Format 'yyyyMMdd-HHmmss')-$([System.Guid]::NewGuid().ToString('N')[0..7] -join '')"
                        $tempProfileDir = Join-Path -Path $tempDir -ChildPath (Split-Path -Leaf $resolvedSourcePath)
                        $archivePath = Join-Path -Path $tempDir -ChildPath "$($task.ProfileName).zip"
                        $finalArchivePath = Join-Path -Path (Split-Path -Parent $resolvedTargetPath) -ChildPath "$($task.ProfileName).zip"
                        
                        try {
                            # Create temp directory and copy profile
                            New-Item -Path $tempDir -ItemType Directory -Force | Out-Null
                            Copy-Item -Path $resolvedSourcePath -Destination $tempProfileDir -Recurse -Force -ErrorAction Stop
                            
                            # Create zip archive
                            Compress-Archive -Path $tempProfileDir -DestinationPath $archivePath -CompressionLevel Optimal -Force
                            
                            # Copy archive to network location
                            Copy-Item -Path $archivePath -Destination $finalArchivePath -Force -ErrorAction Stop
                            
                            $result.Success = $true
                            $result.Message = "Archived and copied to network location"
                        } finally {
                            # Clean up temp directory
                            if (Test-Path -Path $tempDir) {
                                Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
                            }
                        }
                    } else {
                        # Local destination - direct copy
                        Copy-Item -Path $resolvedSourcePath -Destination $resolvedTargetPath -Recurse -Force -ErrorAction Stop
                        $result.Success = $true
                        $result.Message = "Exported via Copy-Item"
                    }
                }
            } catch {
                $result.Message = "Error: $($_.Exception.Message)"
            }
            
            return $result
        }
        
        # Display results
        if (-not $Quiet) {
            Write-Host "`n=== Export Results ===`n" -ForegroundColor Yellow
        }
        
        $successCount = 0
        $failureCount = 0
        
        foreach ($result in $results) {
            if ($result.Success) {
                if (-not $Quiet) {
                    Write-Host "✓ " -ForegroundColor Green -NoNewline
                }
                $successCount++
            } else {
                if (-not $Quiet) {
                    Write-Host "✗ " -ForegroundColor Red -NoNewline
                }
                $failureCount++
            }
            if (-not $Quiet) {
                Write-Host "[$($result.Browser)] Profile '$($result.ProfileName)': $($result.Message)"
            }
        }
        
        if (-not $Quiet) {
            Write-Host "`n=== Summary ===" -ForegroundColor Yellow
            Write-Host "Total profiles: $($results.Count)" -ForegroundColor Cyan
            Write-Host "Successful: $successCount" -ForegroundColor Green
            Write-Host "Failed: $failureCount" -ForegroundColor $(if ($failureCount -gt 0) { 'Red' } else { 'Green' })
        }
    } catch {
        Write-Error "Error during export operation: $($_.Exception.Message)"
    }
}