Files
MyGoNavi/internal/app/windows_msi_update.ps1
Syngnat 7017db418b 🐛 fix(updater): 修复 MSI 更新后任务栏图标失效与桌面快捷方式重建
- 自动迁移引用失效 MSI 缓存图标的旧任务栏固定项并刷新 Shell
- 在在线更新前备份桌面快捷方式状态,成功或失败后安全恢复
- 将桌面快捷方式拆分为条件安装组件并补充 Windows 回归测试
2026-07-18 13:48:35 +08:00

176 lines
6.8 KiB
PowerShell

$ErrorActionPreference = 'Stop'
$Source = $env:GONAVI_UPDATE_SOURCE
$Target = $env:GONAVI_UPDATE_TARGET
$StagedDir = $env:GONAVI_UPDATE_STAGED_DIR
$LogPath = $env:GONAVI_UPDATE_LOG_PATH
$MSILogPath = $env:GONAVI_UPDATE_MSI_LOG_PATH
$MSIExecPath = $env:GONAVI_UPDATE_MSIEXEC_PATH
$HostProcessId = 0
$HostExited = $false
$InstallSucceeded = $false
$LaunchSucceeded = $false
$DesktopShortcutDirectories = @()
$DesktopShortcutState = $null
$DesktopShortcutInstallValue = '1'
function Write-UpdateLog {
param([string]$Message)
if ([string]::IsNullOrWhiteSpace($LogPath)) {
return
}
try {
$timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff'
Add-Content -LiteralPath $LogPath -Value "[$timestamp] $Message" -Encoding UTF8
} catch {
# Logging must never hide the original updater error.
}
}
function Quote-NativeArgument {
param([string]$Value)
return '"' + $Value.Replace('"', '\"') + '"'
}
function Remove-UpdateArtifact {
param([string]$Path)
if ([string]::IsNullOrWhiteSpace($Path)) {
return
}
try {
if (Test-Path -LiteralPath $Path) {
Remove-Item -LiteralPath $Path -Force -Recurse
}
} catch {
Write-UpdateLog ("cleanup failed for " + $Path + ": " + $_.Exception.Message)
}
}
try {
foreach ($requiredPath in @($Source, $Target, $StagedDir, $LogPath, $MSILogPath, $MSIExecPath)) {
if ([string]::IsNullOrWhiteSpace($requiredPath)) {
throw 'missing required MSI updater path'
}
}
if (-not [int]::TryParse($env:GONAVI_UPDATE_PID, [ref]$HostProcessId) -or $HostProcessId -le 0) {
throw 'invalid host process id'
}
if (-not (Test-Path -LiteralPath $Source -PathType Leaf)) {
throw 'MSI package not found'
}
if (-not [string]::Equals([IO.Path]::GetExtension($Source), '.msi', [StringComparison]::OrdinalIgnoreCase)) {
throw 'update package is not an MSI file'
}
if (-not (Test-Path -LiteralPath $MSIExecPath -PathType Leaf)) {
throw 'msiexec.exe not found'
}
$TargetDir = [IO.Path]::GetDirectoryName($Target)
if ([string]::IsNullOrWhiteSpace($TargetDir) -or -not (Test-Path -LiteralPath $TargetDir -PathType Container)) {
throw 'target directory does not exist'
}
Write-UpdateLog 'MSI updater started'
$waitedSeconds = 0
while (Get-Process -Id $HostProcessId -ErrorAction SilentlyContinue) {
if ($waitedSeconds -ge 90) {
throw 'host process still running after 90 seconds'
}
Start-Sleep -Seconds 1
$waitedSeconds++
}
$HostExited = $true
Write-UpdateLog 'host process exited'
Start-Sleep -Seconds 2
$DesktopShortcutDirectories = @(Get-GoNaviDesktopDirectories)
$DesktopShortcutState = Save-GoNaviDesktopShortcutState -TargetPath $Target -BackupDirectory $StagedDir -DesktopDirectories $DesktopShortcutDirectories
if (-not $DesktopShortcutState.Succeeded) {
throw 'desktop shortcut state could not be backed up'
}
$DesktopShortcutInstallValue = $DesktopShortcutState.InstallValue
Write-UpdateLog ("desktop shortcut install value: " + $DesktopShortcutInstallValue)
$MsiArguments = @(
'/i',
(Quote-NativeArgument $Source),
('INSTALLFOLDER=' + (Quote-NativeArgument $TargetDir)),
('INSTALLDESKTOPSHORTCUT=' + $DesktopShortcutInstallValue),
'/passive',
'/norestart',
'/L*v',
(Quote-NativeArgument $MSILogPath)
)
Write-UpdateLog ("launching msiexec; verbose log: " + $MSILogPath)
$InstallerProcess = Start-Process -FilePath $MSIExecPath -Verb RunAs -ArgumentList $MsiArguments -Wait -PassThru -ErrorAction Stop
$InstallerExitCode = $InstallerProcess.ExitCode
Write-UpdateLog ("msiexec exit code: " + $InstallerExitCode)
if ($InstallerExitCode -notin @(0, 1641, 3010)) {
throw ("msiexec failed with exit code " + $InstallerExitCode)
}
$InstallSucceeded = $true
if (-not (Test-Path -LiteralPath $Target -PathType Leaf)) {
throw 'installed application executable not found'
}
if ($DesktopShortcutInstallValue -eq '0') {
if (-not (Remove-GoNaviDesktopShortcutsForTarget -TargetPath $Target -DesktopDirectories $DesktopShortcutDirectories)) {
throw 'unexpected desktop shortcut could not be removed'
}
}
if (-not (Restore-GoNaviDesktopShortcutState -State $DesktopShortcutState -OnlyForeign)) {
throw 'desktop shortcut state could not be restored'
}
[void](Repair-LegacyGoNaviTaskbarPins -TargetPath $Target)
Write-UpdateLog ("launching installed application: " + $Target)
$NewProcess = Start-Process -FilePath $Target -WorkingDirectory $TargetDir -PassThru -ErrorAction Stop
Start-Sleep -Milliseconds 1500
$NewProcess.Refresh()
if ($NewProcess.HasExited) {
throw 'updated application exited immediately after launch'
}
$LaunchSucceeded = $true
Remove-UpdateArtifact $Source
Write-UpdateLog 'MSI update finished'
$CleanupCommand = 'Start-Sleep -Seconds 2; Remove-Item -LiteralPath $env:GONAVI_UPDATE_STAGED_DIR -Recurse -Force -ErrorAction SilentlyContinue'
$EncodedCleanupCommand = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($CleanupCommand))
$CleanupWorkingDirectory = [IO.Path]::GetTempPath()
try {
Start-Process -FilePath 'powershell.exe' -WorkingDirectory $CleanupWorkingDirectory -WindowStyle Hidden -ArgumentList @(
'-NoProfile',
'-NonInteractive',
'-ExecutionPolicy',
'Bypass',
'-EncodedCommand',
$EncodedCleanupCommand
) -ErrorAction Stop | Out-Null
} catch {
Write-UpdateLog ("cleanup scheduler failed: " + $_.Exception.Message)
}
exit 0
} catch {
Write-UpdateLog ("MSI updater failed: " + $_.Exception.Message)
Write-UpdateLog 'MSI package retained for manual install'
if ($DesktopShortcutInstallValue -eq '0') {
[void](Remove-GoNaviDesktopShortcutsForTarget -TargetPath $Target -DesktopDirectories $DesktopShortcutDirectories)
}
[void](Restore-GoNaviDesktopShortcutState -State $DesktopShortcutState)
if ($HostExited -and -not $LaunchSucceeded -and (Test-Path -LiteralPath $Target -PathType Leaf)) {
try {
$TargetDir = [IO.Path]::GetDirectoryName($Target)
Start-Process -FilePath $Target -WorkingDirectory $TargetDir -ErrorAction Stop | Out-Null
if ($InstallSucceeded) {
Write-UpdateLog 'installed application relaunched after updater failure'
} else {
Write-UpdateLog 'previous application relaunched after MSI failure'
}
} catch {
Write-UpdateLog ("application relaunch failed: " + $_.Exception.Message)
}
}
exit 1
}