From d3381ca8a585b875ae25d7ec80f49a97b32a828c Mon Sep 17 00:00:00 2001 From: Jean Date: Fri, 5 Jun 2026 04:32:05 -0400 Subject: [PATCH] feat: add Windows installer packaging via Inno Setup - winutil.iss: Inno Setup script that installs winutil.ps1 to %ProgramFiles%\WinUtil, creates Start Menu + optional Desktop shortcuts patched with Run-as-Administrator flag - build-installer.ps1: one-shot build script (Compile.ps1 -> ISCC.exe -> installer\WinUtil-Setup-.exe); auto-installs Inno Setup via winget if missing - .gitattributes: add *.ico binary to prevent git line-ending corruption of icon files - .gitignore: exclude installer/ build output - docs/static/favicon.ico: regenerated as a clean valid ICO (16/32/48 px) after the original was corrupted by missing binary attr --- .gitattributes | 1 + .gitignore | 1 + build-installer.ps1 | 96 ++++++++++++++++++++++++++++++++ docs/static/favicon.ico | Bin 21897 -> 2270 bytes winutil.iss | 120 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 218 insertions(+) create mode 100644 build-installer.ps1 create mode 100644 winutil.iss diff --git a/.gitattributes b/.gitattributes index da0b45d5a2..29660f68a5 100644 --- a/.gitattributes +++ b/.gitattributes @@ -6,6 +6,7 @@ *.png binary *.jpg binary +*.ico binary config/* diff config/applications.json diff diff --git a/.gitignore b/.gitignore index a97fe97dec..548864410a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ Microsoft.PowerShell.ConsoleHost.dll winutil.exe.config winutil.ps1 binary/ +installer/ # general software/os specific desktop.ini diff --git a/build-installer.ps1 b/build-installer.ps1 new file mode 100644 index 0000000000..4a2abafadb --- /dev/null +++ b/build-installer.ps1 @@ -0,0 +1,96 @@ +<# +.SYNOPSIS + Compiles winutil.ps1 and builds a Windows installer (WinUtil-Setup-.exe). + +.DESCRIPTION + 1. Runs Compile.ps1 to bundle all functions + configs into winutil.ps1. + 2. Locates Inno Setup 6 (ISCC.exe) — installs it via winget if missing. + 3. Invokes ISCC.exe with winutil.iss to produce the installer under .\installer\. + +.EXAMPLE + .\build-installer.ps1 + .\build-installer.ps1 -SkipCompile # reuse an existing winutil.ps1 + .\build-installer.ps1 -Version "26.06.05" +#> + +param ( + [switch]$SkipCompile, + [string]$Version = (Get-Date -Format 'yy.MM.dd') +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$projectRoot = $PSScriptRoot + +function Find-ISCC { + $candidates = @( + 'C:\Program Files (x86)\Inno Setup 6\ISCC.exe', + 'C:\Program Files\Inno Setup 6\ISCC.exe', + "$env:LOCALAPPDATA\Programs\Inno Setup 6\ISCC.exe", + (Get-Command iscc -ErrorAction SilentlyContinue)?.Source + ) | Where-Object { $_ -and (Test-Path $_) } + return $candidates | Select-Object -First 1 +} + +# ── Step 1: Compile ────────────────────────────────────────────────────────── +if (-not $SkipCompile) { + Write-Host "Compiling winutil.ps1..." -ForegroundColor Cyan + & "$projectRoot\Compile.ps1" + if ($LASTEXITCODE -and $LASTEXITCODE -ne 0) { + throw "Compile.ps1 failed (exit $LASTEXITCODE)." + } + Write-Host "Compile succeeded." -ForegroundColor Green +} else { + Write-Host "Skipping compile (-SkipCompile)." -ForegroundColor Yellow +} + +$winutilPs1 = Join-Path $projectRoot 'winutil.ps1' +if (-not (Test-Path $winutilPs1)) { + throw "winutil.ps1 not found at '$winutilPs1'. Run without -SkipCompile first." +} + +# ── Step 2: Locate / install Inno Setup ────────────────────────────────────── +$iscc = Find-ISCC +if (-not $iscc) { + Write-Host "Inno Setup 6 not found. Attempting install via winget..." -ForegroundColor Yellow + winget install --id JRSoftware.InnoSetup --silent --accept-source-agreements --accept-package-agreements + $iscc = Find-ISCC +} + +if (-not $iscc) { + Write-Error @" +Inno Setup 6 (ISCC.exe) was not found and could not be installed automatically. + +Please install it manually from: + https://jrsoftware.org/isdl.php + +Then re-run this script. +"@ + exit 1 +} + +Write-Host "Using ISCC: $iscc" -ForegroundColor Cyan + +# ── Step 3: Create output directory ───────────────────────────────────────── +$installerDir = Join-Path $projectRoot 'installer' +if (-not (Test-Path $installerDir)) { + New-Item -ItemType Directory -Path $installerDir | Out-Null +} + +# ── Step 4: Build installer ────────────────────────────────────────────────── +$issFile = Join-Path $projectRoot 'winutil.iss' +Write-Host "Building installer (version $Version)..." -ForegroundColor Cyan + +& $iscc "/DMyAppVersion=$Version" $issFile + +if ($LASTEXITCODE -ne 0) { + throw "ISCC.exe failed (exit $LASTEXITCODE)." +} + +$outputExe = Join-Path $installerDir "WinUtil-Setup-$Version.exe" +if (Test-Path $outputExe) { + Write-Host "`nInstaller ready: $outputExe" -ForegroundColor Green +} else { + Write-Warning "Build reported success but '$outputExe' was not found. Check ISCC output above." +} diff --git a/docs/static/favicon.ico b/docs/static/favicon.ico index f95955c6dec2db8ead0b9edd26173a42d8740a77..ff0cee0a81af9d10a0591e7031249ac9603fd1fc 100644 GIT binary patch literal 2270 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