07. MSIX packaging, run end to end for the first time

Date: 2026-08-09 Verdict: The MSIX chain has been run end to end for the first time and it did not work as committed. Two manifest defects, D14 and D15, both fixed. Two published claims were found overstated and corrected.

Every earlier report tested the app. This one tests the things around it: the manifest, the package, the listing copy and the assets. That surface had never been executed at all, which is why TODOS.md carried "nothing has been packed as MSIX" as its last open audit item across three reports.


Environment

OS Windows 11 Pro, build 26200
Python 3.12.10
PyInstaller 6.22.0, the version QuickTrueStretch.spec declares verified
Pillow 12.2.0
Packaging tools makeappx, makepri, signtool 10.0.28000.2526
GPU NVIDIA Quadro RTX 4000, driver 32.0.15.7365
Display LG television over HDMI, 1920x1080 at 60 Hz, 100 percent scaling

The build machine had none of the packaging tooling. No Windows SDK, no App Certification Kit, and no PyInstaller. PyInstaller was installed pinned to 6.22.0 to match what the spec declares verified.

For the SDK tools, the full installer was avoided in favour of the Microsoft.Windows.SDK.BuildTools NuGet package, which carries makeappx, makepri and signtool in a 21 MB archive and needs no installer and no administrator rights. That is worth knowing for any future machine. It does not include the Windows App Certification Kit, which is the one part of the chain still unrun.


1. The wordmark changed, and it exposed an undocumented dependency

The wordmark was rebuilt during this pass. It is now two lines, the initials QT set tight with the S pushed away from them so the gap itself shows the stretch, and the full name underneath with Stretch tracked wide. QTS is a space measure and nothing outside that canvas uses it. The title bar, the manifest, the Store listing and every document say QuickTrueStretch in full.

That change is the interesting part, because of what it revealed.

CLAUDE.md said the wordmark sets the window width at 378 usable pixels. That is true, and the reason turned out not to be the text.

Wordmark is a canvas created with a height and no width. The toolkit's default canvas width is 378, and that default is what the window is sized from. The status strip's wrap width of 320, which is the pair that keeps the driver rejection message from clipping, is measured against a number that comes from a toolkit default rather than from anything in the layout.

Replacing an eight character wordmark with a three character one therefore changed nothing about the window width. Measured before and after at 100 percent: 426 physical pixels both times. Had that canvas been given an explicit width at any point, the message clipping fixed in report 04 would have come back silently. This is now commented in the source and stated in CLAUDE.md.

2. D14. The manifest could not be packed

makeappx rejected the committed AppxManifest.xml outright:

error 80080204: App manifest validation error:
Reason: The attribute EntryPoint must be specified if the attribute
Executable on the Application element is specified.

Adding EntryPoint="Windows.FullTrustApplication" produced the opposite complaint:

Reason: Application with RuntimeBehavior value "win32App" must not
declare EntryPoint

The two are not contradictory. A packaged desktop application has exactly two valid shapes and the manifest was carrying half of each:

Shape A Shape B
Executable yes yes
EntryPoint Windows.FullTrustApplication must be absent
uap10:RuntimeBehavior absent win32App
uap10:TrustLevel absent mediumIL
Minimum Windows 1607 1903

The committed manifest declared RuntimeBehavior and TrustLevel without EntryPoint, which reads like shape B, and then failed the shape A check anyway.

Fixed by moving fully to shape A. EntryPoint restored, the two uap10 attributes dropped, and the now unused uap10 namespace removed from xmlns and from IgnorableNamespaces. Shape A was chosen over shape B because it is supported from Windows 10 1607, and the manifest's own MinVersion is 1809. Shape B needs 1903, so the manifest as written also promised support it could not deliver on 1809 to 1903.

The behaviour is identical. Full trust at medium integrity either way.

3. D15. A listing asset was being packed into the product

The 300x300 Store tile was written to packaging/Assets/, and everything in that directory gets copied into the package. Nothing in the manifest references it, so it shipped inside the MSIX as dead weight for a customer to download.

Fixed by writing it to store/ instead, which is where Store listing material lives. Small, but the kind of thing that is much harder to notice once a package is live.


4. The chain, automated

The steps in BUILD-MSIX.md were followed once by hand and then turned into packaging/build-msix.ps1, because the chain has an ordering trap in it. QuickTrueStretch.spec warns and carries on when packaging/icon.ico is missing rather than failing, so building before generating assets produces an exe with PyInstaller's blue floppy disk and a build log that looks fine. That is D12 from report 05, and the only reliable defence is not letting a human choose the order.

The script takes the three Partner Center identity values as parameters rather than reading them from a committed file, which keeps docs/CONVENTIONS.md satisfied. It refuses to pack if a placeholder survives substitution, and it validates the version shape before doing any work.

Two packages were produced from the same tree:

Test Submission
Identity/@Name QTSTest the real Partner Center value
Identity/@Publisher CN=QTSTest the real Partner Center value
Signature throwaway self-signed none, the Store signs it
Size 9.5 MB 9.5 MB
makeappx pack succeeded succeeded

5. D13 reverified on the packaged build

Report 06 fixed the FAQ window carrying Tk's blue feather instead of the app icon, and fixed it from source only. The icon path is where the last three defects have lived, so it was rebuilt and rechecked.

The packaged one-directory build shows the red mark on both the main window and the FAQ window. Confirmed by cropping and magnifying the title bar of each.


6. Layout re-verified after the wordmark change

CLAUDE.md requires a re-measure at all three scales whenever the status strip or the layout moves. The wordmark grew from px(30) to px(52) tall, so the whole grid shifted down by 22 pixels.

Checked on the packaged build at 100 percent, by clicking the 1350x1080 tile, which has no registered mode and produces a genuine -2:

The scaling relationship measured in report 06 still holds across the three scales, now with a 100 percent sample: 426 at 100 percent, 531 at 125, 636 at 150. Against 426 as the base that is 1.247 and 1.493 for an expected 1.25 and 1.5.


7. Two published claims were overstated

Both were in store/listing.md and one was also in README.md. Neither is a code defect, and both would have been read by every customer.

"Open source". CLAUDE.md is explicit that there is no LICENSE file, that all rights are reserved by default plus a no-resale restriction, and that this makes the project source available rather than open source, deliberately. The listing said "open source" twice. Corrected to describe what is actually true, that the source is published and readable, and the audit reports with it.

"Hundreds of samples". Report 05 sampled a driven session 83 times. Corrected to the real figure in both places. docs/CONVENTIONS.md says a number carries its conditions, and inflating one in the direction that flatters the product is exactly the failure mode this project cannot afford, given the pitch is that the claims are checkable.

Two smaller ones went with them. "Starts in about a tenth of a second" described a 112 ms figure that report 05 has since re-measured at 158 ms, and "around 7 MB" described the one-file build when the Store ships the one-directory build inside a 9.5 MB package.


8. A capture method that leaked the desktop

Not a product defect, recorded because the first attempt produced a Store screenshot with the contents of a private backup directory in it.

The screenshot rig called SetForegroundWindow and then CopyFromScreen. SetForegroundWindow fails silently when called from a process that does not hold the foreground lock, so the app never came forward, and the screen grab captured the File Explorer window sitting on top of it. The app's title bar was in the image, with somebody else's file listing below it.

Replaced with PrintWindow using PW_RENDERFULLCONTENT, which renders from the window's own surface and cannot pick up an occluding window. The crop is taken from DWMWA_EXTENDED_FRAME_BOUNDS rather than GetWindowRect, because the latter includes the invisible resize border and drags a few pixels of desktop in on every edge.

Anyone regenerating listing images should use packaging/make_screenshots.py and check the output rather than trusting it.


9. The package installed, and it works from inside the container

Run with elevation, since trusting a test certificate and calling Add-AppxPackage both need it.

FullName    : QTSTest_1.1.0.0_x64__tfk9jy4yd9n6a
Publisher   : CN=QTSTest
InstallDir  : C:\Program Files\WindowsApps\QTSTest_1.1.0.0_x64__tfk9jy4yd9n6a
Status      : Ok
Capabilities: runFullTrust

Launched through its Start menu identity rather than by running the exe, so the process started inside the package container, confirmed by its path under WindowsApps.

The part that mattered. A packaged app runs under restrictions a loose exe does not, and nothing had ever confirmed that ChangeDisplaySettingsW is reachable from inside one. Clicking 1440x1080 returned Now running 1440 x 1080 at 60 Hz, and the desktop genuinely changed. runFullTrust with the Windows.FullTrustApplication entry point does give the container the display API.

Restore native was exercised from the same packaged instance and returned the display to 1920x1080 at 50 Hz, not the 60 Hz the session had been set to for the screenshots. That is correct and worth recording: 60 Hz had been applied dynamically, and ChangeDisplaySettingsW(NULL, 0) restores the registry mode. It is a live demonstration of the claim that this app never persists a display change.

The machine was returned to its previous state afterwards. Package removed, test certificate removed from the machine root store, signing key removed from the user store, all three verified at zero.


What this pass did not test

  1. The Windows App Certification Kit. Not present on the machine and not part of the NuGet tooling. This is the check that catches manifest and asset problems that would otherwise come back from certification days later.
  2. The submission package as a package. Only the test package was installed. The submission build was validated by packing, not by installing, because its publisher does not match any certificate available here. The two are built from the same tree by the same script and differ only in the three identity values.
  3. The upgrade path. Only a first install was exercised. Installing a higher version over an existing one was not.
  4. The site. site/ was validated for well formed markup, working internal links and the repository's writing rules. It has not been deployed, and /privacy has not been confirmed to resolve.
  5. The Store listing itself. Nothing has been uploaded. Character counts in store/listing.md were not re-counted after the wordmark and naming work.
  6. Multi-monitor, still.

Corrections carried to earlier reports