mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-14 19:27:05 -04:00
Merge pull request #466 from tylerjenningsw/enhance/windows-installer-folder
# Conflicts: # contributors/people.toml
This commit is contained in:
8 files changed
+381
-6
No files matched your search
@@ -2,9 +2,9 @@
|
||||
<!--
|
||||
EffectCraft MSI (WiX Toolset v5: `dotnet tool install -g wix`). Built by package.ps1:
|
||||
|
||||
wix build effectcraft.wxs -arch x64 -d Version=1.2.3 -d BinDir=... -d IconPath=... -o out.msi
|
||||
wix build effectcraft.wxs installer-ui.wxs -arch x64 -d Version=1.2.3 -d BinDir=... -d IconPath=... -o out.msi
|
||||
|
||||
Per-machine install into Program Files, a Start Menu shortcut, `.pcraft` owned by EffectCraft,
|
||||
Per-machine install into Program Files (or a folder the user picks), a Start Menu shortcut, `.pcraft` owned by EffectCraft,
|
||||
and EffectCraft offered under "Open with" for PSD/PSB and the image formats it reads (without
|
||||
taking over the user's default app for them).
|
||||
-->
|
||||
@@ -23,6 +23,8 @@
|
||||
<MajorUpgrade AllowSameVersionUpgrades="yes"
|
||||
DowngradeErrorMessage="A newer version of EffectCraft is already installed." />
|
||||
<MediaTemplate EmbedCab="yes" CompressionLevel="high" />
|
||||
<!-- The setup wizard (installer-ui.wxs): pick the folder on a fresh install. -->
|
||||
<UIRef Id="EffectcraftUI" />
|
||||
|
||||
<Icon Id="EffectcraftIcon.ico" SourceFile="$(var.IconPath)" />
|
||||
<Property Id="ARPPRODUCTICON" Value="EffectcraftIcon.ico" />
|
||||
@@ -30,6 +32,25 @@
|
||||
<Property Id="ARPURLUPDATEINFO" Value="https://getartcraft.com/apps/effectcraft" />
|
||||
<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, 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" />
|
||||
</StandardDirectory>
|
||||
@@ -50,6 +71,7 @@
|
||||
</File>
|
||||
|
||||
<RegistryValue Root="HKLM" Key="Software\Microsoft\Windows\CurrentVersion\App Paths\effectcraft.exe" Value="[#EffectcraftExe]" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="Software\Microsoft\Windows\CurrentVersion\App Paths\effectcraft.exe" Name="Path" Value="[INSTALLFOLDER]" Type="string" />
|
||||
</Component>
|
||||
|
||||
<Component Id="EffectcraftCli">
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
EffectCraft installer wizard: original dialogs built from Windows Installer's own controls and
|
||||
events (no WixUI library), included by effectcraft.wxs with <UIRef Id="EffectcraftUI" />.
|
||||
|
||||
fresh install: DestDlg (folder, Change... opens BrowseDlg) -> ReadyDlg -> ProgressDlg -> ExitDlg
|
||||
upgrade: ReadyDlg (the installed copy's folder, not editable) -> ProgressDlg -> ExitDlg
|
||||
already there: MaintDlg (Repair or Remove) -> ProgressDlg -> ExitDlg
|
||||
|
||||
Silent installs (/qn) skip all of this; pass INSTALLFOLDER="..." to choose the folder there.
|
||||
Folder controls edit INSTALLFOLDER indirectly through _BrowseProperty; SetTargetPath checks the
|
||||
path and applies it, and Windows Installer reports a bad path itself.
|
||||
-->
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
||||
<Fragment>
|
||||
<UI Id="EffectcraftUI">
|
||||
<TextStyle Id="EcText" FaceName="Segoe UI" Size="9" />
|
||||
<TextStyle Id="EcTitle" FaceName="Segoe UI" Size="12" Bold="yes" />
|
||||
<Property Id="DefaultUIFont" Value="EcText" />
|
||||
<Property Id="ErrorDialog" Value="ErrorDlg" />
|
||||
<Property Id="_BrowseProperty" Value="INSTALLFOLDER" />
|
||||
|
||||
<!-- Choose the destination folder (fresh installs only). -->
|
||||
<Dialog Id="DestDlg" Width="370" Height="270" Title="[ProductName] Setup">
|
||||
<Control Id="Title" Type="Text" X="20" Y="15" Width="330" Height="20" Transparent="yes" NoPrefix="yes" Text="{\EcTitle}Choose where to install [ProductName]" />
|
||||
<Control Id="Description" Type="Text" X="20" Y="45" Width="330" Height="30" NoPrefix="yes" Text="[ProductName] will be installed in this folder. Type a different folder, or click Change to browse for one." />
|
||||
<Control Id="FolderLabel" Type="Text" X="20" Y="85" Width="330" Height="12" Text="&Destination folder:" />
|
||||
<Control Id="Folder" Type="PathEdit" X="20" Y="100" Width="260" Height="18" Property="_BrowseProperty" Indirect="yes" />
|
||||
<Control Id="ChangeFolder" Type="PushButton" X="290" Y="99" Width="60" Height="20" Text="&Change...">
|
||||
<Publish Event="SpawnDialog" Value="BrowseDlg" />
|
||||
</Control>
|
||||
<Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
|
||||
<Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Text="&Back" Disabled="yes" />
|
||||
<Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="&Next">
|
||||
<Publish Event="SetTargetPath" Value="[_BrowseProperty]" Order="1" />
|
||||
<Publish Event="NewDialog" Value="ReadyDlg" Order="2" />
|
||||
</Control>
|
||||
<Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="Cancel">
|
||||
<Publish Event="SpawnDialog" Value="CancelDlg" />
|
||||
</Control>
|
||||
</Dialog>
|
||||
|
||||
<!-- Folder browser: drive/folder list, up one level, new folder. -->
|
||||
<Dialog Id="BrowseDlg" Width="370" Height="270" Title="[ProductName] Setup">
|
||||
<Control Id="Title" Type="Text" X="20" Y="15" Width="330" Height="20" Transparent="yes" NoPrefix="yes" Text="{\EcTitle}Browse for a folder" />
|
||||
<Control Id="LookInLabel" Type="Text" X="20" Y="47" Width="44" Height="12" Text="&Look in:" />
|
||||
<Control Id="LookIn" Type="DirectoryCombo" X="66" Y="45" Width="220" Height="80" Property="_BrowseProperty" Indirect="yes" Fixed="yes" Remote="yes">
|
||||
<Subscribe Event="IgnoreChange" Attribute="IgnoreChange" />
|
||||
</Control>
|
||||
<Control Id="Up" Type="PushButton" X="292" Y="44" Width="28" Height="18" Text="&Up" ToolTip="Up one level">
|
||||
<Publish Event="DirectoryListUp" Value="0" />
|
||||
</Control>
|
||||
<Control Id="NewFolder" Type="PushButton" X="322" Y="44" Width="28" Height="18" Text="Ne&w" ToolTip="Create a new folder">
|
||||
<Publish Event="DirectoryListNew" Value="0" />
|
||||
</Control>
|
||||
<Control Id="Folders" Type="DirectoryList" X="20" Y="68" Width="330" Height="120" Property="_BrowseProperty" Indirect="yes" Sunken="yes" TabSkip="no" />
|
||||
<Control Id="PathLabel" Type="Text" X="20" Y="196" Width="330" Height="12" Text="&Folder name:" />
|
||||
<Control Id="Path" Type="PathEdit" X="20" Y="210" Width="330" Height="18" Property="_BrowseProperty" Indirect="yes" />
|
||||
<Control Id="OK" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="OK">
|
||||
<Publish Event="SetTargetPath" Value="[_BrowseProperty]" Order="1" />
|
||||
<Publish Event="EndDialog" Value="Return" Order="2" />
|
||||
</Control>
|
||||
<Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="Cancel">
|
||||
<Publish Event="Reset" Value="0" Order="1" />
|
||||
<Publish Event="EndDialog" Value="Return" Order="2" />
|
||||
</Control>
|
||||
</Dialog>
|
||||
|
||||
<!-- Last stop before anything changes: shows the folder that will be used. -->
|
||||
<Dialog Id="ReadyDlg" Width="370" Height="270" Title="[ProductName] Setup">
|
||||
<Control Id="Title" Type="Text" X="20" Y="15" Width="330" Height="20" Transparent="yes" NoPrefix="yes" Text="{\EcTitle}Ready to install [ProductName]" />
|
||||
<Control Id="InstallText" Type="Text" X="20" Y="45" Width="330" Height="24" NoPrefix="yes" Text="[ProductName] [ProductVersion] will be installed in:" HideCondition="WIX_UPGRADE_DETECTED" />
|
||||
<Control Id="UpgradeText" Type="Text" X="20" Y="45" Width="330" Height="24" NoPrefix="yes" Text="[ProductName] is already installed. Version [ProductVersion] will replace it in the same folder:" Hidden="yes" ShowCondition="WIX_UPGRADE_DETECTED" />
|
||||
<Control Id="Destination" Type="Text" X="20" Y="72" Width="330" Height="36" NoPrefix="yes" Text="[INSTALLFOLDER]" />
|
||||
<Control Id="Prompt" Type="Text" X="20" Y="116" Width="330" Height="24" NoPrefix="yes" Text="Click Install to continue." />
|
||||
<Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
|
||||
<Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Text="&Back" DisableCondition="WIX_UPGRADE_DETECTED">
|
||||
<Publish Event="NewDialog" Value="DestDlg" />
|
||||
</Control>
|
||||
<Control Id="Install" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" ElevationShield="yes" Text="&Install">
|
||||
<Publish Event="EndDialog" Value="Return" />
|
||||
</Control>
|
||||
<Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="Cancel">
|
||||
<Publish Event="SpawnDialog" Value="CancelDlg" />
|
||||
</Control>
|
||||
</Dialog>
|
||||
|
||||
<!-- Opening the MSI when this version is already installed. -->
|
||||
<Dialog Id="MaintDlg" Width="370" Height="270" Title="[ProductName] Setup">
|
||||
<Control Id="Title" Type="Text" X="20" Y="15" Width="330" Height="20" Transparent="yes" NoPrefix="yes" Text="{\EcTitle}[ProductName] is already installed" />
|
||||
<Control Id="Description" Type="Text" X="20" Y="45" Width="330" Height="24" NoPrefix="yes" Text="Repair puts back missing or damaged files in its folder. Remove uninstalls it; your projects are not touched." />
|
||||
<Control Id="Repair" Type="PushButton" X="20" Y="80" Width="80" Height="20" Default="yes" ElevationShield="yes" Text="&Repair">
|
||||
<!-- "a": put back every file, whatever its version or date. With "e"/"o" a changed
|
||||
unversioned file (effectcraft-cli.exe has no version resource) counts as a user edit
|
||||
and is kept, and "c" only checks files that carry an MSI checksum. -->
|
||||
<Publish Event="ReinstallMode" Value="amus" Order="1" />
|
||||
<Publish Event="Reinstall" Value="ALL" Order="2" />
|
||||
<Publish Event="EndDialog" Value="Return" Order="3" />
|
||||
</Control>
|
||||
<Control Id="Remove" Type="PushButton" X="20" Y="108" Width="80" Height="20" ElevationShield="yes" Text="Re&move">
|
||||
<Publish Event="Remove" Value="ALL" Order="1" />
|
||||
<Publish Event="EndDialog" Value="Return" Order="2" />
|
||||
</Control>
|
||||
<Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
|
||||
<Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="Cancel">
|
||||
<Publish Event="SpawnDialog" Value="CancelDlg" />
|
||||
</Control>
|
||||
</Dialog>
|
||||
|
||||
<Dialog Id="ProgressDlg" Width="370" Height="270" Title="[ProductName] Setup" Modeless="yes">
|
||||
<Control Id="Title" Type="Text" X="20" Y="15" Width="330" Height="20" Transparent="yes" NoPrefix="yes" Text="{\EcTitle}Working on [ProductName]" />
|
||||
<Control Id="Description" Type="Text" X="20" Y="45" Width="330" Height="24" NoPrefix="yes" Text="Please wait. This can take a minute." />
|
||||
<Control Id="Action" Type="Text" X="20" Y="80" Width="330" Height="12" NoPrefix="yes">
|
||||
<Subscribe Event="ActionText" Attribute="Text" />
|
||||
</Control>
|
||||
<Control Id="Progress" Type="ProgressBar" X="20" Y="96" Width="330" Height="12" ProgressBlocks="yes" Text="Progress">
|
||||
<Subscribe Event="SetProgress" Attribute="Progress" />
|
||||
</Control>
|
||||
<Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
|
||||
<Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Default="yes" Cancel="yes" Text="Cancel">
|
||||
<Publish Event="SpawnDialog" Value="CancelDlg" />
|
||||
</Control>
|
||||
</Dialog>
|
||||
|
||||
<Dialog Id="CancelDlg" Width="260" Height="85" Title="[ProductName] Setup" NoMinimize="yes">
|
||||
<Control Id="Text" Type="Text" X="10" Y="15" Width="240" Height="30" NoPrefix="yes" Text="Cancel [ProductName] setup? Nothing will be changed." />
|
||||
<Control Id="Yes" Type="PushButton" X="132" Y="57" Width="56" Height="17" Text="&Yes">
|
||||
<Publish Event="EndDialog" Value="Exit" />
|
||||
</Control>
|
||||
<Control Id="No" Type="PushButton" X="194" Y="57" Width="56" Height="17" Default="yes" Cancel="yes" Text="&No">
|
||||
<Publish Event="EndDialog" Value="Return" />
|
||||
</Control>
|
||||
</Dialog>
|
||||
|
||||
<!-- Windows Installer's messages (a bad folder, low disk space, ...) and files in use. -->
|
||||
<Dialog Id="ErrorDlg" Width="270" Height="105" Title="[ProductName] Setup" ErrorDialog="yes">
|
||||
<Control Id="ErrorText" Type="Text" X="10" Y="10" Width="250" Height="60" NoPrefix="yes" TabSkip="no" Text="Error" />
|
||||
<Control Id="Y" Type="PushButton" X="100" Y="80" Width="56" Height="17" TabSkip="yes" Text="&Yes"><Publish Event="EndDialog" Value="ErrorYes" /></Control>
|
||||
<Control Id="A" Type="PushButton" X="100" Y="80" Width="56" Height="17" TabSkip="yes" Text="Abort"><Publish Event="EndDialog" Value="ErrorAbort" /></Control>
|
||||
<Control Id="C" Type="PushButton" X="100" Y="80" Width="56" Height="17" TabSkip="yes" Text="Cancel"><Publish Event="EndDialog" Value="ErrorCancel" /></Control>
|
||||
<Control Id="I" Type="PushButton" X="100" Y="80" Width="56" Height="17" TabSkip="yes" Text="&Ignore"><Publish Event="EndDialog" Value="ErrorIgnore" /></Control>
|
||||
<Control Id="N" Type="PushButton" X="100" Y="80" Width="56" Height="17" TabSkip="yes" Text="&No"><Publish Event="EndDialog" Value="ErrorNo" /></Control>
|
||||
<Control Id="O" Type="PushButton" X="100" Y="80" Width="56" Height="17" TabSkip="yes" Text="OK"><Publish Event="EndDialog" Value="ErrorOk" /></Control>
|
||||
<Control Id="R" Type="PushButton" X="100" Y="80" Width="56" Height="17" TabSkip="yes" Text="&Retry"><Publish Event="EndDialog" Value="ErrorRetry" /></Control>
|
||||
</Dialog>
|
||||
|
||||
<Dialog Id="FilesInUse" Width="370" Height="270" Title="[ProductName] Setup" KeepModeless="yes">
|
||||
<Control Id="Title" Type="Text" X="20" Y="15" Width="330" Height="20" Transparent="yes" NoPrefix="yes" Text="{\EcTitle}Close these programs" />
|
||||
<Control Id="Description" Type="Text" X="20" Y="45" Width="330" Height="24" NoPrefix="yes" Text="These programs are using files setup needs to update. Close them, then click Retry." />
|
||||
<Control Id="List" Type="ListBox" X="20" Y="75" Width="330" Height="140" Property="FileInUseProcess" Sunken="yes" TabSkip="yes" />
|
||||
<Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
|
||||
<Control Id="Retry" Type="PushButton" X="168" Y="243" Width="56" Height="17" Default="yes" Text="&Retry"><Publish Event="EndDialog" Value="Retry" /></Control>
|
||||
<Control Id="Ignore" Type="PushButton" X="236" Y="243" Width="56" Height="17" Text="&Ignore"><Publish Event="EndDialog" Value="Ignore" /></Control>
|
||||
<Control Id="Exit" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="E&xit"><Publish Event="EndDialog" Value="Exit" /></Control>
|
||||
</Dialog>
|
||||
|
||||
<!-- The three endings: finished, cancelled, failed. -->
|
||||
<Dialog Id="ExitDlg" Width="370" Height="270" Title="[ProductName] Setup">
|
||||
<Control Id="Title" Type="Text" X="20" Y="15" Width="330" Height="20" Transparent="yes" NoPrefix="yes" Text="{\EcTitle}[ProductName] setup is complete" />
|
||||
<Control Id="Description" Type="Text" X="20" Y="45" Width="330" Height="36" NoPrefix="yes" Text="Click Finish to close setup." />
|
||||
<Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
|
||||
<Control Id="Finish" Type="PushButton" X="304" Y="243" Width="56" Height="17" Default="yes" Cancel="yes" Text="&Finish">
|
||||
<Publish Event="EndDialog" Value="Return" />
|
||||
</Control>
|
||||
</Dialog>
|
||||
<Dialog Id="UserExitDlg" Width="370" Height="270" Title="[ProductName] Setup">
|
||||
<Control Id="Title" Type="Text" X="20" Y="15" Width="330" Height="20" Transparent="yes" NoPrefix="yes" Text="{\EcTitle}Setup was cancelled" />
|
||||
<Control Id="Description" Type="Text" X="20" Y="45" Width="330" Height="36" NoPrefix="yes" Text="[ProductName] was not changed. Run setup again to install it." />
|
||||
<Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
|
||||
<Control Id="Finish" Type="PushButton" X="304" Y="243" Width="56" Height="17" Default="yes" Cancel="yes" Text="&Finish">
|
||||
<Publish Event="EndDialog" Value="Exit" />
|
||||
</Control>
|
||||
</Dialog>
|
||||
<Dialog Id="FatalErrorDlg" Width="370" Height="270" Title="[ProductName] Setup">
|
||||
<Control Id="Title" Type="Text" X="20" Y="15" Width="330" Height="20" Transparent="yes" NoPrefix="yes" Text="{\EcTitle}Setup did not finish" />
|
||||
<Control Id="Description" Type="Text" X="20" Y="45" Width="330" Height="48" NoPrefix="yes" Text="Something went wrong, and setup put back what was there before. Run setup again, or report it at https://github.com/storytold/effectcraft/issues." />
|
||||
<Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
|
||||
<Control Id="Finish" Type="PushButton" X="304" Y="243" Width="56" Height="17" Default="yes" Cancel="yes" Text="&Finish">
|
||||
<Publish Event="EndDialog" Value="Exit" />
|
||||
</Control>
|
||||
</Dialog>
|
||||
|
||||
<InstallUISequence>
|
||||
<Show Dialog="DestDlg" Before="ProgressDlg" Condition="NOT Installed AND NOT WIX_UPGRADE_DETECTED" />
|
||||
<Show Dialog="ReadyDlg" After="DestDlg" Condition="NOT Installed AND WIX_UPGRADE_DETECTED" />
|
||||
<Show Dialog="MaintDlg" After="ReadyDlg" Condition="Installed AND NOT REMOVE AND NOT REINSTALL" />
|
||||
<Show Dialog="ProgressDlg" Before="ExecuteAction" />
|
||||
<Show Dialog="ExitDlg" OnExit="success" />
|
||||
<Show Dialog="UserExitDlg" OnExit="cancel" />
|
||||
<Show Dialog="FatalErrorDlg" OnExit="error" />
|
||||
</InstallUISequence>
|
||||
<!-- msiexec /a (an administrative image) gets the same progress and endings. -->
|
||||
<AdminUISequence>
|
||||
<Show Dialog="ProgressDlg" Before="ExecuteAction" />
|
||||
<Show Dialog="ExitDlg" OnExit="success" />
|
||||
<Show Dialog="UserExitDlg" OnExit="cancel" />
|
||||
<Show Dialog="FatalErrorDlg" OnExit="error" />
|
||||
</AdminUISequence>
|
||||
</UI>
|
||||
</Fragment>
|
||||
</Wix>
|
||||
@@ -93,7 +93,7 @@ Copy-Item (Join-Path $Bin 'effectcraft.exe'), (Join-Path $Bin 'effectcraft-cli.e
|
||||
# ---- MSI ---------------------------------------------------------------------------------------
|
||||
$Msi = Join-Path $Dist "effectcraft-$Version-windows-$Arch.msi"
|
||||
Invoke-Native 'wix build' {
|
||||
wix build (Join-Path $PSScriptRoot 'effectcraft.wxs') -arch $Arch `
|
||||
wix build (Join-Path $PSScriptRoot 'effectcraft.wxs') (Join-Path $PSScriptRoot 'installer-ui.wxs') -arch $Arch `
|
||||
-d "Version=$MsiVersion" -d "BinDir=$Stage" -d "IconPath=$(Join-Path $Root 'assets\app-icon\effectcraft.ico')" `
|
||||
-o $Msi
|
||||
}
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Install-test the EffectCraft MSI's folder handling with stub binaries (no Rust build).
|
||||
|
||||
.DESCRIPTION
|
||||
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 (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 damaged (unversioned) CLI there with the wizard's Repair mode,
|
||||
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.
|
||||
|
||||
.EXAMPLE
|
||||
pwsh packaging/windows/test-msi.ps1
|
||||
#>
|
||||
param([string] $Work = (Join-Path ([IO.Path]::GetTempPath()) 'effectcraft-msi-test'))
|
||||
$ErrorActionPreference = 'Stop'
|
||||
trap { Write-Output "FAILED: $_"; exit 1 }
|
||||
$Root = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
|
||||
$AppPaths = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\effectcraft.exe'
|
||||
|
||||
function Invoke-Native([string] $What, [scriptblock] $Block) {
|
||||
Write-Output "==> $What"
|
||||
& $Block
|
||||
if ($LASTEXITCODE -ne 0) { throw "$What failed with exit code $LASTEXITCODE" }
|
||||
}
|
||||
function Invoke-Msiexec([string] $What, [string[]] $Arguments) {
|
||||
$log = Join-Path $Work "$($What -replace '\W', '-').log"
|
||||
$p = Start-Process msiexec.exe -ArgumentList ($Arguments + '/qn', '/l*v', "`"$log`"") -Wait -PassThru
|
||||
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, [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 ($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"
|
||||
}
|
||||
|
||||
Remove-Item -Recurse -Force $Work -ErrorAction SilentlyContinue
|
||||
$Bin = Join-Path $Work 'bin'
|
||||
New-Item -ItemType Directory -Force -Path $Bin | Out-Null
|
||||
# The MSI only copies these. The app is versioned (it embeds VERSIONINFO); the real CLI has no
|
||||
# version resource, so its stand-in is an unversioned file too: Windows Installer keeps a changed
|
||||
# unversioned file on repair unless the repair mode forces it.
|
||||
Copy-Item (Join-Path $env:SystemRoot 'System32\notepad.exe') (Join-Path $Bin 'effectcraft.exe')
|
||||
[IO.File]::WriteAllText((Join-Path $Bin 'effectcraft-cli.exe'), 'effectcraft-cli stand-in')
|
||||
|
||||
# 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', '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 $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] }
|
||||
}
|
||||
|
||||
# Spaces and a non-ASCII letter (written as a char so the script's own encoding doesn't matter).
|
||||
$Custom = Join-Path $env:SystemDrive "EffectCraft Test $([char]0x00DC)nicode\My Apps"
|
||||
$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 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 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.3') { throw "expected one EffectCraft 0.0.3 in Apps, found: $($arp -join ', ')" }
|
||||
|
||||
# Repair the way the wizard's Repair button does: damage the CLI in place (a later write time, as a
|
||||
# user edit or corruption would have), repair with the wizard's REINSTALLMODE, compare contents.
|
||||
$mode = [regex]::Match((Get-Content -Raw (Join-Path $PSScriptRoot 'installer-ui.wxs')), 'Event="ReinstallMode" Value="([^"]+)"').Groups[1].Value
|
||||
if (-not $mode) { throw 'no ReinstallMode in installer-ui.wxs' }
|
||||
$cli = Join-Path $Custom 'effectcraft-cli.exe'
|
||||
Start-Sleep -Seconds 2
|
||||
[IO.File]::WriteAllText($cli, 'damaged')
|
||||
Invoke-Msiexec "repair ($mode)" @('/i', "`"$($Msi['0.0.3'])`"", 'REINSTALL=ALL', "REINSTALLMODE=$mode")
|
||||
Assert-InstalledIn $Custom
|
||||
if ((Get-FileHash -LiteralPath $cli).Hash -ne (Get-FileHash -LiteralPath (Join-Path $Bin 'effectcraft-cli.exe')).Hash) {
|
||||
throw "repair ($mode) left the damaged effectcraft-cli.exe in place"
|
||||
}
|
||||
Write-Output "ok repair ($mode) restored effectcraft-cli.exe"
|
||||
|
||||
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.3'])`"")
|
||||
Assert-InstalledIn $Default
|
||||
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'
|
||||
Reference in new issue
Block a user