Packaging: upgrading a custom-folder install from 0.6.0 or earlier keeps its folder (#466)
Cause: the folder search read only the App Paths "Path" value, which this branch introduced. 0.6.0 and earlier could already be installed elsewhere with INSTALLFOLDER=..., but have no Path value, so an upgrade went to Program Files while the wizard said it would stay in the same folder. Fix: when there is no Path value, a second search reads the App Paths default value (the full path of effectcraft.exe, written by every release). A "file" registry search with no FileSearch inside yields the folder of that file. Tests: test-msi.ps1 installs a legacy package (the same .wxs without the Path value) into a custom folder, then upgrades it without naming a folder; it fails on the previous .wxs (the upgrade lands in Program Files) and passes now. A further upgrade checks the Path value route.
This commit is contained in:
parent
184df34875
commit
462913a483
|
|
@ -33,14 +33,23 @@
|
|||
<Property Id="ARPHELPLINK" Value="https://discord.gg/artcraft" />
|
||||
|
||||
<!-- An upgrade, repair or silent reinstall goes where the installed copy already is: the App Paths
|
||||
"Path" value written below. A folder passed on the command line (INSTALLFOLDER=...) wins. -->
|
||||
"Path" value written below, or for releases before it (0.6.0 and earlier, which could be put
|
||||
elsewhere with INSTALLFOLDER=...) the folder of the effectcraft.exe that App Paths names. A
|
||||
"file" search with no FileSearch inside yields that file's folder. A folder passed on the
|
||||
command line (INSTALLFOLDER=...) wins. -->
|
||||
<Property Id="INSTALLFOLDER" Secure="yes" />
|
||||
<Property Id="PREVIOUSINSTALLFOLDER" Secure="yes">
|
||||
<RegistrySearch Id="PreviousInstallFolder" Root="HKLM" Type="directory"
|
||||
Key="Software\Microsoft\Windows\CurrentVersion\App Paths\effectcraft.exe" Name="Path" />
|
||||
</Property>
|
||||
<Property Id="PREVIOUSEXEFOLDER" Secure="yes">
|
||||
<RegistrySearch Id="PreviousExeFolder" Root="HKLM" Type="file"
|
||||
Key="Software\Microsoft\Windows\CurrentVersion\App Paths\effectcraft.exe" />
|
||||
</Property>
|
||||
<SetProperty Id="INSTALLFOLDER" Value="[PREVIOUSINSTALLFOLDER]" After="AppSearch" Sequence="both"
|
||||
Condition="PREVIOUSINSTALLFOLDER AND NOT INSTALLFOLDER" />
|
||||
<SetProperty Id="INSTALLFOLDER" Action="SetINSTALLFOLDERFromExe" Value="[PREVIOUSEXEFOLDER]"
|
||||
After="SetINSTALLFOLDER" Sequence="both" Condition="PREVIOUSEXEFOLDER AND NOT INSTALLFOLDER" />
|
||||
|
||||
<StandardDirectory Id="ProgramFiles6432Folder">
|
||||
<Directory Id="INSTALLFOLDER" Name="EffectCraft" />
|
||||
|
|
|
|||
|
|
@ -3,13 +3,15 @@
|
|||
Install-test the EffectCraft MSI's folder handling with stub binaries (no Rust build).
|
||||
|
||||
.DESCRIPTION
|
||||
Builds the MSI twice (versions 0.0.1 and 0.0.2) around two stand-in executables, validates both
|
||||
(ICE), then, silently and per-machine:
|
||||
Builds the MSI around two stand-in executables as 0.0.1 the way 0.6.0 and earlier were (no App
|
||||
Paths "Path" value), then as 0.0.2 and 0.0.3, validates them (ICE), and, silently and per-machine:
|
||||
1. installs 0.0.1 into a folder with spaces and a non-ASCII name (INSTALLFOLDER=...),
|
||||
2. upgrades to 0.0.2 without naming a folder: it must stay in that folder,
|
||||
3. repairs a deleted file there,
|
||||
4. uninstalls: files and the App Paths registration go,
|
||||
5. installs 0.0.2 with no folder: it must land in Program Files, then uninstalls.
|
||||
2. upgrades to 0.0.2 without naming a folder: it must stay in that folder (found from the
|
||||
installed effectcraft.exe, as there is no Path value yet),
|
||||
3. upgrades to 0.0.3 the same way (found from the Path value 0.0.2 wrote),
|
||||
4. repairs a deleted file there,
|
||||
5. uninstalls: files and the App Paths registration go,
|
||||
6. installs 0.0.3 with no folder: it must land in Program Files, then uninstalls.
|
||||
The wizard itself needs a person (or the snapshot steps in the PR); this covers what msiexec /qn
|
||||
and upgrades do with the folder. Needs an elevated shell and WiX v5 (`wix`) on PATH.
|
||||
Run by .github/workflows/packaging-lint.yml.
|
||||
|
|
@ -34,14 +36,18 @@ function Invoke-Msiexec([string] $What, [string[]] $Arguments) {
|
|||
if ($p.ExitCode -ne 0) { Get-Content $log -Tail 60; throw "msiexec ($What) exited $($p.ExitCode)" }
|
||||
Write-Output "ok $What"
|
||||
}
|
||||
function Assert-InstalledIn([string] $Dir) {
|
||||
function Assert-InstalledIn([string] $Dir, [switch] $NoPathValue) {
|
||||
foreach ($exe in 'effectcraft.exe', 'effectcraft-cli.exe') {
|
||||
if (-not (Test-Path -LiteralPath (Join-Path $Dir $exe))) { throw "$exe is not in $Dir" }
|
||||
}
|
||||
$key = Get-ItemProperty -LiteralPath $AppPaths
|
||||
$want = Join-Path $Dir 'effectcraft.exe'
|
||||
if ($key.'(default)' -ne $want) { throw "App Paths points at '$($key.'(default)')', expected '$want'" }
|
||||
if ($key.Path.TrimEnd('\') -ne $Dir.TrimEnd('\')) { throw "App Paths Path is '$($key.Path)', expected '$Dir'" }
|
||||
if ($NoPathValue) {
|
||||
if ($null -ne $key.Path) { throw "the legacy package wrote an App Paths Path value" }
|
||||
} elseif ($key.Path.TrimEnd('\') -ne $Dir.TrimEnd('\')) {
|
||||
throw "App Paths Path is '$($key.Path)', expected '$Dir'"
|
||||
}
|
||||
Write-Output "ok installed in $Dir"
|
||||
}
|
||||
|
||||
|
|
@ -52,11 +58,20 @@ New-Item -ItemType Directory -Force -Path $Bin | Out-Null
|
|||
Copy-Item (Join-Path $env:SystemRoot 'System32\notepad.exe') (Join-Path $Bin 'effectcraft.exe')
|
||||
Copy-Item (Join-Path $env:SystemRoot 'System32\whoami.exe') (Join-Path $Bin 'effectcraft-cli.exe')
|
||||
|
||||
# 0.0.1 stands in for 0.6.0 and earlier: the same package without the App Paths "Path" value.
|
||||
$Wxs = Join-Path $PSScriptRoot 'effectcraft.wxs'
|
||||
$LegacyWxs = Join-Path $Work 'effectcraft-legacy.wxs'
|
||||
$pathValue = '(?m)^.*<RegistryValue [^>]*Name="Path"[^>]*/>\r?\n'
|
||||
$text = Get-Content -Raw $Wxs
|
||||
if ($text -notmatch $pathValue) { throw "no App Paths Path value in $Wxs to leave out" }
|
||||
[IO.File]::WriteAllText($LegacyWxs, ($text -replace $pathValue, ''))
|
||||
|
||||
$Msi = @{}
|
||||
foreach ($v in '0.0.1', '0.0.2') {
|
||||
foreach ($v in '0.0.1', '0.0.2', '0.0.3') {
|
||||
$Msi[$v] = Join-Path $Work "effectcraft-$v.msi"
|
||||
$src = if ($v -eq '0.0.1') { $LegacyWxs } else { $Wxs }
|
||||
Invoke-Native "wix build $v" {
|
||||
wix build (Join-Path $PSScriptRoot 'effectcraft.wxs') (Join-Path $PSScriptRoot 'installer-ui.wxs') -arch x64 `
|
||||
wix build $src (Join-Path $PSScriptRoot 'installer-ui.wxs') -arch x64 `
|
||||
-d "Version=$v" -d "BinDir=$Bin" -d "IconPath=$(Join-Path $Root 'assets\app-icon\effectcraft.ico')" -o $Msi[$v]
|
||||
}
|
||||
Invoke-Native "wix msi validate $v" { wix msi validate $Msi[$v] }
|
||||
|
|
@ -67,27 +82,31 @@ $Custom = Join-Path $env:SystemDrive "EffectCraft Test $([char]0x00DC)nicode\My
|
|||
$Default = Join-Path $env:ProgramFiles 'EffectCraft'
|
||||
if (Test-Path -LiteralPath $AppPaths) { throw 'EffectCraft is already installed here; run this on a clean machine' }
|
||||
|
||||
Invoke-Msiexec 'install to a custom folder' @('/i', "`"$($Msi['0.0.1'])`"", "INSTALLFOLDER=`"$Custom`"")
|
||||
Assert-InstalledIn $Custom
|
||||
Invoke-Msiexec 'install the legacy package to a custom folder' @('/i', "`"$($Msi['0.0.1'])`"", "INSTALLFOLDER=`"$Custom`"")
|
||||
Assert-InstalledIn $Custom -NoPathValue
|
||||
if (Test-Path -LiteralPath $Default) { throw "a custom install also wrote $Default" }
|
||||
|
||||
Invoke-Msiexec 'upgrade without a folder' @('/i', "`"$($Msi['0.0.2'])`"")
|
||||
Invoke-Msiexec 'upgrade the legacy install without a folder' @('/i', "`"$($Msi['0.0.2'])`"")
|
||||
Assert-InstalledIn $Custom
|
||||
if (Test-Path -LiteralPath $Default) { throw "the upgrade moved the install to $Default" }
|
||||
|
||||
Invoke-Msiexec 'upgrade again without a folder' @('/i', "`"$($Msi['0.0.3'])`"")
|
||||
Assert-InstalledIn $Custom
|
||||
# Read single values: Get-ItemProperty throws on some machines' malformed Uninstall entries.
|
||||
$arp = @(Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall' |
|
||||
ForEach-Object { if ($_.GetValue('DisplayName') -eq 'EffectCraft') { $_.GetValue('DisplayVersion') } })
|
||||
if ($arp.Count -ne 1 -or $arp[0] -ne '0.0.2') { throw "expected one EffectCraft 0.0.2 in Apps, found: $($arp -join ', ')" }
|
||||
if ($arp.Count -ne 1 -or $arp[0] -ne '0.0.3') { throw "expected one EffectCraft 0.0.3 in Apps, found: $($arp -join ', ')" }
|
||||
|
||||
Remove-Item -LiteralPath (Join-Path $Custom 'effectcraft-cli.exe')
|
||||
Invoke-Msiexec 'repair' @('/fa', "`"$($Msi['0.0.2'])`"")
|
||||
Invoke-Msiexec 'repair' @('/fa', "`"$($Msi['0.0.3'])`"")
|
||||
Assert-InstalledIn $Custom
|
||||
|
||||
Invoke-Msiexec 'uninstall the custom install' @('/x', "`"$($Msi['0.0.2'])`"")
|
||||
Invoke-Msiexec 'uninstall the custom install' @('/x', "`"$($Msi['0.0.3'])`"")
|
||||
if (Test-Path -LiteralPath (Join-Path $Custom 'effectcraft.exe')) { throw 'uninstall left effectcraft.exe behind' }
|
||||
if (Test-Path -LiteralPath $AppPaths) { throw 'uninstall left the App Paths registration behind' }
|
||||
|
||||
Invoke-Msiexec 'install to the default folder' @('/i', "`"$($Msi['0.0.2'])`"")
|
||||
Invoke-Msiexec 'install to the default folder' @('/i', "`"$($Msi['0.0.3'])`"")
|
||||
Assert-InstalledIn $Default
|
||||
Invoke-Msiexec 'uninstall the default install' @('/x', "`"$($Msi['0.0.2'])`"")
|
||||
Invoke-Msiexec 'uninstall the default install' @('/x', "`"$($Msi['0.0.3'])`"")
|
||||
if (Test-Path -LiteralPath (Join-Path $Default 'effectcraft.exe')) { throw 'uninstall left effectcraft.exe behind' }
|
||||
Write-Output 'MSI folder handling ok'
|
||||
|
|
|
|||
Loading…
Reference in New Issue