From e616e808199336cea560ee8f08bcd39997a2b6d6 Mon Sep 17 00:00:00 2001 From: jkooe <62494645+jkooe@users.noreply.github.com> Date: Mon, 12 Oct 2026 00:05:01 +0800 Subject: [PATCH] =?UTF-8?q?fix(ci):=20=E5=8F=96=E5=AE=89=E8=A3=85=E5=99=A8?= =?UTF-8?q?=E9=80=80=E5=87=BA=E7=A0=81=E6=94=B9=E7=94=A8=20Start-Process?= =?UTF-8?q?=20-Wait=EF=BC=8C&=20=E8=B0=83=20GUI=20=E7=A8=8B=E5=BA=8F?= =?UTF-8?q?=E4=B8=8D=E7=AD=89=E5=BE=85=E4=B9=9F=E4=B8=8D=E8=AE=BE=E9=80=80?= =?UTF-8?q?=E5=87=BA=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 第二个 run(38152850819)两 job 仍同点红,但已是另一处原因,且同样是 「静默失效」:日志里 `msiexec /a 失败(退出码 )` —— $LASTEXITCODE 是空的, 而整步从开始到抛错只过了 0.8 秒。 根因:msiexec.exe 与 NSIS 安装包都是 **GUI 子系统程序**,PowerShell 用 `&` 调用时**既不等待其结束、也不设置 $LASTEXITCODE**(于是 $null -ne 0 判真, 在安装器还没跑完时就当场抛错)。arm64 那条没有 MSI、直奔 NSIS, 失败形态一模一样,正是同一根因的第二个样本。 修法: - 两处都改 Start-Process -Wait -PassThru,读 $p.ExitCode(确定性等待)。 - 参数拼成**单个字符串**传入,避免 -ArgumentList 数组被 PowerShell 自动加引号 而破坏 NSIS `/D=` 的「必须原样、不加引号、且是最后一个参数」语义。 - 新增 Wait-PayloadRoot:安装器可能「自己先退出、由子进程继续装」, 给最多 30 秒收敛时间再判定「找不到 node.exe」,避免竞态误报。 - 打印安装器实际退出码,便于下次一眼看出是哪一类失败。 --- .github/workflows/console-windows.yml | 36 +++++++++++++++++++++------ 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/.github/workflows/console-windows.yml b/.github/workflows/console-windows.yml index 980687a..7e5a476 100644 --- a/.github/workflows/console-windows.yml +++ b/.github/workflows/console-windows.yml @@ -202,6 +202,17 @@ jobs: return $null } + # 安装器可能「自己先退出、再由子进程继续装」,故给它一段收敛时间 + function Wait-PayloadRoot([string[]]$candidates, [int]$seconds = 30) { + $deadline = (Get-Date).AddSeconds($seconds) + do { + $r = Find-PayloadRoot $candidates + if ($r) { return $r } + Start-Sleep -Seconds 1 + } while ((Get-Date) -lt $deadline) + return $null + } + # 解开后的目录里必须三样齐全,且 node.exe 架构要对 function Assert-Payload([string]$root, [string]$label) { $node = Get-ChildItem -Path $root -Recurse -Filter 'node.exe' -ErrorAction SilentlyContinue | @@ -243,13 +254,19 @@ jobs: Remove-Item -Recurse -Force $dest -ErrorAction SilentlyContinue New-Item -ItemType Directory -Force -Path $dest | Out-Null $log = Join-Path $PWD 'msi-extract.log' - & msiexec.exe /a "$($m.FullName)" /qn "TARGETDIR=$dest" /l*v "$log" + # ⚠️ 必须用 Start-Process -Wait -PassThru 取退出码,**不能**用 `& msiexec.exe`: + # msiexec 是 GUI 子系统程序,脚本里 `&` 调它**既不等待、也不设 `$LASTEXITCODE`** + # (实测:整个步骤 0.8 秒就跑到下一行,`$LASTEXITCODE` 为空 → `$null -ne 0` 判真 → 当场抛错)。 + # 参数拼成**单个字符串**传递,避免 -ArgumentList 数组被 PowerShell 自动加引号而破坏 /D= 语义。 + $argLine = "/a `"$($m.FullName)`" /qn TARGETDIR=`"$dest`" /l*v `"$log`"" + $p = Start-Process -FilePath 'msiexec.exe' -ArgumentList $argLine -Wait -PassThru + Write-Host " msiexec 退出码 = $($p.ExitCode)" # 3010 = 成功但需重启,不是失败 - if ($LASTEXITCODE -ne 0 -and $LASTEXITCODE -ne 3010) { + if ($p.ExitCode -ne 0 -and $p.ExitCode -ne 3010) { $tail = (Get-Content $log -Tail 30 -ErrorAction SilentlyContinue) -join "`n" - throw "msiexec /a 失败(退出码 $LASTEXITCODE):$($m.Name)`n$tail" + throw "msiexec /a 失败(退出码 $($p.ExitCode)):$($m.Name)`n$tail" } - $root = Find-PayloadRoot @($dest) + $root = Wait-PayloadRoot @($dest) if (-not $root) { throw "MSI 管理安装后未在 $dest 下找到 node.exe" } Assert-Payload $root $m.Name $checked++ @@ -264,13 +281,16 @@ jobs: $dest = Join-Path $PWD 'pkg-extract/nsis' Remove-Item -Recurse -Force $dest -ErrorAction SilentlyContinue New-Item -ItemType Directory -Force -Path $dest | Out-Null - & $n.FullName /S "/D=$dest" - if ($LASTEXITCODE -ne 0 -and $LASTEXITCODE -ne 3010) { - throw "NSIS 静默安装失败(退出码 $LASTEXITCODE):$($n.Name)" + # 同 MSI:安装器也是 GUI 子系统程序,必须 Start-Process -Wait 才拿得到退出码。 + # 单个字符串传参 → `/D=` 保持原样不加引号(NSIS 的硬要求)。 + $p = Start-Process -FilePath $n.FullName -ArgumentList "/S /D=$dest" -Wait -PassThru + Write-Host " 安装器退出码 = $($p.ExitCode)" + if ($p.ExitCode -ne 0 -and $p.ExitCode -ne 3010) { + throw "NSIS 静默安装失败(退出码 $($p.ExitCode)):$($n.Name)" } # /D= 理论上一律生效,但若模板在 .onInit 里改写了 $INSTDIR 就未必; # 故把默认安装位置一并列为候选,避免「装成功了却因为找不到目录而误报」。 - $root = Find-PayloadRoot @( + $root = Wait-PayloadRoot @( $dest, (Join-Path $env:LOCALAPPDATA 'nodeagent-console'), (Join-Path $env:LOCALAPPDATA 'Programs/nodeagent-console'),