fix(desktop): unblock Windows installer upgrades #1000

This commit is contained in:
程序员阿江(Relakkes)
2026-07-14 03:15:54 +08:00
parent 4c3b08c0a4
commit ecb66bfdd5
6 changed files with 149 additions and 26 deletions
+21 -8
View File
@@ -90,9 +90,6 @@ Function CcHahaFinalInstallDir
FunctionEnd
Function CcHahaRecoverLegacy
InitPluginsDir
File /oname=$PLUGINSDIR\recover-legacy-install-data.ps1 "${BUILD_RESOURCES_DIR}\recover-legacy-install-data.ps1"
ReadRegStr $4 HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation
ReadRegStr $5 HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation
ReadRegStr $R0 HKCU "${UNINSTALL_REGISTRY_KEY}" UninstallString
@@ -119,6 +116,22 @@ Function CcHahaRecoverLegacy
Call CcHahaUninstallerParent
Pop $5
${EndIf}
Push "$INSTDIR"
Call CcHahaFinalInstallDir
Pop $9
${If} $4 == ""
${AndIf} $5 == ""
StrCpy $0 "0"
StrCpy $1 "No registered installation needs legacy data recovery"
DetailPrint "$1"
Return
${EndIf}
InitPluginsDir
File /oname=$PLUGINSDIR\recover-legacy-install-data.ps1 "${BUILD_RESOURCES_DIR}\recover-legacy-install-data.ps1"
ReadEnvStr $2 APPDATA
ReadEnvStr $3 USERPROFILE
ReadEnvStr $6 CLAUDE_CONFIG_DIR
@@ -134,10 +147,6 @@ Function CcHahaRecoverLegacy
Return
${EndIf}
Push "$INSTDIR"
Call CcHahaFinalInstallDir
Pop $9
DetailPrint "Checking registered installations for legacy Claude Code Haha data..."
nsExec::ExecToStack '"$SYSDIR\WindowsPowerShell\v1.0\powershell.exe" -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "$PLUGINSDIR\recover-legacy-install-data.ps1" -PerUserInstallDir "$4" -PerMachineInstallDir "$5" -CandidateInstallDir "$9" -UserDataDir "$2\Claude Code Haha" -RecoveryRoot "$3\Claude Code Haha Data\Recovered" -ProcessName "${PRODUCT_FILENAME}.exe" -ActiveConfigDir "$6" -ActiveConfigManaged "$7" -InstallerIdentitySafety "$8"'
Pop $0
@@ -161,7 +170,11 @@ FunctionEnd
${If} $0 != "0"
DetailPrint "Legacy data recovery stopped the installer (helper exit code: $0; output: $1)"
MessageBox MB_ICONSTOP|MB_OK "Claude Code Haha cannot safely recover data stored inside the old application directory. Close the running app and run this installer normally, not as Administrator. The old version and its data have not been removed.$\r$\n$\r$\n无法安全恢复旧安装目录中的数据。请关闭旧程序,并以普通方式(不要使用“以管理员身份运行”)重新运行安装程序。旧版本和原数据尚未删除。"
${If} $1 == ""
StrCpy $1 "Recovery helper failed without diagnostic output (exit code $0)"
${EndIf}
StrCpy $R2 "$1" 360
MessageBox MB_ICONSTOP|MB_OK "Claude Code Haha stopped setup before removing the old version. Reason: $R2$\r$\n$\r$\nClose the app and retry. If the reason mentions an elevated installer, launch setup normally instead of using Run as administrator.$\r$\n$\r$\nClaude Code Haha 已在删除旧版本前停止安装。原因:$R2$\r$\n$\r$\n请关闭旧程序后重试;如果原因提到安装器权限过高,请直接双击运行,不要使用“以管理员身份运行”。旧版本和原数据尚未删除。"
SetErrorLevel 20
Quit
${EndIf}
+21 -15
View File
@@ -469,7 +469,7 @@ function Assert-NoRunningApplication {
[Parameter(Mandatory = $true)][string]$ProcessName
)
$deadline = [DateTime]::UtcNow.AddSeconds(5)
$deadline = [DateTime]::UtcNow.AddSeconds(30)
do {
try {
$processes = @(Get-CimInstance Win32_Process -ErrorAction Stop)
@@ -642,7 +642,6 @@ function Invoke-LegacyRecovery {
[AllowEmptyString()][string]$ActiveConfigDir,
[AllowEmptyString()][string]$ActiveConfigManaged = '',
[string]$InstallerIdentitySafety = 'trusted-user',
[string[]]$RegisteredInstallDirs = @(),
[switch]$SkipProcessCheck
)
@@ -651,13 +650,6 @@ function Invoke-LegacyRecovery {
if ($existingInstallDirs.Count -eq 0) {
return $null
}
if ($InstallerIdentitySafety -eq 'untrusted-elevated' -and
@(Get-ExistingInstallDirs -InstallDirs $RegisteredInstallDirs).Count -gt 0) {
throw 'An existing registered installation cannot be upgraded from an elevated installer without the original user process. Run the installer normally (not as Administrator).'
}
if (-not $SkipProcessCheck) {
Assert-NoRunningApplication -InstallDirs $existingInstallDirs -ProcessName $ProcessName
}
$source = Get-UnsafeLegacySource `
-InstallDirs $existingInstallDirs `
@@ -671,6 +663,9 @@ function Invoke-LegacyRecovery {
if ($InstallerIdentitySafety -eq 'untrusted-elevated') {
throw 'Legacy data recovery was requested from an elevated installer without the original user process. Run the installer normally (not as Administrator) so recovery is written to the correct Windows user profile.'
}
if (-not $SkipProcessCheck) {
Assert-NoRunningApplication -InstallDirs $existingInstallDirs -ProcessName $ProcessName
}
foreach ($installDir in $potentialInstallDirs) {
if (Test-PathMayBeDeleted -InstallDir $installDir -Candidate $UserDataDir) {
@@ -789,18 +784,29 @@ function Run-SelfTest {
}
Assert-SelfTest -Condition $elevatedFailed -Message 'untrusted elevated recovery did not fail closed'
$elevatedDefaultInstall = Join-Path $testRoot 'elevated default install'
New-Item -ItemType Directory -Path $elevatedDefaultInstall -Force | Out-Null
$elevatedDefaultResult = Invoke-LegacyRecovery `
-InstallDirs @($elevatedDefaultInstall) `
-UserDataDir (Join-Path $testRoot 'elevated default app data') `
-RecoveryRoot (Join-Path $testRoot 'elevated default recovery') -ProcessName $ProcessName `
-ActiveConfigDir '' -InstallerIdentitySafety 'untrusted-elevated' -SkipProcessCheck
Assert-SelfTest -Condition ($null -eq $elevatedDefaultResult) -Message 'elevated default-mode reinstall was blocked without legacy data'
$wrongIdentityInstall = Join-Path $testRoot 'registered shared install'
New-Item -ItemType Directory -Path (Join-Path $wrongIdentityInstall 'custom data') -Force | Out-Null
Set-Content -LiteralPath (Join-Path $wrongIdentityInstall 'custom data\settings.json') -Value 'other-user-data' -NoNewline
$wrongIdentityData = Join-Path $wrongIdentityInstall 'custom data'
New-Item -ItemType Directory -Path $wrongIdentityData -Force | Out-Null
Set-Content -LiteralPath (Join-Path $wrongIdentityData 'settings.json') -Value 'other-user-data' -NoNewline
Write-TestMode -Dir $wrongIdentityData -Value @{ mode = 'portable'; portable_dir = $wrongIdentityData }
$wrongIdentityFailed = $false
try {
Invoke-LegacyRecovery `
-InstallDirs @($wrongIdentityInstall) -RegisteredInstallDirs @($wrongIdentityInstall) `
-InstallDirs @($wrongIdentityInstall) `
-UserDataDir (Join-Path $testRoot 'wrong identity app data') `
-RecoveryRoot (Join-Path $testRoot 'wrong identity recovery') -ProcessName $ProcessName `
-ActiveConfigDir '' -InstallerIdentitySafety 'untrusted-elevated' -SkipProcessCheck | Out-Null
} catch {
$wrongIdentityFailed = $_.Exception.Message.Contains('existing registered installation')
$wrongIdentityFailed = $_.Exception.Message.Contains('another Windows user')
}
Assert-SelfTest -Condition $wrongIdentityFailed -Message 'untrusted elevated installer treated invisible user metadata as safe'
@@ -960,13 +966,13 @@ try {
-ActiveConfigDir $ActiveConfigDir `
-ActiveConfigManaged $ActiveConfigManaged `
-InstallerIdentitySafety $InstallerIdentitySafety `
-RegisteredInstallDirs @($PerUserInstallDir, $PerMachineInstallDir) `
-SkipProcessCheck:$SkipProcessCheck
if (-not [string]::IsNullOrWhiteSpace([string]$result)) {
[Console]::Out.WriteLine("Recovered legacy data to $result")
}
exit 0
} catch {
[Console]::Error.WriteLine($_.Exception.Message)
$message = ([string]$_.Exception.Message) -replace '[\r\n]+', ' '
[Console]::Out.WriteLine("Legacy recovery error: $message")
exit 20
}