Files
lightcraft/packaging/windows/lightcraft.wxs
T
hxwandClaude Opus 5.5 485046bf35 Issue #399: Windows MSI: choose the install folder; upgrades keep it
The interactive installer now walks Welcome -> Destination Folder ->
Ready -> progress -> Completed (from #79). Destination Folder is WiX's
InstallDirDlg: Program Files\LightCraft by default, Change... browses,
and WixUIValidatePath refuses a folder that can't be used.

The chosen folder is written to HKLM\Software\LightCraft\InstallFolder
and read back (into PREVIOUSINSTALLFOLDER, then INSTALLFOLDER after
AppSearch unless given on the command line), so a major upgrade stays in
the user's folder instead of moving back to Program Files. Silent and
passive installs show no dialogs and take INSTALLFOLDER=....

packaging-lint fails if lightcraft.wxs loses the folder page, the
remembered folder or the completion page's launch action.

Tested with wix 5.0.2 (x64, arm64, x86 build) and msiexec /qn: upgrade
from 0.4.0 stays in Program Files; a custom INSTALLFOLDER install, an
upgrade without it (stays), an upgrade with another INSTALLFOLDER (moves
there), and uninstall (files, folder and registry value removed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 09:04:50 -05:00

185 lines
11 KiB
XML

<?xml version="1.0" encoding="utf-8"?>
<!--
LightCraft MSI (WiX Toolset v5: `dotnet tool install -g wix`, plus the UI and Util extensions:
`wix extension add -g WixToolset.UI.wixext/5.0.2 WixToolset.Util.wixext/5.0.2`). Built by package.ps1:
wix build lightcraft.wxs -arch x64 -ext WixToolset.UI.wixext/5.0.2 -ext WixToolset.Util.wixext/5.0.2
-d Version=1.2.3 -d BinDir=... -d IconPath=... -d UiDialogBmp=... -d UiBannerBmp=... -o out.msi
Per-machine install into Program Files (or the folder chosen in the installer, or
`INSTALLFOLDER=...` on the msiexec command line; upgrades keep it), a Start Menu shortcut, `.pcraft` owned by LightCraft,
and LightCraft offered under "Open with" for PSD/PSB and the image formats it reads (without
taking over the user's default app for them).
-->
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
<Package
Name="LightCraft"
Manufacturer="Learning Machines LLC"
Version="$(var.Version)"
UpgradeCode="229BB552-5EFD-479A-BA86-8CB6D926390C"
Scope="perMachine"
Compressed="yes"
Language="1033">
<SummaryInformation Description="LightCraft photo library and raw developer" Manufacturer="Learning Machines LLC" />
<!-- Same-version upgrades let successive pre-releases of one X.Y.Z replace each other
(MSI versions are numeric, so 1.2.3-rc.1 and 1.2.3-rc.2 are both 1.2.3). -->
<MajorUpgrade AllowSameVersionUpgrades="yes"
DowngradeErrorMessage="A newer version of LightCraft is already installed." />
<MediaTemplate EmbedCab="yes" CompressionLevel="high" />
<Icon Id="LightcraftIcon.ico" SourceFile="$(var.IconPath)" />
<Property Id="ARPPRODUCTICON" Value="LightcraftIcon.ico" />
<Property Id="ARPURLINFOABOUT" Value="https://getartcraft.com/apps/lightcraft" />
<Property Id="ARPURLUPDATEINFO" Value="https://getartcraft.com/apps/lightcraft" />
<Property Id="ARPHELPLINK" Value="https://discord.gg/artcraft" />
<StandardDirectory Id="ProgramFiles6432Folder">
<Directory Id="INSTALLFOLDER" Name="LightCraft" />
</StandardDirectory>
<!-- The folder of the installed version (written by LightcraftApp), so an upgrade installs
where the user chose instead of moving back to Program Files (issue #399). Searched into
its own property: AppSearch would otherwise overwrite INSTALLFOLDER=... given to msiexec. -->
<Property Id="PREVIOUSINSTALLFOLDER">
<RegistrySearch Id="PreviousInstallFolder" Root="HKLM" Key="Software\LightCraft" Name="InstallFolder" Type="raw" />
</Property>
<SetProperty Id="INSTALLFOLDER" Value="[PREVIOUSINSTALLFOLDER]" After="AppSearch" Sequence="both"
Condition="PREVIOUSINSTALLFOLDER AND NOT INSTALLFOLDER" />
<StandardDirectory Id="ProgramMenuFolder" />
<Feature Id="Main" Title="LightCraft" Level="1">
<ComponentGroupRef Id="LightcraftFiles" />
</Feature>
<UIRef Id="LightcraftUI" />
</Package>
<!--
Interactive install UI: Welcome -> Destination Folder -> Ready to install -> progress ->
"Completed" (Finish), with a "Launch LightCraft" checkbox (checked by default). Destination
Folder (issue #399) shows Program Files\LightCraft, or the installed version's folder on an
upgrade; Change... browses, and a folder that can't be used (InvalidDirDlg) is refused before
anything is installed. Failure and cancellation end on WiX's standard FatalError / UserExit
dialogs instead of the completion page. Maintenance (running the MSI again, Repair/Remove from
Apps) uses the standard maintenance dialogs.
This is WixUI_InstallDir without its licence-acceptance page: LightCraft is MIT OR Apache-2.0,
which ask nothing of people who just install and run it, so a click-through EULA would only
add a step. Silent (/qn) and basic-UI (/passive, /qb) installs never show these dialogs, so
they are unaffected, and nothing is launched.
The dialogs, strings and error/cancel pages come from WixToolset.UI.wixext; the side and
banner bitmaps are plain panels generated by package.ps1 (no third-party artwork).
-->
<Fragment>
<WixVariable Id="WixUIDialogBmp" Value="$(var.UiDialogBmp)" />
<WixVariable Id="WixUIBannerBmp" Value="$(var.UiBannerBmp)" />
<Property Id="WIXUI_EXITDIALOGOPTIONALTEXT"
Value="LightCraft has been installed. You can start it at any time from the Start menu." />
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT" Value="Launch LightCraft" />
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOX" Value="1" />
<!-- "Launch LightCraft" on Finish. WixUnelevatedShellExec (Util extension) asks the desktop
shell (Explorer) to start the program, so it runs as the signed-in user at normal
integrity even when the installer UI itself is elevated (e.g. msiexec started from an
admin prompt); it never runs from the elevated/SYSTEM server side. It is a UI-only control
event, so silent and passive installs never run it. Return="ignore": a failed launch must
not turn a successful install into an error. -->
<Property Id="WixUnelevatedShellExecTarget" Value="[#LightcraftExe]" />
<CustomAction Id="LaunchLightcraft" BinaryRef="Wix4UtilCA_$(sys.BUILDARCHSHORT)"
DllEntry="WixUnelevatedShellExec" Execute="immediate" Impersonate="yes" Return="ignore" />
<UI Id="LightcraftUI">
<TextStyle Id="WixUI_Font_Normal" FaceName="Tahoma" Size="8" />
<TextStyle Id="WixUI_Font_Bigger" FaceName="Tahoma" Size="12" />
<TextStyle Id="WixUI_Font_Title" FaceName="Tahoma" Size="9" Bold="yes" />
<Property Id="DefaultUIFont" Value="WixUI_Font_Normal" />
<DialogRef Id="ErrorDlg" />
<DialogRef Id="FatalError" />
<DialogRef Id="FilesInUse" />
<DialogRef Id="MsiRMFilesInUse" />
<DialogRef Id="PrepareDlg" />
<DialogRef Id="ProgressDlg" />
<DialogRef Id="ResumeDlg" />
<DialogRef Id="UserExit" />
<DialogRef Id="WelcomeDlg" />
<DialogRef Id="InstallDirDlg" />
<DialogRef Id="BrowseDlg" />
<DialogRef Id="InvalidDirDlg" />
<DialogRef Id="DiskCostDlg" />
<DialogRef Id="VerifyReadyDlg" />
<DialogRef Id="MaintenanceWelcomeDlg" />
<DialogRef Id="MaintenanceTypeDlg" />
<!-- The folder InstallDirDlg edits. -->
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLFOLDER" />
<!-- First install / upgrade: Welcome -> Destination Folder -> Ready to install. A patch skips
the folder (it can't move an installed product). -->
<Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="InstallDirDlg" Condition="NOT Installed" />
<Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg" Condition="Installed AND PATCH" />
<!-- Destination Folder, as in WixUI_InstallDir: Next sets the folder, validates it (WiX's
WixUIValidatePath) and refuses one that can't be used; Change... opens the folder browser,
whose OK validates too. -->
<Publish Dialog="InstallDirDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg" />
<Publish Dialog="InstallDirDlg" Control="Next" Event="SetTargetPath" Value="[WIXUI_INSTALLDIR]" Order="1" />
<Publish Dialog="InstallDirDlg" Control="Next" Event="DoAction" Value="WixUIValidatePath_$(sys.BUILDARCHSHORT)" Order="2"
Condition="NOT WIXUI_DONTVALIDATEPATH" />
<Publish Dialog="InstallDirDlg" Control="Next" Event="SpawnDialog" Value="InvalidDirDlg" Order="3"
Condition="NOT WIXUI_DONTVALIDATEPATH AND WIXUI_INSTALLDIR_VALID&lt;&gt;&quot;1&quot;" />
<Publish Dialog="InstallDirDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg" Order="4"
Condition="WIXUI_DONTVALIDATEPATH OR WIXUI_INSTALLDIR_VALID=&quot;1&quot;" />
<Publish Dialog="InstallDirDlg" Control="ChangeFolder" Property="_BrowseProperty" Value="[WIXUI_INSTALLDIR]" Order="1" />
<Publish Dialog="InstallDirDlg" Control="ChangeFolder" Event="SpawnDialog" Value="BrowseDlg" Order="2" />
<Publish Dialog="BrowseDlg" Control="OK" Event="DoAction" Value="WixUIValidatePath_$(sys.BUILDARCHSHORT)" Order="3"
Condition="NOT WIXUI_DONTVALIDATEPATH" />
<Publish Dialog="BrowseDlg" Control="OK" Event="SpawnDialog" Value="InvalidDirDlg" Order="4"
Condition="NOT WIXUI_DONTVALIDATEPATH AND WIXUI_INSTALLDIR_VALID&lt;&gt;&quot;1&quot;" />
<Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="InstallDirDlg" Order="1"
Condition="NOT Installed" />
<Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg" Order="3"
Condition="Installed AND PATCH" />
<!-- Maintenance: Welcome -> Repair / Remove -> Ready. -->
<Publish Dialog="MaintenanceWelcomeDlg" Control="Next" Event="NewDialog" Value="MaintenanceTypeDlg" />
<Publish Dialog="MaintenanceTypeDlg" Control="RepairButton" Event="NewDialog" Value="VerifyReadyDlg" />
<Publish Dialog="MaintenanceTypeDlg" Control="RemoveButton" Event="NewDialog" Value="VerifyReadyDlg" />
<Publish Dialog="MaintenanceTypeDlg" Control="Back" Event="NewDialog" Value="MaintenanceWelcomeDlg" />
<Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="MaintenanceTypeDlg" Order="2"
Condition="Installed AND NOT PATCH" />
<!-- Completion page: optionally launch, then close. -->
<Publish Dialog="ExitDialog" Control="Finish" Event="DoAction" Value="LaunchLightcraft" Order="1"
Condition="WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 AND NOT Installed" />
<Publish Dialog="ExitDialog" Control="Finish" Event="EndDialog" Value="Return" Order="999" />
<!-- One feature, nothing to change: no "Change" in Apps & features. -->
<Property Id="ARPNOMODIFY" Value="1" />
</UI>
<UIRef Id="WixUI_Common" />
</Fragment>
<Fragment>
<ComponentGroup Id="LightcraftFiles" Directory="INSTALLFOLDER">
<Component Id="LightcraftApp" Guid="8A1D3A39-F7C1-47B1-97AE-40C80BF82F4A">
<File Id="LightcraftExe" Source="$(var.BinDir)\lightcraft.exe" KeyPath="yes">
<Shortcut Id="StartMenuShortcut" Directory="ProgramMenuFolder" Name="LightCraft"
Description="LightCraft photo library and raw developer" WorkingDirectory="INSTALLFOLDER" Advertise="yes"
Icon="LightcraftIcon.ico" IconIndex="0" />
</File>
<RegistryValue Root="HKLM" Key="Software\Microsoft\Windows\CurrentVersion\App Paths\lightcraft.exe" Value="[#LightcraftExe]" Type="string" />
<RegistryValue Root="HKLM" Key="Software\LightCraft" Name="InstallFolder" Value="[INSTALLFOLDER]" Type="string" />
</Component>
<Component Id="LightcraftCli">
<File Id="LightcraftCliExe" Source="$(var.BinDir)\lightcraft-cli.exe" KeyPath="yes" />
</Component>
</ComponentGroup>
</Fragment>
</Wix>