Files
bash-for-windows/install.ps1
Cametendo dd388a7469 Add Windows Terminal installer (install.ps1 / install.bat)
install.ps1 copies bash.exe to %LOCALAPPDATA%\Programs\BashForWindows,
adds it to the user PATH, and injects a "Bash for Windows" profile into
Windows Terminal's settings.json so the shell appears in the + dropdown.

install.bat is a double-click wrapper that bypasses the PS execution policy.

build.sh --release produces a release/ folder ready to zip and distribute.
Supports -Uninstall flag to cleanly remove everything.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 13:01:20 +02:00

217 lines
8.3 KiB
PowerShell

#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 ""