mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-14 19:27:04 -04:00
* #143: Windows MSI desktop shortcut checkbox and install-complete dialog Add native installer dialogs (installer-ui.wxs): a welcome screen with a "Create a desktop shortcut" checkbox (ticked by default), repair/remove, progress, and "Setup completed successfully" / failed / cancelled screens with Finish. Unattended /qn and /qb installs never wait on a dialog and can skip the desktop shortcut with INSTALLDESKTOPSHORTCUT=0. Start Menu and desktop shortcuts are now plain links to pdfcraft.exe in their own components (HKLM key paths, removed on uninstall) instead of advertised MSI shortcuts. test-msi.ps1 checks the compiled MSI's shortcut, checkbox and dialog tables during packaging; the ARM64 install smoke test checks both shortcut targets, their removal on uninstall, and the INSTALLDESKTOPSHORTCUT=0 opt-out. Co-authored-by: ChatGPT <[email protected]> Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JqiBTPWGWbhQDYNFcxFte6 * #143: run test-msi out of process before signing; add files-in-use dialog test-msi.ps1 held the MSI open through a Windows Installer COM handle in package.ps1's own process, so signtool could fail on signed release builds. Run it in a child process and release the COM objects. The hand-authored UI had no MsiRMFilesInUse dialog, so an upgrade or repair while PdfCraft is running could not offer to close it. Add one (OK closes the programs with MSIRMSHUTDOWN=2, Cancel stops setup) and check for it in test-msi.ps1. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JqiBTPWGWbhQDYNFcxFte6 --------- Co-authored-by: ChatGPT <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]> Co-authored-by: Brandon Thomas <[email protected]>
This commit is contained in:
9 files changed
+272
-16
No files matched your search
@@ -46,7 +46,7 @@ jobs:
|
||||
}
|
||||
- name: XML, desktop entry, AppStream, Flatpak manifest
|
||||
run: |
|
||||
xmllint --noout packaging/windows/pdfcraft.wxs packaging/macos/*.plist* packaging/linux/*.xml*
|
||||
xmllint --noout packaging/windows/*.wxs packaging/macos/*.plist* packaging/linux/*.xml*
|
||||
# Windows Installer needs Icon ids ending in .ico or .exe, or shortcuts show a blank icon (#57).
|
||||
if grep -oE '<Icon Id="[^"]*"' packaging/windows/pdfcraft.wxs | grep -vE '\.(ico|exe)"$'; then
|
||||
echo "::error file=packaging/windows/pdfcraft.wxs::Icon Id must end in .ico or .exe"; exit 1
|
||||
|
||||
@@ -68,13 +68,26 @@ jobs:
|
||||
with:
|
||||
name: pdfcraft-windows-arm64
|
||||
path: dist
|
||||
- name: Install the MSI, check it, run it, uninstall it
|
||||
- name: Install the MSI, check it, run it, uninstall it, reinstall without the desktop shortcut
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$msi = (Get-ChildItem dist/*.msi | Select-Object -First 1).FullName
|
||||
$p = Start-Process msiexec.exe -ArgumentList '/i', "`"$msi`"", '/qn', '/l*v', 'install.log' -Wait -PassThru
|
||||
if ($p.ExitCode -ne 0) { Get-Content install.log -Tail 60; throw "msiexec /i exited $($p.ExitCode)" }
|
||||
$shortcuts = @(
|
||||
(Join-Path ([Environment]::GetFolderPath('CommonPrograms')) 'PdfCraft.lnk'),
|
||||
(Join-Path ([Environment]::GetFolderPath('CommonDesktopDirectory')) 'PdfCraft.lnk')
|
||||
)
|
||||
# Plain shortcuts to the program (#143), not advertised ones, so TargetPath is the exe.
|
||||
$exePath = Join-Path $env:ProgramFiles 'PdfCraft\pdfcraft.exe'
|
||||
$wsh = New-Object -ComObject WScript.Shell
|
||||
foreach ($shortcut in $shortcuts) {
|
||||
if (-not (Test-Path -LiteralPath $shortcut)) { throw "installer did not create $shortcut" }
|
||||
$target = $wsh.CreateShortcut($shortcut).TargetPath
|
||||
if ($target -ne $exePath) { throw "$shortcut points to '$target', not $exePath" }
|
||||
Write-Output "ok $shortcut -> $target"
|
||||
}
|
||||
$dir = Join-Path $env:ProgramFiles 'PdfCraft'
|
||||
foreach ($exe in 'pdfcraft.exe', 'pdfcraft-cli.exe') {
|
||||
$path = Join-Path $dir $exe
|
||||
@@ -110,7 +123,19 @@ jobs:
|
||||
$p = Start-Process msiexec.exe -ArgumentList '/x', "`"$msi`"", '/qn' -Wait -PassThru
|
||||
if ($p.ExitCode -ne 0) { throw "msiexec /x exited $($p.ExitCode)" }
|
||||
if (Test-Path (Join-Path $dir 'pdfcraft.exe')) { throw 'uninstall left pdfcraft.exe behind' }
|
||||
foreach ($shortcut in $shortcuts) {
|
||||
if (Test-Path -LiteralPath $shortcut) { throw "uninstall left $shortcut behind" }
|
||||
}
|
||||
Write-Output 'ok uninstalled'
|
||||
# Unattended installs can opt out of the desktop shortcut (#143); the Start Menu one stays.
|
||||
$p = Start-Process msiexec.exe -ArgumentList '/i', "`"$msi`"", '/qn', 'INSTALLDESKTOPSHORTCUT=0', '/l*v', 'install-nodesktop.log' -Wait -PassThru
|
||||
if ($p.ExitCode -ne 0) { Get-Content install-nodesktop.log -Tail 60; throw "msiexec /i INSTALLDESKTOPSHORTCUT=0 exited $($p.ExitCode)" }
|
||||
if (-not (Test-Path -LiteralPath $shortcuts[0])) { throw "INSTALLDESKTOPSHORTCUT=0 also skipped $($shortcuts[0])" }
|
||||
if (Test-Path -LiteralPath $shortcuts[1]) { throw "INSTALLDESKTOPSHORTCUT=0 still created $($shortcuts[1])" }
|
||||
$p = Start-Process msiexec.exe -ArgumentList '/x', "`"$msi`"", '/qn' -Wait -PassThru
|
||||
if ($p.ExitCode -ne 0) { throw "msiexec /x exited $($p.ExitCode)" }
|
||||
if (Test-Path -LiteralPath $shortcuts[0]) { throw "uninstall left $($shortcuts[0]) behind" }
|
||||
Write-Output 'ok INSTALLDESKTOPSHORTCUT=0: Start Menu shortcut only'
|
||||
|
||||
test:
|
||||
name: test (native ARM64)
|
||||
|
||||
@@ -127,6 +127,7 @@ M0 → M1 → M2 → M3 → M4 must happen in order. After M4, M5–M12 can run
|
||||
|
||||
Newest first. One line per session: the date, what moved, and the new overall percentage.
|
||||
|
||||
- **2026-10-07 (community, #143):** Windows MSI gets a full-UI welcome dialog with a "Create a desktop shortcut" checkbox (on by default; `INSTALLDESKTOPSHORTCUT=0` for unattended installs), progress, files in use, repair/remove, and a "Setup completed successfully" screen with Finish (plus distinct failed/cancelled screens). Start Menu and desktop shortcuts are now plain links to `pdfcraft.exe` instead of advertised MSI shortcuts. Packaging checks the compiled MSI's shortcut, checkbox and dialog tables; the ARM64 smoke test checks both links' targets, their removal on uninstall, and the opt-out. Installer polish only; feature counts unchanged. ≈ 30–35%.
|
||||
- **2026-10-07 (session 16):** PrintCraft is renamed PdfCraft: repo `storytold/pdfcraft`, crates and binaries `pdfcraft*`, bundle id `ai.storyteller.pdfcraft`. On first launch the app moves the old PrintCraft settings and crash-recovery folders to the new name, so upgrades keep recent files, preferences and unsaved work. No feature change. ≈ 30–35%.
|
||||
- **2026-10-07 (community, #163):** Fill & Sign shows saved signature/initials previews with add, change and remove controls in the quick picker and left panel. The editor loads the saved typed text or drawn strokes; Apply replaces future placements and Cancel preserves the saved value. Existing annotations stay unchanged. Long typed names shrink to the placement width and include glyph overhangs in preview/PDF bounds. Regression coverage includes persistence, removal/recreation, long names and the opt-in UI control channel. Parity counts and effort estimate unchanged (50.2% shipped, ≈ 30–35% effort).
|
||||
|
||||
|
||||
+14
-4
@@ -93,10 +93,20 @@ neither the MSI nor the portable zip needs the Visual C++ redistributable.
|
||||
- Before packaging, it reads both executables' PE headers: the machine type must match `-Arch`, and
|
||||
`pdfcraft.exe` must be a GUI-subsystem program (no console window, #57) while `pdfcraft-cli.exe`
|
||||
stays a console program.
|
||||
- `pdfcraft.wxs` (WiX v5) installs per machine into Program Files with a Start Menu shortcut and an
|
||||
App Paths entry. The MSI version is the numeric `X.Y.Z` (MSI has no pre-release field), and
|
||||
same-version upgrades are allowed so release candidates replace each other. Icon ids end in `.ico`
|
||||
or `.exe` (Windows Installer requires it); packaging-lint checks this.
|
||||
- `pdfcraft.wxs` (WiX v5) installs per machine into Program Files with a Start Menu shortcut, a
|
||||
desktop shortcut (on by default) and an App Paths entry. Both shortcuts are plain links to
|
||||
`pdfcraft.exe`, not advertised MSI shortcuts (#107, #143). The MSI version is the numeric `X.Y.Z`
|
||||
(MSI has no pre-release field), and same-version upgrades are allowed so release candidates replace
|
||||
each other. Icon ids end in `.ico` or `.exe` (Windows Installer requires it); packaging-lint checks
|
||||
this.
|
||||
- `installer-ui.wxs` supplies native welcome, maintenance, progress, files-in-use and outcome
|
||||
dialogs. Full UI confirms success with Finish; failures and cancellations have distinct messages.
|
||||
The welcome dialog has a "Create a desktop shortcut" checkbox, ticked by default. `/qn` and `/qb`
|
||||
stay unattended and create the desktop shortcut unless `INSTALLDESKTOPSHORTCUT=0` is passed.
|
||||
- `test-msi.ps1 <file.msi>` checks the compiled shortcut and dialog tables. Packaging runs it in a
|
||||
child process before signing, so the MSI isn't held open when signtool runs. The ARM64 install
|
||||
smoke test checks that both all-users shortcuts point at the installed `pdfcraft.exe` and are
|
||||
removed on uninstall, and that `INSTALLDESKTOPSHORTCUT=0` skips the desktop one.
|
||||
- The portable zip holds both executables, the README, the licences, and the OFL licence of each
|
||||
embedded craft-fonts family.
|
||||
- **Signing:** `packaging/windows/sign.ps1` signs both executables and the MSI with `signtool`
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Original, text-only native MSI dialogs. No third-party UI artwork or custom-action DLLs.
|
||||
Full UI gets welcome (with the desktop-shortcut checkbox)/progress/outcome screens; /qn and /qb stay unattended. -->
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
||||
<Fragment>
|
||||
<UI Id="PdfcraftInstallerUI">
|
||||
<TextStyle Id="InstallerNormal" FaceName="Segoe UI" Size="9" />
|
||||
<TextStyle Id="InstallerTitle" FaceName="Segoe UI" Size="12" Bold="yes" />
|
||||
<Property Id="DefaultUIFont" Value="InstallerNormal" />
|
||||
|
||||
<Dialog Id="InstallWelcome" Width="370" Height="180" Title="[ProductName] Setup">
|
||||
<Control Id="Title" Type="Text" X="20" Y="20" Width="330" Height="25" Text="{\InstallerTitle}Install [ProductName]" />
|
||||
<Control Id="Description" Type="Text" X="20" Y="55" Width="330" Height="40"
|
||||
Text="Setup will install [ProductName] for all users and add it to the Start menu. Click Install to continue." />
|
||||
<Control Id="DesktopShortcut" Type="CheckBox" X="20" Y="105" Width="330" Height="17"
|
||||
Property="INSTALLDESKTOPSHORTCUT" CheckBoxValue="1" Text="Create a &desktop shortcut" />
|
||||
<Control Id="Install" Type="PushButton" X="195" Y="145" Width="75" Height="23" Default="yes" ElevationShield="yes" Text="&Install">
|
||||
<Publish Event="EndDialog" Value="Return" />
|
||||
</Control>
|
||||
<Control Id="Cancel" Type="PushButton" X="280" Y="145" Width="70" Height="23" Cancel="yes" Text="Cancel">
|
||||
<Publish Event="EndDialog" Value="Exit" />
|
||||
</Control>
|
||||
</Dialog>
|
||||
|
||||
<Dialog Id="InstallMaintenance" Width="370" Height="180" Title="[ProductName] Setup">
|
||||
<Control Id="Title" Type="Text" X="20" Y="20" Width="330" Height="25" Text="{\InstallerTitle}Maintain [ProductName]" />
|
||||
<Control Id="Description" Type="Text" X="20" Y="55" Width="330" Height="70"
|
||||
Text="[ProductName] is already installed. Choose Repair to restore missing files and shortcuts, or Remove to uninstall it." />
|
||||
<Control Id="Repair" Type="PushButton" X="110" Y="145" Width="75" Height="23" Default="yes" ElevationShield="yes" Text="&Repair">
|
||||
<Publish Property="REINSTALL" Value="ALL" Order="1" />
|
||||
<Publish Property="REINSTALLMODE" Value="amus" Order="2" />
|
||||
<Publish Event="EndDialog" Value="Return" Order="3" />
|
||||
</Control>
|
||||
<Control Id="Remove" Type="PushButton" X="195" Y="145" Width="75" Height="23" ElevationShield="yes" Text="R&emove">
|
||||
<Publish Property="REMOVE" Value="ALL" Order="1" />
|
||||
<Publish Event="EndDialog" Value="Return" Order="2" />
|
||||
</Control>
|
||||
<Control Id="Cancel" Type="PushButton" X="280" Y="145" Width="70" Height="23" Cancel="yes" Text="Cancel">
|
||||
<Publish Event="EndDialog" Value="Exit" />
|
||||
</Control>
|
||||
</Dialog>
|
||||
|
||||
<Dialog Id="InstallProgress" Width="370" Height="180" Title="[ProductName] Setup" Modeless="yes">
|
||||
<Control Id="Title" Type="Text" X="20" Y="20" Width="330" Height="25" Text="{\InstallerTitle}Setting up [ProductName]" />
|
||||
<Control Id="Status" Type="Text" X="20" Y="60" Width="330" Height="40" Text="Please wait while setup completes.">
|
||||
<Subscribe Event="ActionText" Attribute="Text" />
|
||||
</Control>
|
||||
<Control Id="Progress" Type="ProgressBar" X="20" Y="110" Width="330" Height="15" ProgressBlocks="yes">
|
||||
<Subscribe Event="SetProgress" Attribute="Progress" />
|
||||
</Control>
|
||||
<Control Id="Cancel" Type="PushButton" X="280" Y="145" Width="70" Height="23" Cancel="yes" Text="Cancel">
|
||||
<Publish Event="SpawnDialog" Value="ConfirmCancel" />
|
||||
</Control>
|
||||
</Dialog>
|
||||
|
||||
<Dialog Id="ConfirmCancel" Width="300" Height="110" Title="Cancel setup">
|
||||
<Control Id="Description" Type="Text" X="20" Y="20" Width="260" Height="35" Text="Cancel [ProductName] setup?" />
|
||||
<Control Id="Yes" Type="PushButton" X="130" Y="70" Width="70" Height="23" Text="&Yes">
|
||||
<Publish Event="EndDialog" Value="Exit" />
|
||||
</Control>
|
||||
<Control Id="No" Type="PushButton" X="210" Y="70" Width="70" Height="23" Default="yes" Cancel="yes" Text="&No">
|
||||
<Publish Event="EndDialog" Value="Return" />
|
||||
</Control>
|
||||
</Dialog>
|
||||
|
||||
<Dialog Id="InstallComplete" Width="370" Height="180" Title="[ProductName] Setup">
|
||||
<Control Id="Title" Type="Text" X="20" Y="20" Width="330" Height="25" Text="{\InstallerTitle}Setup completed successfully" />
|
||||
<Control Id="Description" Type="Text" X="20" Y="55" Width="330" Height="70"
|
||||
Text="[ProductName] setup has completed successfully. Click Finish to close this window." />
|
||||
<Control Id="Finish" Type="PushButton" X="280" Y="145" Width="70" Height="23" Default="yes" Cancel="yes" Text="&Finish">
|
||||
<Publish Event="EndDialog" Value="Return" />
|
||||
</Control>
|
||||
</Dialog>
|
||||
|
||||
<Dialog Id="InstallFailed" Width="370" Height="180" Title="[ProductName] Setup">
|
||||
<Control Id="Title" Type="Text" X="20" Y="20" Width="330" Height="25" Text="{\InstallerTitle}Setup failed" />
|
||||
<Control Id="Description" Type="Text" X="20" Y="55" Width="330" Height="70"
|
||||
Text="[ProductName] setup could not be completed. Resolve the reported error and run setup again." />
|
||||
<Control Id="Finish" Type="PushButton" X="280" Y="145" Width="70" Height="23" Default="yes" Cancel="yes" Text="&Finish">
|
||||
<Publish Event="EndDialog" Value="Return" />
|
||||
</Control>
|
||||
</Dialog>
|
||||
|
||||
<Dialog Id="InstallCancelled" Width="370" Height="180" Title="[ProductName] Setup">
|
||||
<Control Id="Title" Type="Text" X="20" Y="20" Width="330" Height="25" Text="{\InstallerTitle}Setup cancelled" />
|
||||
<Control Id="Description" Type="Text" X="20" Y="55" Width="330" Height="70"
|
||||
Text="[ProductName] setup was cancelled. Run setup again when you are ready." />
|
||||
<Control Id="Finish" Type="PushButton" X="280" Y="145" Width="70" Height="23" Default="yes" Cancel="yes" Text="&Finish">
|
||||
<Publish Event="EndDialog" Value="Return" />
|
||||
</Control>
|
||||
</Dialog>
|
||||
|
||||
<!-- Shown by Windows Installer (Restart Manager) when an upgrade or repair must replace files
|
||||
that a running PdfCraft holds open. OK closes those programs (MSIRMSHUTDOWN=2) and
|
||||
continues; Cancel ends setup. -->
|
||||
<Dialog Id="MsiRMFilesInUse" Width="370" Height="200" Title="[ProductName] Setup" KeepModeless="yes">
|
||||
<Control Id="Title" Type="Text" X="20" Y="15" Width="330" Height="25" Text="{\InstallerTitle}Files in use" />
|
||||
<Control Id="Description" Type="Text" X="20" Y="45" Width="330" Height="30"
|
||||
Text="These programs are using files that setup needs to update. Click OK to close them and continue, or Cancel to stop setup." />
|
||||
<Control Id="List" Type="ListBox" X="20" Y="80" Width="330" Height="55" Property="FileInUseProcess" Sunken="yes" TabSkip="yes" />
|
||||
<Control Id="OK" Type="PushButton" X="195" Y="165" Width="75" Height="23" Default="yes" Text="OK">
|
||||
<Publish Property="MSIRMSHUTDOWN" Value="2" Order="1" />
|
||||
<Publish Event="EndDialog" Value="Return" Order="2" />
|
||||
</Control>
|
||||
<Control Id="Cancel" Type="PushButton" X="280" Y="165" Width="70" Height="23" Cancel="yes" Text="Cancel">
|
||||
<Publish Event="EndDialog" Value="Exit" />
|
||||
</Control>
|
||||
</Dialog>
|
||||
|
||||
<InstallUISequence>
|
||||
<Show Dialog="InstallWelcome" Before="InstallMaintenance" Condition="UILevel = 5 AND NOT Installed" />
|
||||
<Show Dialog="InstallMaintenance" Before="InstallProgress" Condition="UILevel = 5 AND Installed AND NOT REMOVE AND NOT REINSTALL" />
|
||||
<Show Dialog="InstallProgress" Before="ExecuteAction" Condition="UILevel = 5" />
|
||||
<Show Dialog="InstallComplete" OnExit="success" Condition="UILevel = 5" />
|
||||
<Show Dialog="InstallFailed" OnExit="error" Condition="UILevel = 5" />
|
||||
<Show Dialog="InstallCancelled" OnExit="cancel" Condition="UILevel = 5" />
|
||||
</InstallUISequence>
|
||||
</UI>
|
||||
</Fragment>
|
||||
</Wix>
|
||||
@@ -94,9 +94,13 @@ Copy-Item (Join-Path $Bin 'pdfcraft.exe'), (Join-Path $Bin 'pdfcraft-cli.exe') $
|
||||
$Msi = Join-Path $Dist "pdfcraft-$Version-windows-$Arch.msi"
|
||||
Invoke-Native 'wix build' {
|
||||
wix build (Join-Path $PSScriptRoot 'pdfcraft.wxs') -arch $Arch `
|
||||
(Join-Path $PSScriptRoot 'installer-ui.wxs') `
|
||||
-d "Version=$MsiVersion" -d "BinDir=$Stage" -d "IconPath=$(Join-Path $Root 'assets\app-icon\pdfcraft.ico')" `
|
||||
-o $Msi
|
||||
}
|
||||
# Inspect the built MSI, not just the XML, before signing/publishing it. In a child process, so
|
||||
# its Windows Installer database handle is gone before signtool opens the MSI.
|
||||
Invoke-Native 'test-msi' { & (Get-Process -Id $PID).Path -NoProfile -File (Join-Path $PSScriptRoot 'test-msi.ps1') $Msi }
|
||||
# wix writes its debug symbols (.wixpdb) next to the MSI; keep them out of the release assets.
|
||||
Remove-Item -Force -ErrorAction SilentlyContinue ([IO.Path]::ChangeExtension($Msi, '.wixpdb'))
|
||||
& (Join-Path $PSScriptRoot 'sign.ps1') $Msi
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
<!--
|
||||
PdfCraft MSI (WiX Toolset v5: `dotnet tool install -g wix`). Built by package.ps1:
|
||||
|
||||
wix build pdfcraft.wxs -arch x64 -d Version=1.2.3 -d BinDir=... -d IconPath=... -o out.msi
|
||||
wix build pdfcraft.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, an App Paths entry, and PdfCraft
|
||||
registered as a PDF handler (offered under "Open with" and in Default apps, never forced as the
|
||||
default). Files opened with PdfCraft arrive on its command line.
|
||||
Per-machine install into Program Files, a Start Menu shortcut, a desktop shortcut (on by default,
|
||||
optional), an App Paths entry, and PdfCraft registered as a PDF handler (offered under "Open with"
|
||||
and in Default apps, never forced as the default). Files opened with PdfCraft arrive on its
|
||||
command line.
|
||||
-->
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
||||
<Package
|
||||
@@ -31,11 +32,17 @@
|
||||
<Property Id="ARPURLINFOABOUT" Value="https://getartcraft.com/apps/pdfcraft" />
|
||||
<Property Id="ARPURLUPDATEINFO" Value="https://getartcraft.com/apps/pdfcraft" />
|
||||
<Property Id="ARPHELPLINK" Value="https://discord.gg/artcraft" />
|
||||
<!-- Desktop shortcut (#143): on by default, a checkbox in the full-UI welcome dialog, and
|
||||
INSTALLDESKTOPSHORTCUT=0 on the msiexec command line for unattended installs. Secure, so the
|
||||
choice made in the (unelevated) UI reaches the elevated install. -->
|
||||
<Property Id="INSTALLDESKTOPSHORTCUT" Value="1" Secure="yes" />
|
||||
<UIRef Id="PdfcraftInstallerUI" />
|
||||
|
||||
<StandardDirectory Id="ProgramFiles6432Folder">
|
||||
<Directory Id="INSTALLFOLDER" Name="PdfCraft" />
|
||||
</StandardDirectory>
|
||||
<StandardDirectory Id="ProgramMenuFolder" />
|
||||
<StandardDirectory Id="DesktopFolder" />
|
||||
|
||||
<Feature Id="Main" Title="PdfCraft" Level="1">
|
||||
<ComponentGroupRef Id="PdfcraftFiles" />
|
||||
@@ -45,11 +52,7 @@
|
||||
<Fragment>
|
||||
<ComponentGroup Id="PdfcraftFiles" Directory="INSTALLFOLDER">
|
||||
<Component Id="PdfcraftApp" Guid="E724C1C6-091F-4E21-A685-80D089FDBE21">
|
||||
<File Id="PdfcraftExe" Source="$(var.BinDir)\pdfcraft.exe" KeyPath="yes">
|
||||
<Shortcut Id="StartMenuShortcut" Directory="ProgramMenuFolder" Name="PdfCraft"
|
||||
Description="PdfCraft PDF workbench" WorkingDirectory="INSTALLFOLDER" Advertise="yes"
|
||||
Icon="PdfcraftIcon.ico" IconIndex="0" />
|
||||
</File>
|
||||
<File Id="PdfcraftExe" Source="$(var.BinDir)\pdfcraft.exe" KeyPath="yes" />
|
||||
|
||||
<RegistryValue Root="HKLM" Key="Software\Microsoft\Windows\CurrentVersion\App Paths\pdfcraft.exe" Value="[#PdfcraftExe]" Type="string" />
|
||||
|
||||
@@ -73,6 +76,24 @@
|
||||
<RegistryValue Root="HKLM" Key="Software\RegisteredApplications" Name="PdfCraft" Value="Software\PdfCraft\Capabilities" Type="string" />
|
||||
</Component>
|
||||
|
||||
<!-- Shortcuts (#143) are plain links to pdfcraft.exe, not advertised MSI shortcuts: advertised
|
||||
ones point at a Windows Installer descriptor rather than the program, which Windows can't
|
||||
use as a file handler (#107), and 0.2.1's advertised one went missing from the Start menu
|
||||
(#143). Each has its own component with an HKLM key path, so Windows Installer removes it
|
||||
on uninstall; in this per-machine package both folders are the all-users ones. -->
|
||||
<Component Id="PdfcraftStartMenuShortcut" Directory="ProgramMenuFolder" Guid="DC028192-C233-4F99-9A77-ED458C60DF26">
|
||||
<Shortcut Id="StartMenuShortcut" Name="PdfCraft" Description="PdfCraft PDF workbench"
|
||||
Target="[#PdfcraftExe]" WorkingDirectory="INSTALLFOLDER" Icon="PdfcraftIcon.ico" IconIndex="0" />
|
||||
<RegistryValue Root="HKLM" Key="Software\PdfCraft\Shortcuts" Name="StartMenu" Type="integer" Value="1" KeyPath="yes" />
|
||||
</Component>
|
||||
|
||||
<Component Id="PdfcraftDesktopShortcut" Directory="DesktopFolder" Guid="F6948627-32AB-4D78-8865-0FDF417CE442"
|
||||
Condition="INSTALLDESKTOPSHORTCUT = 1">
|
||||
<Shortcut Id="DesktopShortcut" Name="PdfCraft" Description="PdfCraft PDF workbench"
|
||||
Target="[#PdfcraftExe]" WorkingDirectory="INSTALLFOLDER" Icon="PdfcraftIcon.ico" IconIndex="0" />
|
||||
<RegistryValue Root="HKLM" Key="Software\PdfCraft\Shortcuts" Name="Desktop" Type="integer" Value="1" KeyPath="yes" />
|
||||
</Component>
|
||||
|
||||
<Component Id="PdfcraftCli">
|
||||
<File Id="PdfcraftCliExe" Source="$(var.BinDir)\pdfcraft-cli.exe" KeyPath="yes" />
|
||||
</Component>
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Check the compiled MSI's shortcuts, desktop-shortcut checkbox and full-UI outcome wiring (#143), without installing it.
|
||||
.EXAMPLE
|
||||
pwsh packaging/windows/test-msi.ps1 dist/release/pdfcraft-0.2.1-windows-x64.msi
|
||||
#>
|
||||
param([Parameter(Mandatory)] [string] $Path)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$Installer = New-Object -ComObject WindowsInstaller.Installer
|
||||
$Database = $Installer.OpenDatabase((Resolve-Path -LiteralPath $Path).Path, 0)
|
||||
|
||||
function Read-Row([string] $Sql, [int] $Columns) {
|
||||
$view = $Database.OpenView($Sql)
|
||||
try {
|
||||
[void] $view.Execute()
|
||||
$record = $view.Fetch()
|
||||
if (-not $record) { throw "MSI row missing: $Sql" }
|
||||
$values = @(for ($i = 1; $i -le $Columns; $i++) { $record.StringData($i) })
|
||||
return ,$values
|
||||
} finally { [void] $view.Close() }
|
||||
}
|
||||
|
||||
function Assert-Equal($Actual, $Expected, [string] $What) {
|
||||
if ($Actual -cne $Expected) { throw "$What`: expected '$Expected', got '$Actual'" }
|
||||
}
|
||||
|
||||
# Plain (non-advertised) shortcuts to pdfcraft.exe, each in its own component; the desktop one is
|
||||
# gated by INSTALLDESKTOPSHORTCUT, which defaults to 1 and is secure so the UI choice reaches the
|
||||
# elevated install.
|
||||
foreach ($entry in @(@('StartMenuShortcut', 'ProgramMenuFolder', 'PdfcraftStartMenuShortcut', ''),
|
||||
@('DesktopShortcut', 'DesktopFolder', 'PdfcraftDesktopShortcut', 'INSTALLDESKTOPSHORTCUT = 1'))) {
|
||||
$row = Read-Row ('SELECT `Directory_`, `Target`, `Icon_`, `WkDir`, `Component_` FROM `Shortcut` WHERE `Shortcut` = ''' + $entry[0] + '''') 5
|
||||
Assert-Equal $row[0] $entry[1] "$($entry[0]) directory"
|
||||
Assert-Equal $row[1] '[#PdfcraftExe]' "$($entry[0]) target"
|
||||
Assert-Equal $row[2] 'PdfcraftIcon.ico' "$($entry[0]) icon"
|
||||
Assert-Equal $row[3] 'INSTALLFOLDER' "$($entry[0]) working directory"
|
||||
Assert-Equal $row[4] $entry[2] "$($entry[0]) component"
|
||||
$component = Read-Row ('SELECT `Directory_`, `Condition` FROM `Component` WHERE `Component` = ''' + $entry[2] + '''') 2
|
||||
Assert-Equal $component[0] $entry[1] "$($entry[2]) directory"
|
||||
Assert-Equal $component[1] $entry[3] "$($entry[2]) condition"
|
||||
}
|
||||
$default = Read-Row 'SELECT `Value` FROM `Property` WHERE `Property` = ''INSTALLDESKTOPSHORTCUT''' 1
|
||||
Assert-Equal $default[0] '1' 'Desktop shortcut default'
|
||||
$secure = Read-Row 'SELECT `Value` FROM `Property` WHERE `Property` = ''SecureCustomProperties''' 1
|
||||
if (($secure[0] -split ';') -notcontains 'INSTALLDESKTOPSHORTCUT') { throw "INSTALLDESKTOPSHORTCUT is not secure: '$($secure[0])'" }
|
||||
$checkbox = Read-Row 'SELECT `Type`, `Property`, `Text` FROM `Control` WHERE `Dialog_` = ''InstallWelcome'' AND `Control` = ''DesktopShortcut''' 3
|
||||
Assert-Equal $checkbox[0] 'CheckBox' 'Welcome desktop-shortcut control'
|
||||
Assert-Equal $checkbox[1] 'INSTALLDESKTOPSHORTCUT' 'Welcome checkbox property'
|
||||
if ($checkbox[2] -notmatch 'desktop shortcut') { throw "Welcome checkbox label: '$($checkbox[2])'" }
|
||||
$checked = Read-Row 'SELECT `Value` FROM `CheckBox` WHERE `Property` = ''INSTALLDESKTOPSHORTCUT''' 1
|
||||
Assert-Equal $checked[0] '1' 'Welcome checkbox value'
|
||||
$app = Read-Row 'SELECT `KeyPath` FROM `Component` WHERE `Component` = ''PdfcraftApp''' 1
|
||||
Assert-Equal $app[0] 'PdfcraftExe' 'Shortcut executable key path'
|
||||
$scope = Read-Row 'SELECT `Value` FROM `Property` WHERE `Property` = ''ALLUSERS''' 1
|
||||
Assert-Equal $scope[0] '1' 'Per-machine shortcut scope'
|
||||
|
||||
# Negative sequences are Windows Installer's success/user-exit/failure paths. Only full UI
|
||||
# shows these dialogs: an unattended /qn or /qb install must never wait for a Finish click.
|
||||
foreach ($exit in @(@('InstallComplete', '-1'), @('InstallCancelled', '-2'), @('InstallFailed', '-3'))) {
|
||||
$row = Read-Row ('SELECT `Condition`, `Sequence` FROM `InstallUISequence` WHERE `Action` = ''' + $exit[0] + '''') 2
|
||||
Assert-Equal $row[0] 'UILevel = 5' "$($exit[0]) UI level"
|
||||
Assert-Equal $row[1] $exit[1] "$($exit[0]) exit path"
|
||||
$finish = Read-Row ('SELECT `Type`, `Text` FROM `Control` WHERE `Dialog_` = ''' + $exit[0] + ''' AND `Control` = ''Finish''') 2
|
||||
Assert-Equal $finish[0] 'PushButton' "$($exit[0]) Finish control"
|
||||
Assert-Equal $finish[1] '&Finish' "$($exit[0]) Finish label"
|
||||
$endDialog = Read-Row ('SELECT `Argument` FROM `ControlEvent` WHERE `Dialog_` = ''' + $exit[0] + ''' AND `Control_` = ''Finish'' AND `Event` = ''EndDialog''') 1
|
||||
Assert-Equal $endDialog[0] 'Return' "$($exit[0]) Finish event"
|
||||
}
|
||||
$title = Read-Row 'SELECT `Text` FROM `Control` WHERE `Dialog_` = ''InstallComplete'' AND `Control` = ''Title''' 1
|
||||
if ($title[0] -notmatch 'completed successfully') { throw 'Success dialog does not confirm completion' }
|
||||
$rm = Read-Row 'SELECT `Dialog` FROM `Dialog` WHERE `Dialog` = ''MsiRMFilesInUse''' 1
|
||||
Assert-Equal $rm[0] 'MsiRMFilesInUse' 'Files-in-use dialog'
|
||||
[void] [Runtime.InteropServices.Marshal]::FinalReleaseComObject($Database)
|
||||
[void] [Runtime.InteropServices.Marshal]::FinalReleaseComObject($Installer)
|
||||
Write-Output 'ok MSI: Start Menu shortcut, optional desktop shortcut (default on, checkbox), icon/key path, full-UI success/cancel/error and Finish controls, files-in-use dialog'
|
||||
@@ -9963,7 +9963,7 @@ status = "planned"
|
||||
commands = []
|
||||
tools = []
|
||||
evidence = []
|
||||
notes = ""
|
||||
notes = "Windows MSI creates a Start menu shortcut and an optional (default on) desktop shortcut and confirms full-UI completion with Finish (#143); packaging/windows/test-msi.ps1 checks the compiled MSI and windows-arm64.yml checks installation/removal. Signed installers across platforms remain planned."
|
||||
|
||||
[[feature]]
|
||||
id = "misc.web-deploy"
|
||||
|
||||
Reference in new issue
Block a user