Files
winit-helper/bin/main.ps1

275 lines
9.4 KiB
PowerShell

# Stop on errors to avoid cascading failures.
$ErrorActionPreference = "Stop"
#Set-StrictMode -Version Latest
# === 1. Check dependencies ===
# Make sure the lib directory exists.
if (-not (Test-Path "$PSScriptRoot\..\lib")) {
Write-Error "Cannot find .\lib, some files missing."
exit 1
}
# === 2. Read configuration ===
$jsonPath = "$PSScriptRoot\apps.json"
if (-not (Test-Path $jsonPath)) {
Write-Error "Cannot find: $jsonPath"
exit 1
}
Write-Host "Reading configurations..." -ForegroundColor Cyan
# Read as UTF-8.
$apps = Get-Content $jsonPath -Encoding UTF8 | ConvertFrom-Json
$forcePostInstall = $false
function Unblock-WinGetCache {
param(
[Parameter(Mandatory=$true)] [string]$PackageId
)
$wingetCacheRoot = Join-Path $env:TEMP "WinGet"
if (-not (Test-Path $wingetCacheRoot)) {
return
}
$cacheItems = Get-ChildItem -Path $wingetCacheRoot -Recurse -File -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -like "*\$PackageId.*" }
foreach ($item in $cacheItems) {
try {
Unblock-File -LiteralPath $item.FullName -ErrorAction Stop
Write-Host "-> Unblocked cached installer: $($item.FullName)" -ForegroundColor DarkGray
} catch {
Write-Warning "Failed to unblock cached installer: $($item.FullName), $_"
}
}
}
# === 2.1 Load selection.ini overrides ===
# selection.ini is located in the project root, one level above bin.
$selectionPath = Join-Path (Split-Path $PSScriptRoot -Parent) "selection.ini"
if (Test-Path $selectionPath) {
Write-Host "Loading selection overrides from selection.ini..." -ForegroundColor Cyan
try {
# Store app switches and global options separately.
$selectionConfig = @{}
$optionsConfig = @{}
$currentSection = ""
foreach ($line in (Get-Content $selectionPath -Encoding UTF8)) {
$line = $line.Trim()
if ([string]::IsNullOrWhiteSpace($line) -or $line.StartsWith(";") -or $line.StartsWith("#")) {
continue
}
if ($line -match '^\[(.+)\]$') {
$currentSection = $matches[1].Trim()
continue
}
if ($line -notmatch "=") {
continue
}
# Split on the first equals sign only.
$parts = $line -split '=', 2
if ($parts.Count -eq 2) {
$key = $parts[0].Trim()
$value = $parts[1].Trim()
if ($currentSection -eq "Options") {
$optionsConfig[$key] = $value
} else {
$selectionConfig[$key] = $value
}
}
}
if ($optionsConfig.ContainsKey("ForcePostInstall")) {
$forcePostInstall = $optionsConfig["ForcePostInstall"] -eq "1"
}
# Apply app enabled overrides.
foreach ($app in $apps) {
if ($selectionConfig.ContainsKey($app.Name)) {
# Convert to integer 1 or 0.
$app.Enabled = [int]$selectionConfig[$app.Name]
}
}
} catch {
Write-Warning "Error reading selection.ini: $_"
}
}
# === 2.2 Enable local manifest installation ===
# winget install --manifest requires LocalManifestFiles. Re-running this is harmless.
Write-Host "Enabling winget local manifest support..." -ForegroundColor Cyan
winget settings --enable LocalManifestFiles
# === 3. Main loop ===
foreach ($app in $apps) {
if ($app.Enabled -ne 1) {
Write-Host "[Skipping] $($app.Name)" -ForegroundColor DarkGray
continue # Skip this app.
}
Write-Host "`n==========================================" -ForegroundColor Cyan
Write-Host "Installing: $($app.Name)" -ForegroundColor Yellow
Write-Host "=========================================="
if ($app.Id -eq "System.Config") {
# This is a configuration-only item.
Write-Host "[System Config] Skipping install..." -ForegroundColor Magenta
} else {
# --- Step A: Winget install ---
$manifestPath = $null
$manifestTempDir = $null
if (-not [string]::IsNullOrWhiteSpace($app.Manifest)) {
Write-Host "-> Manifest: $($app.Manifest)"
if ($app.Manifest -match '^https?://') {
$manifestTempDir = Join-Path $env:TEMP ("winit-helper-manifest-" + [guid]::NewGuid().ToString("N"))
New-Item -ItemType Directory -Path $manifestTempDir -Force | Out-Null
$manifestFileName = [System.IO.Path]::GetFileName(([uri]$app.Manifest).AbsolutePath)
if ([string]::IsNullOrWhiteSpace($manifestFileName)) {
$manifestFileName = "$($app.Id).yaml"
}
$manifestPath = Join-Path $manifestTempDir $manifestFileName
Write-Host "-> Downloading custom manifest..."
Invoke-WebRequest -Uri $app.Manifest -OutFile $manifestPath
} else {
$manifestPath = $app.Manifest
if (-not [System.IO.Path]::IsPathRooted($manifestPath)) {
$manifestPath = Join-Path $PSScriptRoot $manifestPath
}
if (-not (Test-Path $manifestPath)) {
Write-Error "Cannot find manifest: $manifestPath"
continue
}
}
$wingetArgs = @(
"install",
"--manifest", $manifestPath,
"--silent",
"--accept-package-agreements",
"--accept-source-agreements",
"--disable-interactivity"
# "--scope", "machine"
)
} else {
$wingetArgs = @(
"install",
"--id", $app.Id,
"-e",
"--silent",
"--accept-package-agreements",
"--accept-source-agreements",
"--disable-interactivity"
# "--scope", "machine"
)
# Add Version only when configured.
if (-not [string]::IsNullOrWhiteSpace($app.Version)) {
Write-Host "-> Version: $($app.Version)"
$wingetArgs += "-v"
$wingetArgs += $app.Version
} else {
Write-Host "-> Version: latest"
}
}
Write-Host "-> Installing via winget..."
# Run install command.
try {
& winget @wingetArgs
$exitCode = $LASTEXITCODE
if ($exitCode -eq -1978335231 -and -not [string]::IsNullOrWhiteSpace($app.Manifest)) {
Write-Warning "winget failed after download. Unblocking cached installer and retrying once..."
Unblock-WinGetCache -PackageId $app.Id
& winget @wingetArgs
$exitCode = $LASTEXITCODE
}
} finally {
if ($manifestTempDir -and (Test-Path $manifestTempDir)) {
Remove-Item -LiteralPath $manifestTempDir -Recurse -Force
}
}
# Check install result (0=success, -1978335189=already installed).
if ($exitCode -eq 0) {
Write-Host "[Success]" -ForegroundColor Green
} elseif ($exitCode -eq -1978335189) {
if ($forcePostInstall) {
Write-Host "[Skip] Already installed, running PostInstall..." -ForegroundColor Yellow
} else {
Write-Host "[Skip] Already installed" -ForegroundColor Yellow
continue # Skip PostInstall when already installed.
}
} else {
Write-Error "[Fail] Error code: $exitCode"
continue # Skip PostInstall on install failure.
}
}
# --- Step B: PostInstall configuration ---
if ($app.PostInstall -and $app.PostInstall.Count -gt 0) {
Write-Host "`n-> Configuring..." -ForegroundColor Cyan
$stepIndex = 0
foreach ($action in $app.PostInstall) {
# Wait between PostInstall steps.
if ($stepIndex -gt 0) {
Write-Host "(Waiting 2 seconds...)" -ForegroundColor DarkGray
Start-Sleep -Seconds 2
}
try {
switch ($action.Type) {
# 1. Copy file.
"FileCopy" {
& "$PSScriptRoot\..\lib\invoke-filecopy.ps1" `
-Source $action.Source `
-Destination $action.Destination
}
# 2. Import registry file.
"RegImport" {
& "$PSScriptRoot\..\lib\invoke-regimport.ps1" `
-Path $action.Path
}
# 3. Run command.
"Command" {
& "$PSScriptRoot\..\lib\invoke-cmdexec.ps1" `
-Command $action.Command `
-WorkDir $action.WorkDir
}
Default {
Write-Warning "`nUnknown action: $($action.Type)"
}
}
} catch {
Write-Error "`nFailed to operate step $($stepIndex + 1): $_"
}
$stepIndex++
}
} else {
Write-Host "-> No configurations." -ForegroundColor DarkGray
}
}
Write-Host "`n=== Done. Need restart ===" -ForegroundColor Green
Pause