<# ================================ Rclone V2 Cipher Builder & Mounter (Final Version) -------------------------------- - This version uses a Scheduled Task to mount the drive, avoiding the need for the 'EnableLinkedConnections' registry change and ensuring drive visibility in Explorer. - The scheduled task is configured to run when the user who runs this script logs on, making the mount persistent. - Allows for easy customization of cache size, duration, and directory via parameters. - Prompts the user to select a configured remote instead of using a hardcoded one. - Builds tomekit/rclone v1.71.0-with-v2-cipher if needed. - Backs up any existing C:\rclone directory before performing a clean build. - Validates that WinFsp was installed with the 'Developer' option. - Provides a robust unmount command that also cleans up the scheduled task. USAGE EXAMPLES -------------- # Run with default settings. It will prompt you to choose a remote to mount. .\Mount-S3Enc.ps1 # Customize cache settings .\Mount-S3Enc.ps1 -CacheSize 50G -CacheDuration 168h -CacheDir 'D:\rclone_cache' # To unmount the drive and remove the startup task, open a NEW PowerShell window and run: .\Mount-S3Enc.ps1 -Unmount ================================ #> [CmdletBinding()] param( # --- Mount Configuration --- [ValidatePattern("^[A-Z]$")][string]$DriveLetter = "X", # --- Cache Configuration --- [string]$CacheSize = "150G", [string]$CacheDuration = "8760h", # Default is 1 year [string]$CacheDir = "C:\rclonecache", # --- Script Behavior Flags --- [switch]$Unmount, [switch]$SkipBuild, [switch]$EnableLongPaths # One-time switch to set LongPathsEnabled registry key ) # --- Prerequisite Checks --- if ($PSVersionTable.PSVersion.Major -lt 5) { Write-Error "This script requires PowerShell version 5.1 or newer to run." Write-Host "Your current version is $($PSVersionTable.PSVersion)." Write-Host "Please update Windows or install PowerShell 7 from the Microsoft Store, then try again." exit } if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { Write-Error "This script must be run as an Administrator to build rclone and manage scheduled tasks." Write-Host "Please re-launch your PowerShell terminal with 'Run as Administrator' and try again." exit } $ErrorActionPreference = "Stop" # --- Helper Functions for colored output function Write-Title($t){ Write-Host "`n==== $t ====" -ForegroundColor Cyan } function Write-Success($m){ Write-Host "[SUCCESS] $m" -ForegroundColor Green } function Write-Warning($m){ Write-Host "[WARNING] $m" -ForegroundColor Yellow } function Write-Failure($m){ Write-Host "[ERROR] $m" -ForegroundColor Red } function Ensure-Dir([string]$p){ if (-not (Test-Path $p)) { New-Item -ItemType Directory -Force -Path $p | Out-Null } } # --- Define Paths & Constants $ConfigDir = Join-Path $env:APPDATA "rclone" $ConfigFile = Join-Path $ConfigDir "rclone.conf" $InstallDir = "C:\rclone" $RcloneExe = Join-Path $InstallDir "rclone.exe" $WantedVersion = "v1.71.0-with-v2-cipher" $TaskName = "RcloneMount_Drive_${DriveLetter}" $InstallerCacheDir = Join-Path $env:TEMP "rclone_script_installers" Ensure-Dir $InstallerCacheDir # --- Unmount Logic if ($Unmount) { Write-Title "Unmounting drive ${DriveLetter}:" # Find any rclone process mounting to the specified drive letter $rcloneProcess = Get-CimInstance Win32_Process | Where-Object { $_.Name -eq "rclone.exe" -and $_.CommandLine -like "*mount*${DriveLetter}:*" } if ($rcloneProcess) { Write-Host "Found running rclone process (PID: $($rcloneProcess.ProcessId)). Forcibly stopping..." Stop-Process -Id $rcloneProcess.ProcessId -Force Write-Success "Rclone process stopped." } else { Write-Host "No rclone mount process found running for drive ${DriveLetter}:." } # Clean up the drive letter and scheduled task try { if (Test-Path "${DriveLetter}:") { Write-Host "Dismounting volume for ${DriveLetter}:..." & cmd /c "mountvol ${DriveLetter}: /d" | Out-Null Start-Sleep -Seconds 1 } Write-Host "Cleaning up scheduled task '$TaskName'..." schtasks /delete /tn $TaskName /f 2>$null | Out-Null Write-Success "Unmount and cleanup complete." } catch { Write-Failure "An error occurred during cleanup: $($_.Exception.Message)" } return } Write-Title "Starting Rclone V2 Setup and Mount" Ensure-Dir $CacheDir if ($EnableLongPaths) { Write-Title "Enabling Windows Long Path Support" try { $regPath = "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" if (-not (Test-Path $regPath)) { New-Item -Path $regPath -Force | Out-Null } Set-ItemProperty -Path $regPath -Name "LongPathsEnabled" -Value 1 -Type DWord -Force Write-Success "Long paths enabled in registry. A reboot may be needed for it to take full effect." } catch { Write-Warning "Could not enable long paths automatically. Error: $($_.Exception.Message)" } } Write-Title "Ensuring Environment Variables" [System.Environment]::SetEnvironmentVariable("RCLONE_CONFIG_DIR", $ConfigDir, "User") $env:RCLONE_CONFIG_DIR = $ConfigDir Write-Success "RCLONE_CONFIG_DIR set to $ConfigDir" function Install-Prereq { param($Name, $Command, $InstallerUri, $InstallerFile) Write-Title "Checking for $Name" if ($Command -and (Get-Command $Command -ErrorAction SilentlyContinue)) { Write-Success "$Name is already installed (command '$Command' found)." return } $downloader = Join-Path $InstallerCacheDir $InstallerFile # --- Download Logic with Retries --- $downloadSuccess = $false $maxRetries = 3 for ($i = 1; $i -le $maxRetries; $i++) { try { # Force a fresh download each time by removing any old installer file if (Test-Path $downloader) { Remove-Item -Path $downloader -Force } Write-Host "Downloading $Name (Attempt $i of $maxRetries)..." Invoke-WebRequest -Uri $InstallerUri -OutFile $downloader $downloadSuccess = $true Write-Success "Download complete." break # Exit the loop on success } catch { Write-Warning "Download attempt $i failed: $($_.Exception.Message)" if ($i -lt $maxRetries) { Start-Sleep -Seconds 5 } } } if (-not $downloadSuccess) { Write-Failure "Automatic download failed after $maxRetries attempts." Write-Warning "Please manually download the installer from:" Write-Host $InstallerUri -ForegroundColor Cyan Write-Warning "And place it in the following folder:" Write-Host $InstallerCacheDir -ForegroundColor Cyan throw "Manual download required. Please place the file in the specified folder and re-run this script." } # --- Installation Logic --- Write-Warning "An interactive installer for $Name will be launched." try { Write-Host "Launching the $Name installer now. Please complete the installation." -ForegroundColor Yellow Start-Process -FilePath $downloader -Wait Write-Host "Installation for $Name has finished." -ForegroundColor Green Read-Host "Please ensure the installation was successful, then press Enter to continue the script..." # Reload the PATH environment variable to detect newly installed commands $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") if ($Command -and (Get-Command $Command -ErrorAction SilentlyContinue)) { Write-Success "$Name successfully installed and verified." } else { throw "$Name was not found after installation. Please ensure it installed correctly and is in your system's PATH." } } catch { Write-Failure "An error occurred during the installation of ${Name}: $($_.Exception.Message)" throw } } function Build-RcloneV2 { Write-Title "Ensuring rclone ($WantedVersion)" $currentVersion = try { (& $RcloneExe --version 2>$null | Select-String -Pattern "rclone (v.*)").Matches.Groups[1].Value } catch { $null } if ($SkipBuild -and (Test-Path $RcloneExe)) { Write-Warning "SkipBuild flag is set. Using existing rclone executable." return } if ($currentVersion -eq $WantedVersion) { Write-Success "Correct rclone version ($WantedVersion) is already built." return } Write-Warning "Required rclone version not found. Starting clean build process..." Install-Prereq -Name "Go" -Command "go" -InstallerUri "https://go.dev/dl/go1.24.5.windows-amd64.msi" -InstallerFile "go.msi" Install-Prereq -Name "Git" -Command "git" -InstallerUri "https://github.com/git-for-windows/git/releases/download/v2.46.0.windows.1/Git-2.46.0-64-bit.exe" -InstallerFile "Git-2.46.0-64-bit.exe" Write-Title "Configuring Build Environment for WinFsp" $winfspIncPath = "C:\Program Files (x86)\WinFsp\inc\fuse" $winfspLibPath = "C:\Program Files (x86)\WinFsp\lib" if (-not (Test-Path $winfspIncPath) -or -not (Test-Path $winfspLibPath)) { Write-Failure "WinFsp developer files not found." Write-Host "Please perform a ONE-TIME manual setup:" -ForegroundColor Yellow Write-Host "1. Download and run the latest WinFsp installer from https://github.com/winfsp/winfsp/releases" -ForegroundColor Yellow Write-Host "2. During installation, ensure the 'Developer' component is selected for installation." -ForegroundColor Yellow throw "Please complete the manual WinFsp setup and re-run the script." } $env:CPATH = $winfspIncPath $env:LIBRARY_PATH = $winfspLibPath Write-Success "Build environment configured." if (Test-Path $InstallDir) { $backupDir = "$($InstallDir)_backup_$(Get-Date -Format 'yyyy-MM-dd_HH-mm-ss')" Write-Warning "Existing '$InstallDir' directory found. Backing it up to '$backupDir'..." Move-Item -Path $InstallDir -Destination $backupDir -Force Write-Success "Backup complete." } Write-Host "Cloning tomekit/rclone repository into '$InstallDir'..." git clone https://github.com/tomekit/rclone.git $InstallDir Push-Location $InstallDir try { git fetch --all --tags git checkout $WantedVersion go run bin/resource_windows.go -version $WantedVersion -syso "resource_windows_$(go env GOARCH).syso" go build -v --ldflags "-s -w -X github.com/rclone/rclone/fs.Version=$WantedVersion" -tags "cmount" -buildmode exe if (-not (Test-Path $RcloneExe)) { throw "Build finished, but rclone.exe was not created." } Write-Success "Rclone ($WantedVersion) built successfully at $RcloneExe" } finally { Pop-Location } } function Select-RcloneRemote { Write-Title "Selecting rclone remote to mount" Ensure-Dir $ConfigDir if (-not (Test-Path $ConfigFile)) { Write-Warning "No rclone.conf found. The config wizard will launch." & $RcloneExe config } $remotes = Get-Content $ConfigFile | Select-String -Pattern "^\[(.*)\]$" | ForEach-Object { $_.Matches.Groups[1].Value } if ($remotes.Count -eq 0) { Write-Warning "No remotes configured. The config wizard will launch." & $RcloneExe config $remotes = Get-Content $ConfigFile | Select-String -Pattern "^\[(.*)\]$" | ForEach-Object { $_.Matches.Groups[1].Value } if ($remotes.Count -eq 0) { throw "No remotes found in configuration file after running config. Please create a remote and try again." } } Write-Host "Please choose a remote to mount:" -ForegroundColor Yellow for ($i = 0; $i -lt $remotes.Count; $i++) { Write-Host " $($i+1): $($remotes[$i])" } $choice = 0 while ($choice -lt 1 -or $choice -gt $remotes.Count) { try { $choice = Read-Host "`nEnter the number of the remote" if ($choice -lt 1 -or $choice -gt $remotes.Count) { Write-Warning "Invalid selection. Please enter a number between 1 and $($remotes.Count)." } } catch { Write-Warning "Invalid input. Please enter a number." } } $selectedRemote = $remotes[$choice-1] Write-Success "You selected '$selectedRemote'." return $selectedRemote } function Start-RcloneMountAsUser { param([string]$SelectedRemoteName) Write-Title "Mounting remote '$SelectedRemoteName' via Scheduled Task" Write-Host "This method ensures the drive is visible in Windows Explorer without requiring a reboot or registry changes." # --- Rclone Mount Command and Flags Explained --- $rcloneArgs = @( # The main command to create a virtual drive from your remote storage. "mount", "${SelectedRemoteName}:", "${DriveLetter}:", # --- Caching Flags: These improve performance by storing data locally. --- # 'full' caches files completely on first access, making subsequent reads much faster. "--vfs-cache-mode=full", # Sets the total maximum size of the file cache on your hard drive. "--vfs-cache-max-size=$CacheSize", # How long to keep unused files in the cache. "--vfs-cache-max-age=$CacheDuration", # How long to cache the directory structure in memory. Reduces API calls. "--dir-cache-time=$CacheDuration", # How long to cache file attributes (size, mod time). Reduces API calls. "--attr-timeout=$CacheDuration", # --- Performance Tuning Flags --- # Don't use modification times from the backend. Can improve performance with S3. "--no-modtime", # Enables support for symbolic links if your storage contains them. "--links", # --- Windows Integration Flags --- # Prevents a console window from popping up for the background rclone process. "--no-console", # Specifies the folder on your local disk where the cached files will be stored. "--cache-dir=$CacheDir" ) $commandToAction = "& '$RcloneExe' $($rcloneArgs -join ' ')" $currentUser = (Get-CimInstance -ClassName Win32_ComputerSystem).UserName Write-Host "Creating scheduled task '$TaskName' to run as '$currentUser' on user logon..." $action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-Command `"$commandToAction`"" $trigger = New-ScheduledTaskTrigger -AtLogon $principal = New-ScheduledTaskPrincipal -UserId $currentUser -RunLevel Limited $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries # Register the task, overwriting if it exists Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force | Out-Null Write-Host "Starting the mount process in the background for the current session..." Start-ScheduledTask -TaskName $TaskName Write-Host "Waiting up to 15 seconds for the drive to appear..." $mounted = $false for ($i = 0; $i -lt 15; $i++) { if (Test-Path "${DriveLetter}:") { $mounted = $true break } Start-Sleep -Seconds 1 Write-Host "." -NoNewline } Write-Host "" if ($mounted) { Write-Success "Successfully mounted $SelectedRemoteName to ${DriveLetter}:" } else { Write-Failure "Mount failed or is taking too long. Check your rclone config and try again." } } # ========================= # MAIN EXECUTION # ========================= try { Write-Title "Checking for existing mount point" if (Test-Path "${DriveLetter}:") { Write-Warning "Mount point ${DriveLetter}: already exists. Attempting to dismount..." try { & cmd /c "mountvol ${DriveLetter}: /d" | Out-Null Start-Sleep -Seconds 2 if (Test-Path "${DriveLetter}:") { throw "Failed to clear stale mount point ${DriveLetter}:." } Write-Success "Successfully cleared stale mount point." } catch { throw "An error occurred while trying to clear the stale mount point: $($_.Exception.Message)" } } else { Write-Success "Mount point ${DriveLetter}: is clear." } Build-RcloneV2 $selectedRemote = Select-RcloneRemote Start-RcloneMountAsUser -SelectedRemoteName $selectedRemote Write-Title "Operation Complete" Write-Host "The script has finished. The drive should appear in Windows Explorer shortly." -ForegroundColor Green Write-Host "To unmount the drive later, run:" Write-Host ".\$(Split-Path -Leaf $PSCommandPath) -Unmount -DriveLetter $DriveLetter" -ForegroundColor Yellow } catch { Write-Failure "Script failed to complete. Error: $($_.Exception.Message)" exit 1 }