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:
216
install.ps1
Normal file
216
install.ps1
Normal 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 ""
|
||||
Reference in New Issue
Block a user