diff --git a/bin/main.ps1 b/bin/main.ps1 index 3dd791a..c7eaaa5 100644 --- a/bin/main.ps1 +++ b/bin/main.ps1 @@ -1,15 +1,15 @@ -# 设置严格模式,遇到错误停止,防止错误的命令雪崩 +# Stop on errors to avoid cascading failures. $ErrorActionPreference = "Stop" #Set-StrictMode -Version Latest -# === 1. 加载依赖库 === -# 确保 lib 目录存在 +# === 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. 读取配置 === +# === 2. Read configuration === $jsonPath = "$PSScriptRoot\apps.json" if (-not (Test-Path $jsonPath)) { Write-Error "Cannot find: $jsonPath" @@ -17,7 +17,7 @@ if (-not (Test-Path $jsonPath)) { } Write-Host "Reading configurations..." -ForegroundColor Cyan -# 使用 UTF8 读取防止中文乱码 +# Read as UTF-8. $apps = Get-Content $jsonPath -Encoding UTF8 | ConvertFrom-Json $forcePostInstall = $false @@ -44,14 +44,14 @@ function Unblock-WinGetCache { } } -# === 2.1 加载外部开关配置 (selection.ini) === -# 尝试查找根目录下的 selection.ini (位于 bin 的上一级) +# === 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 { - # 创建哈希表分别存储 App 开关和全局选项 + # Store app switches and global options separately. $selectionConfig = @{} $optionsConfig = @{} $currentSection = "" @@ -71,7 +71,7 @@ if (Test-Path $selectionPath) { continue } - # 按第一个等号分割,限制分割次数为 2 + # Split on the first equals sign only. $parts = $line -split '=', 2 if ($parts.Count -eq 2) { $key = $parts[0].Trim() @@ -89,10 +89,10 @@ if (Test-Path $selectionPath) { $forcePostInstall = $optionsConfig["ForcePostInstall"] -eq "1" } - # 应用配置 + # Apply app enabled overrides. foreach ($app in $apps) { if ($selectionConfig.ContainsKey($app.Name)) { - # 转换为整数 1 或 0 + # Convert to integer 1 or 0. $app.Enabled = [int]$selectionConfig[$app.Name] } } @@ -101,16 +101,16 @@ if (Test-Path $selectionPath) { } } -# === 2.2 启用本地 Manifest 安装 === -# winget install --manifest 需要先启用 LocalManifestFiles,重复执行不会影响后续安装。 +# === 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. 主循环 === +# === 3. Main loop === foreach ($app in $apps) { if ($app.Enabled -ne 1) { Write-Host "[Skipping] $($app.Name)" -ForegroundColor DarkGray - continue # 立即结束本次循环,进入下一个软件 + continue # Skip this app. } Write-Host "`n==========================================" -ForegroundColor Cyan @@ -118,10 +118,10 @@ foreach ($app in $apps) { Write-Host "==========================================" if ($app.Id -eq "System.Config") { - # 如果是纯配置项,直接打印跳过信息 + # This is a configuration-only item. Write-Host "[System Config] Skipping install..." -ForegroundColor Magenta } else { - # --- 步骤 A: Winget 安装 --- + # --- Step A: Winget install --- $manifestPath = $null $manifestTempDir = $null @@ -173,8 +173,7 @@ foreach ($app in $apps) { # "--scope", "machine" ) - # [版本检查逻辑] - # 检查 Version 是否存在且不为空字符串 + # Add Version only when configured. if (-not [string]::IsNullOrWhiteSpace($app.Version)) { Write-Host "-> Version: $($app.Version)" $wingetArgs += "-v" @@ -186,7 +185,7 @@ foreach ($app in $apps) { Write-Host "-> Installing via winget..." - # 执行安装 + # Run install command. try { & winget @wingetArgs $exitCode = $LASTEXITCODE @@ -204,7 +203,7 @@ foreach ($app in $apps) { } } - # 检查安装结果 (0=成功, -1978335189=已安装) + # Check install result (0=success, -1978335189=already installed). if ($exitCode -eq 0) { Write-Host "[Success]" -ForegroundColor Green } elseif ($exitCode -eq -1978335189) { @@ -212,22 +211,22 @@ foreach ($app in $apps) { Write-Host "[Skip] Already installed, running PostInstall..." -ForegroundColor Yellow } else { Write-Host "[Skip] Already installed" -ForegroundColor Yellow - continue # 已经安装的为了避免覆盖配置,也就不配置了 + continue # Skip PostInstall when already installed. } } else { Write-Error "[Fail] Error code: $exitCode" - continue # 安装失败则跳过后续配置 + continue # Skip PostInstall on install failure. } } - # --- 步骤 B: PostInstall 配置 --- + # --- 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) { - # [间隔逻辑] 如果这不是第一步,先等待 2 秒 + # Wait between PostInstall steps. if ($stepIndex -gt 0) { Write-Host "(Waiting 2 seconds...)" -ForegroundColor DarkGray Start-Sleep -Seconds 2 @@ -236,20 +235,20 @@ foreach ($app in $apps) { try { switch ($action.Type) { - # 1. 复制文件 + # 1. Copy file. "FileCopy" { & "$PSScriptRoot\..\lib\invoke-filecopy.ps1" ` -Source $action.Source ` -Destination $action.Destination } - # 2. 导入注册表 + # 2. Import registry file. "RegImport" { & "$PSScriptRoot\..\lib\invoke-regimport.ps1" ` -Path $action.Path } - # 3. 执行 CMD + # 3. Run command. "Command" { & "$PSScriptRoot\..\lib\invoke-cmdexec.ps1" ` -Command $action.Command ` diff --git a/install.bat b/install.bat index 481252f..51b6cdf 100644 --- a/install.bat +++ b/install.bat @@ -1,26 +1,25 @@ @echo off -:: ========================================== -:: 自动化装机工具启动器 -:: ========================================== +rem ========================================== +rem Winit Helper launcher +rem ========================================== -:: 1. 强制切换到当前批处理文件所在的目录 -:: 这一步至关重要,防止以管理员身份运行时路径变成了 C:\Windows\System32 +rem Always run from the project directory. cd /d "%~dp0" -:: 2. 检查管理员权限 +rem Relaunch as administrator when needed. if not "%1" == "am_admin" ( rem you'd better keep the following line as it is. - powershell start -verb runas '%0' am_admin & exit /b + powershell.exe -NoProfile -Command "Start-Process -FilePath '%~f0' -ArgumentList 'am_admin' -Verb RunAs" + exit /b ) -:: ========================================== -:: 3. 核心执行逻辑 -:: ========================================== +rem ========================================== +rem Run main script +rem ========================================== echo Calling main.ps1... -:: -NoProfile: 不加载用户配置文件,加快启动速度 -:: -ExecutionPolicy Bypass: 绕过默认的脚本执行策略限制 -:: -File: 指定要运行的脚本文件 +rem Remove Zone.Identifier from copied or downloaded project files. +powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "Get-ChildItem -LiteralPath '%~dp0' -Recurse -File -Include *.ps1,*.psm1,*.bat,*.cmd,*.json,*.yaml,*.yml,*.reg | Unblock-File -ErrorAction SilentlyContinue" powershell.exe -NoExit -NoProfile -ExecutionPolicy Bypass -File ".\bin\main.ps1" diff --git a/lib/invoke-cmdexec.ps1 b/lib/invoke-cmdexec.ps1 index df7a41e..e638abe 100644 --- a/lib/invoke-cmdexec.ps1 +++ b/lib/invoke-cmdexec.ps1 @@ -3,36 +3,36 @@ param( [string]$WorkDir = $null ) -# 1. 处理工作目录 +# 1. Resolve work directory. if ([string]::IsNullOrEmpty($WorkDir)) { $WorkDir = $PSScriptRoot } else { - # 如果 WorkDir 里包含变量(如 $env:APPDATA),展开它 + # Expand variables in WorkDir, such as $env:APPDATA. $WorkDir = $ExecutionContext.InvokeCommand.ExpandString($WorkDir) } -# 确保目录存在,否则命令会报错 +# Make sure the directory exists. if (-not (Test-Path $WorkDir)) { Write-Warning "`n[CMD] WARNING: The work dir does not exist, using ($WorkDir)" $WorkDir = $PSScriptRoot } -# 2. 处理命令中的环境变量 +# 2. Expand variables in the command. $ProjectRoot = Split-Path $PSScriptRoot -Parent -# === 替换 $PSScriptRoot 为实际的绝对路径 === -# 注意:要处理反斜杠转义问题,直接用字符串替换最安全 +# Replace $PSScriptRoot with the project root path. +# Direct string replacement is the safest option for backslashes. if ($Command.Contains('$PSScriptRoot')) { $Command = $Command.Replace('$PSScriptRoot', $ProjectRoot) } -# 这一步很关键,让你可以写 "echo $env:USERNAME" +# This allows commands such as "echo $env:USERNAME". $Command = $ExecutionContext.InvokeCommand.ExpandString($Command) Write-Host "`n[CMD] Execute: $Command" -ForegroundColor Gray -# 3. 启动进程 -# /c 表示执行完命令后关闭 cmd 窗口 -# /s 开启参数的一般处理(忽略第一个和最后一个引号,为了兼容复杂引号情况) +# 3. Start cmd.exe. +# /c closes cmd after the command completes. +# /s enables cmd quote handling for complex command strings. $processOptions = @{ FilePath = "cmd.exe" ArgumentList = "/s", "/c", "`"$Command`"" @@ -45,10 +45,10 @@ $processOptions = @{ try { $proc = Start-Process @processOptions - # 4. 检查退出代码 (ExitCode) + # 4. Check exit code. if ($proc.ExitCode -ne 0) { Write-Error "`n[CMD] Failed to execute, exit code: $($proc.ExitCode)" } } catch { Write-Error "`n[CMD] Failed to start process: $_" -} \ No newline at end of file +} diff --git a/lib/invoke-filecopy.ps1 b/lib/invoke-filecopy.ps1 index fe202f4..5e10f3b 100644 --- a/lib/invoke-filecopy.ps1 +++ b/lib/invoke-filecopy.ps1 @@ -7,35 +7,33 @@ param( ) try { - # 1. 路径预处理:展开环境变量 (例如把 $env:APPDATA 变成 C:\Users\...\AppData\Roaming) + # 1. Expand variables in paths. $ResolvedSource = $ExecutionContext.InvokeCommand.ExpandString($Source) $ResolvedDest = $ExecutionContext.InvokeCommand.ExpandString($Destination) - # 2. 处理相对路径 (针对源文件) - # 如果源路径是相对路径 (./assets/...), 尝试将其转换为绝对路径 - # 假设脚本是从项目根目录调用的 + # 2. Resolve relative source paths from the project root. if (-not (Test-Path $ResolvedSource) -and (Test-Path "$PWD\$ResolvedSource")) { $ResolvedSource = Join-Path $PWD $ResolvedSource } - # 3. 再次检查源文件是否存在 + # 3. Make sure the source file exists. if (-not (Test-Path $ResolvedSource -PathType Leaf)) { Write-Warning "`n[FileCopy] [Skip] File not found: $ResolvedSource" - return # 退出脚本 + return } - # 4. 处理目标目录 (自动创建不存在的文件夹) + # 4. Create destination directory when needed. $DestDir = Split-Path -Path $ResolvedDest -Parent if (-not (Test-Path $DestDir)) { Write-Host "`n[FileCopy] Create dir: $DestDir" -ForegroundColor DarkGray New-Item -Path $DestDir -ItemType Directory -Force | Out-Null } - # 5. 执行复制 (Force = 覆盖) + # 5. Copy file. Force overwrites existing files. Copy-Item -Path $ResolvedSource -Destination $ResolvedDest -Force -ErrorAction Stop Write-Host "`n[FileCopy] Success: $ResolvedDest" -ForegroundColor Green } catch { Write-Error "`n[FileCopy] Fail: $_" -} \ No newline at end of file +} diff --git a/lib/invoke-regimport.ps1 b/lib/invoke-regimport.ps1 index a3d1eeb..3820c3c 100644 --- a/lib/invoke-regimport.ps1 +++ b/lib/invoke-regimport.ps1 @@ -4,17 +4,15 @@ param( ) try { - # 1. 路径预处理 - # 展开环境变量 (虽然 .reg 路径通常是固定的,但支持一下没坏处) + # 1. Expand variables in the path. $ResolvedPath = $ExecutionContext.InvokeCommand.ExpandString($Path) - # 2. 处理相对路径 - # 如果路径是相对的 (./assets/...), 转换为绝对路径 + # 2. Resolve relative paths from the project root. if (-not (Test-Path $ResolvedPath) -and (Test-Path "$PWD\$ResolvedPath")) { $ResolvedPath = Join-Path $PWD $ResolvedPath } - # 3. 检查文件是否存在 + # 3. Make sure the registry file exists. if (-not (Test-Path $ResolvedPath)) { Write-Warning "`n[RegImport] [Skip] Cannot find: $ResolvedPath" return @@ -22,8 +20,7 @@ try { Write-Host "`n[RegImport] Importing: $(Split-Path $ResolvedPath -Leaf)" -ForegroundColor Gray - # 4. 调用 reg.exe 导入 - # 使用 Start-Process 以获取退出代码 + # 4. Import with reg.exe and capture the exit code. $proc = Start-Process -FilePath "reg.exe" -ArgumentList "import", "`"$ResolvedPath`"" -Wait -PassThru -NoNewWindow if ($proc.ExitCode -eq 0) { @@ -34,4 +31,4 @@ try { } catch { Write-Error "`n[RegImport] Failed to start process: $_" -} \ No newline at end of file +}