[CmdletBinding()] param( [switch]$NoLaunch, [switch]$SkipShortcuts, [switch]$SkipProcessCheck, [string]$InstallRoot = '', [string]$RoamingRoot = '', [string]$LocalRoot = '' ) $ErrorActionPreference = 'Stop' Set-StrictMode -Version Latest function Copy-Tree { param( [Parameter(Mandatory = $true)][string]$Source, [Parameter(Mandatory = $true)][string]$Destination ) if (-not (Test-Path -LiteralPath $Source -PathType Container)) { throw "Required source directory is missing: $Source" } New-Item -ItemType Directory -Path $Destination -Force | Out-Null & robocopy.exe $Source $Destination /E /COPY:DAT /DCOPY:DAT /R:2 /W:1 /XJ /NFL /NDL /NP $code = $LASTEXITCODE if ($code -gt 7) { throw "Copy failed with robocopy exit code $code ($Source)" } } function Move-ToBackup { param([Parameter(Mandatory = $true)][string]$Path) if (-not (Test-Path -LiteralPath $Path)) { return $null } $parent = Split-Path -Parent $Path $leaf = Split-Path -Leaf $Path $stamp = Get-Date -Format 'yyyyMMdd-HHmmss' $suffix = [Guid]::NewGuid().ToString('N').Substring(0, 6) $backup = Join-Path $parent ($leaf + '.backup-' + $stamp + '-' + $suffix) Move-Item -LiteralPath $Path -Destination $backup return $backup } $packageRoot = Split-Path -Parent $MyInvocation.MyCommand.Path $sourceApp = Join-Path $packageRoot 'App' $sourceRoaming = Join-Path $packageRoot 'Seed\Roaming\BitBrowser' $sourceLocal = Join-Path $packageRoot 'Seed\Local\BitBrowser' $targetApp = if ($InstallRoot) { [System.IO.Path]::GetFullPath($InstallRoot) } else { Join-Path $env:LOCALAPPDATA 'Programs\BitBrowser' } $targetRoaming = if ($RoamingRoot) { [System.IO.Path]::GetFullPath($RoamingRoot) } else { Join-Path $env:APPDATA 'BitBrowser' } $targetLocal = if ($LocalRoot) { [System.IO.Path]::GetFullPath($LocalRoot) } else { Join-Path $env:LOCALAPPDATA 'BitBrowser' } $targetExe = Join-Path $targetApp ([string][char]0x6BD4 + [char]0x7279 + [char]0x6D4F + [char]0x89C8 + [char]0x5668 + '.exe') $expectedExtensionId = 'jiofmdifioeejeilfkpegipdjiopiekl' $sourceDb = Join-Path $sourceRoaming 'db.sqlite' $sourceExtensionManifest = Join-Path $sourceRoaming "BitExtensions\$expectedExtensionId\manifest.json" if (-not $SkipProcessCheck) { $running = @(Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | Where-Object { $_.Name -eq ([string][char]0x6BD4 + [char]0x7279 + [char]0x6D4F + [char]0x89C8 + [char]0x5668 + '.exe') -or ($_.ExecutablePath -and $_.ExecutablePath.StartsWith($targetApp, [System.StringComparison]::OrdinalIgnoreCase)) }) if ($running.Count -gt 0) { throw 'BitBrowser is running. Close it and run this installer again.' } } if (-not (Test-Path -LiteralPath $sourceDb -PathType Leaf)) { throw 'The clean seed database is missing from this package.' } if (-not (Test-Path -LiteralPath $sourceExtensionManifest -PathType Leaf)) { throw 'The YesCaptcha extension is missing from this package.' } Write-Host 'Installing BitBrowser application files...' Copy-Tree -Source $sourceApp -Destination $targetApp $backups = @() $backup = Move-ToBackup -Path $targetRoaming if ($backup) { $backups += $backup } $backup = Move-ToBackup -Path $targetLocal if ($backup) { $backups += $backup } Write-Host 'Installing clean user data and preserved extensions...' Copy-Tree -Source $sourceRoaming -Destination $targetRoaming if (Test-Path -LiteralPath $sourceLocal -PathType Container) { Copy-Tree -Source $sourceLocal -Destination $targetLocal } if (-not (Test-Path -LiteralPath $targetExe -PathType Leaf)) { throw "Installed executable was not found: $targetExe" } $targetDb = Join-Path $targetRoaming 'db.sqlite' $targetExtensionManifest = Join-Path $targetRoaming "BitExtensions\$expectedExtensionId\manifest.json" if (-not (Test-Path -LiteralPath $targetExtensionManifest -PathType Leaf)) { throw "YesCaptcha was not installed: $targetExtensionManifest" } if ((Get-FileHash -LiteralPath $sourceDb -Algorithm SHA256).Hash -ne (Get-FileHash -LiteralPath $targetDb -Algorithm SHA256).Hash) { throw 'The clean database copy failed verification.' } if ((Get-FileHash -LiteralPath $sourceExtensionManifest -Algorithm SHA256).Hash -ne (Get-FileHash -LiteralPath $targetExtensionManifest -Algorithm SHA256).Hash) { throw 'The YesCaptcha extension copy failed verification.' } if (-not $SkipShortcuts) { $shell = New-Object -ComObject WScript.Shell $desktop = [Environment]::GetFolderPath('Desktop') $shortcutPath = Join-Path $desktop 'BitBrowser.lnk' $shortcut = $shell.CreateShortcut($shortcutPath) $shortcut.TargetPath = $targetExe $shortcut.WorkingDirectory = $targetApp $shortcut.IconLocation = $targetExe + ',0' $shortcut.Save() $startMenu = Join-Path $env:APPDATA 'Microsoft\Windows\Start Menu\Programs' $startShortcutPath = Join-Path $startMenu 'BitBrowser.lnk' $startShortcut = $shell.CreateShortcut($startShortcutPath) $startShortcut.TargetPath = $targetExe $startShortcut.WorkingDirectory = $targetApp $startShortcut.IconLocation = $targetExe + ',0' $startShortcut.Save() } Write-Host '' Write-Host "Installed application: $targetApp" Write-Host "Installed clean data: $targetRoaming" Write-Host "Verified extension: YesCaptcha 1.4.7 ($expectedExtensionId)" if ($backups.Count -gt 0) { Write-Host 'Previous data was preserved at:' foreach ($item in $backups) { Write-Host " $item" } } Write-Host 'No browser profiles, cookies, history, cache, proxy settings, or login token were installed.' if (-not $NoLaunch) { Start-Process -FilePath $targetExe -WorkingDirectory $targetApp }