# 设置严格模式,遇到错误停止,防止错误的命令雪崩 $ErrorActionPreference = "Stop" #Set-StrictMode -Version Latest # === 1. 加载依赖库 === # 确保 lib 目录存在 if (-not (Test-Path "$PSScriptRoot\..\lib")) { Write-Error "Cannot find .\lib, some files missing." exit 1 } # === 2. 读取配置 === $jsonPath = "$PSScriptRoot\apps.json" if (-not (Test-Path $jsonPath)) { Write-Error "Cannot find: $jsonPath" exit 1 } Write-Host "Reading configurations..." -ForegroundColor Cyan # 使用 UTF8 读取防止中文乱码 $apps = Get-Content $jsonPath -Encoding UTF8 | ConvertFrom-Json $forcePostInstall = $false # === 2.1 加载外部开关配置 (selection.ini) === # 尝试查找根目录下的 selection.ini (位于 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 { # 创建哈希表分别存储 App 开关和全局选项 $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 } # 按第一个等号分割,限制分割次数为 2 $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" } # 应用配置 foreach ($app in $apps) { if ($selectionConfig.ContainsKey($app.Name)) { # 转换为整数 1 或 0 $app.Enabled = [int]$selectionConfig[$app.Name] } } } catch { Write-Warning "Error reading selection.ini: $_" } } # === 2.2 启用本地 Manifest 安装 === # winget install --manifest 需要先启用 LocalManifestFiles,重复执行不会影响后续安装。 Write-Host "Enabling winget local manifest support..." -ForegroundColor Cyan winget settings --enable LocalManifestFiles # === 3. 主循环 === foreach ($app in $apps) { if ($app.Enabled -ne 1) { Write-Host "[Skipping] $($app.Name)" -ForegroundColor DarkGray continue # 立即结束本次循环,进入下一个软件 } Write-Host "`n==========================================" -ForegroundColor Cyan Write-Host "Installing: $($app.Name)" -ForegroundColor Yellow Write-Host "==========================================" if ($app.Id -eq "System.Config") { # 如果是纯配置项,直接打印跳过信息 Write-Host "[System Config] Skipping install..." -ForegroundColor Magenta } else { # --- 步骤 A: Winget 安装 --- $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" ) # [版本检查逻辑] # 检查 Version 是否存在且不为空字符串 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..." # 执行安装 try { & winget @wingetArgs $exitCode = $LASTEXITCODE } finally { if ($manifestTempDir -and (Test-Path $manifestTempDir)) { Remove-Item -LiteralPath $manifestTempDir -Recurse -Force } } # 检查安装结果 (0=成功, -1978335189=已安装) 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 # 已经安装的为了避免覆盖配置,也就不配置了 } } else { Write-Error "[Fail] Error code: $exitCode" continue # 安装失败则跳过后续配置 } } # --- 步骤 B: PostInstall 配置 --- if ($app.PostInstall -and $app.PostInstall.Count -gt 0) { Write-Host "`n-> Configuring..." -ForegroundColor Cyan $stepIndex = 0 foreach ($action in $app.PostInstall) { # [间隔逻辑] 如果这不是第一步,先等待 2 秒 if ($stepIndex -gt 0) { Write-Host "(Waiting 2 seconds...)" -ForegroundColor DarkGray Start-Sleep -Seconds 2 } try { switch ($action.Type) { # 1. 复制文件 "FileCopy" { & "$PSScriptRoot\..\lib\invoke-filecopy.ps1" ` -Source $action.Source ` -Destination $action.Destination } # 2. 导入注册表 "RegImport" { & "$PSScriptRoot\..\lib\invoke-regimport.ps1" ` -Path $action.Path } # 3. 执行 CMD "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