[CmdletBinding()] param( [string]$SourceApp = 'C:\Program Files\bitbrowser', [string]$OutputDirectory = '', [switch]$SkipArchive ) $ErrorActionPreference = 'Stop' Set-StrictMode -Version Latest function Copy-Tree { param( [Parameter(Mandatory = $true)][string]$Source, [Parameter(Mandatory = $true)][string]$Destination, [string[]]$ExcludeFiles = @() ) New-Item -ItemType Directory -Path $Destination -Force | Out-Null $arguments = @($Source, $Destination, '/E', '/COPY:DAT', '/DCOPY:DAT', '/R:2', '/W:1', '/XJ', '/NFL', '/NDL', '/NP') if ($ExcludeFiles.Count -gt 0) { $arguments += '/XF' $arguments += $ExcludeFiles } & robocopy.exe @arguments $code = $LASTEXITCODE if ($code -gt 7) { throw "Copy failed with robocopy exit code $code ($Source)" } } $scriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path $templateRoot = Join-Path $scriptRoot 'bitbrowser-clean-package' $workspaceRoot = Split-Path -Parent $scriptRoot $sourceExe = Join-Path $SourceApp ([string][char]0x6BD4 + [char]0x7279 + [char]0x6D4F + [char]0x89C8 + [char]0x5668 + '.exe') if (-not (Test-Path -LiteralPath $sourceExe -PathType Leaf)) { throw "BitBrowser executable was not found: $sourceExe" } if (-not $OutputDirectory) { $OutputDirectory = Split-Path -Parent $workspaceRoot } $outputBase = [System.IO.Path]::GetFullPath($OutputDirectory) New-Item -ItemType Directory -Path $outputBase -Force | Out-Null $version = (Get-Item -LiteralPath $sourceExe).VersionInfo.FileVersion if (-not $version) { $version = 'unknown' } $stamp = Get-Date -Format 'yyyyMMdd-HHmmss' $packageName = "BitBrowser-Clean-Share-$version-$stamp" $packageRoot = Join-Path $outputBase $packageName if (Test-Path -LiteralPath $packageRoot) { throw "Refusing to overwrite an existing package: $packageRoot" } $appDestination = Join-Path $packageRoot 'App' $seedRoaming = Join-Path $packageRoot 'Seed\Roaming\BitBrowser' $seedLocal = Join-Path $packageRoot 'Seed\Local\BitBrowser' New-Item -ItemType Directory -Path $seedRoaming -Force | Out-Null New-Item -ItemType Directory -Path $seedLocal -Force | Out-Null Write-Host "Copying application files from $SourceApp" Copy-Tree -Source $SourceApp -Destination $appDestination -ExcludeFiles @('Uninstall BitBrowser.exe', 'Uninstall *.exe', '*.lnk') $sourceRoaming = Join-Path $env:APPDATA 'BitBrowser' $sourceDb = Join-Path $sourceRoaming 'db.sqlite' if (-not (Test-Path -LiteralPath $sourceDb -PathType Leaf)) { throw "BitBrowser database was not found: $sourceDb" } foreach ($directory in @('BitExtensions', 'Chrome-bin', 'chromedriver')) { $source = Join-Path $sourceRoaming $directory if (Test-Path -LiteralPath $source -PathType Container) { Write-Host "Copying $directory" Copy-Tree -Source $source -Destination (Join-Path $seedRoaming $directory) } } $cleanDb = Join-Path $seedRoaming 'db.sqlite' $dbTool = Join-Path $templateRoot 'make_clean_db.py' $python = Get-Command py.exe -ErrorAction SilentlyContinue if (-not $python) { $python = Get-Command python.exe -ErrorAction SilentlyContinue } if (-not $python) { throw 'Python 3 is required to build the sanitized SQLite database.' } Write-Host 'Creating sanitized database...' if ($python.Name -ieq 'py.exe') { & $python.Source -3 $dbTool --source $sourceDb --output $cleanDb } else { & $python.Source $dbTool --source $sourceDb --output $cleanDb } if ($LASTEXITCODE -ne 0) { throw 'Sanitized database creation failed.' } Copy-Item -LiteralPath (Join-Path $templateRoot 'Install-Clean.ps1') -Destination $packageRoot Copy-Item -LiteralPath (Join-Path $templateRoot 'Install-Clean.cmd') -Destination $packageRoot Copy-Item -LiteralPath (Join-Path $templateRoot 'README.txt') -Destination $packageRoot $manifestPath = Join-Path $packageRoot 'SHA256SUMS.txt' Write-Host 'Computing SHA-256 manifest...' $manifestLines = Get-ChildItem -LiteralPath $packageRoot -File -Recurse -Force | Where-Object { $_.FullName -ne $manifestPath } | Sort-Object FullName | ForEach-Object { $relative = $_.FullName.Substring($packageRoot.Length + 1).Replace('\', '/') $hash = (Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256).Hash.ToLowerInvariant() "$hash $relative" } [System.IO.File]::WriteAllLines($manifestPath, $manifestLines, [System.Text.UTF8Encoding]::new($false)) $allFiles = Get-ChildItem -LiteralPath $packageRoot -File -Recurse -Force $totalBytes = ($allFiles | Measure-Object Length -Sum).Sum $result = [ordered]@{ PackageDirectory = $packageRoot Version = $version FileCount = $allFiles.Count UncompressedBytes = $totalBytes Archive = $null ArchiveSha256 = $null } if (-not $SkipArchive) { $sevenZip = Join-Path $SourceApp 'resources\7zip-bin\7za.exe' if (-not (Test-Path -LiteralPath $sevenZip -PathType Leaf)) { throw "7-Zip command was not found: $sevenZip" } $archivePath = Join-Path $outputBase ($packageName + '.zip') Write-Host "Creating archive $archivePath" Push-Location $outputBase try { & $sevenZip a -tzip -mx=5 -mmt=on $archivePath $packageName if ($LASTEXITCODE -ne 0) { throw "7-Zip failed with exit code $LASTEXITCODE" } & $sevenZip t $archivePath if ($LASTEXITCODE -ne 0) { throw "Archive verification failed with exit code $LASTEXITCODE" } } finally { Pop-Location } $result.Archive = $archivePath $result.ArchiveSha256 = (Get-FileHash -LiteralPath $archivePath -Algorithm SHA256).Hash.ToLowerInvariant() } Write-Host '' Write-Host 'Build complete:' $result | ConvertTo-Json -Depth 3