#Requires -Version 5.1 <# .SYNOPSIS Installs bash-for-windows and registers it as a Windows Terminal profile. .DESCRIPTION Copies bash.exe to a permanent location, adds it to the user PATH, and adds a "Bash for Windows" entry to Windows Terminal so it appears in the new-tab dropdown. .EXAMPLE .\install.ps1 .\install.ps1 -InstallDir "$env:APPDATA\BashForWindows" .\install.ps1 -NoPath -NoTerminal .\install.ps1 -Uninstall #> param( [string]$InstallDir = "$env:LOCALAPPDATA\Programs\BashForWindows", [switch]$NoPath, [switch]$NoTerminal, [switch]$Uninstall ) $ErrorActionPreference = "Stop" $ExeName = "bash.exe" $ProfileGuid = "{7a86baf2-f5b9-4b78-b3f8-5e1e4f3d9b2a}" $ProfileName = "Bash for Windows" # ── Pretty output helpers ───────────────────────────────────────────────────── function Write-Header { Write-Host "" Write-Host " bash-for-windows" -ForegroundColor White -NoNewline Write-Host " installer" -ForegroundColor DarkGray Write-Host " ─────────────────────────────────────────" -ForegroundColor DarkGray } function Write-Step { param([string]$s) Write-Host "`n ▸ $s" -ForegroundColor Cyan } function Write-Ok { param([string]$s) Write-Host " ✓ $s" -ForegroundColor Green } function Write-Warn { param([string]$s) Write-Host " ⚠ $s" -ForegroundColor Yellow } function Write-Fatal { param([string]$s) Write-Host " ✗ $s" -ForegroundColor Red; exit 1 } # ── Locate source bash.exe (must live next to this script) ──────────────────── $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition $SourceExe = Join-Path $ScriptDir $ExeName if (-not (Test-Path $SourceExe)) { Write-Header Write-Fatal "'$ExeName' not found next to this script. Expected: $SourceExe" } # ── Windows Terminal settings.json locations ────────────────────────────────── function Get-WTSettingsPath { @( "$env:LOCALAPPDATA\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState\settings.json", "$env:LOCALAPPDATA\Packages\Microsoft.WindowsTerminalPreview_8wekyb3d8bbwe\LocalState\settings.json", "$env:LOCALAPPDATA\Microsoft\Windows Terminal\settings.json" ) | Where-Object { Test-Path $_ } | Select-Object -First 1 } # ── PATH helpers ────────────────────────────────────────────────────────────── function Add-ToUserPath { param([string]$Dir) $current = [Environment]::GetEnvironmentVariable("PATH", "User") -split ";" | Where-Object { $_ -ne "" } if ($Dir -in $current) { Write-Warn "Already in user PATH: $Dir" return } [Environment]::SetEnvironmentVariable("PATH", (($current + $Dir) -join ";"), "User") Write-Ok "Added to user PATH: $Dir" Write-Warn "Re-open your terminal for the PATH change to take effect." } function Remove-FromUserPath { param([string]$Dir) $current = [Environment]::GetEnvironmentVariable("PATH", "User") -split ";" | Where-Object { $_ -ne "" -and $_ -ne $Dir } [Environment]::SetEnvironmentVariable("PATH", ($current -join ";"), "User") Write-Ok "Removed from user PATH: $Dir" } # ── Windows Terminal profile helpers ────────────────────────────────────────── function Register-WTProfile { param([string]$ExePath) $settingsPath = Get-WTSettingsPath if (-not $settingsPath) { Write-Warn "Windows Terminal not found — skipping profile registration." Write-Warn "Install Windows Terminal from the Microsoft Store and re-run the installer." return } try { $raw = Get-Content $settingsPath -Raw -Encoding UTF8 $json = $raw | ConvertFrom-Json } catch { Write-Warn "Could not parse settings.json: $_" return } # Build the profile object $newProfile = [PSCustomObject]@{ guid = $ProfileGuid name = $ProfileName commandline = $ExePath startingDirectory = "%USERPROFILE%" hidden = $false } # Ensure profiles.list exists if (-not $json.PSObject.Properties["profiles"]) { Add-Member -InputObject $json -MemberType NoteProperty ` -Name "profiles" -Value ([PSCustomObject]@{ list = @() }) } if (-not $json.profiles.PSObject.Properties["list"]) { Add-Member -InputObject $json.profiles -MemberType NoteProperty ` -Name "list" -Value @() -Force } $list = @($json.profiles.list) # Find existing entry by GUID $existingIdx = -1 for ($i = 0; $i -lt $list.Count; $i++) { if ($list[$i].guid -eq $ProfileGuid) { $existingIdx = $i; break } } if ($existingIdx -ge 0) { $list[$existingIdx] = $newProfile Write-Ok "Updated existing '$ProfileName' profile." } else { # Prepend so it appears near the top of the dropdown $list = @($newProfile) + $list Write-Ok "Added '$ProfileName' profile." } $json.profiles.list = $list # Write back — high depth so nested keybindings etc. aren't truncated $updated = $json | ConvertTo-Json -Depth 50 [System.IO.File]::WriteAllText($settingsPath, $updated, [System.Text.UTF8Encoding]::new($false)) Write-Ok "Saved settings: $settingsPath" Write-Ok "Open Windows Terminal, click '+', and select '$ProfileName'." } function Remove-WTProfile { $settingsPath = Get-WTSettingsPath if (-not $settingsPath) { return } try { $json = Get-Content $settingsPath -Raw -Encoding UTF8 | ConvertFrom-Json } catch { return } if (-not $json.profiles -or -not $json.profiles.list) { return } $list = @($json.profiles.list) | Where-Object { $_.guid -ne $ProfileGuid } $json.profiles.list = $list $updated = $json | ConvertTo-Json -Depth 50 [System.IO.File]::WriteAllText($settingsPath, $updated, [System.Text.UTF8Encoding]::new($false)) Write-Ok "Removed '$ProfileName' from Windows Terminal." } # ───────────────────────────────────────────────────────────────────────────── # UNINSTALL # ───────────────────────────────────────────────────────────────────────────── if ($Uninstall) { Write-Header Write-Step "Removing files" if (Test-Path $InstallDir) { Remove-Item $InstallDir -Recurse -Force Write-Ok "Deleted: $InstallDir" } else { Write-Warn "Not found (already removed?): $InstallDir" } Write-Step "Removing from PATH" Remove-FromUserPath $InstallDir Write-Step "Removing Windows Terminal profile" Remove-WTProfile Write-Host "`n Uninstall complete." -ForegroundColor Green Write-Host "" exit 0 } # ───────────────────────────────────────────────────────────────────────────── # INSTALL # ───────────────────────────────────────────────────────────────────────────── Write-Header Write-Step "Installing binary" New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null $DestExe = Join-Path $InstallDir $ExeName Copy-Item -Path $SourceExe -Destination $DestExe -Force Write-Ok "Copied to: $DestExe" if (-not $NoPath) { Write-Step "Updating user PATH" Add-ToUserPath $InstallDir } if (-not $NoTerminal) { Write-Step "Registering Windows Terminal profile" Register-WTProfile $DestExe } Write-Host "`n Install complete." -ForegroundColor Green Write-Host ""