Install-BrowserBox-Task.ps1
param ( [string]$UserPassword, [string]$acceptTermsEmail, [string]$hostname ) . $PSScriptRoot\Utils.ps1 $Outer = { try { if (-not ([System.Management.Automation.PSTypeName]'BBInstallerWindowManagement').Type) { Add-Type -AssemblyName System.Windows.Forms; Add-Type @" using System; using System.Runtime.InteropServices; public class BBInstallerWindowManagement { [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); } "@ } $hwnd = (Get-Process -Id $pid).MainWindowHandle # Get the screen width and window width $screenWidth = [System.Windows.Forms.SystemInformation]::VirtualScreen.Width $windowWidth = 600 # Assuming this is your current window width # Calculate the X coordinate $xCoordinate = $screenWidth - $windowWidth # Set the window position: X, Y, Width, Height [BBInstallerWindowManagement]::SetWindowPos($hwnd, [IntPtr]::new(-1), $xCoordinate, 0, $windowWidth, 371, 0x0040) } catch { Write-Output "An error occurred during window management: $_" } finally { Write-Output "Continuing..." } # Set the title of the PowerShell window $Host.UI.RawUI.WindowTitle = "BrowserBox Windows Edition Installer" # Main script flow $Main = { Guard-CheckAndSaveUserAgreement ([ref]$acceptTermsEmail) ([ref]$hostname) Write-Host $acceptTermsEmail $hostname if (Validate-NonEmptyParameters -hostname $hostname -acceptTermsEmail $acceptTermsEmail) { Write-Host "Inputs look good. Proceeding..." } else { Show-Usage Exit } Write-Host "Running PowerShell version: $($PSVersionTable.PSVersion)" # Disabling the progress bar $ProgressPreference = 'SilentlyContinue' CheckForPowerShellCore EnsureRunningAsAdministrator RemoveAnyRestartScript Write-Host "Installing preliminairies..." InstallMSVC EnhancePackageManagers $currentVersion = CheckWingetVersion UpdateWingetIfNeeded $currentVersion UpdatePowerShell EnableWindowsAudio InstallGoogleChrome if (-not (InstallIfNeeded "jq" "jqlang.jq")) { InstallJqDirectly } InstallIfNeeded "vim" "vim.vim" AddVimToPath InstallIfNeeded "git" "Git.Git" InstallFmedia InstallAndLoadNvm nvm install v21 nvm use latest Write-Host "Setting up certificate..." if (Is-HostnameLinkLocal -hostname $hostname) { RunCloserFunctionInNewWindow InstallMkcertAndSetup } else { InstallCertbot OpenFirewallPort -Port 80 Write-Host "Waiting for hostname ($hostname) to resolve to this machine's IP address..." WaitForHostname -hostname $hostname Write-Host "Hostname loaded. Requesting TLS HTTPS certificate from LetsEncrypt..." RequestCertificate -Domain $hostname -TermsEmail $acceptTermsEmail PersistCerts -Domain $hostname } Write-Host "Installing BrowserBox..." Set-Location $env:USERPROFILE Write-Host $PWD git config --global core.symlinks true if (Test-Path .\BrowserBox) { if ( Get-Command Stop-BrowserBox ) { Stop-BrowserBox } else { taskkill /f /im node.exe } try { Remove-Item .\BrowserBox -Recurse -Force } catch { Write-Error "Error over-writing current install files: $_" Remove-Item .\BrowserBox -Recurse } } git clone https://github.com/BrowserBox/BrowserBox.git Set-Location BrowserBox Write-Host "Cleaning non-Windows detritus..." npm run clean Write-Host "Installing dependencies..." npm i Write-Host "Building client..." npm run parcel $globalLocation = Get-DestinationDirectory # Debug: Output the type and value of globalLocation Write-Host "Type of globalLocation: $($globalLocation.GetType().FullName)" Write-Host "Value of globalLocation: $globalLocation" Set-Location $env:USERPROFILE $existingGlobal = Join-Path $globalLocation -ChildPath "BrowserBox" if (Test-Path $existingGlobal) { Write-Host "Cleaning existing global install..." Remove-Item $existingGlobal -Recurse -Force } else { Write-Host "No existing global install found at $existingGlobal" } Write-Host "Moving to global location: $globalLocation" # Ensure BrowserBox is a valid path $browserBoxPath = Join-Path $env:USERPROFILE -ChildPath "BrowserBox" if (Test-Path $browserBoxPath) { Move-Item $browserBoxPath $globalLocation -Force } else { Write-Host "BrowserBox not found at $browserBoxPath" } Write-Host "Full install completed." } # Function Definitions # it's worth noting that while Remote Sound Device from remote desktop works well, # due to RDP audio driver on Windows # there is distortion on some music sounds, for instance # https://www.youtube.com/watch?v=v0wVRG38IYs function InstallJqDirectly { $jqDirectory = "$env:ProgramFiles\jq" if (-not (Test-Path $jqDirectory)) { New-Item -ItemType Directory -Path $jqDirectory -Force } $downloadUrl = "https://github.com/stedolan/jq/releases/latest/download/jq-win64.exe" $destination = "$jqDirectory\jq.exe" DownloadFile -Url $downloadUrl -Destination $destination Add-ToSystemPath $jqDirectory Write-Host "jq has been manually downloaded and added to the system path." RefreshPath } function WaitForHostname { param ( [Parameter(Mandatory=$true)] [string]$hostname, [int]$timeout = 3600, # 1 hour [int]$interval = 10 # 10 seconds ) # Function to get the current external IPv4 address function Get-ExternalIp { $services = @("https://icanhazip.com", "https://ifconfig.me", "https://api.ipify.org") foreach ($service in $services) { try { $ip = Invoke-WebRequest -Uri $service -UseBasicParsing -TimeoutSec 5 if ($ip) { return $ip.Content.Trim() } } catch { continue } } Write-Error "Failed to obtain external IP address" return $null } # Resolve DNS and compare with external IP $externalIp = Get-ExternalIp if (-not $externalIp) { Write-Error "Failed to obtain external IP address" return } $elapsed = 0 while ($elapsed -lt $timeout) { try { $resolvedIp = Resolve-DnsName $hostname -Server "8.8.8.8" | Where-Object { $_.QueryType -eq "A" } | Select-Object -ExpandProperty IPAddress if ($resolvedIp -eq $externalIp) { Write-Host "Hostname resolved to current IP: $hostname -> $resolvedIp" return } else { Write-Host "Waiting for hostname to resolve to current IP ($externalIp)..." Start-Sleep -Seconds $interval $elapsed += $interval } } catch { Write-Host "DNS resolution failed, retrying..." Start-Sleep -Seconds $interval $elapsed += $interval } } Write-Error "Timeout reached. Hostname not resolved or not matching current IP: $hostname" } function Show-Usage { Write-Host "Usage: BrowserBox-Install-Task.ps1 [-acceptTermsEmail <email> -hostname <hostname>]" Write-Host "" Write-Host "This script installs and configures BrowserBox on your Windows system." Write-Host "Parameters:" Write-Host " -acceptTermsEmail The email address used for accepting terms and receiving notifications." Write-Host " Optional if not provided, a GUI prompt will request this information." Write-Host " -hostname The hostname for the BrowserBox service. This is used for certificate generation." Write-Host " Optional if not provided, a GUI prompt will request this information." Write-Host "" Write-Host "Documentation Links:" Write-Host " Terms of Service: https://dosyago.com/terms.txt" Write-Host " Privacy Policy: https://dosyago.com/privacy.txt" Write-Host " License: https://github.com/BrowserBox/BrowserBox/blob/boss/LICENSE.md" Write-Host "" Write-Host "Example:" Write-Host " .\BrowserBox-Install-Task.ps1 -acceptTermsEmail 'user@example.com' -hostname 'yourhostname.com'" Write-Host "" Write-Host "Note: Run this script with administrative privileges for proper installation." } function Validate-NonEmptyParameters { param ( [string]$hostname, [string]$acceptTermsEmail ) # Check if either hostname or acceptTermsEmail is empty if ([string]::IsNullOrWhiteSpace($hostname) -or [string]::IsNullOrWhiteSpace($acceptTermsEmail)) { Write-Host "Hostname and Email must both be provided." return $false } return $true } function RequestCertificate { param ( [string]$Domain, [string]$termsEmail ) try { # Run the Certbot command certbot certonly --standalone --keep -d $Domain --agree-tos -m $termsEmail --no-eff-email } catch { # Handle errors (if any) Write-Error "An error occurred while requesting the certificate: $_" } } function OpenFirewallPort { param ( [string]$Port ) try { netsh advfirewall firewall add rule name="Open Port $Port" dir=in action=allow protocol=TCP localport=$Port Write-Output "Port $Port opened successfully." } catch { Write-Error "Failed to open port $Port with error: $_" } } function Guard-CheckAndSaveUserAgreement { param ( [ref]$acceptTermsEmail, [ref]$hostname ) $configDir = Join-Path $env:USERPROFILE ".config\dosyago\bbpro" $agreementFile = Join-Path $configDir "user_agreement.txt" # Check if the agreement file already exists and contains the agreement if (Test-Path $agreementFile) { $agreementData = Get-Content $agreementFile $agreed = $agreementData | Select-String "Agreed" -Quiet $storedEmail = ($agreementData | Select-String "Email:" | Out-String).Trim().Split(":")[1] $storedHostname = ($agreementData | Select-String "Hostname:" | Out-String).Trim().Split(":")[1] if ($agreed) { if (-not $acceptTermsEmail.Value) { $acceptTermsEmail.Value = $storedEmail } if (-not $hostname.Value) { $hostname.Value = $storedHostname } return } } # Show user agreement dialog try { $userInput = Show-UserAgreementDialog -acceptTermsEmail $acceptTermsEmail.Value -hostname $hostname.Value if ($userInput) { $acceptTermsEmail.Value = $userInput.Email $hostname.Value = $userInput.Hostname } else { Exit } } catch { $userInput = Read-Host "Do you agree to the terms? (Yes/No)" if ($userInput -ne 'Yes') { Write-Output 'You must agree to the terms and conditions to proceed.' Exit } } # Save the user's agreement along with email and hostname if (!(Test-Path $configDir)) { New-Item -Path $configDir -ItemType Directory -Force } $userName = [Environment]::UserName $dateTime = Get-Date -Format "yyyy-MM-dd HH:mm:ss" $agreementText = "$userName,$dateTime,Agreed`nEmail:$($acceptTermsEmail.Value)`nHostname:$($hostname.Value)" $agreementText | Out-File $agreementFile } function EnsureWindowsAudioService { $audioService = Get-Service -Name 'Audiosrv' # Set service to start automatically Set-Service -Name 'Audiosrv' -StartupType Automatic Write-Output "Windows Audio service set to start automatically" # Start the service if it's not running if ($audioService.Status -ne 'Running') { Start-Service -Name 'Audiosrv' Write-Output "Windows Audio service started" } else { Write-Output "Windows Audio service is already running" } } function RemoveAnyRestartScript { $homePath = $HOME $cmdScriptPath = Join-Path -Path $homePath -ChildPath "restart_ps.cmd" # Check if the CMD script exists. If it does, delete it and return (this is the restart phase) if (Test-Path -Path $cmdScriptPath) { Remove-Item -Path $cmdScriptPath } } function PatchNvmPath { $env:NVM_HOME = "$env:appdata\nvm" $env:NVM_SYMLINK = "$env:programfiles\nodejs" $additionalPaths = @( "$env:NVM_HOME", "$env:NVM_SYMLINK", "$env:LOCALAPPDATA\Microsoft\WindowsApps", "$env:LOCALAPPDATA\Microsoft\WinGet\Links", "$env:APPDATA\nvm" ) # Program Files directories $programFilesPaths = @( $env:ProgramFiles ) | Select-Object -Unique foreach ($path in $programFilesPaths) { $nodeJsPath = Join-Path $path "nodejs" if (Test-Path $nodeJsPath) { $additionalPaths += $nodeJsPath } } # Add paths to PATH environment variable, avoiding duplicates $currentPath = $env:PATH.Split(';') $newPath = $currentPath + $additionalPaths | Select-Object -Unique $env:PATH = $newPath -join ";" } function BeginSecurityWarningAcceptLoop { $myshell = New-Object -com "Wscript.Shell" while ($true) { Start-Sleep -Seconds 2 # Check for the presence of the "Security Warning" window if ($myshell.AppActivate("Security Warning")) { $myshell.SendKeys("y") # Wait a bit for the action to be processed Start-Sleep -Seconds 2 # Check if the window is still active; if not, break the loop if (-not $myshell.AppActivate("Security Warning")) { break } } } } function RunCloserFunctionInNewWindow { # Get the function definition as a string $functionToRun = $Function:BeginSecurityWarningAcceptLoop.ToString() # Script content to write to file $scriptContent = @" $functionToRun # Call the function BeginSecurityWarningAcceptLoop # Delete this script file once done Remove-Item -LiteralPath `"$($MyInvocation.ScriptName)`" -Force "@ # Write the script content to a file $scriptPath = ".\AutoAcceptSecurityWarning.ps1" $scriptContent | Out-File -FilePath $scriptPath -Encoding UTF8 # Run the script in a new PowerShell window Start-Process PowerShell -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`"" -WindowStyle Hidden } function EnableWindowsAudio { Write-Output "Enabling windows audio service..." try { Set-Service -Name Audiosrv -StartupType Automatic Start-Service -Name Audiosrv } catch { Write-Output "Error when attempting to enable Windows Audio service: $_" } try { Get-PnpDevice | Where-Object { $_.Class -eq 'AudioEndpoint' } | Select-Object Status, Class, FriendlyName } catch { Write-Output "Error when attempting to list sound devices: $_" } Write-Output "Completed audio service startup attempt." } function InstallFmedia { if (-not (Get-Command "fmedia.exe" -ErrorAction SilentlyContinue) ) { $url = "https://github.com/stsaz/fmedia/releases/download/v1.31/fmedia-1.31-windows-x64.zip" $outputDir = Join-Path $env:ProgramFiles "fmedia" $zipPath = Join-Path $env:TEMP "fmedia.zip" # Download the fmedia zip file Invoke-WebRequest -Uri $url -OutFile $zipPath # Create the output directory if it doesn't exist if (!(Test-Path $outputDir)) { New-Item -Path $outputDir -ItemType Directory } # Extract the zip file Expand-Archive -Path $zipPath -DestinationPath $env:TEMP -Force # Move the extracted files to the correct directory $extractedDir = Join-Path $env:TEMP "fmedia" Get-ChildItem -Path "$extractedDir\*" | ForEach-Object { $destPath = Join-Path $outputDir $_.Name if (Test-Path $destPath) { Remove-Item $destPath -Recurse -Force } Move-Item -Path $_.FullName -Destination $outputDir -Force } # Install fmedia $fmediaExe = Join-Path $outputDir "fmedia.exe" Add-ToSystemPath $outputDir & $fmediaExe --install # Refresh environment variables in the current session RefreshPath } } function InstallPulseAudioForWindows { if (-not (Get-Command "pulseaudio.exe" -ErrorAction SilentlyContinue) ) { $pulseRelease = "https://github.com/pgaskin/pulseaudio-win32/releases/download/v5/pasetup.exe" $destination = Join-Path -Path $env:TEMP -ChildPath "pasetup.exe" Write-Output "Downloading PulseAudio for Windows by Patrick Gaskin..." DownloadFile $pulseRelease $destination Write-Output "Downloaded. Installing PulseAudio for Windows by Patrick Gaskin..." Start-Process -FilePath $destination -ArgumentList '/install', '/silent', '/quiet', '/norestart' -Wait -NoNewWindow Write-Output "Installed PulseAudio for Windows by Patrick Gaskin" AddPulseAudioToPath } else { Write-Output "Pulseaudio is already installed" } } function UpdatePowerShell { if ($PSVersionTable.PSVersion.Major -ge 6) { Write-Output "Recent version of PowerShell already installed. Skipping..." } else { Write-Output "Upgrading PowerShell..." winget install -e --id Microsoft.PowerShell --accept-source-agreements RestartEnvironment } } function InstallGoogleChrome { $chrometest = Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe' if ($chrometest -eq $true) { Write-Output "Chrome is installed" } else { Write-Output "Chrome is not installed" $url = 'https://dl.google.com/tag/s/dl/chrome/install/googlechromestandaloneenterprise64.msi' $destination = Join-Path -Path $env:TEMP -ChildPath "googlechrome.msi" Write-Output "Downloading Google Chrome..." DownloadFile $url $destination Write-Output "Installing Google Chrome silently..." Start-Process -FilePath 'msiexec.exe' -ArgumentList "/i `"$destination`" /qn /norestart" -Wait -NoNewWindow Write-Output "Installation of Google Chrome completed." } } function EnsureWinGet { # Create WinGet Folder New-Item -Path C:\WinGet -ItemType directory -ErrorAction SilentlyContinue # Install VCLibs Invoke-WebRequest -Uri https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx -OutFile "C:\WinGet\Microsoft.VCLibs.x64.14.00.Desktop.appx" Add-AppxPackage "C:\WinGet\Microsoft.VCLibs.x64.14.00.Desktop.appx" # Install Microsoft.UI.Xaml from NuGet Invoke-WebRequest -Uri https://www.nuget.org/api/v2/package/Microsoft.UI.Xaml/2.7.3 -OutFile "C:\WinGet\Microsoft.UI.Xaml.2.7.3.zip" Expand-Archive "C:\WinGet\Microsoft.UI.Xaml.2.7.3.zip" -DestinationPath "C:\WinGet\Microsoft.UI.Xaml.2.7.3" Add-AppxPackage "C:\WinGet\Microsoft.UI.Xaml.2.7.3\tools\AppX\x64\Release\Microsoft.UI.Xaml.2.7.appx" # Install latest WinGet from GitHub Invoke-WebRequest -Uri https://github.com/microsoft/winget-cli/releases/latest/download/Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle -OutFile "C:\WinGet\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle" Add-AppxPackage "C:\WinGet\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle" # Fix Permissions TAKEOWN /F "C:\Program Files\WindowsApps" /R /A /D Y ICACLS "C:\Program Files\WindowsApps" /grant Administrators:F /T # Add Environment Path $ResolveWingetPath = Resolve-Path "C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_*_x64__8wekyb3d8bbwe" if ($ResolveWingetPath) { $WingetPath = $ResolveWingetPath[-1].Path } $ENV:PATH += ";$WingetPath" $SystemEnvPath = [System.Environment]::GetEnvironmentVariable('PATH', [System.EnvironmentVariableTarget]::Machine) $SystemEnvPath += ";$WingetPath;" setx /M PATH "$SystemEnvPath" } function PersistCerts { param ( [string]$Domain ) # Call the function to copy the certificates Copy-CertbotCertificates -Domain $Domain # Schedule the renewal task Schedule-CertbotRenewalTask -Domain $Domain } function Schedule-CertbotRenewalTask { param ( [string]$Domain, [string]$ScriptPath = "$env:TEMP\RenewAndCopyCerts.ps1" ) # Create the PowerShell script using a here-string $scriptContent = @" certbot renew --quiet # Assuming Copy-CertbotCertificates is defined or loaded Copy-CertbotCertificates -Domain "$Domain" "@ # Write the script to a file $scriptContent | Out-File -FilePath $ScriptPath -Force # Schedule the task $action = New-ScheduledTaskAction -Execute 'Powershell.exe' -Argument "-ExecutionPolicy Bypass -File `"$ScriptPath`"" $trigger = New-ScheduledTaskTrigger -Daily -At 3am $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest Register-ScheduledTask -TaskName "CertbotRenewal" -Action $action -Trigger $trigger -Settings $settings -Principal $principal -Force } function Copy-CertbotCertificates { param ( [string]$Domain ) $certbotLivePath = "C:\Certbot\live\$Domain" $destinationPath = Join-Path $HOME "sslcerts" # Create sslcerts directory if it doesn't exist if (-not (Test-Path $destinationPath)) { New-Item -Path $destinationPath -ItemType Directory } # Copy certificates to the sslcerts directory if (Test-Path $certbotLivePath) { Copy-Item -Path "$certbotLivePath\*" -Destination $destinationPath -Force } else { Write-Error "Certificates for $Domain not found at $certbotLivePath" } } function InstallCertbot { $url = "https://github.com/BrowserBox/BrowserBox/releases/download/v7.0/certbot-beta-installer-win_amd64.exe" $destination = Join-Path -Path $env:TEMP -ChildPath "certbot-beta-installer-win_amd64.exe" Write-Output "Downloading LetsEncrypt Certbot..." DownloadFile -Url $url -Destination $destination Write-Output "Installing LetsEncrypt Certbot silently..." #Start-Process "msiexec.exe" -ArgumentList "/i `"$destination`" /quiet /norestart" -Wait Start-Process "$destination" -ArgumentList "/S" -Wait RefreshPath Write-Output "Installation of LetsEncrypt Certbot completed." } function InstallMSVC { $url = 'https://aka.ms/vs/17/release/vc_redist.x64.exe' $destination = Join-Path -Path $env:TEMP -ChildPath "vc_redist.x64.exe" Write-Output "Downloading Microsoft Visual C++ Redistributable..." DownloadFile -Url $url -Destination $destination Write-Output "Installing Microsoft Visual C++ Redistributable silently..." Start-Process -FilePath $destination -ArgumentList '/install', '/silent', '/quiet', '/norestart' -Wait -NoNewWindow Write-Output "Installation of Microsoft Visual C++ Redistributable completed." } function Is-HostnameLinkLocal { param ( [string]$hostname ) try { $ipAddresses = [System.Net.Dns]::GetHostAddresses($hostname) | Where-Object { $_.AddressFamily -eq 'InterNetwork' } if ($ipAddresses.Count -eq 0) { Write-Error "No IPv4 address found for $hostname" return $false } $ipAddress = $ipAddresses[0] $bytes = $ipAddress.GetAddressBytes() # Check for private IP ranges (e.g., 192.168.x.x, 10.x.x.x, 172.16.x.x - 172.31.x.x, 127.x.x.x) if (($bytes[0] -eq 127) -or ($bytes[0] -eq 10) -or ($bytes[0] -eq 172 -and $bytes[1] -ge 16 -and $bytes[1] -le 31) -or ($bytes[0] -eq 192 -and $bytes[1] -eq 168) ) { return $true } } catch { Write-Error "Failed to resolve hostname: $_" } return $false } function CheckMkcert { if (Get-Command mkcert -ErrorAction SilentlyContinue) { Write-Host "Mkcert is already installed." return $true } else { Write-Host "Mkcert is not installed." return $false } } function InstallMkcert { Write-Output "Installing mkcert..." try { $archMap = @{ "0" = "x86"; "5" = "arm"; "6" = "ia64"; "9" = "amd64"; "12" = "arm64"; } $cpuArch = (Get-CimInstance -ClassName Win32_Processor).Architecture $arch = $archMap["$cpuArch"] # Create the download URL $url = "https://dl.filippo.io/mkcert/latest?for=windows/$arch" # Download mkcert.exe to a temporary location $tempPath = [System.IO.Path]::GetTempFileName() + ".exe" Invoke-WebRequest -Uri $url -OutFile $tempPath -UseBasicParsing # Define a good location to place mkcert.exe (within the system PATH) $destPath = "C:\Windows\System32\mkcert.exe" # Move the downloaded file to the destination Move-Item -Path $tempPath -Destination $destPath -Force # Run mkcert.exe -install mkcert -install } catch { Write-Error "An error occurred while fetching the latest release: $_" } } function InstallMkcertAndSetup { if (-not (CheckMkcert)) { InstallMkcert } $sslCertsDir = "$HOME\sslcerts" if (-not (Test-Path $sslCertsDir)) { New-Item -ItemType Directory -Path $sslCertsDir } # Change directory to the SSL certificates directory Set-Location $sslCertsDir # Generate SSL certificates for localhost mkcert -key-file privkey.pem -cert-file fullchain.pem localhost 127.0.0.1 link.local } function CheckNvm { if (-not (Get-Command "nvm.exe" -ErrorAction SilentlyContinue) ) { Write-Host "NVM is not installed." return $false } else { Write-Host "NVM is already installed." return $true } } function EnhancePackageManagers { try { Install-PackageProvider -Name NuGet -MinimumVersion 2.9 -Force -ErrorAction SilentlyContinue } catch { Write-Output "Error installing NuGet provider: $_" } try { Install-PackageProvider -Name NuGet -Force -Scope CurrentUser -ErrorAction SilentlyContinue } catch { Write-Output "Error installing NuGet provider: $_" } try { Import-PackageProvider -Name NuGet -Force -ErrorAction SilentlyContinue } catch { Write-Output "Error importing NuGet provider: $_" } Set-PSRepository -Name PSGallery -InstallationPolicy Trusted try { Install-Module -Name PackageManagement -Repository PSGallery -Force -ErrorAction SilentlyContinue } catch { Write-Output "Error installing PackageManagement provider: $_" } Install-Module -Name Microsoft.WinGet.Client try { Repair-WinGetPackageManager -AllUsers } catch { Write-Output "Could not repair WinGet ($_) will try to install instead." EnsureWinGet } } function RefreshPath { $env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User") } function DeleteNodeAndNvm { if (Get-Command "node.exe" -ErrorAction SilentlyContinue) { if (Get-Command "pm2" -ErrorAction SilentlyContinue) { pm2 delete all } } taskkill /f /im nvm.exe taskkill /f /im node.exe Remove-Item -Recurse -Force "$env:programfiles\nodejs" Remove-Item -Recurse -Force "${env:ProgramFiles(x86)}\nodejs" Remove-Item -Recurse -Force "$env:ProgramW6432\nodejs" Remove-Item -Recurse -Force "$env:appdata\nvm" } function InstallAndLoadNvm { if (-not (Get-Command "nvm.exe" -ErrorAction SilentlyContinue) ) { Write-Output "NVM is not installed." DeleteNodeAndNvm InstallNvm RefreshPath PatchNvmPath #RestartEnvironment Write-Output "NVM has been installed and added to the path for the current session." } else { Write-Output "NVM is already installed" } } function RestartShell { Write-Output "Relaunching shell and running this script again..." $scriptPath = $($MyInvocation.ScriptName) $userPasswordArg = if ($UserPassword) { "-UserPassword `"$UserPassword`"" } else { "" } # Relaunch the script with administrative rights using the current PowerShell version $psExecutable = Join-Path -Path $PSHOME -ChildPath "powershell.exe" if ($PSVersionTable.PSVersion.Major -ge 6) { $psExecutable = Join-Path -Path $PSHOME -ChildPath "pwsh.exe" } $arguments = "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`" $userPasswordArg" (Start-Process $psExecutable -Verb RunAs -ArgumentList $arguments -PassThru) Exit } function RestartEnvironment { param ( [string]$ScriptPath = $($MyInvocation.ScriptName) ) RemoveAnyRestartScript $homePath = $HOME $cmdScriptPath = Join-Path -Path $homePath -ChildPath "restart_ps.cmd" $userPasswordArg = if ($UserPassword) { "-UserPassword `"$UserPassword`"" } else { "" } $cmdContent = @" @echo off echo Waiting for PowerShell to close... timeout /t 4 /nobreak > NUL start pwsh -NoExit -File "$ScriptPath" $userPasswordArg timeout /t 2 "@ Write-Output "Restarting at $cmdScriptPath with: $cmdContent" # Write the CMD script to disk $cmdContent | Set-Content -Path $cmdScriptPath # Launch the CMD script to restart PowerShell Write-Output "$cmdScriptPath" Start-Process "cmd.exe" -ArgumentList "/c `"$cmdScriptPath`"" # (thoroughly) Exit the current PowerShell session taskkill /f /im "powershell.exe" taskkill /f /im "pwsh.exe" Exit } function Get-LatestReleaseDownloadUrl { param ( [string]$userRepo = "coreybutler/nvm-windows" ) try { $apiUrl = "https://api.github.com/repos/$userRepo/releases/latest" $latestRelease = Invoke-RestMethod -Uri $apiUrl $downloadUrl = $latestRelease.assets | Where-Object { $_.name -like "*.exe" } | Select-Object -ExpandProperty browser_download_url if ($downloadUrl) { return $downloadUrl } else { throw "Download URL not found." } } catch { Write-Error "An error occurred while fetching the latest release: $_" } } function InstallNvm { Write-Output "Installing NVM..." try { $latestNvmDownloadUrl = Get-LatestReleaseDownloadUrl Write-Output "Downloading NVM from $latestNvmDownloadUrl..." # Define the path for the downloaded installer $installerPath = Join-Path -Path $env:TEMP -ChildPath "nvm-setup.exe" # Download the installer DownloadFile $latestNvmDownloadUrl $installerPath # Execute the installer Write-Output "Running NVM installer..." Start-Process -FilePath $installerPath -ArgumentList '/install', '/silent', '/quiet', '/norestart', '/passive' -Wait -NoNewWindow Write-Output "NVM installation completed." } catch { Write-Error "Failed to install NVM: $_" } } function CheckForPowerShellCore { $pwshPath = (Get-Command pwsh -ErrorAction SilentlyContinue).Source if ($null -ne $pwshPath) { if ($PSVersionTable.PSVersion.Major -eq 5) { Write-Output "Running with latest PowerShell version..." $scriptPath = $($MyInvocation.ScriptName) $userPasswordArg = if ($UserPassword) { "-UserPassword `"$UserPassword`"" } else { "" } Start-Process $pwshPath -ArgumentList "-NoProfile", "-File", "`"$scriptPath`" $userPasswordArg" Write-Output "Done" Exit } } } function EnsureRunningAsAdministrator { try { if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { Write-Output "Not currently Administrator. Upgrading privileges..." # Get the current script path $scriptPath = $($MyInvocation.ScriptName) $userPasswordArg = if ($UserPassword) { "-UserPassword `"$UserPassword`"" } else { "" } # Relaunch the script with administrative rights using the current PowerShell version $psExecutable = Join-Path -Path $PSHOME -ChildPath "powershell.exe" if ($PSVersionTable.PSVersion.Major -ge 6) { $psExecutable = Join-Path -Path $PSHOME -ChildPath "pwsh.exe" } $arguments = "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`" $userPasswordArg" (Start-Process $psExecutable -Verb RunAs -ArgumentList $arguments -PassThru) Exit } } catch { Write-Output "An error occurred: $_" } finally { Write-Output "Continuing..." } } function Show-UserAgreementDialog { param ( [string]$acceptTermsEmail, [string]$hostname ) Add-Type -AssemblyName System.Windows.Forms $form = New-Object System.Windows.Forms.Form $form.Text = 'BrowserBox User Agreement' $form.TopMost = $true $form.StartPosition = 'CenterScreen' $form.ClientSize = New-Object System.Drawing.Size(400, 250) $label = New-Object System.Windows.Forms.Label $label.Text = 'Please enter the required information and agree to our terms to continue.' $label.Width = 360 $label.Height = 60 $label.Location = New-Object System.Drawing.Point(20, 20) $label.AutoSize = $true $form.Controls.Add($label) if (-not $hostname ) { $domainLabel = New-Object System.Windows.Forms.Label $domainLabel.Text = 'Domain Name:' $domainLabel.Location = New-Object System.Drawing.Point(20, 90) $domainLabel.AutoSize = $true $form.Controls.Add($domainLabel) $domainTextBox = New-Object System.Windows.Forms.TextBox $domainTextBox.Location = New-Object System.Drawing.Point(110, 85) $domainTextBox.Size = New-Object System.Drawing.Size(260, 20) $form.Controls.Add($domainTextBox) } if (-not $acceptTermsEmail) { $emailLabel = New-Object System.Windows.Forms.Label $emailLabel.Text = 'Email:' $emailLabel.Location = New-Object System.Drawing.Point(20, 120) $emailLabel.AutoSize = $true $form.Controls.Add($emailLabel) $emailTextBox = New-Object System.Windows.Forms.TextBox $emailTextBox.Location = New-Object System.Drawing.Point(110, 115) $emailTextBox.Size = New-Object System.Drawing.Size(260, 20) $form.Controls.Add($emailTextBox) } # Add clickable link for Terms of Service $termsLink = New-Object System.Windows.Forms.LinkLabel $termsLink.Text = "Terms of Service" $termsLink.AutoSize = $true $termsLink.Location = New-Object System.Drawing.Point(20, 160) $termsLink.Add_Click({ Start-Process "https://dosyago.com/terms.txt" }) $form.Controls.Add($termsLink) # Add clickable link for Privacy Policy $privacyLink = New-Object System.Windows.Forms.LinkLabel $privacyLink.Text = "Privacy Policy" $privacyLink.AutoSize = $true $privacyLink.Location = New-Object System.Drawing.Point(20, 180) $privacyLink.Add_Click({ Start-Process "https://dosyago.com/privacy.txt" }) $form.Controls.Add($privacyLink) # Add clickable link for License $licenseLink = New-Object System.Windows.Forms.LinkLabel $licenseLink.Text = "License" $licenseLink.AutoSize = $true $licenseLink.Location = New-Object System.Drawing.Point(20, 200) $licenseLink.Add_Click({ Start-Process "https://github.com/BrowserBox/BrowserBox/blob/boss/LICENSE.md" }) $form.Controls.Add($licenseLink) $continueButton = New-Object System.Windows.Forms.Button $continueButton = New-Object System.Windows.Forms.Button $continueButton.Text = 'Agree && Continue' # Fixed ampersand display $continueButton.Width = 150 # Increased width of the button $continueButton.Location = New-Object System.Drawing.Point(114, 200) # Adjust location if needed $continueButton.Add_Click({ if ($domainTextBox.Text -eq '' -or $emailTextBox.Text -eq '') { [System.Windows.Forms.MessageBox]::Show('Please fill in all required fields') } else { $form.DialogResult = [System.Windows.Forms.DialogResult]::OK } }) $form.Controls.Add($continueButton) $cancelButton = New-Object System.Windows.Forms.Button $cancelButton.Text = 'Cancel' $cancelButton.Location = New-Object System.Drawing.Point(280, 200) $cancelButton.DialogResult = [System.Windows.Forms.DialogResult]::Cancel $form.Controls.Add($cancelButton) $form.AcceptButton = $continueButton $form.CancelButton = $cancelButton # Add the SetForegroundWindow function using P/Invoke Add-Type @" using System; using System.Runtime.InteropServices; public class NativeMethods { [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr hWnd); } "@ # After the form is initialized, bring it to the foreground $form.Add_Shown({ $form.Activate() [NativeMethods]::SetForegroundWindow($form.Handle) }) if ($form.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { return @{ 'Email' = if ($acceptTermsEmail) { $acceptTermsEmail } else { $emailTextBox.Text } 'Hostname' = if ($hostname) { $hostname } else { $domainTextBox.Text } } } else { Exit } } function CheckWingetVersion { $currentVersion = & winget --version $currentVersion = $currentVersion -replace 'v', '' return $currentVersion } function UpdateWingetIfNeeded { param ([string]$currentVersion) $targetVersion = "1.6" if (-not (Is-VersionGreaterThan -currentVersion $currentVersion -targetVersion $targetVersion)) { Write-Output "Updating Winget to a newer version..." Invoke-WebRequest -Uri https://aka.ms/getwinget -OutFile winget.msixbundle Add-AppxPackage winget.msixbundle Remove-Item winget.msixbundle } else { Write-Output "Winget version ($currentVersion) is already greater than $targetVersion." } } function AddVimToPath { $vimPaths = @("C:\Program Files (x86)\Vim\vim*\vim.exe", "C:\Program Files\Vim\vim*\vim.exe") $vimExecutable = $null foreach ($path in $vimPaths) { $vimExecutable = Get-ChildItem -Path $path -ErrorAction SilentlyContinue | Select-Object -First 1 if ($vimExecutable -ne $null) { break } } if ($vimExecutable -ne $null) { $vimDirectory = [System.IO.Path]::GetDirectoryName($vimExecutable.FullName) Add-ToSystemPath $vimDirectory } else { Write-Warning "Vim executable not found. Please add Vim to the PATH manually." } } function AddPulseAudioToPath { $paPaths = @("C:\Program Files (x86)\PulseAudio\bin\pulseaudio.exe", "C:\Program Files\PulseAudio\bin\pulseaudio.exe") $paExecutable = $null foreach ($path in $paPaths) { $paExecutable = Get-ChildItem -Path $path -ErrorAction SilentlyContinue | Select-Object -First 1 if ($paExecutable -ne $null) { break } } if ($paExecutable -ne $null) { $paDirectory = [System.IO.Path]::GetDirectoryName($paExecutable.FullName) Add-ToSystemPath $paDirectory } else { Write-Warning "PulseAudio executable not found. Please add PulseAudio to the PATH manually." } } function Is-VersionGreaterThan { param ( [string]$currentVersion, [string]$targetVersion ) return [Version]$currentVersion -gt [Version]$targetVersion } function Install-PackageViaWinget { param ([string]$packageId) try { $null = winget install -e --id $packageId --accept-source-agreements 2>&1 if ($?) { Write-Output "Successfully installed $packageId" return $true } else { Write-Error "Failed to install $packageId" return $false } } catch { Write-Error "Failed to install $packageId : $_" return $false } } function Add-ToSystemPath { param ([string]$pathToAdd) $currentPath = [Environment]::GetEnvironmentVariable("Path", [EnvironmentVariableTarget]::Machine) if (-not $currentPath.Contains($pathToAdd)) { $newPath = $currentPath + ";" + $pathToAdd [Environment]::SetEnvironmentVariable("Path", $newPath, [EnvironmentVariableTarget]::Machine) Write-Output "Added $pathToAdd to system PATH." } else { Write-Output "$pathToAdd is already in system PATH." } } function DownloadFile { param ( [string]$Url, [string]$Destination ) $webClient = New-Object System.Net.WebClient $webClient.DownloadFile($Url, "$Destination") } function InstallIfNeeded { param ( [string]$packageName, [string]$packageId ) if (-not (Get-Command $packageName -ErrorAction SilentlyContinue)) { return Install-PackageViaWinget $packageId } else { Write-Output "$packageName is already installed." return $true } } Write-Output "" # Executor helper try { & $Main Write-Output "Next steps: Initialize-BrowserBox, then Start-Browserbox" } catch { Write-Output "An error occurred: $_" $Error[0] | Format-List -Force } finally { Write-Output "Install Exiting..." } } & $Outer |