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>
This commit is contained in:
Cametendo
2026-05-26 13:01:20 +02:00
parent 8c6a2ab4c2
commit dd388a7469
4 changed files with 261 additions and 4 deletions

1
.gitignore vendored
View File

@@ -1,4 +1,5 @@
build/
release/
*.exe
*.o
tmp/

View File

@@ -2,18 +2,39 @@
set -e
BUILD_DIR="build"
RELEASE_DIR="release"
mkdir -p "$BUILD_DIR"
echo "=== Building bash-for-windows ==="
# Linux build
echo " -> Linux..."
# Linux debug build
echo " -> Linux (debug)..."
go build -ldflags="-s -w" -o "$BUILD_DIR/bash-windows" .
# Windows cross-compile
# Windows release build
echo " -> Windows (x86_64)..."
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s -w" -o "$BUILD_DIR/bash-windows.exe" .
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 \
go build -ldflags="-s -w" -o "$BUILD_DIR/bash-windows.exe" .
echo ""
echo "=== Build complete ==="
ls -lh "$BUILD_DIR/"
# ── Release package ───────────────────────────────────────────────────────────
if [[ "${1:-}" == "--release" ]]; then
echo ""
echo "=== Building release package ==="
rm -rf "$RELEASE_DIR"
mkdir -p "$RELEASE_DIR"
cp "$BUILD_DIR/bash-windows.exe" "$RELEASE_DIR/bash.exe"
cp install.ps1 "$RELEASE_DIR/"
cp install.bat "$RELEASE_DIR/"
echo ""
echo "Release folder: $RELEASE_DIR/"
ls -lh "$RELEASE_DIR/"
echo ""
echo "Distribute the contents of $RELEASE_DIR/ as a zip."
echo "Users run install.bat (or install.ps1) to install."
fi

19
install.bat Normal file
View File

@@ -0,0 +1,19 @@
@echo off
setlocal
REM Run the PowerShell installer from the same directory as this .bat file.
REM Works even if PowerShell's execution policy is Restricted.
echo.
echo bash-for-windows installer
echo.
powershell.exe -NoProfile -ExecutionPolicy Bypass ^
-File "%~dp0install.ps1" %*
if %ERRORLEVEL% NEQ 0 (
echo.
echo Installation failed. See error above.
pause
exit /b %ERRORLEVEL%
)

216
install.ps1 Normal file
View File

@@ -0,0 +1,216 @@
#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 ""