From 36f10a7f41d4044f7500bb62231952d22f6d76e5 Mon Sep 17 00:00:00 2001 From: John Lambert Date: Wed, 29 Apr 2026 17:00:14 -0400 Subject: [PATCH 1/5] Add wix6 skills --- .../fieldworks-installer-diagnostics/SKILL.md | 74 +++++++++++++++ .../references/evidence-and-commands.md | 55 ++++++++++++ .../references/log-triage.md | 69 ++++++++++++++ .../fieldworks-wix6-build-migration/SKILL.md | 71 +++++++++++++++ .../references/official-wix-build-notes.md | 32 +++++++ .../references/repo-build-map.md | 60 +++++++++++++ .../SKILL.md | 49 ++++++++++ .github/skills/fieldworks-wix6-ui/SKILL.md | 65 ++++++++++++++ .../references/official-ui-notes.md | 28 ++++++ .../references/ui-repo-map.md | 34 +++++++ .../fieldworks-wix6-upgrade-patching/SKILL.md | 89 +++++++++++++++++++ .../official-external-upgrade-notes.md | 34 +++++++ .../references/upgrade-patch-checklist.md | 41 +++++++++ 13 files changed, 701 insertions(+) create mode 100644 .github/skills/fieldworks-installer-diagnostics/SKILL.md create mode 100644 .github/skills/fieldworks-installer-diagnostics/references/evidence-and-commands.md create mode 100644 .github/skills/fieldworks-installer-diagnostics/references/log-triage.md create mode 100644 .github/skills/fieldworks-wix6-build-migration/SKILL.md create mode 100644 .github/skills/fieldworks-wix6-build-migration/references/official-wix-build-notes.md create mode 100644 .github/skills/fieldworks-wix6-build-migration/references/repo-build-map.md create mode 100644 .github/skills/fieldworks-wix6-migration-coordinator/SKILL.md create mode 100644 .github/skills/fieldworks-wix6-ui/SKILL.md create mode 100644 .github/skills/fieldworks-wix6-ui/references/official-ui-notes.md create mode 100644 .github/skills/fieldworks-wix6-ui/references/ui-repo-map.md create mode 100644 .github/skills/fieldworks-wix6-upgrade-patching/SKILL.md create mode 100644 .github/skills/fieldworks-wix6-upgrade-patching/references/official-external-upgrade-notes.md create mode 100644 .github/skills/fieldworks-wix6-upgrade-patching/references/upgrade-patch-checklist.md diff --git a/.github/skills/fieldworks-installer-diagnostics/SKILL.md b/.github/skills/fieldworks-installer-diagnostics/SKILL.md new file mode 100644 index 0000000000..a2dfa584a9 --- /dev/null +++ b/.github/skills/fieldworks-installer-diagnostics/SKILL.md @@ -0,0 +1,74 @@ +--- +name: fieldworks-installer-diagnostics +description: Use this skill for FieldWorks installer logging, diagnostics, evidence collection, and failure triage: double-click does nothing, bundle exits or hangs, uninstall hangs, ARP issues, prerequisite failures, custom action failures, MSI Return value 3, Burn detect/plan/apply analysis, Event Viewer/crash dumps, VM evidence folders, and before/after installer snapshots. Trigger for any FieldWorks installer debugging or diagnostic request. +--- + +# FieldWorks Installer Diagnostics + +This skill is evidence-first. Installer problems are often ambiguous until Burn logs, MSI logs, and machine state snapshots are collected. + +## Load References When Needed + +- Read `references/evidence-and-commands.md` for exact commands and evidence locations. +- Read `references/log-triage.md` for Burn/MSI/custom action triage recipes. + +## First Moves + +1. Read `.github/instructions/installer.instructions.md` and `.github/instructions/debugging.instructions.md`. +2. Read `scripts/Agent/Invoke-Installer.ps1`, `Invoke-InstallerCheck.ps1`, `Collect-InstallerSnapshot.ps1`, and `Compare-InstallerSnapshots.ps1` if changing or using diagnostics scripts. +3. Read `specs/001-wix-v6-migration/verification-matrix.md`, `golden-install-checklist.md`, and `REMAINING_WIX6_ISSUES.md` for the expected evidence shape. +4. Identify which artifact is under test: WiX 3 default or WiX 6 opt-in. Do not assume helper defaults point at WiX 6. + +## Evidence-First Workflow + +1. Create or choose an evidence folder. Repo convention: `Output/InstallerEvidence//` for agent scripts, or `C:\Temp\FwInstallerEvidence\YYYY-MM-DD\` for VM/manual runs. +2. Capture the command, artifact path, version, SHA256 if relevant, and machine snapshot/VM state. +3. Run the bundle or MSI with logging. For WiX 6 artifacts, pass explicit `-InstallerPath` when using helper scripts. +4. Include temp logs when chained packages or Burn package logs may be separate. +5. If the UI exits silently, capture Event Viewer entries and crash dumps. +6. Summarize the first failing layer: Burn bootstrapper, chained package/MSI, custom action, Windows Installer engine, or environment. + +## Commands + +Prefer the repo helper for repeatable runs: + +```powershell +./scripts/Agent/Invoke-Installer.ps1 -InstallerType Bundle -InstallerPath '.\FLExInstaller\wix6\bin\x64\Debug\FieldWorksBundle.exe' -IncludeTempLogs +./scripts/Agent/Invoke-Installer.ps1 -InstallerType Msi -InstallerPath '.\FLExInstaller\wix6\bin\x64\Debug\en-US\FieldWorks.msi' -IncludeTempLogs +``` + +Use explicit manual commands when needed: + +```powershell +.\FLExInstaller\wix6\bin\x64\Debug\FieldWorksBundle.exe /log C:\Temp\FwInstallerEvidence\bundle.log +msiexec /i .\FLExInstaller\wix6\bin\x64\Debug\en-US\FieldWorks.msi /l*v C:\Temp\FwInstallerEvidence\msi-install.log +msiexec /x {PRODUCT-CODE} /l*v C:\Temp\FwInstallerEvidence\msi-uninstall.log +``` + +For before/after evidence: + +```powershell +./scripts/Agent/Collect-InstallerSnapshot.ps1 -OutputPath C:\Temp\FwInstallerEvidence\before.json -Name before +./scripts/Agent/Collect-InstallerSnapshot.ps1 -OutputPath C:\Temp\FwInstallerEvidence\after.json -Name after +./scripts/Agent/Compare-InstallerSnapshots.ps1 -BeforeSnapshotPath C:\Temp\FwInstallerEvidence\before.json -AfterSnapshotPath C:\Temp\FwInstallerEvidence\after.json +``` + +## Triage Priorities + +- Bundle log first for Burn detection, prerequisite planning, cache/download failures, related bundle handling, and package log paths. +- MSI verbose log first for directory properties, feature states, standard action sequencing, and custom action failures. +- Search MSI logs for `Return value 3`, then walk upward to the failing action and property context. +- Search bundle logs for `Detected package`, `Planned package`, `Applying execute package`, `Error 0x`, and final result code. +- For custom actions, find the WiX action ID in `Framework.wxs`, then map it to the DllEntry in `CustomAction.cs`. +- For hangs, identify the last completed phase/action and whether a UI prompt, process-close prompt, files-in-use dialog, or elevation prompt is waiting. + +## Report Format + +When reporting diagnostics, include: + +- Artifact tested and command used. +- Evidence folder and primary logs. +- Failure layer and first meaningful error. +- Relevant Burn/MSI action/package names. +- Machine state: clean, prereqs present, old FieldWorks installed, offline, upgrade, repair, or uninstall. +- Next smallest fix or next evidence needed. diff --git a/.github/skills/fieldworks-installer-diagnostics/references/evidence-and-commands.md b/.github/skills/fieldworks-installer-diagnostics/references/evidence-and-commands.md new file mode 100644 index 0000000000..7dee9e2b28 --- /dev/null +++ b/.github/skills/fieldworks-installer-diagnostics/references/evidence-and-commands.md @@ -0,0 +1,55 @@ +# FieldWorks Installer Evidence And Commands + +## Artifact Paths + +WiX 3 default: + +- `FLExInstaller/bin/x64/Debug/en-US/FieldWorks.msi` +- `FLExInstaller/bin/x64/Debug/FieldWorksBundle.exe` + +WiX 6 opt-in: + +- `FLExInstaller/wix6/bin/x64/Debug/en-US/FieldWorks.msi` +- `FLExInstaller/wix6/bin/x64/Debug/FieldWorksBundle.exe` +- `FLExInstaller/wix6/bin/x64/Debug/FieldWorksOfflineBundle.exe` + +Adjust `Debug` to `Release` as needed. + +## Helper Script Notes + +- `scripts/Agent/Invoke-Installer.ps1` creates `Output/InstallerEvidence//` by default. +- The helper's default artifact resolver historically points at `FLExInstaller/bin/...`; pass `-InstallerPath` explicitly for WiX 6 artifacts. +- Use `-IncludeTempLogs` for Burn package logs and chained package logs. +- Use `-SummarizeMsiFileAccess` when validating installed file payloads from MSI logs. + +## Common Runs + +Interactive WiX 6 bundle: + +```powershell +./scripts/Agent/Invoke-Installer.ps1 -InstallerType Bundle -InstallerPath '.\FLExInstaller\wix6\bin\x64\Debug\FieldWorksBundle.exe' -IncludeTempLogs +``` + +Passive WiX 6 bundle: + +```powershell +./scripts/Agent/Invoke-Installer.ps1 -InstallerType Bundle -InstallerPath '.\FLExInstaller\wix6\bin\x64\Debug\FieldWorksBundle.exe' -Arguments @('/passive') -IncludeTempLogs +``` + +Direct MSI full UI: + +```powershell +msiexec /i .\FLExInstaller\wix6\bin\x64\Debug\en-US\FieldWorks.msi /qf /l*v C:\Temp\FwInstallerEvidence\msi-ui.log +``` + +Uninstall by product code: + +```powershell +msiexec /x {PRODUCT-CODE} /l*v C:\Temp\FwInstallerEvidence\msi-uninstall.log +``` + +## Crash Evidence + +- Event Viewer -> Windows Logs -> Application: `.NET Runtime`, `Application Error`, and `MsiInstaller`. +- Crash dumps: `%LOCALAPPDATA%\CrashDumps\` for bundle, custom BA, or `msiexec.exe` crashes. +- FieldWorks debug traces can be enabled with `FieldWorks.Diagnostics.config` as described in `.github/instructions/debugging.instructions.md`. diff --git a/.github/skills/fieldworks-installer-diagnostics/references/log-triage.md b/.github/skills/fieldworks-installer-diagnostics/references/log-triage.md new file mode 100644 index 0000000000..24ef13ac86 --- /dev/null +++ b/.github/skills/fieldworks-installer-diagnostics/references/log-triage.md @@ -0,0 +1,69 @@ +# Installer Log Triage + +## Burn Bundle Log + +Read in phase order: + +1. Header/version/command line. +2. Detect phase: `Detected package`, related bundle detection, registry searches, prerequisite state. +3. Plan phase: `Planned package`, requested action, cache strategy, dependency registration, feature states. +4. Cache/download phase: URL, payload hash/signature, cache path. +5. Apply phase: `Applying execute package`, package arguments, package-specific log path. +6. Completion: package result, restart state, final HRESULT. + +Useful strings: + +- `WixBundleLog` +- `WixBundleLog_AppMsiPackage` +- `Detected package:` +- `Planned package:` +- `Applying execute package:` +- `Error 0x` +- `restart:` +- `Will not uninstall package` +- `found dependents` + +## MSI Verbose Log + +Read around the first real failure, not the final summary only. + +Useful strings: + +- `Return value 3` +- `CustomAction` +- `Action start` +- `Action ended` +- `APPFOLDER` +- `DATAFOLDER` +- `WIX_UPGRADE_DETECTED` +- `FindRelatedProducts` +- `RemoveExistingProducts` +- `InstallValidate` +- `PATCH` +- `PATCHNEWSUMMARYSUBJECT` + +When `Return value 3` appears, walk upward to find the custom action or standard action that failed, then inspect properties and command line immediately before it. + +## Custom Actions + +Map WiX action ID to DLL entry: + +- `CheckApplicationPath` -> `CheckAppPath` +- `VerifyDataPath` -> `VerifyDataDirPath` +- `CloseApplications` -> `ClosePrompt` +- `DeleteRegistryVersionNumber` -> `DeleteVersionNumberFromRegistry` + +Common causes: + +- Required session properties not initialized before UI sequence. +- Deferred action needs `CustomActionData` but reads session properties directly. +- Missing `CustomActions.CA.dll` or runtime config in the Binary payload. +- Process-close prompt waiting for user action. + +## Symptom Shortcuts + +- Double-click does nothing: run with `/log`; check Burn condition failures, BA/theme load failure, Event Viewer, crash dumps. +- Bundle waits after detect: interactive bundle may be waiting for user action. Use `/passive` or full UI intentionally. +- MSI UI missing from bundle: inspect `bal:DisplayInternalUICondition`, UI level, and package log. +- Uninstall hangs: find whether Burn, MSI, files-in-use, CloseApplications, or ARP invocation is waiting. +- Duplicate ARP entries: inventory bundle vs MSI registrations and check package visibility/ARP properties. diff --git a/.github/skills/fieldworks-wix6-build-migration/SKILL.md b/.github/skills/fieldworks-wix6-build-migration/SKILL.md new file mode 100644 index 0000000000..9475e860c5 --- /dev/null +++ b/.github/skills/fieldworks-wix6-build-migration/SKILL.md @@ -0,0 +1,71 @@ +--- +name: fieldworks-wix6-build-migration +description: Use this skill for FieldWorks WiX Toolset build migration work: moving PatchableInstaller/genericinstaller behavior into WiX 6, fixing Build/Installer*.targets, SDK-style .wixproj files, InstallerToolset selection, prerequisite download/staging, CI installer builds, signing knobs, artifact paths, and local developer build failures. Trigger whenever the user mentions FieldWorks WiX build, Wix3/Wix6 toolset selection, PatchableInstaller migration, genericinstaller removal, or installer artifacts. +--- + +# FieldWorks WiX 6 Build Migration + +This skill handles build infrastructure and migration mechanics. Keep it separate from UI behavior, runtime diagnostics, and patch/upgrade validation unless those areas are the cause of a build failure. + +## Load References When Needed + +- Read `references/repo-build-map.md` for FieldWorks-specific targets, projects, commands, and artifact paths. +- Read `references/official-wix-build-notes.md` for WiX Toolset v4/v6 migration and MSBuild facts. + +## First Moves + +1. Confirm current branch/worktree and whether the task is about WiX 3 default, WiX 6 opt-in, or both. +2. Read `FLExInstaller/AGENTS.md`, `.github/instructions/installer.instructions.md`, and `.github/instructions/build.instructions.md`. +3. Inspect `Build/InstallerBuild.proj` to confirm which target file is imported for `InstallerToolset=Wix3|Wix6`. +4. Inspect `Build/Installer.targets`, `Build/Installer.Wix3.targets`, and relevant `.wixproj` files before editing. +5. Search for active references to `PatchableInstaller`, `genericinstaller`, `candle.exe`, `light.exe`, `insignia.exe`, and `heat.exe`; classify each as WiX 3 legacy, WiX 6 active, or documentation/reference. + +## FieldWorks Build Rules + +- Default installer build is WiX 3: `./build.ps1 -BuildInstaller`. +- WiX 6 is opt-in: `./build.ps1 -BuildInstaller -InstallerToolset Wix6`. +- Do not make WiX 6 schema changes in `FLExInstaller/` root files. Keep WiX 6 authoring under `FLExInstaller/wix6/`. +- Do not reintroduce a `genericinstaller` submodule. If old behavior is needed, migrate the specific source/config into the repo and document why. +- Do not remove or break the WiX 3 path during the transition. +- Use pinned repo package versions, not whatever WiX happens to be globally installed. +- WiX 6 projects should be SDK-style and use WiX extension `PackageReference`s such as Bal, Util, NetFx, UI, and Heat as appropriate. +- WiX v6 source uses `http://wixtoolset.org/schemas/v4/wxs`; there is no v6 schema namespace. + +## PatchableInstaller Migration Workflow + +When moving behavior out of the old generic installer/PatchableInstaller model: + +1. Identify the old source of behavior in `PatchableInstaller/`, `Build/Installer.legacy.targets`, or WiX 3 includes. +2. Find the WiX 6 destination: `FLExInstaller/wix6/Shared/Base`, `Shared/Common`, `Shared/CustomActions`, `Shared/ProcRunner`, or a `.wixproj` target. +3. Port only the needed behavior. Avoid copying whole legacy scripts or batch-driven assumptions into the WiX 6 path. +4. Convert old command-line parameters into MSBuild properties and `DefineConstants` rather than batch variables. +5. Add validation that fails loudly when required payloads or prerequisites are missing. +6. Update specs/docs only when behavior or build commands change. + +## Build Failure Workflow + +1. Run or ask for the exact command and configuration, including `InstallerToolset`. +2. Validate prerequisites with `./Build/Agent/Setup-InstallerBuild.ps1 -ValidateOnly` when the failure smells environmental. +3. If WiX 6 fails, inspect `FieldWorks.Installer.wixproj`, `FieldWorks.Bundle.wixproj`, and `FieldWorks.OfflineBundle.wixproj` before touching MSBuild targets. +4. For WiX errors, map the error to authoring/project/tooling. Do not hide warnings by suppression unless the repo already documents that suppression. +5. Preserve `.wixpdb` outputs; they are useful for diagnostics and future patch baselines. + +## Validation + +Prefer these checks, scaled to the change: + +- `./Build/Agent/Setup-InstallerBuild.ps1 -ValidateOnly` +- `./build.ps1 -BuildInstaller -InstallerToolset Wix6 -Configuration Debug` +- `./build.ps1 -BuildInstaller -InstallerToolset Wix6 -Configuration Release` for release-path changes +- Confirm artifacts under `FLExInstaller/wix6/bin/x64//` and `en-US/`. +- For CI or pre-commit work, use the repo VS Code task `CI: Full local check` when appropriate. + +## Output Expectations + +When reporting a build migration result, include: + +- The active toolset and entry point. +- The target/project files touched. +- The legacy behavior replaced or isolated. +- The artifacts expected and where they land. +- The validation run and remaining risk. diff --git a/.github/skills/fieldworks-wix6-build-migration/references/official-wix-build-notes.md b/.github/skills/fieldworks-wix6-build-migration/references/official-wix-build-notes.md new file mode 100644 index 0000000000..7339f6bc78 --- /dev/null +++ b/.github/skills/fieldworks-wix6-build-migration/references/official-wix-build-notes.md @@ -0,0 +1,32 @@ +# Official WiX Build And Migration Notes + +Use these notes for WiX Toolset, not the Wix website builder. + +## Official Sources + +- WiX source: https://github.com/wixtoolset/wix +- FireGiant docs: https://docs.firegiant.com/wix/ +- v3 to v4 migration WIP: https://docs.firegiant.com/wix/development/wips/4561-migrate-v3-source-code-to-v4/ +- MSBuild SDK docs: https://docs.firegiant.com/wix/tools/msbuild/ +- Preprocessor docs: https://docs.firegiant.com/wix/tools/preprocessor/ +- Extension docs: https://docs.firegiant.com/wix/tools/wixext/ + +## Migration Facts To Remember + +- WiX v6 authoring still uses the v4 XML namespace. +- v3 `candle.exe` and `light.exe` thinking maps to `wix build` or SDK-style MSBuild. +- SDK-style `.wixproj` files can use `ProjectReference`s; WiX creates bind paths and preprocessor variables for referenced projects. +- Extensions are NuGet packages in modern WiX, not only `-ext` paths to globally installed DLLs. +- Useful MSBuild properties include `DefineConstants`, `BindPath`, `InstallerPlatform`, `OutputType`, `VerboseOutput`, `SuppressIces`, `SuppressValidation`, and `*AdditionalOptions`. +- `Product` became `Package`; old `Package` metadata moved/reorganized. +- `Component/@Win64` became `Bitness`; default bitness follows `InstallerPlatform`/`-arch`. +- `RemotePayload` became package-specific payload elements such as `ExePackagePayload`, `MsiPackagePayload`, and `MspPackagePayload`. +- `ExePackage` command attributes became `InstallArguments`, `RepairArguments`, and `UninstallArguments`. +- `DisplayInternalUI` moved out of core Burn package authoring; use Bal `DisplayInternalUICondition` when appropriate. + +## Build Quality Rules + +- Do not suppress validation to get past real authoring problems. +- Keep warnings visible unless the repo has an intentional suppression with a reason. +- Preserve `.wixpdb` outputs for diagnostics and patch baselines. +- Treat Heat as technical debt. If using generated fragments, guard component/file identity carefully. diff --git a/.github/skills/fieldworks-wix6-build-migration/references/repo-build-map.md b/.github/skills/fieldworks-wix6-build-migration/references/repo-build-map.md new file mode 100644 index 0000000000..1cd7b921a3 --- /dev/null +++ b/.github/skills/fieldworks-wix6-build-migration/references/repo-build-map.md @@ -0,0 +1,60 @@ +# FieldWorks Installer Build Map + +## Core Files + +- `build.ps1`: top-level wrapper. Installer builds are requested with `-BuildInstaller`; patch builds with `-BuildPatch`; WiX 6 is selected with `-InstallerToolset Wix6`. +- `Build/InstallerBuild.proj`: central installer MSBuild project. Defaults `InstallerToolset` to `Wix3`; imports `Installer.Wix3.targets` or `Installer.targets`. +- `Build/Installer.Wix3.targets`: WiX 3 route. Verifies legacy inputs and delegates to `PatchableInstaller/BaseInstallerBuild` scripts. +- `Build/Installer.targets`: WiX 6 route. Stages payloads and builds `FieldWorks.Bundle.wixproj` plus `FieldWorks.OfflineBundle.wixproj`. +- `Build/Installer.legacy.targets`: historical WiX 3 reference. Use for understanding old patch/build behavior, not as a WiX 6 implementation pattern. + +## WiX 6 Project Files + +- `FLExInstaller/wix6/FieldWorks.Installer.wixproj`: MSI project; builds the main package from staged inputs and custom action output. +- `FLExInstaller/wix6/FieldWorks.Bundle.wixproj`: online Burn bundle; downloads/stages prerequisite payloads and chains the MSI. +- `FLExInstaller/wix6/FieldWorks.OfflineBundle.wixproj`: offline Burn bundle; embeds repo-local prerequisite payloads from `FLExInstaller/wix6/libs`. +- `FLExInstaller/wix6/Shared/CustomActions/CustomActions/CustomActions.csproj`: WiX DTF custom action project. +- `FLExInstaller/wix6/Shared/ProcRunner/`: helper executable source used by installer custom behavior. + +## Commands + +Preferred local commands: + +```powershell +./Build/Agent/Setup-InstallerBuild.ps1 -ValidateOnly +./build.ps1 -BuildInstaller +./build.ps1 -BuildInstaller -InstallerToolset Wix6 +./build.ps1 -BuildInstaller -Configuration Release -InstallerToolset Wix6 +``` + +Use direct MSBuild only when debugging build infrastructure: + +```powershell +msbuild Build/InstallerBuild.proj /t:BuildInstaller /p:Configuration=Debug /p:Platform=x64 /p:InstallerToolset=Wix6 +``` + +Patch caution: `build.ps1 -BuildPatch` routes to `/t:BuildPatch`. Before promising WiX 6 patch support, inspect whether the WiX 6 import actually defines the target and whether `.msp` authoring exists. + +## Artifacts + +WiX 3 default: + +- `FLExInstaller/bin/x64//en-US/FieldWorks.msi` +- `FLExInstaller/bin/x64//FieldWorksBundle.exe` + +WiX 6 opt-in: + +- `FLExInstaller/wix6/bin/x64//en-US/FieldWorks.msi` +- `FLExInstaller/wix6/bin/x64//en-US/FieldWorks.wixpdb` +- `FLExInstaller/wix6/bin/x64//FieldWorksBundle.exe` +- `FLExInstaller/wix6/bin/x64//FieldWorksBundle.wixpdb` +- `FLExInstaller/wix6/bin/x64//FieldWorksOfflineBundle.exe` when the offline project builds successfully. + +## Hidden-Dependency Checks + +- Staged payload root: `BuildDir/FieldWorks_InstallerInput__/`. +- Localization staging depends on `Output///` existing. +- Bundle theme files are staged by flat filename into the culture output folder. +- Offline prerequisites depend on files in `FLExInstaller/wix6/libs`. +- Heat/harvesting changes can break component identity and future patching. +- Custom action entry points referenced in `Framework.wxs` must match methods in `CustomAction.cs`. diff --git a/.github/skills/fieldworks-wix6-migration-coordinator/SKILL.md b/.github/skills/fieldworks-wix6-migration-coordinator/SKILL.md new file mode 100644 index 0000000000..7bf221d1dc --- /dev/null +++ b/.github/skills/fieldworks-wix6-migration-coordinator/SKILL.md @@ -0,0 +1,49 @@ +--- +name: fieldworks-wix6-migration-coordinator +description: Use this skill whenever the user mentions FieldWorks, FLEx, WiX installer migration, WiX 3, WiX 6, PatchableInstaller, genericinstaller, Burn, MSI bundles, installer UI, installer patching, or installer diagnostics in the FieldWorks repo. Always use it for broad FieldWorks WiX Toolset migration questions, even if the user only says "wix"; do not use Wix.com website-builder sources. +--- + +# FieldWorks WiX 6 Migration Coordinator + +Use this as the routing and guardrail skill for FieldWorks installer migration work. It keeps broad migration requests from becoming one undifferentiated installer soup. + +## First Moves + +1. Confirm the repo root by finding `FieldWorks.sln`, `build.ps1`, and `FLExInstaller/`. +2. Read the active repo guidance before edits: `AGENTS.md`, `FLExInstaller/AGENTS.md`, `.github/instructions/installer.instructions.md`, `.github/instructions/navigation.instructions.md`, and `.github/instructions/terminal.instructions.md`. +3. Read the current migration state: `specs/001-wix-v6-migration/spec.md`, `tasks.md`, `quickstart.md`, `wix3-to-wix6-audit.md`, `REMAINING_WIX6_ISSUES.md`, `BUNDLE_UI.md`, `verification-matrix.md`, and `golden-install-checklist.md`. +4. Treat all web research as WiX Toolset research. Avoid Wix.com/site-builder material entirely. + +## Pick The Right Specialist + +- Use `fieldworks-wix6-build-migration` for build orchestration, toolset selection, MSBuild targets, `.wixproj` changes, prerequisite downloads, CI checks, and moving old PatchableInstaller/genericinstaller build logic into the WiX 6 path. +- Use `fieldworks-wix6-ui` for bundle/MSI GUI problems, blank or misrendered UI, WixStdBA theme assets, MSI internal UI, dual-directory selection, feature-tree dialogs, and UI screenshot/evidence work. +- Use `fieldworks-wix6-upgrade-patching` for installing WiX 6 over WiX 3, single-instance guarantees, ARP duplication, uninstall/repair compatibility, base builds, MSP patches, `.wixpdb` baselines, and component/product-code stability. +- Use `fieldworks-installer-diagnostics` for runtime failures, "double-click does nothing", uninstall hangs, custom-action failures, evidence folders, Burn/MSI log triage, Event Viewer, crash dumps, and snapshot comparisons. + +## Non-Negotiable Context + +- FieldWorks is in a transition period: WiX 3 remains the default installer path, WiX 6 is opt-in. +- WiX 3 authoring lives in `FLExInstaller/` and uses the restored `PatchableInstaller/` legacy pipeline. +- WiX 6 authoring lives under `FLExInstaller/wix6/` and should not reuse WiX 3 `.wxi` files. +- The genericinstaller submodule should remain removed. Do not reintroduce a submodule checkout as a solution. +- WiX 6 authoring still uses the v4 XML namespace: `http://wixtoolset.org/schemas/v4/wxs`. +- Prefer `./build.ps1` and `./test.ps1` over ad-hoc `msbuild` or direct tool invocations unless debugging build infrastructure. +- Installer changes often cross build targets, WiX XML, custom actions, Burn behavior, registry state, and VM evidence. Use structural navigation for hidden dependencies before editing. + +## Safe Work Pattern + +1. State which specialist area applies and why. +2. Gather evidence from repo files and current logs before proposing changes. +3. Keep WiX 3 and WiX 6 paths isolated unless the task explicitly concerns compatibility between them. +4. Make the smallest change that addresses the root cause. +5. Validate with the relevant build/test/evidence checklist, or clearly report why validation could not be run. + +## Source Credibility + +Prefer sources in this order: + +1. FieldWorks repo docs, specs, and current code. +2. Official WiX Toolset and FireGiant docs. +3. WiX maintainer posts, WiX GitHub issues/discussions, and WiX source/tests. +4. Stack Overflow/blog posts only as practical hints that must be converted into repo-specific tests. diff --git a/.github/skills/fieldworks-wix6-ui/SKILL.md b/.github/skills/fieldworks-wix6-ui/SKILL.md new file mode 100644 index 0000000000..24870a9e69 --- /dev/null +++ b/.github/skills/fieldworks-wix6-ui/SKILL.md @@ -0,0 +1,65 @@ +--- +name: fieldworks-wix6-ui +description: Use this skill for FieldWorks WiX 6 installer GUI work: bundle window does not display, MSI internal UI handoff, WixStdBA theme/layout/assets, dual-directory App + Project Data selection, feature-tree dialogs, full/passive/quiet UI behavior, screenshots, and bundle/MSI UI parity with the old WiX 3 installer. Trigger strongly for any FieldWorks installer UI, GUI, dialog, theme, UX, or DisplayInternalUICondition task. +--- + +# FieldWorks WiX 6 UI Skill + +This skill keeps FieldWorks installer UI work grounded in the actual Burn + MSI architecture. The goal is not a pretty standalone screen; it is a correct installer flow with evidence. + +## Load References When Needed + +- Read `references/ui-repo-map.md` for the FieldWorks UI file map and checks. +- Read `references/official-ui-notes.md` for WiX Toolset UI/Burn facts. + +## First Moves + +1. Read `specs/001-wix-v6-migration/BUNDLE_UI.md` and `verification-matrix.md`. +2. Inspect `FLExInstaller/wix6/Shared/Base/Bundle.wxs`, `OfflineBundle.wxs`, `BundleTheme.xml`, `BundleTheme.wxl`, and `FieldWorks.Bundle.wixproj` for bundle UI behavior. +3. Inspect `Framework.wxs`, `WixUI_DialogFlow.wxs`, `GIInstallDirDlg.wxs`, and `GICustomizeDlg.wxs` for MSI UI behavior. +4. If custom actions affect UI, inspect `Shared/CustomActions/CustomActions/CustomAction.cs` and the custom action IDs in `Framework.wxs`. +5. Gather screenshots/logs before changing visual or sequencing behavior. + +## Mental Model + +- FieldWorks uses Burn/WixStdBA as a prerequisite/bootstrapper shell and the MSI UI for the real FieldWorks choices. +- The expected interactive flow is usually: bundle welcome/license -> prerequisite progress -> MSI internal UI opens for directories/features -> bundle success/failure. +- Seeing two windows during the MSI handoff can be correct: WixStdBA window behind, Windows Installer UI in front. +- Bundle Options UI is intentionally suppressed so MSI owns app/data directories and feature selection. +- Full UI should show MSI internal UI. `/quiet` must not show UI; `/passive` should remain non-interactive/minimal. + +## FieldWorks UI Anchors + +- `Bundle.wxs`: `bal:WixStandardBootstrapperApplication`, theme payloads, `SuppressOptionsUI`, `MsiPackage`, `bal:DisplayInternalUICondition`, `LogPathVariable`, `Visible`. +- `FieldWorks.Bundle.wixproj`: `StageBundlePayloads` and flat filename staging for theme assets. +- `Framework.wxs`: `MajorUpgrade`, `WIXUI_INSTALLDIR=APPFOLDER`, `WIXUI_PROJECTSDIR=DATAFOLDER`, data/app folder registry searches, custom actions, UIRefs. +- `WixUI_DialogFlow.wxs`: dialog transitions, browse events, path validation, maintenance/update path. +- `GIInstallDirDlg.wxs`: dual directory controls and data-folder lock explanation. +- `GICustomizeDlg.wxs`: feature tree selection UI. +- `CustomFeatures.wxi` and `CustomComponents.wxi`: feature/component definitions that must match the UI tree. + +## Common UI Failure Modes + +- Bundle appears blank or exits: missing theme payload, BA load failure, condition failure, wrong architecture, or no visible full UI. +- MSI UI never appears from bundle: `bal:DisplayInternalUICondition` not authored, condition false, wrong namespace/extension, or non-full UI mode. +- MSI UI appears in `/quiet` or `/passive`: condition too broad. +- Directory fields do not behave: `WIXUI_INSTALLDIR`, `WIXUI_PROJECTSDIR`, `APPFOLDER`, `DATAFOLDER`, or registry search values are not initialized when the dialog appears. +- Feature tree is wrong: feature IDs, levels, component refs, or feature-group wiring drifted from `CustomFeatures.wxi`/`CustomComponents.wxi`. +- ARP or uninstall UI path is odd: bundle/MSI visibility and maintenance behavior are interacting; use the upgrade/patching skill too. + +## UI Change Workflow + +1. Identify whether the issue is bundle UI, MSI UI, or handoff between them. +2. Prove the current behavior with screenshots and a bundle log. For MSI UI issues, capture an MSI verbose log too. +3. Make one class of change at a time: theme asset/staging, bundle BA metadata, MSI dialog wiring, custom action/property initialization, or feature authoring. +4. Preserve localization: put translatable strings in existing `.wxl`/`.resx` patterns, not hardcoded source where localization is expected. +5. Validate full, passive, and quiet modes when changing `DisplayInternalUICondition` or dialog sequencing. + +## Evidence Checklist + +- Bundle command and UI level used. +- Screenshot of bundle welcome/license screen. +- Screenshot of MSI destination folders dialog. +- Screenshot of feature selection dialog if touched. +- Bundle log path and `WixBundleLog_AppMsiPackage` MSI log path. +- Confirmation that `/quiet` and `/passive` do not show unexpected MSI UI. diff --git a/.github/skills/fieldworks-wix6-ui/references/official-ui-notes.md b/.github/skills/fieldworks-wix6-ui/references/official-ui-notes.md new file mode 100644 index 0000000000..27ae89c145 --- /dev/null +++ b/.github/skills/fieldworks-wix6-ui/references/official-ui-notes.md @@ -0,0 +1,28 @@ +# Official WiX UI And Burn Notes + +Use these for WiX Toolset, not Wix.com. + +## Important Sources + +- MsiPackage schema: https://docs.firegiant.com/wix/schema/wxs/msipackage/ +- Bundle schema: https://docs.firegiant.com/wix/schema/wxs/bundle/ +- Bal extension source/tests: https://github.com/wixtoolset/wix/tree/main/src/ext/Bal +- Built-in BA migration post: https://rseanhall.com/blog/2021/06/06/v4-breaking-changes-ref-builtin-ba/ +- Bundle architecture post: https://rseanhall.com/blog/2021/06/08/v4-breaking-changes-bundles-respect-arch/ + +## Facts To Encode In UI Work + +- `bal:DisplayInternalUICondition` controls whether Burn shows the authored MSI UI. It is from the Bal extension namespace. +- `DisplayInternalUI` as a core `MsiPackage` attribute is not the FieldWorks WiX 6 approach. +- When internal MSI UI is shown, it appears on top of the bootstrapper UI; it is not embedded in WixStdBA. +- WixStdBA does not support EmbeddedUI. +- `MsiPackage/@Visible` controls whether the MSI appears in Programs and Features. The default is no, and FieldWorks currently authors `Visible="no"`. +- `MsiPackage/@LogPathVariable` defaults to `WixBundleLog_[PackageId]`; FieldWorks uses `WixBundleLog_AppMsiPackage` explicitly. +- WiX 4+ bundles respect architecture. In an x64-only product, review registry searches, BA payload architecture, and old Win64 assumptions. +- Theme assets must be present in the BA container/output using the exact names referenced by the theme. + +## Debugging Hints From WiX Source + +- WixStdBA creates its UI on a UI thread and reports theme/window creation failures to Burn logs. +- WixStdBA evaluates `DisplayInternalUICondition` during MSI planning and only sets MSI UI level when the condition is true. +- WixInternalUIBootstrapperApplication is a different BA with different warnings and behavior; do not assume WixStdBA and IUIBA behave the same. diff --git a/.github/skills/fieldworks-wix6-ui/references/ui-repo-map.md b/.github/skills/fieldworks-wix6-ui/references/ui-repo-map.md new file mode 100644 index 0000000000..40c6a07d31 --- /dev/null +++ b/.github/skills/fieldworks-wix6-ui/references/ui-repo-map.md @@ -0,0 +1,34 @@ +# FieldWorks UI Repo Map + +## Bundle UI + +- `FLExInstaller/wix6/Shared/Base/Bundle.wxs`: online bundle. Uses WixStdBA, custom theme payloads, `SuppressOptionsUI="yes"`, and `bal:DisplayInternalUICondition="WixBundleUILevel >= 4"` on `AppMsiPackage`. +- `FLExInstaller/wix6/Shared/Base/OfflineBundle.wxs`: offline bundle equivalent. Keep UI behavior in sync unless intentionally different. +- `FLExInstaller/wix6/Shared/Base/BundleTheme.xml`: WixStdBA theme layout. +- `FLExInstaller/wix6/Shared/Base/BundleTheme.wxl`: WixStdBA theme strings. +- `FLExInstaller/wix6/FieldWorks.Bundle.wixproj`: stages `BundleTheme.xml`, `.wxl`, `fw-logo.png`, background assets, and `License.htm` into the culture output folder by flat filename. + +## MSI UI + +- `FLExInstaller/wix6/Shared/Base/Framework.wxs`: package root, properties, custom actions, `WIXUI_INSTALLDIR`, `WIXUI_PROJECTSDIR`, UI refs, app/data folder registry searches. +- `FLExInstaller/wix6/Shared/Base/WixUI_DialogFlow.wxs`: custom dialog navigation. +- `FLExInstaller/wix6/Shared/Base/GIWelcomeDlg.wxs`: welcome/update entry. +- `FLExInstaller/wix6/Shared/Base/GISetupTypeDlg.wxs`: typical/custom/complete route. +- `FLExInstaller/wix6/Shared/Base/GIInstallDirDlg.wxs`: app folder and project data folder controls. +- `FLExInstaller/wix6/Shared/Base/GICustomizeDlg.wxs`: feature selection tree. +- `FLExInstaller/wix6/Shared/Base/GIProgressDlg.wxs`: progress and patch/update text. +- `FLExInstaller/wix6/Shared/Base/WixUI_en-us.wxl`: MSI UI strings. + +## Feature Tree Inputs + +- `FLExInstaller/wix6/Shared/Common/CustomFeatures.wxi`: features and feature levels. +- `FLExInstaller/wix6/Shared/Common/CustomComponents.wxi`: component groups, shortcuts, environment variables, URL protocol registration. +- Harvested component groups from the WiX 6 build must match feature refs. + +## High-Value Checks + +- `Bundle.wxs` should hide the MSI in ARP with `Visible="no"` unless intentionally changing ARP behavior. +- `LogPathVariable="WixBundleLog_AppMsiPackage"` should stay available for MSI log discovery. +- `WixStdBASuppressOptionsUI`/`SuppressOptionsUI` keeps bundle Options UI from competing with the MSI directory UI. +- `GIInstallDirDlg` uses indirect PathEdit controls. Make sure the properties point to directory IDs and are initialized before the dialog opens. +- The data folder should lock on upgrade only when the registry/search state proves an existing data folder. diff --git a/.github/skills/fieldworks-wix6-upgrade-patching/SKILL.md b/.github/skills/fieldworks-wix6-upgrade-patching/SKILL.md new file mode 100644 index 0000000000..cef98a9fb4 --- /dev/null +++ b/.github/skills/fieldworks-wix6-upgrade-patching/SKILL.md @@ -0,0 +1,89 @@ +--- +name: fieldworks-wix6-upgrade-patching +description: Use this skill for FieldWorks installer upgrade and patch work: installing WiX 6 over prior WiX 3 builds, single-instance guarantees, MajorUpgrade behavior, ARP duplicate entries, uninstall/repair after upgrade, BuildPatch, base build artifacts, MSP creation, PatchBaseline, .wixpdb baselines, component GUID/file ID stability, and replacing PatchableInstaller patch infrastructure. Trigger whenever the user mentions upgrade, patch, MSP, base build, ARP, repair, uninstall compatibility, ProductCode, UpgradeCode, or Burn provider keys. +--- + +# FieldWorks WiX 6 Upgrade And Patching + +This skill handles the part of the migration most likely to hurt later: compatibility with existing installs and the discipline needed for future patches. + +## Load References When Needed + +- Read `references/upgrade-patch-checklist.md` for FieldWorks-specific gates. +- Read `references/official-external-upgrade-notes.md` for WiX/Windows Installer guidance and external lessons. + +## First Moves + +1. Read `specs/001-wix-v6-migration/REMAINING_WIX6_ISSUES.md`, `verification-matrix.md`, `golden-install-checklist.md`, and `parity-check.md`. +2. Inspect `Framework.wxs` for `MajorUpgrade`, `UpgradeCode`, path registry searches, `WIX_UPGRADE_DETECTED`, and `AllowSameVersionUpgrades`. +3. Inspect `Bundle.wxs`/`OfflineBundle.wxs` for bundle identity, `RelatedBundle`, `MsiPackage`, `Visible`, `ProviderKey`, and any `Provides` children. +4. Inspect `Build/InstallerBuild.proj`, `Build/Installer.targets`, and `Build/Installer.Wix3.targets` before assuming patch targets exist for WiX 6. +5. For actual failures, use the diagnostics skill to collect bundle/MSI logs and ARP/registry snapshots first. + +## Hard Requirements + +- FieldWorks must be single-instance. WiX 6 must not allow side-by-side FieldWorks installs from any previous WiX 3 or WiX 6 generation. +- Upgrade must preserve user data and settings while replacing the old install. +- WiX 3 remains the default build during transition. Do not break it while building WiX 6 upgrade behavior. +- Major upgrades are the safer default for broad installer changes. MSP patches require stricter component-rule discipline. + +## WiX 3 To WiX 6 Upgrade Matrix + +Validate these scenarios before declaring upgrade support complete: + +- Clean WiX 6 bundle install. +- Prior WiX 3 MSI -> WiX 6 MSI. +- Prior WiX 3 bundle -> WiX 6 bundle. +- WiX 6 repair after WiX 3-to-WiX 6 upgrade. +- WiX 6 uninstall after WiX 3-to-WiX 6 upgrade. +- Downgrade attempt from newer install. +- Interrupted upgrade rollback. +- Same-version dev build replacement if `AllowSameVersionUpgrades` is in play. + +Capture bundle logs, MSI logs, ARP snapshots, path registry exports, and before/after install/data folder listings. + +## Burn Provider Compatibility + +Public WiX issue history shows v3 and v4+ bundles can disagree about package dependency/provider keys. For upgrades from a WiX 3 bundle to WiX 6: + +- Review whether FieldWorks needs explicit provider compatibility. +- Consider whether a package-level `` matching the old MSI provider/product identity is needed. +- Test repair/uninstall after adding any provider workaround; it can fix upgrade but expose repair behavior. +- Do not guess ProductCode/ProviderKey values. Extract them from the baseline artifact or registry evidence. + +## ARP Duplication Workflow + +When Programs and Features shows duplicate or strange FieldWorks entries: + +1. Inventory bundle entries and MSI entries separately. +2. Check `Bundle` identity/name/version/upgrade provider behavior. +3. Check `MsiPackage Visible`, MSI `ARPSYSTEMCOMPONENT`, `ARPNOMODIFY`, `ARPNOREMOVE`, and bundle `DisableModify`/`DisableRemove`. +4. Use Windows Installer product inventory and registry snapshots to identify which product/package registered each ARP row. +5. Verify uninstall from ARP does not hang and removes the expected entry only. + +## MSP Patch Gate + +Before creating a WiX 6 MSP path, classify the change: + +- Safe-ish for patch: changed file contents with stable component GUIDs/file IDs and unchanged component ownership. +- Risky for patch: removed files, renamed files, moved directories, changed component GUIDs, changed key paths, feature tree reshaping, or harvested fragment identity churn. +- Prefer a major upgrade when component rules are uncertain. + +Patch infrastructure must preserve: + +- Target and update `.msi` files. +- Target and update `.wixpdb` files. +- Source payload directories or enough `.msi` information for extraction. +- Bind paths for target/update payloads. +- Generated fragments and ID maps. +- Build logs and installer version metadata. + +## Output Expectations + +For upgrade/patch work, report: + +- Baseline artifact and update artifact identities. +- UpgradeCode, ProductCode, package identity, and first-three-field version relationship. +- Whether the result is a major upgrade, repair, uninstall, or MSP patch path. +- Evidence captured and any matrix rows proven. +- Residual risk around Burn provider compatibility or component-rule stability. diff --git a/.github/skills/fieldworks-wix6-upgrade-patching/references/official-external-upgrade-notes.md b/.github/skills/fieldworks-wix6-upgrade-patching/references/official-external-upgrade-notes.md new file mode 100644 index 0000000000..607e4a5e13 --- /dev/null +++ b/.github/skills/fieldworks-wix6-upgrade-patching/references/official-external-upgrade-notes.md @@ -0,0 +1,34 @@ +# Official And External Upgrade/Patch Notes + +Use these for WiX Toolset and Windows Installer only. + +## Official Sources + +- Patches: https://docs.firegiant.com/wix/tools/patches/ +- Patch schema: https://docs.firegiant.com/wix/schema/wxs/patch/ +- PatchBaseline schema: https://docs.firegiant.com/wix/schema/wxs/patchbaseline/ +- MajorUpgrade schema: https://docs.firegiant.com/wix/schema/wxs/majorupgrade/ +- MsiPackage schema: https://docs.firegiant.com/wix/schema/wxs/msipackage/ +- Bundle schema: https://docs.firegiant.com/wix/schema/wxs/bundle/ +- Windows Installer patching: https://learn.microsoft.com/windows/win32/msi/patching + +## Key Facts + +- Major upgrades are simpler and safer than MSPs for broad product changes. +- MSI version comparison ignores the fourth field. Do not depend on fourth-field changes for upgrade detection. +- `AllowSameVersionUpgrades` can be useful for dev builds but can also mask versioning mistakes. +- WiX 4+ patch authoring can use `.wixpdb` or `.msi` baselines with `PatchBaseline`. +- `.wixpdb` baselines need correct bind paths to target/update payload files. +- `.msi` baselines can be easier to use but require extraction and can be slower. + +## External Lessons Worth Testing + +- WiX issue #7778: v3 and v4+ Burn package dependency keys can be incompatible. A package-level `` can preserve compatibility in some scenarios, but repair/uninstall must be tested afterward. +- WiX 4+ bundles respect `-arch`; old WiX 3 x86-Burn assumptions can break x64 registry searches, BA payload loading, and dependency detection. +- Stable component GUIDs, stable file IDs, and stable generated fragment identity matter for MSPs. +- Custom action failures should be diagnosed from MSI logs, not from UI symptoms. + +## Treat With Caution + +- v4/v5 guidance usually applies conceptually to v6, but verify exact package names and schema support against the WiX version pinned in the repo. +- Stack Overflow/blog advice is useful for symptoms, not authoritative design. Convert it into a FieldWorks repro and validation checklist. diff --git a/.github/skills/fieldworks-wix6-upgrade-patching/references/upgrade-patch-checklist.md b/.github/skills/fieldworks-wix6-upgrade-patching/references/upgrade-patch-checklist.md new file mode 100644 index 0000000000..b21b340ba9 --- /dev/null +++ b/.github/skills/fieldworks-wix6-upgrade-patching/references/upgrade-patch-checklist.md @@ -0,0 +1,41 @@ +# FieldWorks Upgrade And Patch Checklist + +## FieldWorks Files To Inspect + +- `specs/001-wix-v6-migration/REMAINING_WIX6_ISSUES.md`: active upgrade, ARP, and uninstall risks. +- `specs/001-wix-v6-migration/verification-matrix.md`: rows for upgrade, registry, uninstall, offline, custom actions, signing. +- `specs/001-wix-v6-migration/golden-install-checklist.md`: VM scenarios A-F. +- `specs/001-wix-v6-migration/parity-check.md`: existing WiX 3 vs WiX 6 evidence. +- `FLExInstaller/wix6/Shared/Base/Framework.wxs`: MSI major upgrade, properties, registry searches, custom action sequencing. +- `FLExInstaller/wix6/Shared/Base/Bundle.wxs`: bundle identity, related bundle upgrade, app MSI package, ARP visibility. +- `Build/Installer.targets`: WiX 6 build targets. Verify patch target presence before using `-BuildPatch -InstallerToolset Wix6`. +- `Build/Installer.legacy.targets`: old patch process and warnings about patch fragility. + +## Evidence To Capture + +- Bundle log for install/upgrade/uninstall. +- Package MSI log via `WixBundleLog_AppMsiPackage` or explicit `msiexec /l*v`. +- ARP screenshots before and after. +- Registry exports for FieldWorks keys and Windows uninstall keys. +- File listings for app folder and data folder. +- Product inventory showing ProductCode, UpgradeCode, version, and package source/cache. +- `.msi`, `.wixpdb`, and SHA256 hashes for baseline and update artifacts. + +## Upgrade Pass Criteria + +- Existing WiX 3 install is detected. +- WiX 6 install does not create side-by-side FieldWorks instances. +- Old install is removed/replaced. +- App folder defaults or preserved paths match expected behavior. +- Data folder remains in place and is not duplicated. +- User settings and projects survive. +- ARP shows one sensible FieldWorks entry. +- Repair and uninstall work after upgrade. + +## Patch Pass Criteria + +- Target and update baselines are archived with `.wixpdb` and payloads. +- Component GUIDs and file IDs are stable across target/update where required. +- Patch authoring uses `Patch`/`PatchBaseline` and produces a valid `.msp`. +- Patch install, repair, uninstall, rollback, and supersedence behavior are tested. +- Build logs prove which target/update artifacts were used. From 4f0e052a1b2279b5eaa7f2c1ad3bb7c58cdd3142 Mon Sep 17 00:00:00 2001 From: John Lambert Date: Wed, 29 Apr 2026 19:59:30 -0400 Subject: [PATCH 2/5] Update skills and specs Co-authored-by: Copilot --- .../fieldworks-installer-diagnostics/SKILL.md | 2 +- .../references/evidence-and-commands.md | 4 +- .../fieldworks-wix6-build-migration/SKILL.md | 6 +- .../references/repo-build-map.md | 6 +- .../SKILL.md | 4 +- .../fieldworks-wix6-upgrade-patching/SKILL.md | 2 +- .serena/project.yml | 141 +++++++++++------- specs/001-wix-v6-migration/BUNDLE_UI.md | 35 +++-- .../REMAINING_WIX6_ISSUES.md | 41 ++++- .../evidence-collection-checklist.md | 63 ++++++++ specs/001-wix-v6-migration/parity-check.md | 6 +- specs/001-wix-v6-migration/plan.md | 41 +++-- specs/001-wix-v6-migration/quickstart.md | 29 ++-- specs/001-wix-v6-migration/research.md | 4 +- specs/001-wix-v6-migration/spec.md | 78 ++++++---- specs/001-wix-v6-migration/tasks.md | 59 +++++--- .../verification-matrix.md | 12 +- .../wix3-to-wix6-audit.md | 43 +++--- 18 files changed, 377 insertions(+), 199 deletions(-) create mode 100644 specs/001-wix-v6-migration/evidence-collection-checklist.md diff --git a/.github/skills/fieldworks-installer-diagnostics/SKILL.md b/.github/skills/fieldworks-installer-diagnostics/SKILL.md index a2dfa584a9..859d6ee97e 100644 --- a/.github/skills/fieldworks-installer-diagnostics/SKILL.md +++ b/.github/skills/fieldworks-installer-diagnostics/SKILL.md @@ -17,7 +17,7 @@ This skill is evidence-first. Installer problems are often ambiguous until Burn 1. Read `.github/instructions/installer.instructions.md` and `.github/instructions/debugging.instructions.md`. 2. Read `scripts/Agent/Invoke-Installer.ps1`, `Invoke-InstallerCheck.ps1`, `Collect-InstallerSnapshot.ps1`, and `Compare-InstallerSnapshots.ps1` if changing or using diagnostics scripts. 3. Read `specs/001-wix-v6-migration/verification-matrix.md`, `golden-install-checklist.md`, and `REMAINING_WIX6_ISSUES.md` for the expected evidence shape. -4. Identify which artifact is under test: WiX 3 default or WiX 6 opt-in. Do not assume helper defaults point at WiX 6. +4. Identify which artifact is under test: WiX 3 fallback/current default or WiX 6 migration path. Do not assume helper defaults point at WiX 6. ## Evidence-First Workflow diff --git a/.github/skills/fieldworks-installer-diagnostics/references/evidence-and-commands.md b/.github/skills/fieldworks-installer-diagnostics/references/evidence-and-commands.md index 7dee9e2b28..2fab7ba94b 100644 --- a/.github/skills/fieldworks-installer-diagnostics/references/evidence-and-commands.md +++ b/.github/skills/fieldworks-installer-diagnostics/references/evidence-and-commands.md @@ -2,12 +2,12 @@ ## Artifact Paths -WiX 3 default: +WiX 3 fallback/current default: - `FLExInstaller/bin/x64/Debug/en-US/FieldWorks.msi` - `FLExInstaller/bin/x64/Debug/FieldWorksBundle.exe` -WiX 6 opt-in: +WiX 6 migration path: - `FLExInstaller/wix6/bin/x64/Debug/en-US/FieldWorks.msi` - `FLExInstaller/wix6/bin/x64/Debug/FieldWorksBundle.exe` diff --git a/.github/skills/fieldworks-wix6-build-migration/SKILL.md b/.github/skills/fieldworks-wix6-build-migration/SKILL.md index 9475e860c5..5a47d3a8af 100644 --- a/.github/skills/fieldworks-wix6-build-migration/SKILL.md +++ b/.github/skills/fieldworks-wix6-build-migration/SKILL.md @@ -14,7 +14,7 @@ This skill handles build infrastructure and migration mechanics. Keep it separat ## First Moves -1. Confirm current branch/worktree and whether the task is about WiX 3 default, WiX 6 opt-in, or both. +1. Confirm current branch/worktree and whether the task is about the WiX 3 fallback, the WiX 6 migration path, or the switch to WiX 6 as default. 2. Read `FLExInstaller/AGENTS.md`, `.github/instructions/installer.instructions.md`, and `.github/instructions/build.instructions.md`. 3. Inspect `Build/InstallerBuild.proj` to confirm which target file is imported for `InstallerToolset=Wix3|Wix6`. 4. Inspect `Build/Installer.targets`, `Build/Installer.Wix3.targets`, and relevant `.wixproj` files before editing. @@ -22,8 +22,8 @@ This skill handles build infrastructure and migration mechanics. Keep it separat ## FieldWorks Build Rules -- Default installer build is WiX 3: `./build.ps1 -BuildInstaller`. -- WiX 6 is opt-in: `./build.ps1 -BuildInstaller -InstallerToolset Wix6`. +- Current default installer build is WiX 3 fallback: `./build.ps1 -BuildInstaller`. +- WiX 6 is currently selected explicitly: `./build.ps1 -BuildInstaller -InstallerToolset Wix6`; treat that path as the migration target. - Do not make WiX 6 schema changes in `FLExInstaller/` root files. Keep WiX 6 authoring under `FLExInstaller/wix6/`. - Do not reintroduce a `genericinstaller` submodule. If old behavior is needed, migrate the specific source/config into the repo and document why. - Do not remove or break the WiX 3 path during the transition. diff --git a/.github/skills/fieldworks-wix6-build-migration/references/repo-build-map.md b/.github/skills/fieldworks-wix6-build-migration/references/repo-build-map.md index 1cd7b921a3..c5e774734d 100644 --- a/.github/skills/fieldworks-wix6-build-migration/references/repo-build-map.md +++ b/.github/skills/fieldworks-wix6-build-migration/references/repo-build-map.md @@ -4,7 +4,7 @@ - `build.ps1`: top-level wrapper. Installer builds are requested with `-BuildInstaller`; patch builds with `-BuildPatch`; WiX 6 is selected with `-InstallerToolset Wix6`. - `Build/InstallerBuild.proj`: central installer MSBuild project. Defaults `InstallerToolset` to `Wix3`; imports `Installer.Wix3.targets` or `Installer.targets`. -- `Build/Installer.Wix3.targets`: WiX 3 route. Verifies legacy inputs and delegates to `PatchableInstaller/BaseInstallerBuild` scripts. +- `Build/Installer.Wix3.targets`: WiX 3 fallback route. Verifies legacy inputs and delegates to legacy genericinstaller/PatchableInstaller scripts when that external checkout is present. - `Build/Installer.targets`: WiX 6 route. Stages payloads and builds `FieldWorks.Bundle.wixproj` plus `FieldWorks.OfflineBundle.wixproj`. - `Build/Installer.legacy.targets`: historical WiX 3 reference. Use for understanding old patch/build behavior, not as a WiX 6 implementation pattern. @@ -37,12 +37,12 @@ Patch caution: `build.ps1 -BuildPatch` routes to `/t:BuildPatch`. Before promisi ## Artifacts -WiX 3 default: +WiX 3 fallback/current default: - `FLExInstaller/bin/x64//en-US/FieldWorks.msi` - `FLExInstaller/bin/x64//FieldWorksBundle.exe` -WiX 6 opt-in: +WiX 6 migration path: - `FLExInstaller/wix6/bin/x64//en-US/FieldWorks.msi` - `FLExInstaller/wix6/bin/x64//en-US/FieldWorks.wixpdb` diff --git a/.github/skills/fieldworks-wix6-migration-coordinator/SKILL.md b/.github/skills/fieldworks-wix6-migration-coordinator/SKILL.md index 7bf221d1dc..a023757599 100644 --- a/.github/skills/fieldworks-wix6-migration-coordinator/SKILL.md +++ b/.github/skills/fieldworks-wix6-migration-coordinator/SKILL.md @@ -23,8 +23,8 @@ Use this as the routing and guardrail skill for FieldWorks installer migration w ## Non-Negotiable Context -- FieldWorks is in a transition period: WiX 3 remains the default installer path, WiX 6 is opt-in. -- WiX 3 authoring lives in `FLExInstaller/` and uses the restored `PatchableInstaller/` legacy pipeline. +- FieldWorks is in a transition period: the repo currently defaults installer builds to WiX 3, but the migration target is WiX 6-first and ultimately WiX 6 as default. +- WiX 3 authoring lives in `FLExInstaller/` and legacy jobs may still checkout `sillsdev/genericinstaller` as `PatchableInstaller/`; `PatchableInstaller/` is not restored in this worktree. - WiX 6 authoring lives under `FLExInstaller/wix6/` and should not reuse WiX 3 `.wxi` files. - The genericinstaller submodule should remain removed. Do not reintroduce a submodule checkout as a solution. - WiX 6 authoring still uses the v4 XML namespace: `http://wixtoolset.org/schemas/v4/wxs`. diff --git a/.github/skills/fieldworks-wix6-upgrade-patching/SKILL.md b/.github/skills/fieldworks-wix6-upgrade-patching/SKILL.md index cef98a9fb4..89efa79428 100644 --- a/.github/skills/fieldworks-wix6-upgrade-patching/SKILL.md +++ b/.github/skills/fieldworks-wix6-upgrade-patching/SKILL.md @@ -24,7 +24,7 @@ This skill handles the part of the migration most likely to hurt later: compatib - FieldWorks must be single-instance. WiX 6 must not allow side-by-side FieldWorks installs from any previous WiX 3 or WiX 6 generation. - Upgrade must preserve user data and settings while replacing the old install. -- WiX 3 remains the default build during transition. Do not break it while building WiX 6 upgrade behavior. +- WiX 3 remains the current default build during transition, but the migration target is WiX 6-first. Do not break the fallback while building WiX 6 upgrade behavior, and do not treat the current default as the desired final state. - Major upgrades are the safer default for broad installer changes. MSP patches require stricter component-rule discipline. ## WiX 3 To WiX 6 Upgrade Matrix diff --git a/.serena/project.yml b/.serena/project.yml index 63d7787207..2d85369457 100644 --- a/.serena/project.yml +++ b/.serena/project.yml @@ -1,27 +1,29 @@ + + # list of languages for which language servers are started; choose from: -# al bash clojure cpp csharp csharp_omnisharp -# dart elixir elm erlang fortran go -# haskell java julia kotlin lua markdown -# nix perl php python python_jedi r -# rego ruby ruby_solargraph rust scala swift -# terraform typescript typescript_vts zig +# al bash clojure cpp csharp +# csharp_omnisharp dart elixir elm erlang +# fortran fsharp go groovy haskell +# haxe java julia kotlin lua +# markdown +# matlab nix pascal perl php +# php_phpactor powershell python python_jedi r +# rego ruby ruby_solargraph rust scala +# swift terraform toml typescript typescript_vts +# vue yaml zig +# (This list may be outdated. For the current list, see values of Language enum here: +# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py +# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.) # Note: # - For C, use cpp # - For JavaScript, use typescript -# Language Server Details (Serena AUTO-DOWNLOADS these on first startup): -# - csharp: Uses Microsoft.CodeAnalysis.LanguageServer (Roslyn) - same engine as VS Code's C# extension -# Downloads from Azure NuGet feed; also downloads .NET 9 runtime if needed -# ⚠️ KNOWN ISSUE: Fails on T-Mobile Home Internet due to IPv6 routing issues to Azure blob storage -# - csharp_omnisharp: Uses OmniSharp (alternative) - also auto-downloads -# - cpp: Uses clangd (auto-downloads v19.1.2 on Windows/Mac; Linux needs `apt install clangd`) +# - For Free Pascal/Lazarus, use pascal # Special requirements: -# - csharp: Requires the presence of a .sln file in the project folder. +# Some languages require additional setup/installations. +# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers # When using multiple languages, the first language server that supports a given file will be used for that file. # The first language is the default language and the respective language server will be used as a fallback. # Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored. -# -# T-Mobile IPv6 workaround: Use csharp_omnisharp instead of csharp if you see Azure NuGet download errors. -# The issue is that Python urllib prefers IPv6, and T-Mobile's IPv6 routing to Azure blob storage is broken. languages: - csharp_omnisharp - cpp @@ -30,14 +32,12 @@ languages: # For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings encoding: "utf-8" -# whether to use the project's gitignore file to ignore files -# Added on 2025-04-07 +# whether to use project's .gitignore files to ignore files ignore_all_files_in_gitignore: true -# list of additional paths to ignore -# same syntax as gitignore, so you can use * and ** -# Was previously called `ignored_dirs`, please update your config if you are using that. -# Added (renamed) on 2025-04-07 +# list of additional paths to ignore in this project. +# Same syntax as gitignore, so you can use * and **. +# Note: global ignored_paths from serena_config.yml are also applied additively. ignored_paths: - Output/** - Obj/** @@ -52,54 +52,63 @@ ignored_paths: # Added on 2025-04-18 read_only: false -# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details. +# initial prompt for the project. It will always be given to the LLM upon activating the project +# (contrary to the memories, which are loaded on demand). +initial_prompt: | + FieldWorks (FLEx) is a Windows-first linguistics suite by SIL International. + Key build facts: + - Uses MSBuild Traversal SDK via FieldWorks.proj (21 ordered phases, 110+ projects) + - Native C++ (Phase 2) must build before managed code (Phases 3+) + - Build command: .\build.ps1 or msbuild FieldWorks.proj /p:Configuration=Debug /p:Platform=x64 /m + - Check .github/instructions/*.instructions.md for coding guidelines (managed, native, testing, etc.) + - Per-folder AGENTS.md files describe component contracts and dependencies + +# list of tool names to exclude. +# This extends the existing exclusions (e.g. from the global configuration) +# # Below is the complete list of tools for convenience. -# To make sure you have the latest list of tools, and to view their descriptions, +# To make sure you have the latest list of tools, and to view their descriptions, # execute `uv run scripts/print_tool_overview.py`. # -# * `activate_project`: Activates a project by name. +# * `activate_project`: Activates a project based on the project name or path. # * `check_onboarding_performed`: Checks whether project onboarding was already performed. # * `create_text_file`: Creates/overwrites a file in the project directory. -# * `delete_lines`: Deletes a range of lines within a file. -# * `delete_memory`: Deletes a memory from Serena's project-specific memory store. +# * `delete_memory`: Delete a memory file. Should only happen if a user asks for it explicitly, +# for example by saying that the information retrieved from a memory file is no longer correct +# or no longer relevant for the project. +# * `edit_memory`: Replaces content matching a regular expression in a memory. # * `execute_shell_command`: Executes a shell command. -# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced. -# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type). -# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type). +# * `find_file`: Finds files in the given relative paths +# * `find_referencing_symbols`: Finds symbols that reference the given symbol using the language server backend +# * `find_symbol`: Performs a global (or local) search using the language server backend. # * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes. # * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file. -# * `initial_instructions`: Gets the initial instructions for the current project. -# Should only be used in settings where the system prompt cannot be set, -# e.g. in clients you have no control over, like Claude Desktop. +# * `initial_instructions`: Provides instructions Serena usage (i.e. the 'Serena Instructions Manual') +# for clients that do not read the initial instructions when the MCP server is connected. # * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol. -# * `insert_at_line`: Inserts content at a given line in a file. # * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol. # * `list_dir`: Lists files and directories in the given directory (optionally with recursion). -# * `list_memories`: Lists memories in Serena's project-specific memory store. +# * `list_memories`: List available memories. Any memory can be read using the `read_memory` tool. # * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building). -# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context). # * `read_file`: Reads a file within the project directory. -# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store. -# * `remove_project`: Removes a project from the Serena configuration. -# * `replace_lines`: Replaces a range of lines within a file with new content. -# * `replace_symbol_body`: Replaces the full definition of a symbol. -# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen. +# * `read_memory`: Read the content of a memory file. This tool should only be used if the information +# is relevant to the current task. You can infer whether the information +# is relevant from the memory file name. +# You should not read the same memory file multiple times in the same conversation. +# * `rename_memory`: Renames or moves a memory. Moving between project and global scope is supported +# (e.g., renaming "global/foo" to "bar" moves it from global to project scope). +# * `rename_symbol`: Renames a symbol throughout the codebase using language server refactoring capabilities. +# For JB, we use a separate tool. +# * `replace_content`: Replaces content in a file (optionally using regular expressions). +# * `replace_symbol_body`: Replaces the full definition of a symbol using the language server backend. +# * `safe_delete_symbol`: # * `search_for_pattern`: Performs a search for a pattern in the project. -# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase. -# initial prompt for the project. It will always be given to the LLM upon activating the project -# (contrary to the memories, which are loaded on demand). -initial_prompt: | - FieldWorks (FLEx) is a Windows-first linguistics suite by SIL International. - Key build facts: - - Uses MSBuild Traversal SDK via FieldWorks.proj (21 ordered phases, 110+ projects) - - Native C++ (Phase 2) must build before managed code (Phases 3+) - - Build command: .\build.ps1 or msbuild FieldWorks.proj /p:Configuration=Debug /p:Platform=x64 /m - - Check .github/instructions/*.instructions.md for coding guidelines (managed, native, testing, etc.) - - Per-folder AGENTS.md files describe component contracts and dependencies -# project_name: Intentionally left out so that the folder name will be used and worktrees will not conflict +# * `write_memory`: Write some information (utf-8-encoded) about this project that can be useful for future tasks to a memory in md format. +# The memory name should be meaningful. excluded_tools: [] -# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default) +# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default). +# This extends the existing inclusions (e.g. from the global configuration). included_optional_tools: [] # list of mode names to that are always to be included in the set of active modes @@ -123,7 +132,6 @@ fixed_tools: [] # the name by which the project can be referenced within Serena project_name: FieldWorks - # time budget (seconds) per tool call for the retrieval of additional symbol information # such as docstrings or parameter information. # This overrides the corresponding setting in the global configuration; see the documentation there. @@ -136,3 +144,26 @@ symbol_info_budget: # Note: the backend is fixed at startup. If a project with a different backend # is activated post-init, an error will be returned. language_backend: + +# line ending convention to use when writing source files. +# Possible values: unset (use global setting), "lf", "crlf", or "native" (platform default) +# This does not affect Serena's own files (e.g. memories and configuration files), which always use native line endings. +line_ending: + +# list of regex patterns which, when matched, mark a memory entry as read‑only. +# Extends the list from the global configuration, merging the two lists. +read_only_memory_patterns: [] + +# list of regex patterns for memories to completely ignore. +# Matching memories will not appear in list_memories or activate_project output +# and cannot be accessed via read_memory or write_memory. +# To access ignored memory files, use the read_file tool on the raw file path. +# Extends the list from the global configuration, merging the two lists. +# Example: ["_archive/.*", "_episodes/.*"] +ignored_memory_patterns: [] + +# advanced configuration option allowing to configure language server-specific options. +# Maps the language key to the options. +# Have a look at the docstring of the constructors of the LS implementations within solidlsp (e.g., for C# or PHP) to see which options are available. +# No documentation on options means no options are available. +ls_specific_settings: {} diff --git a/specs/001-wix-v6-migration/BUNDLE_UI.md b/specs/001-wix-v6-migration/BUNDLE_UI.md index f2d735fab3..cdc563d737 100644 --- a/specs/001-wix-v6-migration/BUNDLE_UI.md +++ b/specs/001-wix-v6-migration/BUNDLE_UI.md @@ -1,11 +1,11 @@ # Bundle UI (WiX v6) — FieldWorks -This document describes the desired UI flow for `FieldWorksBundle.exe` and provides a checklist of changes needed to implement that flow starting from the current bundle UI state in this worktree. +This document describes the desired UI flow for `FieldWorksBundle.exe` and tracks the remaining runtime verification needed for the current WiX 6 bundle/MSI UI implementation. ## Scope -- Applies to the **online** bundle authored in [FLExInstaller/Shared/Base/Bundle.wxs](../../FLExInstaller/Shared/Base/Bundle.wxs). -- Uses **WixStdBA (WixStandardBootstrapperApplication)** with a custom theme authored in [FLExInstaller/Shared/Base/BundleTheme.xml](../../FLExInstaller/Shared/Base/BundleTheme.xml) + [FLExInstaller/Shared/Base/BundleTheme.wxl](../../FLExInstaller/Shared/Base/BundleTheme.wxl). +- Applies to the **online** bundle authored in [FLExInstaller/wix6/Shared/Base/Bundle.wxs](../../FLExInstaller/wix6/Shared/Base/Bundle.wxs) and the offline bundle authored in [FLExInstaller/wix6/Shared/Base/OfflineBundle.wxs](../../FLExInstaller/wix6/Shared/Base/OfflineBundle.wxs). +- Uses **WixStdBA (WixStandardBootstrapperApplication)** with a custom theme authored in [FLExInstaller/wix6/Shared/Base/BundleTheme.xml](../../FLExInstaller/wix6/Shared/Base/BundleTheme.xml) + [FLExInstaller/wix6/Shared/Base/BundleTheme.wxl](../../FLExInstaller/wix6/Shared/Base/BundleTheme.wxl). - Goal: **mirror the MSI UI** as closely as practical while still letting Burn handle prerequisites, detection, logging, and restart. ## Reference screenshots (expected UX) @@ -51,7 +51,7 @@ The MSI UI shows two destination folders. The expected defaults are: - `.NET 4.8 web` package group - `vcredists` package group - `AppMsiPackage` (FieldWorks MSI) -- In [FLExInstaller/Shared/Base/Bundle.wxs](../../FLExInstaller/Shared/Base/Bundle.wxs), the `AppMsiPackage` currently has: +- In [FLExInstaller/wix6/Shared/Base/Bundle.wxs](../../FLExInstaller/wix6/Shared/Base/Bundle.wxs), the `AppMsiPackage` currently has: - `Visible="no"` - MSI internal UI is enabled for **full UI** runs (see note below) - MSI logging captured via `LogPathVariable="WixBundleLog_AppMsiPackage"`. @@ -63,29 +63,26 @@ The MSI UI shows two destination folders. The expected defaults are: The WiX v6 mechanism for showing MSI internal UI from a bundle is `bal:DisplayInternalUICondition` on `MsiPackage`. - Current bundle authoring uses: `bal:DisplayInternalUICondition="WixBundleUILevel >= 4"` - - This turns on MSI internal UI only for **full UI** runs. + - This turns on MSI internal UI only for **full UI** runs (`BOOTSTRAPPER_DISPLAY_FULL` follows passive/none display levels in the Burn enum). - It avoids MSI dialogs in `/passive` and `/quiet` runs (runtime verification still required). Reference docs: - https://docs.firegiant.com/wix/schema/wxs/msipackage/ (see `DisplayInternalUICondition`) - https://docs.firegiant.com/wix/tools/burn/builtin-variables/ (see `WixBundleUILevel`) -Attempting to force `REBOOT=ReallySuppress` via `MsiProperty` also fails (error `WIX0365`: `REBOOT` is bootstrapper-controlled). Enabling true MSI dialog UI from the bundle therefore requires either: - -- upgrading the WiX toolset packages to a version where `DisplayInternalUI` is supported in this project, or -- switching to the “bundle-only UI styled to match MSI” approach (Option B). +Attempting to force `REBOOT=ReallySuppress` via `MsiProperty` also fails (error `WIX0365`: `REBOOT` is bootstrapper-controlled). Reboot behavior therefore needs runtime validation rather than another MSI property override. ### Payload staging constraint (important) -WixStdBA binds theme resources by *filename* at runtime. This worktree stages flat-named copies of the theme and its assets into the culture output folder via [FLExInstaller/wix6/FieldWorks.Bundle.wixproj](../../FLExInstaller/wix6/FieldWorks.Bundle.wixproj) target `StageBundlePayloads`. +WixStdBA binds theme resources by *filename* at runtime. This worktree stages flat-named copies of the theme and its assets into the culture output folder via [FLExInstaller/wix6/FieldWorks.Bundle.wixproj](../../FLExInstaller/wix6/FieldWorks.Bundle.wixproj) target `StageBundlePayloads` and [FLExInstaller/wix6/FieldWorks.OfflineBundle.wixproj](../../FLExInstaller/wix6/FieldWorks.OfflineBundle.wixproj) target `StageOfflineBundlePayloads`. That staging approach is required for stability of any custom theme/asset work. ## Target UI approach -There are two viable approaches. The recommended one is **Option A**. +The implemented approach is **Option A**. -### Option A (recommended): Burn handles prereqs + MSI shows internal UI +### Option A: Burn handles prereqs + MSI shows internal UI In this approach: @@ -98,9 +95,9 @@ This provides the closest parity to the MSI UI with the least custom theming wor With WixStdBA, MSI internal UI is shown in the **Windows Installer UI window** (msiexec) rather than being embedded in the bootstrapper window. Expect “two windows” during the handoff. -### Option B: Keep bundle-only UI and only style it to look like MSI +### Deferred fallback: Keep bundle-only UI and only style it to look like MSI -This is the current direction of the custom theme: MSI-like background, sizes, and copy. This can be kept even if Option A is implemented, because Option A still needs a small bootstrapper shell. +The custom theme still matters because Option A needs a small bootstrapper shell. A bundle-only UI should be treated as a fallback, not the primary migration plan, because duplicating MSI directory and feature behavior in WixStdBA would add avoidable maintenance risk. ## Recommended interactive UI flow (Option A) @@ -187,7 +184,9 @@ Key requirement: (Implementation note: the bundle currently gates `bal:DisplayInternalUICondition` on `WixBundleUILevel >= 4` (intended to mean full UI only). This still needs explicit verification.) -## Implementation checklist (from current state) +## Implementation and Verification Checklist + +The core authoring for MSI internal UI handoff is in place. Most remaining items below are runtime verification, not missing authoring. ### Branding and identity requirements @@ -215,8 +214,8 @@ Key requirement: - [ ] Align the Welcome bundle screen with the reference screenshot: - Keep the license hyperlink and acceptance checkbox. - Welcome copy should explain prerequisites → then MSI UI. -- [x] Make the Install page button label/copy reflect the handoff. - - Suggested: keep button text “Install”, but change the page text to mention the MSI installer will open. +- [ ] Make the Install page copy reflect the handoff. + - Suggested: keep button text “Install”, but change the page text to mention the FieldWorks MSI installer will open for folders/features after prerequisite checks. - [x] Keep restart UI gated on `WixStdBARestartRequired`. - Restart-related controls in `Success`/`Failure` use `VisibleCondition="WixStdBARestartRequired"`. - [x] Options page decision: hide it. @@ -259,7 +258,7 @@ Key requirement: ## Notes / known gaps -- The offline bundle in this repo ([FLExInstaller/Shared/Base/OfflineBundle.wxs](../../FLExInstaller/Shared/Base/OfflineBundle.wxs)) is not currently wired into the WiX v6 build outputs. +- The offline bundle in this repo ([FLExInstaller/wix6/Shared/Base/OfflineBundle.wxs](../../FLExInstaller/wix6/Shared/Base/OfflineBundle.wxs)) is wired into the WiX 6 build via [FLExInstaller/wix6/FieldWorks.OfflineBundle.wixproj](../../FLExInstaller/wix6/FieldWorks.OfflineBundle.wixproj), but disconnected-machine runtime validation is still pending. - `DisplayInternalUI` (legacy attribute) is not supported by WiX v6 schema here; use `bal:DisplayInternalUICondition` instead. - Runtime behavior still needs verification for: - normal interactive install diff --git a/specs/001-wix-v6-migration/REMAINING_WIX6_ISSUES.md b/specs/001-wix-v6-migration/REMAINING_WIX6_ISSUES.md index 5cb4cbb7f5..9d97548581 100644 --- a/specs/001-wix-v6-migration/REMAINING_WIX6_ISSUES.md +++ b/specs/001-wix-v6-migration/REMAINING_WIX6_ISSUES.md @@ -1,6 +1,8 @@ -# Remaining WiX 6 Issues +# Remaining WiX 6 Issues and Validation Gaps -This is a working TODO list of remaining issues for the WiX 6 installer, based on current observed behavior. +This is a working TODO list of blocking issues and validation gaps for the WiX 6 installer, based on current observed behavior and the repo state. + +**Release posture**: Do not make WiX 6 the default installer route until the WiX 3 to WiX 6 upgrade, ARP presentation, uninstall, online install, and offline install checks below have evidence. The repo still defaults `InstallerToolset` to `Wix3`, which should be treated as a migration blocker or explicit release decision. ## Upgrade over WiX 3 (keep settings, same install shape) @@ -15,6 +17,8 @@ Goal: installing the WiX 6 bundle over an existing WiX 3 FieldWorks install shou - [ ] Upgrade keeps all user settings (projects, preferences, registry-based settings, config files, etc.). - [ ] Upgrade keeps all user data in-place (no duplication or orphaned data folders). - [ ] Add explicit evidence for the above (bundle log + MSI log + before/after install paths + ARP snapshot). +- [ ] Verify Burn provider compatibility across WiX 3 to WiX 6. The current WiX 6 `MsiPackage` has no package-level ``; extract the WiX 3 baseline provider/package identity and determine whether a compatibility provider key is required. +- [ ] Test same-version major upgrades for WiX 6 dev builds: install a WiX 6 build, reinstall the same `VersionNumber`, and confirm it replaces rather than side-by-side installs. ## Product icon parity @@ -51,3 +55,36 @@ Observed: uninstall from Settings / Add-Remove Programs gets stuck and does not - [ ] Identify whether the hang is in Burn (bootstrapper) vs MSI execution vs a custom action. - [ ] Fix uninstall to complete successfully from ARP without user intervention. - [ ] Verify post-uninstall cleanup matches expectations (ARP removed, registry keys cleaned up, shortcuts removed, env vars restored if applicable). + +Diagnostics to capture for every uninstall hang reproduction: + +- Bundle uninstall log and MSI verbose uninstall log. +- `%TEMP%` WiX/Burn logs and any `WixBundleLog_AppMsiPackage` path from the bundle log. +- Before/after snapshots from `scripts/Agent/Collect-InstallerSnapshot.ps1`. +- Event Viewer Application entries around the hang. +- Crash dumps from `%LOCALAPPDATA%\CrashDumps` if present. + +Use [evidence-collection-checklist.md](evidence-collection-checklist.md) for symptom-specific evidence requirements. + +## Online and offline clean-machine validation + +- [ ] Online bundle installs on a clean VM with internet access, downloads or skips prerequisites correctly, shows MSI internal UI in full UI mode, and launches FieldWorks after install. +- [ ] Passive and quiet online installs do not show MSI internal UI and return expected exit codes. +- [ ] Offline bundle `FieldWorksOfflineBundle.exe` installs on a disconnected VM using embedded/local prerequisites only. +- [ ] Offline bundle evidence includes proof no network was required. + +## Burn engine signing and code signing parity + +Goal: Ensure signed WiX 6 bundles match the WiX 3 release expectations. + +- [ ] Verify `signingProxy.bat` signs the MSI and both bundle EXEs when signing is enabled. +- [ ] Verify Burn engine/container signing behavior is equivalent to the old WiX 3 `insignia` flow, or document the WiX 6 SDK signing behavior that replaces it. +- [ ] Capture Authenticode evidence for MSI, online bundle, and offline bundle. + +## Patch/MSP support is deferred + +The WiX 6 migration currently supports MSI major upgrades, not WiX 6 MSP patch generation. The legacy `BuildPatch` target remains in the WiX 3 path only. + +- [ ] Define a separate WiX 6 patch design before adding any `BuildPatch` support. +- [ ] Include `PatchBaseline`, base MSI and `.wixpdb` retention, component GUID stability, and repair/uninstall compatibility in that design. +- [ ] Do not treat `build.ps1 -BuildPatch -InstallerToolset Wix6` as supported until the design is implemented and tested. diff --git a/specs/001-wix-v6-migration/evidence-collection-checklist.md b/specs/001-wix-v6-migration/evidence-collection-checklist.md new file mode 100644 index 0000000000..ba7a50045d --- /dev/null +++ b/specs/001-wix-v6-migration/evidence-collection-checklist.md @@ -0,0 +1,63 @@ +# Evidence Collection Checklist + +Use this checklist when a WiX 6 installer validation run fails. The goal is to preserve enough evidence to decide whether the failure is in Burn, MSI authoring, custom actions, prerequisites, machine state, or the legacy-to-WiX6 upgrade path. + +## General Run Evidence + +- Bundle or MSI artifact path tested. For WiX 6, use explicit paths under `FLExInstaller/wix6/bin/x64//`. +- Exact command line used. +- Machine state: clean VM, local dev PC, upgraded machine, online/offline, installed prerequisites. +- Bundle log and MSI verbose log. +- Before/after snapshots from `scripts/Agent/Collect-InstallerSnapshot.ps1` where practical. +- ARP/Settings screenshot after install, upgrade, and uninstall. + +## Silent Exit or No UI + +- Bundle log from `FieldWorksBundle.exe /log ` or `%TEMP%` Burn logs. +- Event Viewer Application entries for `FieldWorksBundle.exe`, `msiexec.exe`, `.NET Runtime`, and `Application Error`. +- Crash dumps from `%LOCALAPPDATA%\CrashDumps` if present. + +## MSI UI Does Not Appear From Bundle + +- Bundle log showing `WixBundleUILevel` and planning for `AppMsiPackage`. +- `AppMsiPackage` authoring evidence for `bal:DisplayInternalUICondition`. +- Full, passive, and quiet mode results. Full UI should show MSI dialogs; passive/quiet should not. + +## WiX 3 to WiX 6 Upgrade Fails + +- Before-upgrade snapshot from a WiX 3 install. +- WiX 6 bundle log and MSI log from the upgrade run. +- Log evidence for related bundle/package detection and `RemoveExistingProducts`. +- ARP screenshot before and after upgrade. +- Registry exports for FieldWorks install path, data path, version, and uninstall entries. +- Note whether a package-level `` compatibility decision has been tested. + +## Duplicate ARP Entries or Wrong Size + +- Settings/Apps screenshot showing all FieldWorks/SIL entries. +- Registry exports from `HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall` and `HKLM\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall` for matching entries. +- Bundle log lines for package registration and visibility. +- MSI ARP property evidence, especially `ARPSYSTEMCOMPONENT` and package `Visible` state. + +## Uninstall Hangs + +- Bundle uninstall log and MSI verbose uninstall log. +- Last action visible in the bundle and MSI logs. +- Event Viewer entries at the hang time. +- Crash dumps if present. +- Screenshot of any visible prompt or blocked UI. +- Running process list for `FieldWorks*`, `msiexec`, and bundle processes. + +## Offline Install Fails + +- Exact `FieldWorksOfflineBundle.exe` path and file hash. +- Proof the VM was disconnected before the run. +- Bundle log showing local prerequisite resolution, not network download attempts. +- Presence of embedded or staged prerequisite payloads in the bundle/cache. + +## Custom Action Failure + +- MSI log around `Return value 3` or the last `Action start` before a hang. +- Property values immediately before the failing custom action. +- Any FieldWorks trace log if diagnostics were enabled. +- Map the action to the `FLExInstaller/wix6/Shared/CustomActions` source before changing authoring. diff --git a/specs/001-wix-v6-migration/parity-check.md b/specs/001-wix-v6-migration/parity-check.md index a61e1f6051..680744743b 100644 --- a/specs/001-wix-v6-migration/parity-check.md +++ b/specs/001-wix-v6-migration/parity-check.md @@ -232,12 +232,12 @@ Populate one row per behavior/step. Keep entries short and point to the most spe | Behavior / step (short) | Was implicit before (WiX3 baseline) | How it’s expressed in v6 | Where it lives now (path + identifier) | Status | Notes / evidence | |---|---|---|---|---|---| -| Example: CA runtime selection | DTF/SfxCA expected `CustomAction.config` present in CA payload (in WiX3-era tooling this was typically ensured by the CA project output) | Explicitly copied to output and packaged | `FLExInstaller/Shared/CustomActions/CustomActions/CustomActions.csproj` (`Content Include="CustomAction.config"` + `CopyToOutputDirectory`) | ☑ | Confirmed in project file; historic example (optional external baseline): `genericinstaller/CustomActions/CustomActions/CustomActions.csproj` copies `CustomAction.config` to output | +| Example: CA runtime selection | DTF/SfxCA expected `CustomAction.config` present in CA payload (in WiX3-era tooling this was typically ensured by the CA project output) | Explicitly copied to output and packaged | `FLExInstaller/wix6/Shared/CustomActions/CustomActions/CustomActions.csproj` (`Content Include="CustomAction.config"` + `CopyToOutputDirectory`) | ☑ | Confirmed in project file; historic example (optional external baseline): `genericinstaller/CustomActions/CustomActions/CustomActions.csproj` copies `CustomAction.config` to output | | Example: L10n payload inclusion | Legacy pipeline staged + harvested l10n implicitly | Heat harvest + feature reference | `FLExInstaller/wix6/FieldWorks.Installer.wixproj` (`HarvestedL10nFiles`) + `Framework.wxs` feature refs | ☐ | Install file listing | -| Example: Fonts install semantics | Font components existed, relied on legacy schema allowances | Explicit components with registry gating + Permanent | `FLExInstaller/Fonts.wxi` (`ComponentGroup Fonts`) + referenced from `Framework.wxs` | ☐ | Registry + file check | +| Example: Fonts install semantics | Font components existed, relied on legacy schema allowances | Explicit components with registry gating + Permanent | `FLExInstaller/wix6/Fonts.wxi` (`ComponentGroup Fonts`) + referenced from `Framework.wxs` | ☐ | Registry + file check | | Staged payload roots exist | `Build/Installer.targets` defined versioned staging dirs (e.g., `$(SafeApplicationName)_$(MinorVersion)_Build_$(Platform)` + `objects/...` suffixes) | Explicit staging target creates expected roots under `FieldWorks_InstallerInput__\objects\...` | `Build/Installer.targets` (`StageInstallerInputs`) and `BuildDir\FieldWorks_InstallerInput_Release_x64\objects\*` | ☑ | Verified on 2025-12-17 (Release build) | | Bundle logs captured | Ad-hoc logging varied by invocation | Standardized helper writes log under repo output | `scripts/Agent/Invoke-Installer.ps1` (bundle log path) | ☑ | `Output\InstallerEvidence\20251217-local-bundle-passive\bundle.log` | -| FLEx Bridge offline prereq (bundle) | Bundle included/offered offline bridge prereq | `ExePackage` is defined in the shared prereqs include and referenced from the bundle chain via `PackageGroupRef` | `FLExInstaller/Shared/Common/Redistributables.wxi` (`PackageGroup FlexBridgeInstaller` + `ExePackage FBInstaller`) and `PackageGroup vcredists` (`PackageGroupRef FlexBridgeInstaller`) | ☑ | Evidence: `Output\InstallerEvidence\flexbridge-parity\bundle.log` contains `Detected package: FBInstaller` and `Planned package: FBInstaller` (still needs clean-VM behavior validation) | +| FLEx Bridge offline prereq (bundle) | Bundle included/offered offline bridge prereq | `ExePackage` is defined in the shared prereqs include and referenced from the bundle chain via `PackageGroupRef` | `FLExInstaller/wix6/Shared/Common/Redistributables.wxi` (`PackageGroup FlexBridgeInstaller` + `ExePackage FBInstaller`) and `PackageGroup vcredists` (`PackageGroupRef FlexBridgeInstaller`) | ☑ | Evidence: `Output\InstallerEvidence\flexbridge-parity\bundle.log` contains `Detected package: FBInstaller` and `Planned package: FBInstaller` (still needs clean-VM behavior validation) | | MSI ICE validation | Typically implicit (or run manually) | Explicit `wix.exe msi validate -sice ICE60 -wx` in build | `FLExInstaller/wix6/FieldWorks.Installer.wixproj` (`WindowsInstallerValidation`) | ☑ | Present in Release build output | | | | | | | | | | | | | | | diff --git a/specs/001-wix-v6-migration/plan.md b/specs/001-wix-v6-migration/plan.md index 4c6f27848f..595f3e6441 100644 --- a/specs/001-wix-v6-migration/plan.md +++ b/specs/001-wix-v6-migration/plan.md @@ -7,50 +7,51 @@ ## Summary -Run WiX 3 and WiX 6 installers in parallel during a transition period. WiX 3 remains the **default** installer build in the `FLExInstaller/` root, while WiX 6 is moved under `FLExInstaller/wix6/` as an opt-in path. This preserves the release/9.3-compatible layout while continuing the WiX 6 migration work (SDK-style projects, MSBuild targets, and in-tree shared code). +Finish the WiX 6 installer migration while keeping the WiX 3 path available as a temporary fallback. The current repo still defaults installer builds to WiX 3 through `Build/InstallerBuild.proj`, but the migration target is WiX 6 as the primary/default path once validation gates pass. WiX 6 authoring lives under `FLExInstaller/wix6/` and uses SDK-style projects, MSBuild targets, and in-tree shared code. To keep the migration maintainable, we also standardize on C# 8 language features for managed code (while keeping nullable reference type analysis disabled initially to avoid warnings-as-errors churn). -Installer testing will be performed on the **local development PC** only (no Hyper-V or Windows Sandbox lanes). +Installer testing should prefer clean VM evidence for release decisions. Local development PC runs are useful for fast triage, but they are not enough to sign off upgrade, ARP, uninstall, or offline behavior. ## Transition Plan: Parallel WiX 3 + WiX 6 Installers (NEW) ### Objectives -- Restore the WiX 3 installer project from `release/9.3` and keep it buildable. -- Add a **toolset selection switch** (default **WiX 3**, opt-in **WiX 6**). +- Keep the WiX 3 fallback buildable only while WiX 6 validation is incomplete. +- Add a **toolset selection switch** (currently default **WiX 3**, migration target **WiX 6**). - Keep WiX 3 inputs isolated from WiX 6 schema changes. +- Add a tracked decision point for switching the default to WiX 6. ### Known changes that can break WiX 3 (must be reversed or isolated) - `FLExInstaller/*.wxi` now contain WiX 4+/v6 namespaces and constructs (breaks WiX 3). - `Build/Installer.targets` was rewritten for the WiX 6 MSBuild pipeline (WiX 3 batch flow removed). -- Legacy `PatchableInstaller` expectations were removed/quarantined; WiX 3 requires these inputs. +- Legacy `PatchableInstaller` expectations were removed/quarantined. `PatchableInstaller/` is not present in this worktree; some legacy CI workflows still checkout `sillsdev/genericinstaller` into that path. - Custom action wiring switched to WiX 4+ binaries (e.g., `Wix4UtilCA_X64`). -### Files/folders to pull from `release/9.3` +### Current legacy fallback inputs -Pull these **verbatim** from the `release/9.3` worktree to restore WiX 3 support: +The current worktree has these legacy fallback pieces: -- `Build/Installer.targets` (legacy WiX 3 orchestration + batch script invocation) +- `Build/Installer.Wix3.targets` (legacy WiX 3 orchestration + batch script invocation) - `FLExInstaller/*.wxi` (WiX 3-compatible includes, preserved in root) -- `PatchableInstaller/` full tree (BaseInstallerBuild, Common, CustomActions, ProcRunner, CreateUpdatePatch, libs, resources, `Directory.Build.props`, README, `.gitignore`, `.gitattributes`) +- No in-tree `PatchableInstaller/` folder. Existing base/patch installer workflows still checkout the external genericinstaller repo for legacy jobs and must be cleaned up or explicitly labeled as legacy. ### Build + Documentation updates -- Add `InstallerToolset=Wix3|Wix6` (default **Wix3**) to `build.ps1` and `Build/Orchestrator.proj`. -- Add explicit targets: `BuildInstallerWix3` and `BuildInstallerWix6` (with `BuildInstaller` routing to Wix3 by default). +- Add `InstallerToolset=Wix3|Wix6` (current default **Wix3**) to `build.ps1` and `Build/InstallerBuild.proj`. +- Route `BuildInstaller` through the selected imported target and keep `BuildInstallerWix6` as an explicit WiX 6 entry point. - Move WiX 6 assets to `FLExInstaller/wix6/` (projects + shared authoring) and keep WiX 3 in root. - Split WiX 3 vs WiX 6 include paths (root vs `FLExInstaller/wix6/Shared/`) to avoid toolset conflicts. -- Update docs (`ReadMe.md`, `specs/001-wix-v6-migration/quickstart.md`, `FLExInstaller/COPILOT.md`) to describe both build paths. -- Update CI to build Wix3 by default, plus an opt-in Wix6 job or flag. +- Update docs (`ReadMe.md`, `specs/001-wix-v6-migration/quickstart.md`, `FLExInstaller/COPILOT.md`) to describe WiX 6 as the migration target and WiX 3 as fallback. +- Update CI to add a WiX 6 installer lane with artifact upload, and retire or explicitly label legacy genericinstaller-dependent jobs. ## Technical Context **Language/Version**: WiX Toolset v6, MSBuild (VS 2022), C# 8 (Custom Actions; nullable analysis disabled initially) **Primary Dependencies**: WixToolset.Sdk, WixToolset.UI.wixext, WixToolset.Util.wixext, WixToolset.NetFx.wixext, WixToolset.Bal.wixext **Storage**: N/A (Installer) -**Testing**: Snapshot/compare-based evidence collection on local development PC; WixToolset.Heat for harvesting (if used) +**Testing**: Snapshot/compare-based evidence collection; clean VM runs for release gates; local development PC runs for triage; WixToolset.Heat v6 for harvesting **Target Platform**: Windows (x64/x86) **Project Type**: Installer (MSI & Bundle) **Performance Goals**: N/A @@ -66,7 +67,7 @@ Pull these **verbatim** from the `release/9.3` worktree to restore WiX 3 support - **III. Internationalization**: Installer must support multiple locales (FR-005). - **IV. User-Centered Stability**: Offline installation support is critical (FR-004). - **V. Licensing**: WiX is open source (MS-RL/MIT). -- **VI. Documentation Fidelity**: COPILOT.md in FLExInstaller and PatchableInstaller must be updated. +- **VI. Documentation Fidelity**: `FLExInstaller` docs and this spec must describe the actual WiX 6 route, artifact paths, and legacy fallback state. ## Project Structure @@ -95,22 +96,18 @@ FLExInstaller/wix6/ Shared/ ... -PatchableInstaller/ # Restored for WiX 3 path (legacy batch pipeline) - BaseInstallerBuild/ - ... - Build/ Installer.targets # WiX 6 targets Installer.Wix3.targets # WiX 3 targets ... ` -**Structure Decision**: Keep WiX 3 authoring in `FLExInstaller/` root (minimize release/9.3 diffs) and move WiX 6 authoring under `FLExInstaller/wix6/`. PatchableInstaller is restored in-tree for WiX 3, while WiX 6 continues to use MSBuild + SDK-style projects with in-tree shared code. +**Structure Decision**: Keep WiX 3 authoring in `FLExInstaller/` root as fallback and WiX 6 authoring under `FLExInstaller/wix6/`. `PatchableInstaller/` is not restored in-tree; any remaining genericinstaller dependency must be migrated into WiX 6 or isolated in explicitly legacy CI jobs. ## Complexity Tracking N/A - No constitution violations. -## Installer Verification: Local Development PC Only +## Installer Verification -Installer validation will be performed on the **local development PC** without sandboxing or Hyper-V. Evidence capture follows the same log collection conventions (bundle/MSI logs and screenshots where needed) but is executed directly on the developer machine. +Use local development PC validation for fast feedback, but use clean VM evidence for release-critical claims: WiX 3 to WiX 6 upgrade, ARP entry count, uninstall cleanup, online prerequisite behavior, and offline disconnected installs. Evidence capture follows the log collection conventions in `verification-matrix.md` and `golden-install-checklist.md`. diff --git a/specs/001-wix-v6-migration/quickstart.md b/specs/001-wix-v6-migration/quickstart.md index 0d2c79fd28..4d5b2620bc 100644 --- a/specs/001-wix-v6-migration/quickstart.md +++ b/specs/001-wix-v6-migration/quickstart.md @@ -12,46 +12,49 @@ To build the installer, run the following command from the repository root: ```powershell -# Build the installer (Debug configuration, default = WiX 3) +# Build the installer (Debug configuration, current default = WiX 3 fallback) ./build.ps1 -BuildInstaller # Build the WiX 6 installer (Debug) ./build.ps1 -BuildInstaller -InstallerToolset Wix6 -# Or run MSBuild directly (WiX 3 default) -msbuild Build/Orchestrator.proj /t:BuildInstaller /p:Configuration=Debug /p:Platform=x64 /p:config=release +# Or run MSBuild directly (WiX 3 fallback) +msbuild Build/InstallerBuild.proj /t:BuildInstaller /p:Configuration=Debug /p:Platform=x64 /p:InstallerToolset=Wix3 # Or run MSBuild directly (WiX 6) -msbuild Build/Orchestrator.proj /t:BuildInstaller /p:Configuration=Debug /p:Platform=x64 /p:config=release /p:InstallerToolset=Wix6 +msbuild Build/InstallerBuild.proj /t:BuildInstaller /p:Configuration=Debug /p:Platform=x64 /p:InstallerToolset=Wix6 -# Build Release version (WiX 3 default) +# Build Release version (WiX 3 fallback) ./build.ps1 -BuildInstaller -Configuration Release # Build Release version (WiX 6) ./build.ps1 -BuildInstaller -Configuration Release -InstallerToolset Wix6 -# Or run MSBuild directly (WiX 3 default) -msbuild Build/Orchestrator.proj /t:BuildInstaller /p:Configuration=Release /p:Platform=x64 /p:config=release +# Or run MSBuild directly (WiX 3 fallback) +msbuild Build/InstallerBuild.proj /t:BuildInstaller /p:Configuration=Release /p:Platform=x64 /p:InstallerToolset=Wix3 # Or run MSBuild directly (WiX 6) -msbuild Build/Orchestrator.proj /t:BuildInstaller /p:Configuration=Release /p:Platform=x64 /p:config=release /p:InstallerToolset=Wix6 +msbuild Build/InstallerBuild.proj /t:BuildInstaller /p:Configuration=Release /p:Platform=x64 /p:InstallerToolset=Wix6 ``` +The migration target is WiX 6. `Build/InstallerBuild.proj` currently defaults `InstallerToolset` to `Wix3`, so pass `-InstallerToolset Wix6` or `/p:InstallerToolset=Wix6` whenever validating the migration path. + ## Artifacts -WiX 3 build artifacts are produced under `FLExInstaller/bin///` (bundle outputs are culture-specific under `en-US/`). +WiX 3 fallback artifacts are produced under `FLExInstaller/bin///` (bundle outputs are culture-specific under `en-US/`). This path is retained only for transition safety. -WiX 6 build artifacts are produced under `FLExInstaller/wix6/bin///` (bundle outputs are culture-specific under `en-US/`). +WiX 6 build artifacts are produced under `FLExInstaller/wix6/bin///` (MSI outputs are culture-specific under `en-US/`). - `FieldWorks.msi`: The main MSI package. -- `FieldWorksBundle.exe`: The bootstrapper bundle (includes prerequisites). +- `FieldWorksBundle.exe`: The online bootstrapper bundle. +- `FieldWorksOfflineBundle.exe`: The offline bootstrapper bundle with local prerequisite payloads embedded. - `*.wixpdb`: Debug symbols for MSI/bundle. ## Customization via MSBuild properties (FR-008) Override installer identity/versioning by passing MSBuild properties. Common properties: -- **WiX 3 default path** (`FLExInstaller/` + `PatchableInstaller/`): +- **WiX 3 fallback path** (`FLExInstaller/` plus legacy genericinstaller inputs when needed): - `ApplicationName`, `SafeApplicationName`, `Manufacturer`, `SafeManufacturer` - `UpgradeCodeGuid`, `BuildVersionSegment` - `InstallersBaseDir`, `AppBuildDir`, `BinDirSuffix`, `DataDirSuffix`, `L10nDirSuffix`, `FontDirSuffix` @@ -76,6 +79,8 @@ Examples: - [ ] `FLExInstaller/wix6/bin/x64/Debug/en-US/FieldWorks.wixpdb` - [ ] `FLExInstaller/wix6/bin/x64/Debug/FieldWorksBundle.exe` - [ ] `FLExInstaller/wix6/bin/x64/Debug/FieldWorksBundle.wixpdb` +- [ ] `FLExInstaller/wix6/bin/x64/Debug/FieldWorksOfflineBundle.exe` +- [ ] `FLExInstaller/wix6/bin/x64/Debug/FieldWorksOfflineBundle.wixpdb` ## Troubleshooting diff --git a/specs/001-wix-v6-migration/research.md b/specs/001-wix-v6-migration/research.md index d3da394dc7..01c75816c3 100644 --- a/specs/001-wix-v6-migration/research.md +++ b/specs/001-wix-v6-migration/research.md @@ -10,7 +10,7 @@ **Findings**: - `PatchableInstaller` contains `BaseInstallerBuild` (UI, common wxs) and `CustomActions`. - `FLExInstaller` references these files. -- **Decision**: Move `PatchableInstaller/BaseInstallerBuild` and `PatchableInstaller/CustomActions` into `FLExInstaller/Shared` or similar. +- **Decision**: Move/migrate `PatchableInstaller/BaseInstallerBuild` and `PatchableInstaller/CustomActions` behavior into `FLExInstaller/wix6/Shared`. - **Task**: Audit `FLExInstaller` .wxs files to find all references to `PatchableInstaller`. ## 2. WiX v6 Conversion @@ -44,7 +44,7 @@ - `buildMsi.bat` uses `heat.exe` to harvest `MASTERBUILDDIR` and `MASTERDATADIR`. - **Decision**: - Create `Build/Installer.targets` to define the build process. - - Migrate `ProcRunner` to `FLExInstaller/Shared/ProcRunner`. + - Migrate `ProcRunner` to `FLExInstaller/wix6/Shared/ProcRunner`. - Use `` in `.wixproj` to replace `heat.exe` calls. - **Task**: Define targets for `Restore`, `Build`, `Sign`, `Publish`. - **Prerequisites**: Use `DownloadFile` task or similar in MSBuild to fetch .NET/C++ redistributables if not present (FR-011). diff --git a/specs/001-wix-v6-migration/spec.md b/specs/001-wix-v6-migration/spec.md index d92392bf16..997698a43c 100644 --- a/specs/001-wix-v6-migration/spec.md +++ b/specs/001-wix-v6-migration/spec.md @@ -2,7 +2,7 @@ **Feature Branch**: 001-wix-v6-migration **Created**: 2025-12-11 -**Status**: Draft +**Status**: Draft - harmonized with current repo state **Input**: User description: "Migrate WiX 3.11 installer to WiX v6, modernize build process, and remove genericinstaller submodule." ## User Scenarios & Testing *(mandatory)* @@ -13,7 +13,7 @@ A developer wants to build the installer locally to verify changes or create a r **Why this priority**: This is the core development workflow. Without a working build, no other testing or deployment is possible. -**Independent Test**: Run the installer build on a clean developer machine (e.g., `./build.ps1 -BuildInstaller`, or `msbuild Build/Orchestrator.proj /t:BuildInstaller /p:Configuration=Debug /p:Platform=x64 /p:config=release`) and verify that a valid .exe or .msi is produced. +**Independent Test**: Run the WiX 6 installer build on a clean developer machine (e.g., `./build.ps1 -BuildInstaller -InstallerToolset Wix6`, or `msbuild Build/InstallerBuild.proj /t:BuildInstaller /p:Configuration=Debug /p:Platform=x64 /p:InstallerToolset=Wix6`) and verify that the MSI plus online and offline bundle artifacts are produced. **Acceptance Scenarios**: @@ -24,7 +24,7 @@ A developer wants to build the installer locally to verify changes or create a r ### User Story 2 - CI Builds Installer (Priority: P1) -The Continuous Integration (CI) system needs to build the installer automatically on code changes. The workflow must be updated to use the new WiX v6 build process. +The Continuous Integration (CI) system needs to build the installer automatically on code changes. The workflow must be updated to use the WiX 6 build process as the migration target and keep any WiX 3 lane explicitly transitional. **Why this priority**: Automated builds are essential for quality assurance and release management. @@ -32,7 +32,7 @@ The Continuous Integration (CI) system needs to build the installer automaticall **Acceptance Scenarios**: -1. **Given** a push to the repository, **When** the CI workflow triggers, **Then** the installer build step executes the new MSBuild targets and succeeds. +1. **Given** a push to the repository, **When** the CI workflow triggers, **Then** a WiX 6 installer build step executes the SDK-style MSBuild targets and succeeds. 2. **Given** the build completes, **When** artifacts are inspected, **Then** the installer files are present and valid. --- @@ -87,56 +87,61 @@ An end user (or admin) wants to install the product on a machine without interne - **FR-010**: The installer UI MUST be ported from the existing custom implementation to maintain the "Dual Directory" selection (App + Project Data) and custom feature tree behavior. - **FR-011**: The build process MUST automatically download required prerequisites (e.g., .NET runtimes, C++ redistributables) during the build phase using MSBuild targets, rather than relying on pre-existing files. -## Transition Plan: Parallel WiX 3 + WiX 6 Installers (NEW) +## Current Migration State: WiX 6-first with Temporary WiX 3 Fallback -**Goal**: Run WiX 3 and WiX 6 installers in parallel for a transition period, with WiX 3 as the **default** installer build, and WiX 6 as an opt-in path. +**Goal**: Finish the WiX 6 migration first, then switch the default installer build to WiX 6 once the validation gates pass. The current repo still keeps a WiX 3 fallback path for transition safety, but WiX 3 is no longer the desired end state. + +**Current repo snapshot**: + +- `Build/InstallerBuild.proj` currently defaults `InstallerToolset` to `Wix3` and conditionally imports `Build/Installer.Wix3.targets` or `Build/Installer.targets`. +- The WiX 6 route is explicit: `./build.ps1 -BuildInstaller -InstallerToolset Wix6` or `msbuild Build/InstallerBuild.proj /t:BuildInstaller /p:InstallerToolset=Wix6`. +- The WiX 6 route builds `FLExInstaller/wix6/FieldWorks.Bundle.wixproj` and `FLExInstaller/wix6/FieldWorks.OfflineBundle.wixproj`. +- `PatchableInstaller/` is not present in this worktree. Existing CI workflows still checkout `sillsdev/genericinstaller` as `PatchableInstaller/` for legacy installer jobs. +- `build.ps1 -BuildPatch -InstallerToolset Wix6` is not a supported path until WiX 6 patch/MSP work is designed and implemented. ### Transitional Requirements - **TR-001**: The build system MUST support building **either WiX 3 or WiX 6** installers via an explicit MSBuild property or build script flag. -- **TR-002**: The **default installer build** (no property/flag) MUST produce the **WiX 3** installer artifacts. +- **TR-002**: The current default installer build produces WiX 3 artifacts. This is a temporary compatibility state, not the migration target. - **TR-003**: WiX 3 build inputs MUST be preserved and isolated from WiX 6 schema changes (no shared `.wxi` files between toolsets). -- **TR-004**: CI MUST build WiX 3 by default, with an additional opt-in path for WiX 6 builds. -- **TR-005**: Documentation MUST clearly describe how to build **WiX 3 (default)** and **WiX 6 (opt-in)** installers, including artifact locations. +- **TR-004**: CI MUST add a WiX 6 installer lane that does not require `genericinstaller` or `PatchableInstaller/`; any WiX 3 lane must be named as legacy/transition. +- **TR-005**: Documentation MUST clearly describe how to build WiX 6 installers, where artifacts are produced, and which WiX 3/genericinstaller dependencies remain transitional. +- **TR-006**: Before the migration is considered complete, the repo default MUST be changed from WiX 3 to WiX 6, or an explicit release decision must document why that switch is deferred. -**Note**: During the transition, **FR-001/FR-002/FR-003 apply to the WiX 6 path only**. The WiX 3 path intentionally retains its legacy build flow until the transition ends. +**Note**: During the transition, **FR-001/FR-002/FR-003 apply to the WiX 6 path only**. The WiX 3 path intentionally retains its legacy build flow only as a fallback until the transition ends. ### Changes in this spec that can break WiX 3 (must be reversed or isolated) - **WiX 6 schema changes to `FLExInstaller/*.wxi`** (namespace and element changes) make those files incompatible with WiX 3. - **`Build/Installer.targets` rewritten for WiX 6** and **WiX 3 batch pipelines removed/quarantined** (see tasks T040–T043, T030–T031). -- **Removal of `PatchableInstaller`/`genericinstaller` assumptions** eliminates WiX 3 build inputs and scripts. +- **Removal of `PatchableInstaller`/`genericinstaller` assumptions** eliminates WiX 3 build inputs and scripts. The current worktree has removed the in-tree `PatchableInstaller/` folder, but CI still checks out the external repo for legacy jobs. - **Custom action wiring changed to WiX 4+ binaries** (e.g., `Wix4UtilCA_X64`), incompatible with WiX 3. -### Files/folders to restore from `release/9.3` worktree (WiX 3) +### Current legacy WiX 3 fallback state -Pull these **verbatim** from the `release/9.3` worktree to re-introduce the WiX 3 installer project: +The transition originally considered restoring the full WiX 3 project tree. The current worktree instead has this split: -- `Build/Installer.targets` (WiX 3 build orchestration, batch script invocation, and staging rules) -- `FLExInstaller/*.wxi` (WiX 3-compatible includes, kept in root to minimize changes from `release/9.3`) -- `PatchableInstaller/` (full tree) - - `BaseInstallerBuild/` (WiX 3 authoring, dialogs, batch scripts) - - `Common/` (shared includes/templates) - - `CustomActions/` (legacy CA project and build scripts) - - `ProcRunner/` - - `CreateUpdatePatch/` - - `libs/` - - `resources/` - - `Directory.Build.props`, `README.md`, `.gitignore`, `.gitattributes` +- `Build/Installer.Wix3.targets` contains the legacy WiX 3 orchestration. +- `FLExInstaller/*.wxi` remains the WiX 3-compatible FieldWorks include baseline. +- `PatchableInstaller/` is **not** restored in-tree. +- `.github/workflows/base-installer-cd.yml` and `.github/workflows/patch-installer-cd.yml` still checkout `sillsdev/genericinstaller` into `PatchableInstaller/` for legacy CI behavior. + +This means “WiX 6-first” cleanup is not complete: the repo has removed the in-tree generic installer, but CI still depends on it for some legacy jobs. Do not reintroduce a `genericinstaller` submodule as the migration solution; migrate any remaining required behavior into the WiX 6 path or keep it behind an explicitly legacy job until that job is retired. ### What else must change (high-level) - **Split installer inputs by toolset** with **WiX 3 in `FLExInstaller/` root** and **WiX 6 under `FLExInstaller/wix6/`**, and prevent cross-use. - **Relocate WiX 6 assets** (projects + shared authoring) under `FLExInstaller/wix6/` to avoid collisions with WiX 3 authoring. -- **Add toolset selection property** (e.g., `InstallerToolset=Wix3|Wix6`) to `build.ps1` and `Build/Orchestrator.proj` with **default = Wix3**. -- **Provide dual build targets** in MSBuild (e.g., `BuildInstallerWix3`, `BuildInstallerWix6`). -- **Update documentation + CI** so WiX 3 is the default build and WiX 6 is opt-in. +- **Add toolset selection property** (`InstallerToolset=Wix3|Wix6`) to `build.ps1` and `Build/InstallerBuild.proj`. The current default is Wix3; the migration target is to change the default to Wix6 after validation. +- **Provide clear MSBuild entry points** for the imported WiX 3 and WiX 6 routes. Current state has `BuildInstaller` imported per toolset plus `BuildInstallerWix6`; an explicit WiX 3 alias is not required if `InstallerToolset=Wix3` remains documented. +- **Update documentation + CI** so WiX 6 is the primary migration path, and WiX 3 is described as transitional fallback. ### Success Criteria -- **Build Success**: WiX 3 (default) and WiX 6 (opt-in) installers both build successfully on developer machines and CI. -- **No Legacy Submodule**: The genericinstaller submodule remains removed, even though `PatchableInstaller/` is restored in-tree for WiX 3. -- **Functional Parity**: Both installer paths provide the same installation options (features, locales, offline/online) as the previous version. +- **Build Success**: WiX 6 online bundle, offline bundle, MSI, and `.wixpdb` files build successfully on developer machines and CI without a `genericinstaller` checkout. +- **Default Switch Ready**: `InstallerToolset=Wix6` is proven enough that the repo can make WiX 6 the default installer route, or the remaining blockers are explicitly tracked. +- **No Legacy Submodule**: The genericinstaller submodule remains removed, and no WiX 6 build or validation lane requires a `PatchableInstaller/` checkout. +- **Functional Parity**: The WiX 6 path provides the same installation options (features, locales, offline/online) as the previous version. - **Modernization (WiX 6 path)**: WiX 6 build uses MSBuild + WiX 6 tools only, with no legacy batch scripts in the WiX 6 path. ### Assumptions @@ -146,6 +151,19 @@ Pull these **verbatim** from the `release/9.3` worktree to re-introduce the WiX - WiX v6 supports the specific prerequisites (e.g., .NET Framework versions) required by the application. - The team accepts the "breaking changes" inherent in moving to WiX v6 (e.g., different CLI tools, different project file format). +### Patch/MSP Support (future / out of scope for first WiX 6 migration) + +The first migration target is a working WiX 6 MSI + online/offline bundle using major upgrades. Patch infrastructure (MSP generation, `PatchBaseline`, base-build `.wixpdb` retention, and a WiX 6 `BuildPatch` target) is intentionally separate work. + +Future patch work must prove: + +- Component GUID and file identity stability across base/update builds. +- Base MSI and `.wixpdb` artifact retention for patch creation. +- A WiX 6 replacement for the legacy `PatchableInstaller/CreateUpdatePatch` and `buildPatch.bat` flow. +- MSP apply, repair, uninstall, and upgrade behavior on clean machines. + +Until that work exists, `build.ps1 -BuildPatch -InstallerToolset Wix6` should be treated as unsupported. + ### Key Entities - **Installer Bundle**: The executable that manages prerequisites and the MSI installation. diff --git a/specs/001-wix-v6-migration/tasks.md b/specs/001-wix-v6-migration/tasks.md index 5932695900..4e3534f063 100644 --- a/specs/001-wix-v6-migration/tasks.md +++ b/specs/001-wix-v6-migration/tasks.md @@ -1,43 +1,44 @@ # Tasks: WiX v6 Migration **Feature**: WiX v6 Migration -**Status**: Planned +**Status**: In Progress - WiX 6-first migration, legacy WiX 3 fallback still present **Spec**: [specs/001-wix-v6-migration/spec.md](specs/001-wix-v6-migration/spec.md) ## Phase 1: Setup (Project Initialization) **Goal**: Initialize the new WiX v6 project structure and migrate shared code from `genericinstaller`. -- [x] T001 Create `FLExInstaller/Shared` directory structure -- [x] T002 Copy `PatchableInstaller/BaseInstallerBuild` content to `FLExInstaller/Shared/Base` -- [x] T003 Copy `PatchableInstaller/CustomActions` content to `FLExInstaller/Shared/CustomActions` -- [x] T036 Copy `PatchableInstaller/ProcRunner` content to `FLExInstaller/Shared/ProcRunner` +- [x] T001 Create `FLExInstaller/wix6/Shared` directory structure +- [x] T002 Copy/migrate legacy `BaseInstallerBuild` content to `FLExInstaller/wix6/Shared/Base` +- [x] T003 Copy/migrate legacy `CustomActions` content to `FLExInstaller/wix6/Shared/CustomActions` +- [x] T036 Copy/migrate legacy `ProcRunner` content to `FLExInstaller/wix6/Shared/ProcRunner` - [x] T004 Create `FLExInstaller/wix6/FieldWorks.Installer.wixproj` (SDK-style) - [x] T005 Create `FLExInstaller/wix6/FieldWorks.Bundle.wixproj` (SDK-style) - [x] T006 Create `Build/Installer.targets` skeleton -## Phase 0b: Parallel WiX 3 + WiX 6 Transition (NEW) +## Phase 0b: Parallel WiX 3 + WiX 6 Transition -**Goal**: Reintroduce WiX 3 installer inputs, keep WiX 6 available, and make WiX 3 the default build. +**Goal**: Keep the WiX 3 path available as a temporary fallback while finishing the WiX 6 migration and preparing to make WiX 6 the default installer path. - [x] T080 Restore `Build/Installer.targets` from `release/9.3` as `Build/Installer.Wix3.targets` (preserve legacy batch flow) - [x] T081 Restore `FLExInstaller/*.wxi` from `release/9.3` into `FLExInstaller/` root (WiX 3 stays in main path) -- [x] T082 Restore `PatchableInstaller/` tree from `release/9.3` (BaseInstallerBuild, Common, CustomActions, ProcRunner, CreateUpdatePatch, libs, resources, props/README) +- [x] T082 Keep `PatchableInstaller/` out of the worktree; migrate required genericinstaller behavior into `FLExInstaller/wix6/` instead - [x] T083 Move WiX 6 assets under `FLExInstaller/wix6/` (projects + shared authoring) -- [x] T084 Add `InstallerToolset=Wix3|Wix6` selection to `build.ps1` and `Build/Orchestrator.proj` (default **Wix3**) -- [x] T085 Add explicit targets `BuildInstallerWix3` and `BuildInstallerWix6` with `BuildInstaller` routed to Wix3 by default +- [x] T084 Add `InstallerToolset=Wix3|Wix6` selection to `build.ps1` and `Build/InstallerBuild.proj` (current default **Wix3**) +- [x] T085 Route `BuildInstaller` through the selected toolset and add `BuildInstallerWix6` as an explicit WiX 6 entry point - [x] T086 Ensure WiX 3 and WiX 6 include paths are isolated (root vs `FLExInstaller/wix6/Shared/`) -- [x] T087 Update documentation for dual builds (ReadMe, quickstart, `FLExInstaller/COPILOT.md`) and call out default Wix3 -- [x] T088 Update CI to build Wix3 by default and add an opt-in Wix6 lane +- [x] T087 Update documentation for dual builds (ReadMe, quickstart, `FLExInstaller/COPILOT.md`) and call out current Wix3 default +- [ ] T088 Update CI to build WiX 6 as the migration lane, with any WiX 3 jobs clearly marked legacy/transition - [x] T089 Add guardrails that fail the Wix3 build if WiX 6 namespaces/refs leak into Wix3 inputs - [x] T090 Define MSBuild override properties for installer customization (FR-008) and document them for both toolsets +- [ ] T095 Decide and implement the default switch from `InstallerToolset=Wix3` to `InstallerToolset=Wix6`, or document the release blocker keeping WiX 3 as default ## Phase 2: Foundational (Blocking Prerequisites) **Goal**: Convert core WiX source files and Custom Actions to be compatible with WiX v6. - [x] T007 [P] Convert `FLExInstaller/*.wxs` and `*.wxl` files to WiX v6 syntax using `wix convert` -- [x] T008 [P] Convert `FLExInstaller/Shared/Base/*.wxs` and `*.wxl` files to WiX v6 syntax +- [x] T008 [P] Convert `FLExInstaller/wix6/Shared/Base/*.wxs` and `*.wxl` files to WiX v6 syntax - [x] T009 [P] Update Custom Actions project to target .NET 4.8 - [x] T037 [P] Update `ProcRunner` project to target .NET 4.8 - [x] T010 Implement `DownloadPrerequisites` target in `Build/Installer.targets` (FR-011) @@ -48,11 +49,11 @@ ### Phase 2a: Toolchain Purge (WiX 6-only) -**Goal**: Remove all remaining WiX 3 build-time tool dependencies and legacy build scripts from the active build path. +**Goal**: Remove all remaining WiX 3 build-time tool dependencies and legacy build scripts from the WiX 6 active build path. - [x] T040 Ensure installer build does not rely on WiX 3 binaries on PATH (harvesting uses WixToolset.Heat v6 from NuGet) - [x] T041 Remove reliance on `candle.exe`/`light.exe`/`insignia.exe` in any invoked build step (MSBuild/CI) -- [x] T042 Remove or quarantine legacy `build*.bat` scripts under `FLExInstaller/Shared/Base/` so they cannot be mistaken as source-of-truth +- [x] T042 Remove or quarantine legacy `build*.bat` scripts under `FLExInstaller/wix6/Shared/Base/` so they cannot be mistaken as source-of-truth - [x] T043 Add a pinned, reproducible acquisition path for WiX 6 tooling (NuGet/MSBuild SDK) used by both local builds and CI ## Phase 3: User Story 1 - Developer Builds Installer (P1) @@ -80,7 +81,7 @@ **Goal**: Automate the installer build in CI. - [ ] T020 [US2] Update `.github/workflows/main.yml` (or relevant workflow) to install WiX v6 -- [ ] T021 [US2] Update CI workflow to call `msbuild Build/Orchestrator.proj /t:BuildInstaller` +- [ ] T021 [US2] Update CI workflow to call `msbuild Build/InstallerBuild.proj /t:BuildInstaller /p:InstallerToolset=Wix6` - [ ] T022 [US2] Configure CI environment variables for Code Signing - [ ] T023 [US2] Verify CI build artifacts are uploaded @@ -88,8 +89,9 @@ **Goal**: CI proves the installer is fully WiX 6-based and does not rely on external templates. -- [ ] T047 [US2] Add a CI check that fails if the build invokes WiX 3 tools (`heat.exe`, `candle.exe`, `light.exe`, `insignia.exe`) +- [ ] T047 [US2] Add a CI check that fails if the WiX 6 build invokes legacy WiX 3 tools (`candle.exe`, `light.exe`, `insignia.exe`) or an unpinned system `heat.exe` - [ ] T048 [US2] Add a CI check that fails if any build step requires a `PatchableInstaller/` directory or `genericinstaller` checkout +- [ ] T096 [US2] Add a WiX 6 installer CI job that runs `./build.ps1 -BuildInstaller -InstallerToolset Wix6` and uploads MSI, online bundle, offline bundle, and `.wixpdb` artifacts ## Phase 5: User Story 3 - End User Installation (Online) (P1) @@ -116,16 +118,16 @@ **Goal**: Ensure the installer works for offline users (bundled prerequisites). -- [ ] T027 [US4] Configure Bundle to support offline layout creation -- [ ] T028 [US4] Verify `msbuild /t:Publish` (or similar) creates offline layout +- [x] T027 [US4] Configure WiX 6 offline bundle authoring to embed local prerequisites +- [ ] T028 [US4] Verify the offline bundle artifact installs successfully without network access - [ ] T029 [US4] Test Offline Install on disconnected VM (Manual Verification) ### Phase 6a: Offline Bundle Wiring (WiX 6) **Goal**: Produce an offline-capable bundle from WiX 6 build outputs (not legacy candle/light scripts). -- [ ] T055 [US4] Wire `OfflineBundle.wxs` into the WiX 6 build (second bundle project or conditional compile/output) -- [ ] T056 [US4] Define the offline distribution format (single EXE vs layout folder) and make it reproducible via MSBuild target +- [x] T055 [US4] Wire `OfflineBundle.wxs` into the WiX 6 build via `FieldWorks.OfflineBundle.wixproj` and `Build/Installer.targets` +- [x] T056 [US4] Define the offline distribution format as a single `FieldWorksOfflineBundle.exe` artifact produced by MSBuild - [ ] T057 [US4] Verify offline prerequisites are embedded/available (NetFx, VC++ redists, FLEx Bridge offline) ## Phase 7: Polish & Cross-Cutting Concerns @@ -143,8 +145,9 @@ **Goal**: Ensure the repository no longer instructs or requires developers/CI to fetch `genericinstaller`/`PatchableInstaller`. - [x] T058 Remove any developer setup steps that clone/expect `PatchableInstaller` (e.g., `Setup-Developer-Machine.ps1`) -- [x] T059 Remove workspace configuration references that pull `genericinstaller` into the worktree (optional but recommended) -- [x] T060 Repo-wide verification: no remaining references to `PatchableInstaller` in active build paths (docs/scripts/targets) +- [ ] T059 Remove workflow references that pull `genericinstaller` into the worktree (`base-installer-cd.yml`, `patch-installer-cd.yml`) +- [ ] T060 Repo-wide verification: no remaining references to `PatchableInstaller` in WiX 6 active build paths, CI jobs, and developer setup docs +- [ ] T097 Retire or clearly label legacy WiX 3 CI jobs that still checkout `sillsdev/genericinstaller` as `PatchableInstaller/` ## Phase 8: Validation & Regression Proof @@ -174,6 +177,16 @@ - [ ] T093 Run Wix6 opt-in installer locally and archive MSI/bundle logs - [ ] T094 Document evidence locations and expectations for local-only testing in spec docs +### Phase 9: WiX 6 Upgrade and Patch Follow-up + +**Goal**: Separate the major-upgrade migration from future MSP patch work so the WiX 6 migration can finish without pretending patch support already exists. + +- [ ] T098 [US3] Validate WiX 3 to WiX 6 upgrade detection, including whether a package-level `` is required for Burn provider compatibility +- [ ] T099 [US3] Validate same-version major upgrades from WiX 6 dev builds do not create side-by-side installs +- [ ] T100 [US3] Diagnose ARP duplicate package entries and uninstall hang using bundle/MSI logs plus before/after snapshots +- [ ] T101 Define a separate WiX 6 patch/MSP design: `PatchBaseline`, base `.wixpdb` retention, component GUID stability, and a replacement for legacy `buildPatch.bat` +- [ ] T102 Implement WiX 6 `BuildPatch` only after T101 is approved and validated + ## Dependencies diff --git a/specs/001-wix-v6-migration/verification-matrix.md b/specs/001-wix-v6-migration/verification-matrix.md index 61c1698437..c74c989f27 100644 --- a/specs/001-wix-v6-migration/verification-matrix.md +++ b/specs/001-wix-v6-migration/verification-matrix.md @@ -6,12 +6,14 @@ - Treat each row as a *claim* that must be proven. - For each row, attach evidence (logs/screenshots/file listings) and record where it lives. - Prefer running on a clean VM snapshot and keeping evidence in a consistent location (e.g., `C:\Temp\FwInstallerEvidence\\...`). +- For failure triage, use [evidence-collection-checklist.md](evidence-collection-checklist.md). ## Evidence conventions - **Bundle log**: run the bundle with a log switch (recommended): `FieldWorks.exe /log C:\Temp\FwInstallerEvidence\bundle.log` - **MSI log**: run MSI with verbose logging: `msiexec /i FieldWorks.msi /l*v C:\Temp\FwInstallerEvidence\msi-install.log` - **Uninstall log**: `msiexec /x {PRODUCT-CODE} /l*v C:\Temp\FwInstallerEvidence\msi-uninstall.log` (or uninstall via ARP and capture bundle/MSI logs if possible) +- **Agent-script evidence**: repository helper scripts write under `Output/InstallerEvidence//`. When testing WiX 6 with `scripts/Agent/Invoke-Installer.ps1`, pass an explicit `-InstallerPath` pointing at `FLExInstaller/wix6/bin/...`; otherwise the default resolver may select legacy WiX 3 artifact paths. > Note: exact command-line switches can vary by bootstrapper; if `/log` is not accepted, capture `%TEMP%` logs and record their names/paths in the Evidence field. @@ -21,7 +23,9 @@ |---|---|---|---|---| | Toolchain | Build/install does not require `genericinstaller` checkout | Build in a fresh clone/worktree without genericinstaller present | Build logs showing success + no missing path errors | ☐ | | Toolchain | Build does not invoke WiX 3 tools (`candle.exe`, `light.exe`, `insignia.exe`) | Run build with command echoing enabled (or CI check) and search logs | Build logs (or CI log) demonstrating absence of those tool names (note: WiX v6 Heat is currently expected and emits `HEAT5149`) | ☐ | +| Toolchain | WiX 6 is the intended migration default | Inspect `Build/InstallerBuild.proj` default and build scripts; decide/switch default after gates | Change record or release decision explaining why `InstallerToolset` remains `Wix3` | ☐ | | Build outputs | Local build produces expected artifacts | Build installer and confirm output filenames/locations | File listing of output folder + hashes | ☑ | +| Build outputs | Offline bundle artifact is produced | Run `./build.ps1 -BuildInstaller -InstallerToolset Wix6` and inspect `FLExInstaller/wix6/bin/x64//` | File listing showing `FieldWorksOfflineBundle.exe` and `.wixpdb` | ☐ | | Staging | Installer input staging copies correct payload | Inspect staged input folder(s) and compare to expected | Folder tree / file listing of staged inputs | ☑ | | Harvesting | MSI contains harvested app files under APPFOLDER | Install, then verify files exist under chosen APPFOLDER; optionally inspect MSI tables | MSI log + file listing under install dir | ☐ | | Harvesting | MSI contains harvested data files under DATAFOLDER | Install with non-default data dir and verify data payload exists | MSI log + file listing under data dir | ☐ | @@ -29,6 +33,8 @@ | UI (features) | Feature selection UI matches expected feature tree | Choose a non-default feature subset and verify installed components reflect it | Screenshots + MSI log showing feature states | ☐ | | UI flow | Dialog flow and navigation behave correctly | Run through install and confirm Back/Next/Cancel behavior | Bundle log + screenshots of key dialogs | ☐ | | Upgrade | Major upgrade removes prior version without side-by-side | Install old version, then install new version; verify single entry + expected behavior | MSI/bundle logs for upgrade + ARP screenshot | ☐ | +| Upgrade | WiX 3 to WiX 6 bundle detection works across Burn provider changes | Install WiX 3 bundle, then run WiX 6 bundle; inspect detection/plan/apply logs | Bundle log showing related bundle/package detection, planned uninstall/upgrade, and final single ARP entry | ☐ | +| Upgrade | Provider-key compatibility decision is recorded | Extract baseline WiX 3 provider/package identity and compare to WiX 6 authoring | Notes showing whether package-level `` is needed, plus test evidence after any change | ☐ | | Upgrade | Data path lock behavior on upgrade (if required) | Upgrade with existing data folder and confirm rules (e.g., data path fixed) | MSI/bundle logs + screenshot of any explanation text | ☐ | | Registry | Install writes expected HKLM keys/values (paths + version) | After install, inspect registry values | Export of relevant registry keys (reg export) | ☐ | | Registry | Uninstall removes expected registry keys/values | Uninstall; confirm keys removed | Export before/after + uninstall log | ☐ | @@ -38,13 +44,15 @@ | Env vars | Environment variables are created/updated correctly | Check System Environment Variables (and PATH modifications) | Screenshot/export of env vars + reboot/logoff note | ☐ | | Env vars | Env vars removed/restored on uninstall | Uninstall; confirm env vars removed and PATH restored (as appropriate) | Before/after env snapshot + uninstall log | ☐ | | Custom actions | Custom actions run without blocking UI (no modal asserts) | Run install/uninstall/upgrade; check for assertion dialogs/hangs | Bundle/MSI logs + any trace log if enabled | ☐ | +| Uninstall | Uninstall via ARP completes without hang | Install WiX 6 bundle, uninstall from Settings/Apps, monitor completion | Bundle uninstall log, MSI uninstall log, Event Viewer entries, before/after snapshot | ☐ | | Prereqs (online) | Online bundle downloads prerequisites successfully | Install on VM with internet; observe downloads succeed | Bundle log + screenshot of progress | ☐ | | Prereqs (detect) | Prereq detection works (skips if already installed) | Preinstall VC++/.NET; run bundle and confirm it skips | Bundle log showing detection results | ☐ | | FLEx Bridge | Offline FLEx Bridge prerequisite is detected/installed as expected | Validate on clean VM and on VM with existing FLEx Bridge | Bundle log + registry evidence | ☐ | | Theme/license | Bundle theme and license UX appear correct | Run bundle and confirm theme + license link/text | Screenshot(s) | ☐ | -| Offline mode | Offline installer story works end-to-end | Build offline artifact/layout; install on disconnected VM | Bundle log + proof no network required | ☐ | +| Offline mode | Offline installer story works end-to-end | Build `FieldWorksOfflineBundle.exe`; install on disconnected VM | Bundle log + proof no network required + prerequisite detection/install evidence | ☐ | | Localization | At least one non-English locale builds and runs end-to-end | Build localized bundle/MSI; run and confirm UI strings load | Screenshot(s) + artifact listing | ☐ | | Signing | Signing is applied where required | Inspect signatures of MSI/EXE; ensure verification passes | Sigcheck output or file properties screenshots | ☐ | +| Patch/MSP | WiX 6 patch support is explicitly deferred or implemented | Review targets and patch design before using `-BuildPatch -InstallerToolset Wix6` | Design note or build/test evidence for WiX 6 `BuildPatch` | ☐ | ## Notes / deviations @@ -53,3 +61,5 @@ Record any deviations from expected behavior here, with links to logs/evidence a - 2025-12-17: Release build outputs + staging roots captured in [specs/001-wix-v6-migration/parity-check.md](specs/001-wix-v6-migration/parity-check.md) (hashes + staging root). - 2025-12-17: Bundle log captured at `Output\InstallerEvidence\20251217-local-bundle-passive\bundle.log` (local run; not a clean VM). - 2025-12-17: FLEx Bridge package is now present in the bundle chain: `Output\InstallerEvidence\flexbridge-parity\bundle.log` contains `Detected package: FBInstaller` and `Planned package: FBInstaller` (still needs clean-VM behavior validation). +- Current repo: `FieldWorks.OfflineBundle.wixproj` is wired into `Build/Installer.targets`; offline artifact production and disconnected-machine runtime behavior are separate claims and both need evidence. +- Current repo: `Build/InstallerBuild.proj` still defaults to `Wix3`; treat this as a migration blocker until switched or documented. diff --git a/specs/001-wix-v6-migration/wix3-to-wix6-audit.md b/specs/001-wix-v6-migration/wix3-to-wix6-audit.md index 19074ef2fd..af651cad0d 100644 --- a/specs/001-wix-v6-migration/wix3-to-wix6-audit.md +++ b/specs/001-wix-v6-migration/wix3-to-wix6-audit.md @@ -12,11 +12,12 @@ This worktree contains a WiX 6 (SDK-style) MSI + Burn bundle implementation with the critical installer UX pieces (dual-directory UI + feature tree) and the key registry/shortcut behaviors implemented in WiX authoring. -However, this is not yet a fully “WiX 6-native” build pipeline: +However, this is not yet a finished WiX 6 migration: -- The MSI project still **uses WiX 3 `heat.exe`** to harvest binaries/data, then runs **WiX 6 `wix.exe`** to compile the converted harvest output. -- The legacy WiX 3 batch scripts are still present (and appear to describe the historical pipeline), but the new “source of truth” build path is via MSBuild targets + the SDK-style `.wixproj` files. -- Several spec tasks remain incomplete (notably: end-to-end local artifact verification, CI integration, online/offline end-user validation). +- The MSI project now uses **WixToolset.Heat 6.0.2** from NuGet/repo packages to harvest binaries/data, then compiles with the WiX 6 SDK. +- The legacy WiX 3 batch scripts are still present for the fallback route, but the migration source of truth is the MSBuild target plus SDK-style `.wixproj` files under `FLExInstaller/wix6/`. +- Several spec tasks remain incomplete, notably: CI integration, WiX 3 to WiX 6 upgrade validation, ARP/uninstall diagnostics, and clean-machine online/offline end-user validation. +- `Build/InstallerBuild.proj` still defaults `InstallerToolset` to `Wix3`; this is a current-state mismatch with the WiX 6-first migration goal. ## Scope of audit @@ -47,6 +48,7 @@ This audit therefore treats “WiX 3.x” as **(FieldWorks FLExInstaller include - MSBuild target entry points: `Build/Installer.targets` - MSI project: `FLExInstaller/wix6/FieldWorks.Installer.wixproj` - Bundle project: `FLExInstaller/wix6/FieldWorks.Bundle.wixproj` +- Offline bundle project: `FLExInstaller/wix6/FieldWorks.OfflineBundle.wixproj` ### WiX authoring (MSI + bundle) @@ -81,13 +83,13 @@ This audit therefore treats “WiX 3.x” as **(FieldWorks FLExInstaller include | Component | WiX 3.x (legacy) implementation | WiX 6 (current) implementation | Parity | Notes / evidence | |---|---|---|---|---| -| MSI build pipeline | `genericinstaller/BaseInstallerBuild/buildMsi.bat` (heat → candle/light → sign) | `FLExInstaller/wix6/FieldWorks.Installer.wixproj` (heat → `wix.exe convert` → SDK compile) | PARTIAL | Still depends on `heat.exe` (WiX 3). The “WiX 6 compile” is via WixToolset.Sdk. | -| Bundle build pipeline | `genericinstaller/BaseInstallerBuild/buildExe.bat` (candle/light, online+offline, insignia signing) | `FLExInstaller/wix6/FieldWorks.Bundle.wixproj` (SDK bundle build) + `StageBundlePayloads` downloads | PARTIAL | The WiX 6 project only compiles `Shared/Base/Bundle.wxs`; offline bundle output is not clearly produced. | +| MSI build pipeline | `genericinstaller/BaseInstallerBuild/buildMsi.bat` (heat → candle/light → sign) | `FLExInstaller/wix6/FieldWorks.Installer.wixproj` (WixToolset.Heat 6.0.2 → SDK compile) | PASS | Harvesting is pinned to WixToolset.Heat 6.0.2 and no longer depends on WiX 3 tools on PATH. | +| Bundle build pipeline | `genericinstaller/BaseInstallerBuild/buildExe.bat` (candle/light, online+offline, insignia signing) | `FLExInstaller/wix6/FieldWorks.Bundle.wixproj` and `FLExInstaller/wix6/FieldWorks.OfflineBundle.wixproj` (SDK bundle builds) + payload staging | PARTIAL | Online and offline bundles are wired, but signing parity and clean-machine runtime validation are not yet proven. | | Artifact orchestration | (historically batch-driven) `buildBaseInstaller.bat` chains `buildMsi.bat` + `buildExe.bat` | `Build/Installer.targets` provides `BuildInstaller` target | PASS | `BuildInstaller` depends on staging + prerequisites + ProcRunner build. | | Input staging for harvest | (legacy assumed in external scripts; not in-repo baseline) | `Build/Installer.targets` target `StageInstallerInputs` | PASS | Copies Output + DistFiles + fonts + ICU + localizations into `BuildDir/FieldWorks_InstallerInput_*`. | | Harvesting app files | `buildMsi.bat` uses `heat.exe dir %MASTERBUILDDIR% ... -cg HarvestedAppFiles ... -dr APPFOLDER` | `FieldWorks.Installer.wixproj` target `HarvestAppAndData` uses `heat.exe dir $(_MasterBuildDirFull) ... -cg HarvestedAppFiles ... -dr APPFOLDER` | PASS | Same component group ID + same directory ref pattern. | | Harvesting data files | `buildMsi.bat` uses `heat.exe dir %MASTERDATADIR% ... -cg HarvestedDataFiles ... -dr HARVESTDATAFOLDER` | `FieldWorks.Installer.wixproj` target `HarvestAppAndData` uses `heat.exe dir $(_MasterDataDirFull) ... -cg HarvestedDataFiles ... -dr HARVESTDATAFOLDER` | PASS | Same component group ID + same directory ref pattern. | -| MSI major upgrade | (legacy expected) | `Framework.wxs` `` | PASS | Major upgrade is explicitly declared in MSI authoring. Downgrades are allowed; the newer product is removed early to avoid file-versioning blocks. | +| MSI major upgrade | (legacy expected) | `Framework.wxs` ``; bundles declare `RelatedBundle Action="Upgrade"` | PARTIAL | Major upgrade is authored, but WiX 3 to WiX 6 upgrade behavior, same-version dev upgrades, ARP results, and uninstall behavior still need evidence. | | ARP/installer metadata | (legacy expected) | `Framework.wxs` sets `ARPPRODUCTICON`, `ARPNOREMOVE`, `DISPLAYNAME`, `FULL_VERSION_NUMBER` | PASS | Matches the typical FieldWorks behavior (remove disabled, branded icon). | | Dual-directory UI (App + Data) | (legacy expected; custom dialogs) | `GIInstallDirDlg.wxs` + `Framework.wxs` sets `WIXUI_INSTALLDIR=APPFOLDER`, `WIXUI_PROJECTSDIR=DATAFOLDER` | PASS | Both directories are first-class and driven by custom UI. | | Custom feature selection UI | (legacy expected; custom dialog) | `GICustomizeDlg.wxs` + `CustomFeatures.wxi` | PASS | Feature tree is defined in include and shown via dialog flow. | @@ -97,29 +99,32 @@ This audit therefore treats “WiX 3.x” as **(FieldWorks FLExInstaller include | Registry: settings/data directories | (legacy expected) | `Framework.wxs` components `RegKeySettingsDir` and `HarvestDataDir` write HKLM values | PASS | Also uses `RegistrySearch` to read prior install values. | | Registry: uninstall cleanup | (legacy expected) | `Framework.wxs` uses `ForceDeleteOnUninstall="yes"` for HKLM key | PASS | Combined with conditional CA `DeleteRegistryVersionNumber` on uninstall. | | Shortcuts (desktop/start menu) | (legacy expected) | `Framework.wxs` components `ApplicationShortcutDesktop` and `ApplicationShortcutMenu` | PASS | Desktop + start menu + uninstall shortcut are present. | -| Start menu shortcuts (help/docs/utilities) | FieldWorks includes at `6a2d976e`: `FLExInstaller/CustomComponents.wxi` `StartMenuShortcuts` group | `FLExInstaller/Shared/Common/CustomComponents.wxi` `StartMenuShortcuts` group | PASS | Adds shortcuts like Help, Morphology Intro, Unicode Character Editor, EULA, and font documentation. | -| Environment variables | FieldWorks includes at `6a2d976e`: `FLExInstaller/CustomComponents.wxi` `FwEnvironmentVars` group | `FLExInstaller/Shared/Common/CustomComponents.wxi` `FwEnvironmentVars` group | PASS | Sets system env vars including `PATH` prefix, `FIELDWORKSDIR`, `ICU_DATA`, and `WEBONARY_API`. | -| URL protocol registration | FieldWorks includes at `6a2d976e`: `FLExInstaller/CustomComponents.wxi` registry under HKCR | `FLExInstaller/Shared/Common/CustomComponents.wxi` registry under HKCR | PASS | Registers `silfw` URL protocol and command handler. | +| Start menu shortcuts (help/docs/utilities) | FieldWorks includes at `6a2d976e`: `FLExInstaller/CustomComponents.wxi` `StartMenuShortcuts` group | `FLExInstaller/wix6/Shared/Common/CustomComponents.wxi` `StartMenuShortcuts` group | PASS | Adds shortcuts like Help, Morphology Intro, Unicode Character Editor, EULA, and font documentation. | +| Environment variables | FieldWorks includes at `6a2d976e`: `FLExInstaller/CustomComponents.wxi` `FwEnvironmentVars` group | `FLExInstaller/wix6/Shared/Common/CustomComponents.wxi` `FwEnvironmentVars` group | PASS | Sets system env vars including `PATH` prefix, `FIELDWORKSDIR`, `ICU_DATA`, and `WEBONARY_API`. | +| URL protocol registration | FieldWorks includes at `6a2d976e`: `FLExInstaller/CustomComponents.wxi` registry under HKCR | `FLExInstaller/wix6/Shared/Common/CustomComponents.wxi` registry under HKCR | PASS | Registers `silfw` URL protocol and command handler. | | ProcRunner install | (legacy expected) | `Framework.wxs` installs `ProcRunner_5.0.exe` under CommonFiles | PASS | Component `ProcRunner` is referenced by the main feature. | | Custom actions (path checks, close apps, etc.) | (legacy expected) | `Framework.wxs` defines CA entries from `CustomActions.CA.dll` + includes `CustomActionSteps.wxi` | PASS | Core CA hooks present; detailed sequencing is inside include. | -| Bundle prerequisites: .NET 4.8 | `genericinstaller/BaseInstallerBuild/Bundle.wxs` defines `NetFx48Web` | `FLExInstaller/Shared/Base/Bundle.wxs` references `NetFx48Web` | PASS | Online bootstrapper includes netfx group. | +| Bundle prerequisites: .NET 4.8 | `genericinstaller/BaseInstallerBuild/Bundle.wxs` defines `NetFx48Web` | `FLExInstaller/wix6/Shared/Base/Bundle.wxs` references `NetFx48Web` | PASS | Online bootstrapper includes netfx group. | | Bundle prerequisites: VC++ redists | (legacy expected) | `Bundle.wxs` defines `redist_vc*` groups + `Redistributables.wxi` composes them | PASS | Uses registry detection for each VC redist. | -| Bundle prerequisite: FLEx Bridge offline | FieldWorks includes at `6a2d976e`: `FLExInstaller/Redistributables.wxi` adds `FLExBridge_Offline.exe` in `FlexBridgeInstaller` group | `FLExInstaller/Shared/Common/Redistributables.wxi` adds `FLExBridge_Offline.exe` | PASS | Detected via registry under `SIL\FLEx Bridge\9`. | +| Bundle prerequisite: FLEx Bridge offline | FieldWorks includes at `6a2d976e`: `FLExInstaller/Redistributables.wxi` adds `FLExBridge_Offline.exe` in `FlexBridgeInstaller` group | `FLExInstaller/wix6/Shared/Common/Redistributables.wxi` adds `FLExBridge_Offline.exe` | PASS | Detected via registry under `SIL\FLEx Bridge\9`. | | Bundle theme + license UX | (legacy expected) | `Bundle.wxs` uses `WixStandardBootstrapperApplication Theme="hyperlinkLicense"` and theme variables | PASS | Theme + WXL are referenced; license is bundled from resources. | -| Offline bundle | `genericinstaller/BaseInstallerBuild/buildExe.bat` builds OfflineBundle via candle/light | `OfflineBundle.wxs` exists but is not compiled by `FieldWorks.Bundle.wixproj` | GAP | Offline bundle scenario appears not wired into current WiX 6 build output. | +| Offline bundle | `genericinstaller/BaseInstallerBuild/buildExe.bat` builds OfflineBundle via candle/light | `FieldWorks.OfflineBundle.wixproj` compiles `Shared/Base/OfflineBundle.wxs`; `Build/Installer.targets` invokes it in `BuildInstaller` | PARTIAL | Offline artifact production is wired. Disconnected-machine install and prerequisite embedding behavior still need runtime evidence. | | Code signing | `buildMsi.bat`/`buildExe.bat` call `signingProxy` and use `insignia` for bundle engine | `FieldWorks.Installer.wixproj`/`FieldWorks.Bundle.wixproj` call `signingProxy.bat` after build | PARTIAL | Bundle engine-signing parity with WiX 3/`insignia` is not demonstrated in the WiX 6 targets. | ## Notable divergences / risks -1. **Heat.exe dependency remains:** The MSI build depends on WiX 3 `heat.exe` for harvesting. This is likely intentional short-term, but it means a “pure WiX 6” toolchain isn’t achieved yet. -2. **Offline install story incomplete:** The presence of `OfflineBundle.wxs` and the legacy batch suggests offline bundles existed historically, but the current WiX 6 project compiles only `Bundle.wxs`. -3. **End-to-end verification not recorded:** Spec task `T019` (“Verify local build produces FieldWorks.msi and FieldWorks.exe”) is still unchecked; likewise CI integration tasks are unchecked. +1. **Default still points at WiX 3:** `Build/InstallerBuild.proj` defaults `InstallerToolset` to `Wix3`. That is acceptable only as a transition state; the migration is not complete until the default is switched or the blocker is explicitly documented. +2. **CI still has genericinstaller/PatchableInstaller dependencies:** base and patch installer workflows still checkout `sillsdev/genericinstaller` as `PatchableInstaller/` for legacy jobs. +3. **Burn provider compatibility is unproven:** The bundles use `RelatedBundle Action="Upgrade"`, but `AppMsiPackage` has no package-level ``. WiX 3 to WiX 6 upgrade detection and repair/uninstall behavior must be validated before release. +4. **Offline install story is built but unverified:** The offline bundle project is wired and produces a distinct artifact, but disconnected-machine install has not been proven. +5. **Patch/MSP support is not implemented for WiX 6:** `BuildPatch` remains a WiX 3 target. A WiX 6 patch design must cover `PatchBaseline`, `.wixpdb` retention, component GUID stability, and replacement of `buildPatch.bat`. ## Recommendations (to finish parity) -- Wire offline bundle output into the WiX 6 build (either compile `OfflineBundle.wxs` as a second bundle project/output, or implement layout creation per the spec). -- Replace the custom `heat.exe` harvesting step with a WiX 6-compatible harvesting mechanism (or a controlled, version-pinned `heat.exe` tool acquisition step) so the build is reproducible. -- Complete `T019` + add a short “artifact checklist” (expected filenames + locations) so regressions are easy to spot. +- Add a WiX 6 CI lane that builds without `genericinstaller`/`PatchableInstaller/` and uploads MSI, online bundle, offline bundle, and `.wixpdb` artifacts. +- Decide when to switch `InstallerToolset` default to `Wix6`; treat a continued WiX 3 default as an explicit release blocker. +- Validate WiX 3 to WiX 6 upgrade, ARP presentation, and uninstall behavior with logs and before/after snapshots. +- Keep WiX 6 patch/MSP work out of the first migration unless a separate design covers baseline artifacts and component identity stability. ## Files reviewed From 0d7993d6f64c21f4bf0c7cc99c8a5954a4ac0ebd Mon Sep 17 00:00:00 2001 From: John Lambert Date: Wed, 29 Apr 2026 20:15:21 -0400 Subject: [PATCH 3/5] partial implementation --- .vscode/tasks.json | 26 ++ .../WixInstallerArtifactsTests.cs | 139 +++++++- .../WixInstallerArtifactsTests.cs | 120 +++++-- scripts/Agent/Invoke-Installer.ps1 | 312 +++++++++++++++++- scripts/Agent/Invoke-InstallerCheck.ps1 | 8 +- scripts/Agent/README.md | 10 + 6 files changed, 561 insertions(+), 54 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index e83806466c..37651de523 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -496,6 +496,32 @@ }, "problemMatcher": [] }, + { + "label": "Installer Evidence: WiX6 UI screenshots (Debug, x64)", + "type": "shell", + "command": "./scripts/Agent/Invoke-Installer.ps1 -InstallerType Bundle -InstallerToolset Wix6 -Configuration Debug -Platform x64 -CaptureScreenshots -ScreenshotDurationSeconds 20 -ScreenshotIntervalSeconds 2 -StopAfterScreenshots -NoWait -IncludeTempLogs", + "detail": "Launch WiX 6 bundle UI, capture screenshots under Output/InstallerEvidence//screenshots, then stop before install", + "options": { + "shell": { + "executable": "powershell.exe", + "args": ["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command"] + } + }, + "problemMatcher": [] + }, + { + "label": "Installer Check: WiX6 Bundle (Debug, x64)", + "type": "shell", + "command": "./scripts/Agent/Invoke-InstallerCheck.ps1 -InstallerType Bundle -InstallerToolset Wix6 -Configuration Debug -Platform x64", + "detail": "Run snapshot -> install WiX 6 bundle -> snapshot -> diff (writes evidence under Output/InstallerEvidence/)", + "options": { + "shell": { + "executable": "powershell.exe", + "args": ["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command"] + } + }, + "problemMatcher": [] + }, // ==================== Git ==================== { diff --git a/Src/InstallValidator/InstallValidatorTests/WixInstallerArtifactsTests.cs b/Src/InstallValidator/InstallValidatorTests/WixInstallerArtifactsTests.cs index 7fdc400832..14ea4eb7e6 100644 --- a/Src/InstallValidator/InstallValidatorTests/WixInstallerArtifactsTests.cs +++ b/Src/InstallValidator/InstallValidatorTests/WixInstallerArtifactsTests.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Runtime.InteropServices; using System.Text; +using System.Xml.Linq; using NUnit.Framework; namespace SIL.InstallValidator @@ -22,22 +23,29 @@ public void InstallerArtifactsExist_AndMsiHasExpectedProperties() { var repoRoot = FindRepoRoot(); if (repoRoot == null) - Assert.Ignore("Could not locate repo root (FieldWorks.sln)." ); + Assert.Ignore("Could not locate repo root (FieldWorks.sln)."); - var installerBinDir = Path.Combine(repoRoot, "FLExInstaller", "bin"); + var installerBinDir = Path.Combine(repoRoot, "FLExInstaller", "wix6", "bin"); if (!Directory.Exists(installerBinDir)) - Assert.Ignore("FLExInstaller/bin not found; installer likely not built in this checkout."); + Assert.Ignore( + "FLExInstaller/wix6/bin not found. Run build.ps1 -BuildInstaller -InstallerToolset Wix6 first."); var msiPath = Directory .GetFiles(installerBinDir, "FieldWorks.msi", SearchOption.AllDirectories) - .Where(p => p.EndsWith(Path.Combine("en-US", "FieldWorks.msi"), StringComparison.OrdinalIgnoreCase)) + .Where(path => path.EndsWith( + Path.Combine("en-US", "FieldWorks.msi"), + StringComparison.OrdinalIgnoreCase)) .OrderByDescending(File.GetLastWriteTimeUtc) .FirstOrDefault(); if (msiPath == null) - Assert.Ignore("No FieldWorks.msi found under FLExInstaller/bin/**/en-US. Run build.ps1 -BuildInstaller first."); + Assert.Ignore( + "No FieldWorks.msi found under FLExInstaller/wix6/bin/**/en-US. Run build.ps1 -BuildInstaller -InstallerToolset Wix6 first."); - Assert.That(new FileInfo(msiPath).Length, Is.GreaterThan(1024 * 1024), "MSI should be > 1MB"); + Assert.That( + new FileInfo(msiPath).Length, + Is.GreaterThan(1024 * 1024), + "MSI should be > 1MB"); var msiDir = Path.GetDirectoryName(msiPath); Assert.That(msiDir, Is.Not.Null); @@ -45,18 +53,36 @@ public void InstallerArtifactsExist_AndMsiHasExpectedProperties() var wixpdbPath = Path.Combine(msiDir!, "FieldWorks.wixpdb"); Assert.That(File.Exists(wixpdbPath), Is.True, "Expected MSI .wixpdb next to the MSI"); - var bundleDir = Directory.GetParent(msiDir!)?.Parent?.FullName; // .../bin/x64/Debug + var bundleDir = Directory.GetParent(msiDir!)?.FullName; // .../bin/x64/Debug if (!string.IsNullOrWhiteSpace(bundleDir)) { var bundleExe = Path.Combine(bundleDir, "FieldWorksBundle.exe"); var bundlePdb = Path.Combine(bundleDir, "FieldWorksBundle.wixpdb"); - Assert.That(File.Exists(bundleExe), Is.True, "Expected FieldWorksBundle.exe next to the culture folder"); - Assert.That(File.Exists(bundlePdb), Is.True, "Expected FieldWorksBundle.wixpdb next to the bundle exe"); + var offlineBundleExe = Path.Combine(bundleDir, "FieldWorksOfflineBundle.exe"); + var offlineBundlePdb = Path.Combine(bundleDir, "FieldWorksOfflineBundle.wixpdb"); + Assert.That( + File.Exists(bundleExe), + Is.True, + "Expected FieldWorksBundle.exe next to the culture folder"); + Assert.That( + File.Exists(bundlePdb), + Is.True, + "Expected FieldWorksBundle.wixpdb next to the bundle exe"); + Assert.That( + File.Exists(offlineBundleExe), + Is.True, + "Expected FieldWorksOfflineBundle.exe next to the online bundle exe"); + Assert.That( + File.Exists(offlineBundlePdb), + Is.True, + "Expected FieldWorksOfflineBundle.wixpdb next to the offline bundle exe"); } using (var msi = MsiDatabase.OpenReadOnly(msiPath)) { - Assert.That(msi.GetProperty("ProductName"), Is.EqualTo("FieldWorks Language Explorer")); + Assert.That( + msi.GetProperty("ProductName"), + Is.EqualTo("FieldWorks Language Explorer")); Assert.That(msi.GetProperty("Manufacturer"), Is.EqualTo("SIL International")); var productVersion = msi.GetProperty("ProductVersion"); @@ -67,6 +93,99 @@ public void InstallerArtifactsExist_AndMsiHasExpectedProperties() } } + [Test] + [Category("InstallerArtifacts")] + public void BundleAuthoringIncludes_FLExBridgeOfflinePrereq_InOnlineAndOfflineChains() + { + var repoRoot = FindRepoRoot(); + if (repoRoot == null) + Assert.Ignore("Could not locate repo root (FieldWorks.sln)."); + + var wix6Root = Path.Combine(repoRoot, "FLExInstaller", "wix6"); + var redistributablesWxi = Path.Combine( + wix6Root, + "Shared", + "Common", + "Redistributables.wxi"); + Assert.That( + File.Exists(redistributablesWxi), + Is.True, + "Expected WiX 6 Redistributables.wxi to exist"); + + var wxiDoc = XDocument.Load(redistributablesWxi, LoadOptions.None); + XNamespace wxsNs = "http://wixtoolset.org/schemas/v4/wxs"; + XNamespace utilNs = "http://wixtoolset.org/schemas/v4/wxs/util"; + + Assert.That( + wxiDoc + .Descendants(utilNs + "RegistrySearch") + .Any(element => (string)element.Attribute("Variable") == "FLExBridge"), + Is.True, + "Expected util:RegistrySearch Variable='FLExBridge' for prereq detection"); + + var flexBridgeGroup = wxiDoc + .Descendants(wxsNs + "PackageGroup") + .FirstOrDefault(element => (string)element.Attribute("Id") == "FlexBridgeInstaller"); + Assert.That( + flexBridgeGroup, + Is.Not.Null, + "Expected PackageGroup Id='FlexBridgeInstaller'"); + + var fbInstaller = flexBridgeGroup! + .Descendants(wxsNs + "ExePackage") + .FirstOrDefault(element => (string)element.Attribute("Id") == "FBInstaller"); + Assert.That(fbInstaller, Is.Not.Null, "Expected ExePackage Id='FBInstaller'"); + Assert.That( + ((string?)fbInstaller!.Attribute("SourceFile") ?? string.Empty).Contains("FLExBridge_Offline.exe"), + Is.True, + "Expected FBInstaller SourceFile to reference FLExBridge_Offline.exe"); + Assert.That( + (string?)fbInstaller.Attribute("DetectCondition"), + Is.EqualTo("FLExBridge"), + "Expected FBInstaller DetectCondition='FLExBridge'"); + + var vcredistsGroup = wxiDoc + .Descendants(wxsNs + "PackageGroup") + .FirstOrDefault(element => (string)element.Attribute("Id") == "vcredists"); + Assert.That( + vcredistsGroup, + Is.Not.Null, + "Expected PackageGroup Id='vcredists'"); + Assert.That( + vcredistsGroup! + .Descendants(wxsNs + "PackageGroupRef") + .Any(element => (string)element.Attribute("Id") == "FlexBridgeInstaller"), + Is.True, + "Expected vcredists group to reference FlexBridgeInstaller"); + + AssertBundleReferencesVcredists( + Path.Combine(wix6Root, "Shared", "Base", "Bundle.wxs")); + AssertBundleReferencesVcredists( + Path.Combine(wix6Root, "Shared", "Base", "OfflineBundle.wxs")); + } + + private static void AssertBundleReferencesVcredists(string bundleWxs) + { + Assert.That( + File.Exists(bundleWxs), + Is.True, + "Expected bundle authoring file to exist"); + var bundleText = File.ReadAllText(bundleWxs); + Assert.That( + bundleText.Contains("Redistributables.wxi"), + Is.True, + "Expected bundle authoring to include Redistributables.wxi"); + + var bundleDoc = XDocument.Load(bundleWxs, LoadOptions.None); + XNamespace wxsNs = "http://wixtoolset.org/schemas/v4/wxs"; + Assert.That( + bundleDoc + .Descendants(wxsNs + "PackageGroupRef") + .Any(element => (string)element.Attribute("Id") == "vcredists"), + Is.True, + "Expected bundle Chain to reference PackageGroupRef Id='vcredists'"); + } + private static string? FindRepoRoot() { var dir = new DirectoryInfo(TestContext.CurrentContext.TestDirectory); diff --git a/Src/InstallValidator/InstallerArtifactsTests/WixInstallerArtifactsTests.cs b/Src/InstallValidator/InstallerArtifactsTests/WixInstallerArtifactsTests.cs index 05a9fc7212..6e7c5f52e1 100644 --- a/Src/InstallValidator/InstallerArtifactsTests/WixInstallerArtifactsTests.cs +++ b/Src/InstallValidator/InstallerArtifactsTests/WixInstallerArtifactsTests.cs @@ -23,22 +23,29 @@ public void InstallerArtifactsExist_AndMsiHasExpectedProperties() { var repoRoot = FindRepoRoot(); if (repoRoot == null) - Assert.Ignore("Could not locate repo root (FieldWorks.sln)." ); + Assert.Ignore("Could not locate repo root (FieldWorks.sln)."); - var installerBinDir = Path.Combine(repoRoot, "FLExInstaller", "bin"); + var installerBinDir = Path.Combine(repoRoot, "FLExInstaller", "wix6", "bin"); if (!Directory.Exists(installerBinDir)) - Assert.Ignore("FLExInstaller/bin not found; installer likely not built in this checkout."); + Assert.Ignore( + "FLExInstaller/wix6/bin not found. Run build.ps1 -BuildInstaller -InstallerToolset Wix6 first."); var msiPath = Directory .GetFiles(installerBinDir, "FieldWorks.msi", SearchOption.AllDirectories) - .Where(p => p.EndsWith(Path.Combine("en-US", "FieldWorks.msi"), StringComparison.OrdinalIgnoreCase)) + .Where(path => path.EndsWith( + Path.Combine("en-US", "FieldWorks.msi"), + StringComparison.OrdinalIgnoreCase)) .OrderByDescending(File.GetLastWriteTimeUtc) .FirstOrDefault(); if (msiPath == null) - Assert.Ignore("No FieldWorks.msi found under FLExInstaller/bin/**/en-US. Run build.ps1 -BuildInstaller first."); + Assert.Ignore( + "No FieldWorks.msi found under FLExInstaller/wix6/bin/**/en-US. Run build.ps1 -BuildInstaller -InstallerToolset Wix6 first."); - Assert.That(new FileInfo(msiPath).Length, Is.GreaterThan(1024 * 1024), "MSI should be > 1MB"); + Assert.That( + new FileInfo(msiPath).Length, + Is.GreaterThan(1024 * 1024), + "MSI should be > 1MB"); var msiDir = Path.GetDirectoryName(msiPath); Assert.That(msiDir, Is.Not.Null); @@ -51,13 +58,31 @@ public void InstallerArtifactsExist_AndMsiHasExpectedProperties() { var bundleExe = Path.Combine(bundleDir, "FieldWorksBundle.exe"); var bundlePdb = Path.Combine(bundleDir, "FieldWorksBundle.wixpdb"); - Assert.That(File.Exists(bundleExe), Is.True, "Expected FieldWorksBundle.exe next to the culture folder"); - Assert.That(File.Exists(bundlePdb), Is.True, "Expected FieldWorksBundle.wixpdb next to the bundle exe"); + var offlineBundleExe = Path.Combine(bundleDir, "FieldWorksOfflineBundle.exe"); + var offlineBundlePdb = Path.Combine(bundleDir, "FieldWorksOfflineBundle.wixpdb"); + Assert.That( + File.Exists(bundleExe), + Is.True, + "Expected FieldWorksBundle.exe next to the culture folder"); + Assert.That( + File.Exists(bundlePdb), + Is.True, + "Expected FieldWorksBundle.wixpdb next to the bundle exe"); + Assert.That( + File.Exists(offlineBundleExe), + Is.True, + "Expected FieldWorksOfflineBundle.exe next to the online bundle exe"); + Assert.That( + File.Exists(offlineBundlePdb), + Is.True, + "Expected FieldWorksOfflineBundle.wixpdb next to the offline bundle exe"); } using (var msi = MsiDatabase.OpenReadOnly(msiPath)) { - Assert.That(msi.GetProperty("ProductName"), Does.StartWith("FieldWorks Language Explorer")); + Assert.That( + msi.GetProperty("ProductName"), + Does.StartWith("FieldWorks Language Explorer")); Assert.That(msi.GetProperty("Manufacturer"), Is.EqualTo("SIL International")); var productVersion = msi.GetProperty("ProductVersion"); @@ -70,46 +95,44 @@ public void InstallerArtifactsExist_AndMsiHasExpectedProperties() [Test] [Category("InstallerArtifacts")] - public void BundleIncludes_FLExBridgeOfflinePrereq_InChain() + public void BundleAuthoringIncludes_FLExBridgeOfflinePrereq_InOnlineAndOfflineChains() { var repoRoot = FindRepoRoot(); if (repoRoot == null) - Assert.Ignore("Could not locate repo root (FieldWorks.sln)." ); - - var installerBinDir = Path.Combine(repoRoot, "FLExInstaller", "bin"); - if (!Directory.Exists(installerBinDir)) - Assert.Ignore("FLExInstaller/bin not found; installer likely not built in this checkout."); - - var flexBridgeOfflineExe = Path.Combine(repoRoot, "FLExInstaller", "libs", "FLExBridge_Offline.exe"); - if (!File.Exists(flexBridgeOfflineExe)) - Assert.Ignore("FLExInstaller/libs/FLExBridge_Offline.exe not found. Run build.ps1 -BuildInstaller to stage prerequisites."); - Assert.That(new FileInfo(flexBridgeOfflineExe).Length, Is.GreaterThan(1024 * 1024), "FLExBridge_Offline.exe should be > 1MB"); + Assert.Ignore("Could not locate repo root (FieldWorks.sln)."); + var wix6Root = Path.Combine(repoRoot, "FLExInstaller", "wix6"); // Guard the intended chain wiring in WiX authoring. This is purposefully non-installing. // Binary .wixpdb contents are not stable for string scanning, so validate the authoring structure instead. - var redistributablesWxi = Path.Combine(repoRoot, "FLExInstaller", "Shared", "Common", "Redistributables.wxi"); - Assert.That(File.Exists(redistributablesWxi), Is.True, "Expected Redistributables.wxi to exist"); - - var bundleWxs = Path.Combine(repoRoot, "FLExInstaller", "Shared", "Base", "Bundle.wxs"); - Assert.That(File.Exists(bundleWxs), Is.True, "Expected Bundle.wxs to exist"); - - var bundleText = File.ReadAllText(bundleWxs); - Assert.That(bundleText.Contains("Redistributables.wxi"), Is.True, "Expected Bundle.wxs to include Redistributables.wxi"); + var redistributablesWxi = Path.Combine( + wix6Root, + "Shared", + "Common", + "Redistributables.wxi"); + Assert.That( + File.Exists(redistributablesWxi), + Is.True, + "Expected Redistributables.wxi to exist"); var wxiDoc = XDocument.Load(redistributablesWxi, LoadOptions.None); XNamespace wxsNs = "http://wixtoolset.org/schemas/v4/wxs"; XNamespace utilNs = "http://wixtoolset.org/schemas/v4/wxs/util"; Assert.That( - wxiDoc.Descendants(utilNs + "RegistrySearch").Any(e => (string)e.Attribute("Variable") == "FLExBridge"), + wxiDoc + .Descendants(utilNs + "RegistrySearch") + .Any(element => (string)element.Attribute("Variable") == "FLExBridge"), Is.True, "Expected util:RegistrySearch Variable='FLExBridge' for prereq detection"); var flexBridgeGroup = wxiDoc .Descendants(wxsNs + "PackageGroup") .FirstOrDefault(e => (string)e.Attribute("Id") == "FlexBridgeInstaller"); - Assert.That(flexBridgeGroup, Is.Not.Null, "Expected PackageGroup Id='FlexBridgeInstaller'"); + Assert.That( + flexBridgeGroup, + Is.Not.Null, + "Expected PackageGroup Id='FlexBridgeInstaller'"); var fbInstaller = flexBridgeGroup .Descendants(wxsNs + "ExePackage") @@ -118,23 +141,52 @@ public void BundleIncludes_FLExBridgeOfflinePrereq_InChain() var sourceFileAttr = fbInstaller.Attribute("SourceFile"); var sourceFile = sourceFileAttr != null ? sourceFileAttr.Value : string.Empty; - Assert.That(sourceFile.Contains("FLExBridge_Offline.exe"), Is.True, "Expected FBInstaller SourceFile to reference FLExBridge_Offline.exe"); + Assert.That( + sourceFile.Contains("FLExBridge_Offline.exe"), + Is.True, + "Expected FBInstaller SourceFile to reference FLExBridge_Offline.exe"); var detectConditionAttr = fbInstaller.Attribute("DetectCondition"); var detectCondition = detectConditionAttr != null ? detectConditionAttr.Value : null; - Assert.That(detectCondition, Is.EqualTo("FLExBridge"), "Expected FBInstaller DetectCondition='FLExBridge'"); + Assert.That( + detectCondition, + Is.EqualTo("FLExBridge"), + "Expected FBInstaller DetectCondition='FLExBridge'"); var vcredistsGroup = wxiDoc .Descendants(wxsNs + "PackageGroup") .FirstOrDefault(e => (string)e.Attribute("Id") == "vcredists"); Assert.That(vcredistsGroup, Is.Not.Null, "Expected PackageGroup Id='vcredists'"); Assert.That( - vcredistsGroup.Descendants(wxsNs + "PackageGroupRef").Any(e => (string)e.Attribute("Id") == "FlexBridgeInstaller"), + vcredistsGroup + .Descendants(wxsNs + "PackageGroupRef") + .Any(element => (string)element.Attribute("Id") == "FlexBridgeInstaller"), Is.True, "Expected vcredists group to reference FlexBridgeInstaller"); + AssertBundleReferencesVcredists( + Path.Combine(wix6Root, "Shared", "Base", "Bundle.wxs")); + AssertBundleReferencesVcredists( + Path.Combine(wix6Root, "Shared", "Base", "OfflineBundle.wxs")); + } + + private static void AssertBundleReferencesVcredists(string bundleWxs) + { + Assert.That( + File.Exists(bundleWxs), + Is.True, + "Expected bundle authoring file to exist"); + var bundleText = File.ReadAllText(bundleWxs); + Assert.That( + bundleText.Contains("Redistributables.wxi"), + Is.True, + "Expected bundle authoring to include Redistributables.wxi"); + var bundleDoc = XDocument.Load(bundleWxs, LoadOptions.None); + XNamespace wxsNs = "http://wixtoolset.org/schemas/v4/wxs"; Assert.That( - bundleDoc.Descendants(wxsNs + "PackageGroupRef").Any(e => (string)e.Attribute("Id") == "vcredists"), + bundleDoc + .Descendants(wxsNs + "PackageGroupRef") + .Any(element => (string)element.Attribute("Id") == "vcredists"), Is.True, "Expected bundle Chain to reference PackageGroupRef Id='vcredists'"); } diff --git a/scripts/Agent/Invoke-Installer.ps1 b/scripts/Agent/Invoke-Installer.ps1 index b4c2f3bb62..c18f971135 100644 --- a/scripts/Agent/Invoke-Installer.ps1 +++ b/scripts/Agent/Invoke-Installer.ps1 @@ -12,6 +12,10 @@ param( [ValidateSet('x64', 'x86')] [string]$Platform = 'x64', + [Parameter(Mandatory = $false)] + [ValidateSet('Wix3', 'Wix6')] + [string]$InstallerToolset = 'Wix3', + [Parameter(Mandatory = $false)] [string]$InstallerPath, @@ -27,6 +31,18 @@ param( [Parameter(Mandatory = $false)] [switch]$NoWait, + [Parameter(Mandatory = $false)] + [switch]$CaptureScreenshots, + + [Parameter(Mandatory = $false)] + [int]$ScreenshotDurationSeconds = 20, + + [Parameter(Mandatory = $false)] + [int]$ScreenshotIntervalSeconds = 2, + + [Parameter(Mandatory = $false)] + [switch]$StopAfterScreenshots, + [Parameter(Mandatory = $false)] [int]$TimeoutSeconds = 0, @@ -74,7 +90,11 @@ function Resolve-DefaultInstallerPath { [string]$Platform ) - $installerDir = Join-Path $RepoRoot ('FLExInstaller\bin\{0}\{1}' -f $Platform, $Configuration) + if ($InstallerToolset -eq 'Wix6') { + $installerDir = Join-Path $RepoRoot ('FLExInstaller\wix6\bin\{0}\{1}' -f $Platform, $Configuration) + } else { + $installerDir = Join-Path $RepoRoot ('FLExInstaller\bin\{0}\{1}' -f $Platform, $Configuration) + } if ($ResolvedType -eq 'Msi') { return Join-Path $installerDir 'en-US\FieldWorks.msi' @@ -175,6 +195,226 @@ function Write-MsiFileAccessSummary { [System.IO.File]::WriteAllLines($SummaryPath, $lines, $utf8NoBom) } +function Add-ScreenshotSupport { + if ('InstallerWindowNativeMethods' -as [type]) { + return + } + + Add-Type -AssemblyName System.Drawing + Add-Type -TypeDefinition @' +using System; +using System.Runtime.InteropServices; + +public static class InstallerWindowNativeMethods +{ + [StructLayout(LayoutKind.Sequential)] + public struct RECT + { + public int Left; + public int Top; + public int Right; + public int Bottom; + } + + [DllImport("user32.dll", SetLastError = true)] + public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); +} +'@ +} + +function Get-SafeFileName { + param( + [Parameter(Mandatory = $true)] + [string]$Value + ) + + $fileName = $Value + foreach ($invalidChar in [System.IO.Path]::GetInvalidFileNameChars()) { + $fileName = $fileName.Replace($invalidChar, '_') + } + + if ([string]::IsNullOrWhiteSpace($fileName)) { + return 'window' + } + + return $fileName +} + +function Get-InstallerWindowCandidates { + param( + [Parameter(Mandatory = $true)] + [int]$RootProcessId, + [Parameter(Mandatory = $true)] + [string]$InstallerPath + ) + + $processNames = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase) + $installerProcessName = [System.IO.Path]::GetFileNameWithoutExtension($InstallerPath) + $null = $processNames.Add($installerProcessName) + $null = $processNames.Add('FieldWorksBundle') + $null = $processNames.Add('FieldWorksOfflineBundle') + $null = $processNames.Add('msiexec') + + try { + $rootProcess = Get-Process -Id $RootProcessId -ErrorAction Stop + $null = $processNames.Add($rootProcess.ProcessName) + } catch { } + + $windows = New-Object 'System.Collections.Generic.List[object]' + foreach ($process in Get-Process -ErrorAction SilentlyContinue) { + if (-not $processNames.Contains($process.ProcessName)) { continue } + if ($process.MainWindowHandle -eq [IntPtr]::Zero) { continue } + + $title = $process.MainWindowTitle + if ([string]::IsNullOrWhiteSpace($title)) { + $title = $process.ProcessName + } + + $windows.Add([pscustomobject]@{ + ProcessId = $process.Id + ProcessName = $process.ProcessName + WindowHandle = $process.MainWindowHandle + Title = $title + }) + } + + return $windows +} + +function Save-WindowScreenshot { + param( + [Parameter(Mandatory = $true)] + [IntPtr]$WindowHandle, + [Parameter(Mandatory = $true)] + [string]$Path + ) + + Add-ScreenshotSupport + + $rect = New-Object InstallerWindowNativeMethods+RECT + if (-not [InstallerWindowNativeMethods]::GetWindowRect($WindowHandle, [ref]$rect)) { + return $false + } + + $width = $rect.Right - $rect.Left + $height = $rect.Bottom - $rect.Top + if ($width -le 0 -or $height -le 0) { + return $false + } + + $bitmap = New-Object System.Drawing.Bitmap $width, $height + $graphics = [System.Drawing.Graphics]::FromImage($bitmap) + try { + $graphics.CopyFromScreen($rect.Left, $rect.Top, 0, 0, $bitmap.Size) + $bitmap.Save($Path, [System.Drawing.Imaging.ImageFormat]::Png) + return $true + } finally { + $graphics.Dispose() + $bitmap.Dispose() + } +} + +function Capture-InstallerScreenshots { + param( + [Parameter(Mandatory = $true)] + [int]$ProcessId, + [Parameter(Mandatory = $true)] + [string]$InstallerPath, + [Parameter(Mandatory = $true)] + [string]$EvidenceDir, + [Parameter(Mandatory = $true)] + [int]$DurationSeconds, + [Parameter(Mandatory = $true)] + [int]$IntervalSeconds + ) + + if ($DurationSeconds -lt 1) { $DurationSeconds = 1 } + if ($IntervalSeconds -lt 1) { $IntervalSeconds = 1 } + + $screenshotDir = Join-Path $EvidenceDir 'screenshots' + Ensure-Directory -Path $screenshotDir + + $manifestPath = Join-Path $screenshotDir 'manifest.txt' + $manifest = New-Object 'System.Collections.Generic.List[string]' + $manifest.Add(('Started: {0:o}' -f (Get-Date))) + $manifest.Add(('InstallerPath: {0}' -f $InstallerPath)) + $manifest.Add(('RootProcessId: {0}' -f $ProcessId)) + $manifest.Add(('DurationSeconds: {0}' -f $DurationSeconds)) + $manifest.Add(('IntervalSeconds: {0}' -f $IntervalSeconds)) + $manifest.Add('') + + $deadline = (Get-Date).AddSeconds($DurationSeconds) + $captureIndex = 0 + + while ((Get-Date) -lt $deadline) { + $windows = @(Get-InstallerWindowCandidates -RootProcessId $ProcessId -InstallerPath $InstallerPath) + if ($windows.Count -eq 0) { + $manifest.Add(('{0:o} No visible installer windows found.' -f (Get-Date))) + } else { + foreach ($window in $windows) { + $captureIndex++ + $safeTitle = Get-SafeFileName -Value $window.Title + $fileName = ('{0:0000}-{1}-{2}.png' -f $captureIndex, $window.ProcessName, $safeTitle) + $screenshotPath = Join-Path $screenshotDir $fileName + if (Save-WindowScreenshot -WindowHandle $window.WindowHandle -Path $screenshotPath) { + $manifest.Add(('{0:o} {1} PID={2} Title="{3}" Path={4}' -f (Get-Date), $window.ProcessName, $window.ProcessId, $window.Title, $screenshotPath)) + } else { + $manifest.Add(('{0:o} Failed to capture {1} PID={2} Title="{3}"' -f (Get-Date), $window.ProcessName, $window.ProcessId, $window.Title)) + } + } + } + + Start-Sleep -Seconds $IntervalSeconds + } + + $manifest.Add('') + $manifest.Add(('Finished: {0:o}' -f (Get-Date))) + $utf8NoBom = New-Object System.Text.UTF8Encoding($false) + [System.IO.File]::WriteAllLines($manifestPath, $manifest, $utf8NoBom) + + return $screenshotDir +} + +function Stop-InstallerProcessAfterScreenshots { + param( + [Parameter(Mandatory = $true)] + [System.Diagnostics.Process]$Process + ) + + $Process.Refresh() + if ($Process.HasExited) { + return $false + } + + Write-Output "Stopping installer process after screenshot capture: $($Process.Id)" + Stop-Process -Id $Process.Id -Force -ErrorAction Stop + try { + Wait-Process -Id $Process.Id -Timeout 5 -ErrorAction SilentlyContinue + } catch { } + + return $true +} + +function Get-ProcessExitState { + param( + [Parameter(Mandatory = $true)] + [System.Diagnostics.Process]$Process + ) + + $Process.Refresh() + if ($Process.HasExited) { + return [pscustomobject]@{ + ExitCode = [int]$Process.ExitCode + IsRunning = $false + } + } + + return [pscustomobject]@{ + ExitCode = $null + IsRunning = $true + } +} + $repoRoot = Resolve-RepoRoot $resolvedType = Resolve-InstallerType -InstallerType $InstallerType -InstallerPath $InstallerPath @@ -184,7 +424,7 @@ if ([string]::IsNullOrWhiteSpace($InstallerPath)) { } if (!(Test-Path -LiteralPath $InstallerPath)) { - throw "Installer path not found: $InstallerPath. Build it first (e.g., .\\build.ps1 -BuildInstaller -Configuration $Configuration)." + throw "Installer path not found: $InstallerPath. Build it first (e.g., .\build.ps1 -BuildInstaller -Configuration $Configuration -InstallerToolset $InstallerToolset)." } $InstallerPath = (Resolve-Path -LiteralPath $InstallerPath).Path @@ -216,6 +456,17 @@ if ($resolvedType -eq 'Msi') { if ($PSCmdlet.ShouldProcess($InstallerPath, 'Run MSI')) { $process = Start-Process -FilePath 'msiexec.exe' -ArgumentList $msiArgs -PassThru + $screenshotDir = $null + $stoppedAfterScreenshots = $false + + if ($CaptureScreenshots) { + $screenshotDir = Capture-InstallerScreenshots -ProcessId $process.Id -InstallerPath $InstallerPath -EvidenceDir $evidenceDir -DurationSeconds $ScreenshotDurationSeconds -IntervalSeconds $ScreenshotIntervalSeconds + Write-Output "Screenshots: $screenshotDir" + } + + if ($CaptureScreenshots -and $StopAfterScreenshots) { + $stoppedAfterScreenshots = Stop-InstallerProcessAfterScreenshots -Process $process + } if (-not $NoWait) { if ($TimeoutSeconds -gt 0) { @@ -225,12 +476,23 @@ if ($resolvedType -eq 'Msi') { } } - $exitCode = $process.ExitCode + $exitState = Get-ProcessExitState -Process $process + $exitCode = $exitState.ExitCode $finished = Get-Date - $global:LASTEXITCODE = $exitCode + if ($stoppedAfterScreenshots) { + $exitCode = 0 + $global:LASTEXITCODE = 0 + Write-Output "ExitCode: 0 (stopped after screenshots)" + } elseif ($null -eq $exitCode) { + $global:LASTEXITCODE = 0 + Write-Output "ExitCode: " + } else { + $global:LASTEXITCODE = $exitCode + Write-Output "ExitCode: $exitCode" + } - Write-Output "ExitCode: $exitCode" + Write-Output "ProcessId: $($process.Id)" if ($IncludeTempLogs) { Copy-TempLogs -Started $started -Finished $finished -DestinationDir $evidenceDir @@ -248,6 +510,11 @@ if ($resolvedType -eq 'Msi') { EvidenceDir = $evidenceDir PrimaryLogPath = $msiLog ExitCode = $exitCode + ProcessExitCode = $exitState.ExitCode + ProcessId = $process.Id + IsRunning = $exitState.IsRunning + StoppedAfterScreenshots = $stoppedAfterScreenshots + ScreenshotDir = $screenshotDir } } @@ -255,6 +522,7 @@ if ($resolvedType -eq 'Msi') { return $result } + if ($null -eq $exitCode) { exit 0 } exit $exitCode } } else { @@ -280,6 +548,17 @@ if ($resolvedType -eq 'Msi') { if ($PSCmdlet.ShouldProcess($InstallerPath, 'Run bundle')) { $process = Start-Process -FilePath $InstallerPath -ArgumentList $bundleArgs -PassThru + $screenshotDir = $null + $stoppedAfterScreenshots = $false + + if ($CaptureScreenshots) { + $screenshotDir = Capture-InstallerScreenshots -ProcessId $process.Id -InstallerPath $InstallerPath -EvidenceDir $evidenceDir -DurationSeconds $ScreenshotDurationSeconds -IntervalSeconds $ScreenshotIntervalSeconds + Write-Output "Screenshots: $screenshotDir" + } + + if ($CaptureScreenshots -and $StopAfterScreenshots) { + $stoppedAfterScreenshots = Stop-InstallerProcessAfterScreenshots -Process $process + } if (-not $NoWait) { try { @@ -295,12 +574,23 @@ if ($resolvedType -eq 'Msi') { } } - $exitCode = $process.ExitCode + $exitState = Get-ProcessExitState -Process $process + $exitCode = $exitState.ExitCode $finished = Get-Date - $global:LASTEXITCODE = $exitCode + if ($stoppedAfterScreenshots) { + $exitCode = 0 + $global:LASTEXITCODE = 0 + Write-Output "ExitCode: 0 (stopped after screenshots)" + } elseif ($null -eq $exitCode) { + $global:LASTEXITCODE = 0 + Write-Output "ExitCode: " + } else { + $global:LASTEXITCODE = $exitCode + Write-Output "ExitCode: $exitCode" + } - Write-Output "ExitCode: $exitCode" + Write-Output "ProcessId: $($process.Id)" if ($IncludeTempLogs) { Copy-TempLogs -Started $started -Finished $finished -DestinationDir $evidenceDir @@ -323,6 +613,11 @@ if ($resolvedType -eq 'Msi') { EvidenceDir = $evidenceDir PrimaryLogPath = $bundleLog ExitCode = $exitCode + ProcessExitCode = $exitState.ExitCode + ProcessId = $process.Id + IsRunning = $exitState.IsRunning + StoppedAfterScreenshots = $stoppedAfterScreenshots + ScreenshotDir = $screenshotDir } } @@ -330,6 +625,7 @@ if ($resolvedType -eq 'Msi') { return $result } + if ($null -eq $exitCode) { exit 0 } exit $exitCode } } diff --git a/scripts/Agent/Invoke-InstallerCheck.ps1 b/scripts/Agent/Invoke-InstallerCheck.ps1 index 575a0e248b..13f5f5846a 100644 --- a/scripts/Agent/Invoke-InstallerCheck.ps1 +++ b/scripts/Agent/Invoke-InstallerCheck.ps1 @@ -12,6 +12,10 @@ param( [ValidateSet('x64', 'x86')] [string]$Platform = 'x64', + [Parameter(Mandatory = $false)] + [ValidateSet('Wix3', 'Wix6')] + [string]$InstallerToolset = 'Wix3', + [Parameter(Mandatory = $false)] [string]$InstallerPath, @@ -75,7 +79,7 @@ Write-Output "Collecting snapshot: before install" & "$repoRoot\scripts\Agent\Collect-InstallerSnapshot.ps1" -OutputPath $beforePath -Name 'before-install' -MaxFileCount $MaxFileCount Write-Output "Running installer" -$installResult = & "$repoRoot\scripts\Agent\Invoke-Installer.ps1" -InstallerType $InstallerType -Configuration $Configuration -Platform $Platform -InstallerPath $InstallerPath -LogRoot $LogRoot -RunId $RunId -Arguments $InstallArguments -IncludeTempLogs -PassThru -NoExit +$installResult = & "$repoRoot\scripts\Agent\Invoke-Installer.ps1" -InstallerType $InstallerType -Configuration $Configuration -Platform $Platform -InstallerToolset $InstallerToolset -InstallerPath $InstallerPath -LogRoot $LogRoot -RunId $RunId -Arguments $InstallArguments -IncludeTempLogs -PassThru -NoExit if ($null -eq $installResult) { throw "Invoke-Installer did not return a result." @@ -97,7 +101,7 @@ if ($RunUninstall) { } else { Write-Output "Running uninstall" $uninstallRunId = "$RunId-uninstall" - $uninstallResult = & "$repoRoot\scripts\Agent\Invoke-Installer.ps1" -InstallerType 'Bundle' -Configuration $Configuration -Platform $Platform -InstallerPath $InstallerPath -LogRoot $LogRoot -RunId $uninstallRunId -Arguments $UninstallArguments -IncludeTempLogs -PassThru -NoExit + $uninstallResult = & "$repoRoot\scripts\Agent\Invoke-Installer.ps1" -InstallerType 'Bundle' -Configuration $Configuration -Platform $Platform -InstallerToolset $InstallerToolset -InstallerPath $InstallerPath -LogRoot $LogRoot -RunId $uninstallRunId -Arguments $UninstallArguments -IncludeTempLogs -PassThru -NoExit $uninstallExit = [int]$uninstallResult.ExitCode Write-Output "Uninstall exit code: $uninstallExit" diff --git a/scripts/Agent/README.md b/scripts/Agent/README.md index bbf92689f9..f673cfb8e2 100644 --- a/scripts/Agent/README.md +++ b/scripts/Agent/README.md @@ -25,4 +25,14 @@ PowerShell scripts for Copilot auto-approval. Use these instead of commands with Get-Help .\scripts\Agent\Git-Search.ps1 -Full ``` +Useful WiX 6 evidence runs: + +```powershell +# Launch the WiX 6 bundle, capture visible installer window screenshots, then stop the bundle before install. +.\scripts\Agent\Invoke-Installer.ps1 -InstallerType Bundle -InstallerToolset Wix6 -Configuration Debug -Platform x64 -CaptureScreenshots -ScreenshotDurationSeconds 20 -StopAfterScreenshots -NoWait + +# Run the WiX 6 bundle through the snapshot/install evidence wrapper. +.\scripts\Agent\Invoke-InstallerCheck.ps1 -InstallerType Bundle -InstallerToolset Wix6 -Configuration Debug -Platform x64 +``` + Native C++ tests are dispatched by `test.ps1` to `Build/scripts/Invoke-CppTest.ps1`. From da95673b1d76ae4beb06f52d039b3c170077d4f9 Mon Sep 17 00:00:00 2001 From: John Lambert Date: Thu, 30 Apr 2026 08:16:36 -0400 Subject: [PATCH 4/5] More progress + add MCP server for winapp --- .../fieldworks-installer-diagnostics/SKILL.md | 10 + .../SKILL.md | 9 + .github/skills/fieldworks-wix6-ui/SKILL.md | 16 +- .../fieldworks-wix6-upgrade-patching/SKILL.md | 9 + .github/workflows/CI.yml | 97 ++++++ .github/workflows/base-installer-cd.yml | 17 +- .github/workflows/patch-installer-cd.yml | 12 +- .../Agent/Test-Wix6InstallerBuildEvidence.ps1 | 278 ++++++++++++++++++ FLExInstaller/wix6/FieldWorks.Bundle.wixproj | 1 + .../wix6/FieldWorks.Installer.wixproj | 1 + .../wix6/FieldWorks.OfflineBundle.wixproj | 1 + .../CustomActions/CustomActions.csproj | 1 + scripts/Agent/Collect-InstallerSnapshot.ps1 | 121 +++++++- scripts/Agent/Compare-InstallerSnapshots.ps1 | 140 ++++++++- scripts/Agent/Invoke-Installer.ps1 | 101 ++++++- scripts/Agent/README.md | 11 +- .../evidence-collection-checklist.md | 2 + specs/001-wix-v6-migration/quickstart.md | 32 +- specs/001-wix-v6-migration/tasks.md | 14 +- .../verification-matrix.md | 8 +- 20 files changed, 816 insertions(+), 65 deletions(-) create mode 100644 Build/Agent/Test-Wix6InstallerBuildEvidence.ps1 diff --git a/.github/skills/fieldworks-installer-diagnostics/SKILL.md b/.github/skills/fieldworks-installer-diagnostics/SKILL.md index 859d6ee97e..e0cb65825b 100644 --- a/.github/skills/fieldworks-installer-diagnostics/SKILL.md +++ b/.github/skills/fieldworks-installer-diagnostics/SKILL.md @@ -28,6 +28,16 @@ This skill is evidence-first. Installer problems are often ambiguous until Burn 5. If the UI exits silently, capture Event Viewer entries and crash dumps. 6. Summarize the first failing layer: Burn bootstrapper, chained package/MSI, custom action, Windows Installer engine, or environment. +## WinApp MCP Runtime Evidence + +When the WinApp MCP server is available, use it to make UI diagnostics repeatable: + +- Launch or attach to installer windows and wait for input idle before diagnosing UI state. +- If the app snapshot is empty, list visible desktop windows and use `get_focused_element` plus keyboard navigation to identify and exercise controls. +- Record the observed focus path and control names in the evidence notes, especially for silent/blank UI, MSI handoff, uninstall prompts, and ARP uninstall hangs. +- For safe UI-only smoke tests, stop after proving the license checkbox/Install/Cancel path; do not proceed into install unless the scenario requires machine changes. +- For hangs, combine WinApp focused-element/window evidence with bundle/MSI logs, before/after snapshots, Event Viewer entries, and process lists. + ## Commands Prefer the repo helper for repeatable runs: diff --git a/.github/skills/fieldworks-wix6-migration-coordinator/SKILL.md b/.github/skills/fieldworks-wix6-migration-coordinator/SKILL.md index a023757599..475dfc0492 100644 --- a/.github/skills/fieldworks-wix6-migration-coordinator/SKILL.md +++ b/.github/skills/fieldworks-wix6-migration-coordinator/SKILL.md @@ -39,6 +39,15 @@ Use this as the routing and guardrail skill for FieldWorks installer migration w 4. Make the smallest change that addresses the root cause. 5. Validate with the relevant build/test/evidence checklist, or clearly report why validation could not be run. +## WinApp MCP UI Automation + +When the WinApp MCP server is installed, prefer it for Windows installer UI proof before falling back to raw screenshots or manual notes. + +- Use WinApp app/window tools to launch or attach to `FieldWorksBundle.exe`, `FieldWorksOfflineBundle.exe`, or `msiexec.exe` and collect UI snapshots where available. +- WixStdBA may expose little or no UI Automation tree at the launcher PID. If the app snapshot is empty, inspect visible desktop windows, use the focused element, and drive the UI with keyboard navigation (`Tab`, `Shift+Tab`, `Space`, `Return`, `Esc`) or coordinates. +- For safe click-through smoke tests, prove focus and interaction by toggling the license checkbox, reaching the Install button, then canceling and confirming exit. Do not click Install unless the task explicitly calls for an install/upgrade/uninstall run on that machine. +- Always pair WinApp UI evidence with bundle/MSI logs in `Output/InstallerEvidence//` or the VM evidence folder. + ## Source Credibility Prefer sources in this order: diff --git a/.github/skills/fieldworks-wix6-ui/SKILL.md b/.github/skills/fieldworks-wix6-ui/SKILL.md index 24870a9e69..9c7c1ada7e 100644 --- a/.github/skills/fieldworks-wix6-ui/SKILL.md +++ b/.github/skills/fieldworks-wix6-ui/SKILL.md @@ -18,7 +18,7 @@ This skill keeps FieldWorks installer UI work grounded in the actual Burn + MSI 2. Inspect `FLExInstaller/wix6/Shared/Base/Bundle.wxs`, `OfflineBundle.wxs`, `BundleTheme.xml`, `BundleTheme.wxl`, and `FieldWorks.Bundle.wixproj` for bundle UI behavior. 3. Inspect `Framework.wxs`, `WixUI_DialogFlow.wxs`, `GIInstallDirDlg.wxs`, and `GICustomizeDlg.wxs` for MSI UI behavior. 4. If custom actions affect UI, inspect `Shared/CustomActions/CustomActions/CustomAction.cs` and the custom action IDs in `Framework.wxs`. -5. Gather screenshots/logs before changing visual or sequencing behavior. +5. Gather screenshots/logs before changing visual or sequencing behavior. If WinApp MCP is available, use it to drive the visible installer UI and record the controls/states observed. ## Mental Model @@ -55,9 +55,23 @@ This skill keeps FieldWorks installer UI work grounded in the actual Burn + MSI 4. Preserve localization: put translatable strings in existing `.wxl`/`.resx` patterns, not hardcoded source where localization is expected. 5. Validate full, passive, and quiet modes when changing `DisplayInternalUICondition` or dialog sequencing. +## WinApp MCP Click-Through Workflow + +Use WinApp MCP for bundle/MSI UI smoke tests whenever the server is available. + +1. Launch the bundle with an explicit log path, for example `FieldWorksBundle.exe /log Output\InstallerEvidence\\bundle.log`. +2. Wait for input idle, then request a UI snapshot. If the app-root snapshot has no accessible elements, list desktop windows and use the focused element/keyboard navigation. +3. Safe bundle smoke test: focus Cancel, Tab/Shift+Tab through `license terms`, `I agree to the license terms.`, `Install`, and `Cancel`; press Space on the license checkbox; verify focus can reach `Install`; then Cancel and confirm exit. +4. For install-intent tests, click `Install` only when the machine/VM is prepared for an install run and evidence collection is in place. +5. If the MSI internal UI opens, attach to or inspect the visible `msiexec` window and capture the Destination Folders and feature-selection screens. + +Known behavior: the Burn launcher PID may not own the visible WixStdBA window after startup. If `get_snapshot` returns no accessible elements, use desktop window discovery, `get_focused_element`, keyboard commands, or coordinate clicks rather than concluding the UI is untestable. + ## Evidence Checklist - Bundle command and UI level used. +- WinApp MCP app/window IDs or visible window title/PID used for automation. +- Focus/click path used to reach key controls when UI Automation does not expose a full element tree. - Screenshot of bundle welcome/license screen. - Screenshot of MSI destination folders dialog. - Screenshot of feature selection dialog if touched. diff --git a/.github/skills/fieldworks-wix6-upgrade-patching/SKILL.md b/.github/skills/fieldworks-wix6-upgrade-patching/SKILL.md index 89efa79428..c7fb43fa94 100644 --- a/.github/skills/fieldworks-wix6-upgrade-patching/SKILL.md +++ b/.github/skills/fieldworks-wix6-upgrade-patching/SKILL.md @@ -61,6 +61,15 @@ When Programs and Features shows duplicate or strange FieldWorks entries: 4. Use Windows Installer product inventory and registry snapshots to identify which product/package registered each ARP row. 5. Verify uninstall from ARP does not hang and removes the expected entry only. +## WinApp MCP For Upgrade/Uninstall Runs + +Use WinApp MCP to observe and drive UI during upgrade, repair, and uninstall evidence runs: + +- Attach to the visible bundle/MSI/ARP window and capture the title, PID, focused element, and any prompt text before clicking. +- For ARP uninstall hangs, use WinApp to identify whether focus is on a hidden prompt, MSI dialog, bundle confirmation, or UAC-adjacent window before changing authoring. +- During WiX 3 to WiX 6 upgrade tests, use WinApp screenshots/focus traces for the bundle welcome/license page, MSI destination folders page, feature tree, progress, completion, and any maintenance prompt. +- Do not use WinApp to bypass prompts silently. The point is to document and reproduce the user-visible path while bundle/MSI logs and snapshots capture the underlying state. + ## MSP Patch Gate Before creating a WiX 6 MSP path, classify the change: diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index c578ee748d..68b9049a40 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -61,6 +61,103 @@ jobs: ./Output/**/*.log ./Output/**/*.log.stderr + wix6_installer_build: + name: Build WiX 6 installer artifacts + runs-on: windows-latest + env: + CROWDIN_API_KEY: ${{ secrets.FLEX_CROWDIN_API }} + LcmRootDir: ${{ github.workspace }}/Localizations/LCM + steps: + - name: Checkout Files + uses: actions/checkout@v6 + id: checkout + + - name: Checkout Helps + uses: actions/checkout@v6 + with: + repository: 'sillsdev/FwHelps' + ref: 'develop' + fetch-depth: 0 + path: 'DistFiles/Helps' + + - name: Checkout Localizations + uses: actions/checkout@v6 + with: + repository: 'sillsdev/FwLocalizations' + ref: 'develop' + fetch-depth: 0 + path: 'Localizations' + + - name: Checkout liblcm + uses: actions/checkout@v6 + with: + repository: 'sillsdev/liblcm' + ref: 'master' + fetch-depth: 0 + path: 'Localizations/LCM' + + - name: Setup dotnet + uses: actions/setup-dotnet@v5 + with: + dotnet-version: | + 8.0.x + + - name: Validate installer prerequisites + shell: powershell + run: | + .\Build\Agent\Setup-InstallerBuild.ps1 -ValidateOnly + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + + - name: Build WiX 6 installer + shell: powershell + run: | + $buildLog = Join-Path $pwd 'wix6-installer-build.log' + $exitCode = 0 + Start-Transcript -Path $buildLog -Force + try { + .\build.ps1 -BuildInstaller -InstallerToolset Wix6 -Configuration Release -Verbosity detailed -MsBuildArgs @('/bl:wix6-installer-build.binlog','/p:FastBundleBuild=0') + $exitCode = $LASTEXITCODE + } + catch { + Write-Error $_ + $exitCode = 1 + } + finally { + Stop-Transcript + } + if ($exitCode -ne 0) { exit $exitCode } + + - name: Audit WiX 6 installer evidence + shell: powershell + run: | + .\Build\Agent\Test-Wix6InstallerBuildEvidence.ps1 -Configuration Release -Platform x64 -BuildLogPath .\wix6-installer-build.log -ReportPath .\Output\InstallerEvidence\wix6-ci-build-audit.txt + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + + - name: Upload WiX 6 installer artifacts + uses: actions/upload-artifact@v7 + if: ${{ !cancelled() }} + with: + name: wix6-installer-artifacts + if-no-files-found: error + path: | + FLExInstaller/wix6/bin/x64/Release/FieldWorksBundle.exe + FLExInstaller/wix6/bin/x64/Release/FieldWorksBundle.wixpdb + FLExInstaller/wix6/bin/x64/Release/FieldWorksOfflineBundle.exe + FLExInstaller/wix6/bin/x64/Release/FieldWorksOfflineBundle.wixpdb + FLExInstaller/wix6/bin/x64/Release/en-US/FieldWorks.msi + FLExInstaller/wix6/bin/x64/Release/en-US/FieldWorks.wixpdb + + - name: Upload WiX 6 build evidence + uses: actions/upload-artifact@v7 + if: ${{ always() }} + with: + name: wix6-installer-build-evidence + if-no-files-found: warn + path: | + wix6-installer-build.log + wix6-installer-build.binlog + Output/InstallerEvidence/** + publish_test_results: name: Publish Test Results if: ${{ !cancelled() }} diff --git a/.github/workflows/base-installer-cd.yml b/.github/workflows/base-installer-cd.yml index dc85a8d83a..bc0bf4ce37 100644 --- a/.github/workflows/base-installer-cd.yml +++ b/.github/workflows/base-installer-cd.yml @@ -1,6 +1,8 @@ -name: Base Installer +name: Legacy Base Installer (WiX 3) # Builds the FieldWorks Base Installers (On- and Offline) (x64). +# Transitional WiX 3 release lane: this workflow still checks out PatchableInstaller/genericinstaller. +# The WiX 6 migration lane lives in CI.yml and must not depend on PatchableInstaller. # If `make_release` is true: # - Uploads installers to https://flex-updates.s3.amazonaws.com/?prefix=jobs/FieldWorks-Win-all-Release-Base. # - Tags the repository. @@ -23,7 +25,7 @@ on: required: false default: 'develop' installer_ref: - description: 'Commit-ish for PatchableInstaller repository' + description: 'Commit-ish for PatchableInstaller repository (legacy WiX 3 lane only)' required: false default: 'master' localizations_ref: @@ -40,7 +42,7 @@ on: default: 'false' concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + group: ${{ github.workflow }}-${{ github.ref || github.run_id }} cancel-in-progress: true jobs: @@ -49,7 +51,7 @@ jobs: CROWDIN_API_KEY: ${{ secrets.FLEX_CROWDIN_API }} LcmRootDir: ${{ github.workspace }}/Localizations/LCM FILESTOSIGNLATER: ./signExternally - name: Build Debug and run Tests + name: Build legacy WiX 3 base installer and run tests runs-on: windows-latest steps: - name: Compute build number @@ -60,6 +62,7 @@ jobs: $combined = $lastJenkins + $githubRun echo "Calculated build number: $combined" echo "FW_BUILD_NUMBER=$combined" >> $env:GITHUB_ENV + echo "fw_build_number=$combined" >> $env:GITHUB_OUTPUT - name: Checkout Files uses: actions/checkout@v6 @@ -77,7 +80,7 @@ jobs: fetch-depth: 0 path: 'DistFiles/Helps' - - name: Checkout PatchableInstaller + - name: Checkout PatchableInstaller (legacy WiX 3 only) uses: actions/checkout@v6 id: installer-checkout with: @@ -295,8 +298,8 @@ jobs: uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe with: target_commitish: ${{ github.event.inputs.fw_ref || github.ref }} - tag_name: build-${{ env.FW_BUILD_NUMBER }} - name: "FieldWorks Base Build #${{ env.FW_BUILD_NUMBER }}" + tag_name: build-${{ steps.build_number.outputs.fw_build_number }} + name: "FieldWorks Base Build #${{ steps.build_number.outputs.fw_build_number }}" draft: false prerelease: true fail_on_unmatched_files: true diff --git a/.github/workflows/patch-installer-cd.yml b/.github/workflows/patch-installer-cd.yml index 4233836bc1..a70855c6a8 100644 --- a/.github/workflows/patch-installer-cd.yml +++ b/.github/workflows/patch-installer-cd.yml @@ -1,6 +1,8 @@ -name: Patch Installer +name: Legacy Patch Installer (WiX 3) # Builds the FieldWorks Patch Installer on the specified `base_release` +# Transitional WiX 3 patch lane: WiX 6 patch/MSP support is deferred and not provided by this workflow. +# This workflow still checks out PatchableInstaller/genericinstaller for the legacy patch process. # If `make_release` is true, uploads installers to https://flex-updates.s3.amazonaws.com/?prefix=jobs/FieldWorks-Win-all-Release-Patch. # Saves the build log as an artifact of the workflow run. # Note: FW_BUILD_NUMBER is higher than GITHUB_RUN_NUMBER because it needs to be higher than the build number on artifacts from our previous system. @@ -23,7 +25,7 @@ on: required: false default: 'develop' installer_ref: - description: 'Commit-ish for PatchableInstaller repository' + description: 'Commit-ish for PatchableInstaller repository (legacy WiX 3 lane only)' required: false default: 'master' localizations_ref: @@ -50,7 +52,7 @@ on: default: 'true' concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + group: ${{ github.workflow }}-${{ github.ref || github.run_id }} cancel-in-progress: true jobs: @@ -63,7 +65,7 @@ jobs: FILESTOSIGNLATER: ./signExternally GH_TOKEN: ${{ github.token }} BASE_BUILD_NUMBER: ${{ inputs.base_build_number || '1416' }} - name: Build Debug and run Tests + name: Build legacy WiX 3 patch installer and run tests runs-on: windows-latest steps: - name: Compute build number for archival @@ -92,7 +94,7 @@ jobs: fetch-depth: 0 path: 'DistFiles/Helps' - - name: Checkout PatchableInstaller + - name: Checkout PatchableInstaller (legacy WiX 3 only) uses: actions/checkout@v6 id: installer-checkout with: diff --git a/Build/Agent/Test-Wix6InstallerBuildEvidence.ps1 b/Build/Agent/Test-Wix6InstallerBuildEvidence.ps1 new file mode 100644 index 0000000000..aa829bee7e --- /dev/null +++ b/Build/Agent/Test-Wix6InstallerBuildEvidence.ps1 @@ -0,0 +1,278 @@ +[CmdletBinding()] +param( + [Parameter(Mandatory = $false)] + [string]$RepoRoot, + + [Parameter(Mandatory = $false)] + [ValidateSet('Debug', 'Release')] + [string]$Configuration = 'Release', + + [Parameter(Mandatory = $false)] + [ValidateSet('x64')] + [string]$Platform = 'x64', + + [Parameter(Mandatory = $false)] + [string]$BuildLogPath, + + [Parameter(Mandatory = $false)] + [string]$ReportPath, + + [Parameter(Mandatory = $false)] + [string]$WixVersion = '6.0.2', + + [Parameter(Mandatory = $false)] + [long]$MinimumOfflineBundleBytes = 50000000, + + [Parameter(Mandatory = $false)] + [switch]$SkipBuildLogAudit, + + [Parameter(Mandatory = $false)] + [switch]$AllowPatchableInstallerDirectory +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +function Resolve-DefaultRepoRoot { + $repoRootPath = Resolve-Path -LiteralPath (Join-Path $PSScriptRoot '..\..') + return $repoRootPath.Path +} + +function New-DirectoryIfMissing { + param([Parameter(Mandatory = $true)][string]$Path) + + if (!(Test-Path -LiteralPath $Path)) { + $null = New-Item -ItemType Directory -Path $Path -Force + } +} + +function Convert-ToRepoRelativePath { + param( + [Parameter(Mandatory = $true)] + [string]$Path, + [Parameter(Mandatory = $true)] + [string]$Root + ) + + $fullPath = [System.IO.Path]::GetFullPath($Path) + $fullRoot = [System.IO.Path]::GetFullPath($Root).TrimEnd('\', '/') + if ($fullPath.StartsWith($fullRoot, [System.StringComparison]::OrdinalIgnoreCase)) { + return $fullPath.Substring($fullRoot.Length).TrimStart('\', '/') + } + + return $fullPath +} + +function Add-CheckResult { + param( + [Parameter(Mandatory = $true)] + [bool]$Passed, + [Parameter(Mandatory = $true)] + [string]$Message + ) + + if ($Passed) { + $script:ReportLines.Add(('[OK] {0}' -f $Message)) + return + } + + $script:ReportLines.Add(('[FAIL] {0}' -f $Message)) + $script:Failures.Add($Message) +} + +function Add-WarningResult { + param([Parameter(Mandatory = $true)][string]$Message) + + $script:ReportLines.Add(('[WARN] {0}' -f $Message)) + $script:Warnings.Add($Message) +} + +function Add-FileEvidence { + param( + [Parameter(Mandatory = $true)] + [string]$Label, + [Parameter(Mandatory = $true)] + [string]$Path, + [Parameter(Mandatory = $true)] + [string]$Root, + [Parameter(Mandatory = $false)] + [switch]$Hash + ) + + if (!(Test-Path -LiteralPath $Path -PathType Leaf)) { + Add-CheckResult -Passed $false -Message ("Missing {0}: {1}" -f $Label, (Convert-ToRepoRelativePath -Path $Path -Root $Root)) + return + } + + $item = Get-Item -LiteralPath $Path + $relativePath = Convert-ToRepoRelativePath -Path $item.FullName -Root $Root + Add-CheckResult -Passed $true -Message ("{0}: {1}" -f $Label, $relativePath) + $script:EvidenceLines.Add(("{0}`t{1}`t{2}" -f $Label, $relativePath, $item.Length)) + + if ($Hash) { + $fileHash = Get-FileHash -LiteralPath $item.FullName -Algorithm SHA256 + $script:EvidenceLines.Add(("{0} SHA256`t{1}`t{2}" -f $Label, $relativePath, $fileHash.Hash)) + } +} + +function Test-BuildLogForForbiddenReferences { + param( + [Parameter(Mandatory = $true)] + [string]$Path, + [Parameter(Mandatory = $true)] + [string]$ExpectedWixVersion + ) + + if (!(Test-Path -LiteralPath $Path -PathType Leaf)) { + Add-CheckResult -Passed $false -Message ("Build log not found: {0}" -f $Path) + return + } + + Add-CheckResult -Passed $true -Message ("Build log found: {0}" -f (Convert-ToRepoRelativePath -Path $Path -Root $RepoRoot)) + + $lines = Get-Content -LiteralPath $Path + $legacyToolPattern = [regex]'(?i)(^|[\\/\s"''])(candle|light|insignia)\.exe(["''\s]|$)' + $legacyPathPattern = [regex]'(?i)\b(PatchableInstaller|genericinstaller)\b' + $heatPattern = [regex]'(?i)\bheat\.exe\b' + $heatDiagnosticPattern = [regex]'(?i)^\s*heat\.exe\s*:\s*(warning|error)\s+HEAT\d+:' + $expectedHeatPatternText = ('(?i)(packages|\.nuget[\\/]packages)[\\/]wixtoolset\.heat[\\/]{0}[\\/]tools[\\/]net472[\\/]x64[\\/]heat\.exe' -f [regex]::Escape($ExpectedWixVersion)) + $expectedHeatPattern = [regex]$expectedHeatPatternText + $heatLines = New-Object 'System.Collections.Generic.List[string]' + + for ($i = 0; $i -lt $lines.Count; $i++) { + $line = $lines[$i] + $lineNumber = $i + 1 + + if ($legacyPathPattern.IsMatch($line)) { + Add-CheckResult -Passed $false -Message ("Build log references legacy generic installer path at line {0}: {1}" -f $lineNumber, $line.Trim()) + } + + if ($legacyToolPattern.IsMatch($line)) { + Add-CheckResult -Passed $false -Message ("Build log references legacy WiX 3 tool at line {0}: {1}" -f $lineNumber, $line.Trim()) + } + + if ($heatPattern.IsMatch($line)) { + if ($heatDiagnosticPattern.IsMatch($line)) { + continue + } + + $heatLines.Add($line.Trim()) + if (-not $expectedHeatPattern.IsMatch($line)) { + Add-CheckResult -Passed $false -Message ("Heat invocation is not from WixToolset.Heat {0} at line {1}: {2}" -f $ExpectedWixVersion, $lineNumber, $line.Trim()) + } + } + } + + if ($heatLines.Count -eq 0) { + Add-WarningResult -Message 'No heat.exe invocation was found in the build log. This can happen on incremental builds, but clean CI should show WixToolset.Heat usage.' + } else { + Add-CheckResult -Passed $true -Message ("Build log contains {0} WixToolset.Heat invocation/reference line(s)." -f $heatLines.Count) + } +} + +if ([string]::IsNullOrWhiteSpace($RepoRoot)) { + $RepoRoot = Resolve-DefaultRepoRoot +} + +$RepoRoot = (Resolve-Path -LiteralPath $RepoRoot).Path + +if ([string]::IsNullOrWhiteSpace($ReportPath)) { + $ReportPath = Join-Path $RepoRoot 'Output\InstallerEvidence\wix6-installer-build-evidence.txt' +} + +$reportDir = Split-Path -Parent $ReportPath +New-DirectoryIfMissing -Path $reportDir + +$script:Failures = New-Object 'System.Collections.Generic.List[string]' +$script:Warnings = New-Object 'System.Collections.Generic.List[string]' +$script:ReportLines = New-Object 'System.Collections.Generic.List[string]' +$script:EvidenceLines = New-Object 'System.Collections.Generic.List[string]' + +$script:ReportLines.Add('WiX 6 installer build evidence') +$script:ReportLines.Add(('Started: {0:o}' -f (Get-Date))) +$script:ReportLines.Add(('RepoRoot: {0}' -f $RepoRoot)) +$script:ReportLines.Add(('Configuration: {0}' -f $Configuration)) +$script:ReportLines.Add(('Platform: {0}' -f $Platform)) +$script:ReportLines.Add(('Expected WixToolset version: {0}' -f $WixVersion)) +$script:ReportLines.Add('') + +$patchableInstallerPath = Join-Path $RepoRoot 'PatchableInstaller' +if ($AllowPatchableInstallerDirectory) { + Add-WarningResult -Message 'PatchableInstaller directory check is disabled for this run.' +} else { + Add-CheckResult -Passed (!(Test-Path -LiteralPath $patchableInstallerPath)) -Message 'PatchableInstaller directory is absent from the WiX 6 build worktree.' +} + +$wixOutputDir = Join-Path $RepoRoot ('FLExInstaller\wix6\bin\{0}\{1}' -f $Platform, $Configuration) +$wixCultureDir = Join-Path $wixOutputDir 'en-US' + +$expectedArtifacts = @( + @{ Label = 'MSI'; Path = Join-Path $wixCultureDir 'FieldWorks.msi' }, + @{ Label = 'MSI wixpdb'; Path = Join-Path $wixCultureDir 'FieldWorks.wixpdb' }, + @{ Label = 'Online bundle'; Path = Join-Path $wixOutputDir 'FieldWorksBundle.exe' }, + @{ Label = 'Online bundle wixpdb'; Path = Join-Path $wixOutputDir 'FieldWorksBundle.wixpdb' }, + @{ Label = 'Offline bundle'; Path = Join-Path $wixOutputDir 'FieldWorksOfflineBundle.exe' }, + @{ Label = 'Offline bundle wixpdb'; Path = Join-Path $wixOutputDir 'FieldWorksOfflineBundle.wixpdb' } +) + +foreach ($artifact in $expectedArtifacts) { + Add-FileEvidence -Label $artifact.Label -Path $artifact.Path -Root $RepoRoot -Hash +} + +$offlineBundlePath = Join-Path $wixOutputDir 'FieldWorksOfflineBundle.exe' +if ((Test-Path -LiteralPath $offlineBundlePath -PathType Leaf) -and $MinimumOfflineBundleBytes -gt 0) { + $offlineBundle = Get-Item -LiteralPath $offlineBundlePath + Add-CheckResult -Passed ($offlineBundle.Length -ge $MinimumOfflineBundleBytes) -Message ("Offline bundle is at least {0} bytes (actual: {1})." -f $MinimumOfflineBundleBytes, $offlineBundle.Length) +} + +$offlineSourceDir = Join-Path $wixCultureDir 'SourceDir' +$offlinePayloads = @( + 'ndp48-x86-x64-allos-enu.exe', + ('vcredist_2008_{0}.exe' -f $Platform), + ('vcredist_2010_{0}.exe' -f $Platform), + ('vcredist_2012_{0}.exe' -f $Platform), + ('vcredist_2013_{0}.exe' -f $Platform), + ('vcredist_2015-19_{0}.exe' -f $Platform) +) + +foreach ($payload in $offlinePayloads) { + Add-FileEvidence -Label 'Offline prerequisite payload' -Path (Join-Path $offlineSourceDir $payload) -Root $RepoRoot +} + +Add-FileEvidence -Label 'Offline FLEx Bridge payload' -Path (Join-Path $RepoRoot 'FLExInstaller\wix6\libs\FLExBridge_Offline.exe') -Root $RepoRoot + +if (-not $SkipBuildLogAudit) { + if ([string]::IsNullOrWhiteSpace($BuildLogPath)) { + Add-WarningResult -Message 'No build log was supplied; skipping legacy tool/path log audit.' + } else { + $resolvedBuildLog = $BuildLogPath + if (-not [System.IO.Path]::IsPathRooted($resolvedBuildLog)) { + $resolvedBuildLog = Join-Path $RepoRoot $resolvedBuildLog + } + + Test-BuildLogForForbiddenReferences -Path $resolvedBuildLog -ExpectedWixVersion $WixVersion + } +} + +$script:ReportLines.Add('') +$script:ReportLines.Add('File evidence') +$script:ReportLines.Add("Label`tPath`tValue") +$script:ReportLines.AddRange($script:EvidenceLines) +$script:ReportLines.Add('') +$script:ReportLines.Add(('Warnings: {0}' -f $script:Warnings.Count)) +$script:ReportLines.Add(('Failures: {0}' -f $script:Failures.Count)) +$script:ReportLines.Add(('Finished: {0:o}' -f (Get-Date))) + +$utf8NoBom = New-Object System.Text.UTF8Encoding($false) +[System.IO.File]::WriteAllLines($ReportPath, $script:ReportLines, $utf8NoBom) + +Write-Output "WiX 6 build evidence report: $ReportPath" +foreach ($line in $script:ReportLines) { + Write-Output $line +} + +if ($script:Failures.Count -gt 0) { + exit 1 +} + +exit 0 \ No newline at end of file diff --git a/FLExInstaller/wix6/FieldWorks.Bundle.wixproj b/FLExInstaller/wix6/FieldWorks.Bundle.wixproj index a9928157c9..f202766f6a 100644 --- a/FLExInstaller/wix6/FieldWorks.Bundle.wixproj +++ b/FLExInstaller/wix6/FieldWorks.Bundle.wixproj @@ -8,6 +8,7 @@ {RawFileName};{HintPathFromItem};$(ReferencePaths) + wixext6 {RawFileName};{HintPathFromItem};$(ReferencePaths) + wixext6 {RawFileName};{HintPathFromItem};$(ReferencePaths) + wixext6 {RawFileName};{HintPathFromItem};$(ReferencePaths) - wixext6 + wixext6 {RawFileName};{HintPathFromItem};$(ReferencePaths) - wixext6 + wixext6