Repository navigation
Build-PSBuildUpdatableHelp cannot succeed: missing landing page, undefined $moduleOutDir, unbound Module #169
Description
Found while writing the baseline test coverage for #149. Build-PSBuildUpdatableHelp cannot
succeed as currently wired — not "is untested", but "throws on the first locale, every time,
on every platform that reaches the cab step."
There are three defects. The first masks the second and third.
1. The module landing page is never generated
New-ExternalHelpCab -LandingPagePath requires the PlatyPS module page,
<DocsPath>/<locale>/<Module>.md. Build-PSBuildMarkdown calls New-MarkdownHelp without
-WithModulePage, so that file is never created. Observed against the test fixture:
MARKDOWN FILES: Get-Widget.md, Set-Widget.md
UPDATABLE HELP ERROR: System.Management.Automation.ParameterBindingValidationException ::
Cannot validate argument on parameter 'LandingPagePath'. Path
'...\docs\en-US\PSBuildTestFixture.md' is not a file.Only per-command markdown is produced. The landing page the cab step depends on is not.
2. $moduleOutDir is an undefined variable
PowerShellBuild/Public/Build-PSBuildUpdatableHelp.ps1:59:
CabFilesFolder = [IO.Path]::Combine($moduleOutDir, $locale)$moduleOutDir is not a parameter, is not assigned in the function, and is not set by the
GenerateUpdatableHelp task — the task passes only DocsPath and OutputPath. Nothing in
psakeFile.ps1 or build.properties.ps1 defines it either (build.properties.ps1 has
$outDir; the module output path lives at $PSBPreference.Build.ModuleOutDir). With defect 1
fixed, this throws ArgumentNullException from [IO.Path]::Combine.
The value it wants is $PSBPreference.Build.ModuleOutDir, which the task does not pass, so
the fix needs a new parameter rather than a rename.
3. Module is never supplied, and its default resolves to nothing
[string]$Module = $ModuleName$ModuleName is a parent-scope variable that the GenerateUpdatableHelp task does not set.
The task calls Build-PSBuildUpdatableHelp -DocsPath ... -OutputPath ... with no -Module, so
$Module binds to an empty string and LandingPagePath becomes <DocsPath>/<locale>/.md. A
defaulted-from-caller-scope parameter is fragile regardless; it should be Mandatory or
defaulted from a passed-in value.
Why this went unnoticed
GenerateUpdatableHelp is opt-in — nothing depends on it, so a default build never runs it.
This repository also does not run its own docs tasks at all: the root psakeFile.ps1 goes
Init → Clean → Build → Analyze → Pester → Publish. And the function had no tests. Three layers
of "nothing looks at this."
Relationship to the PlatyPS migration
This is #152's problem to absorb. That ticket is scoped as a port of
Build-PSBuildUpdatableHelp to New-HelpCabinetFile; it is really a port plus these three
fixes, because there is no working behavior to port. The map already flags #152 as the riskiest
link in the chain — this is why.
The baseline tests added in #149 encode the desired behavior and are skipped with a pointer
here, so they turn green when this is fixed rather than having to be written twice.
Done when
Build-PSBuildUpdatableHelp produces a .cab and a HelpInfo.xml for the test fixture on
Windows, and the skipped assertions in tests/Build-PSBuildHelp.tests.ps1 are unskipped and
passing.
Activity
tablackburn commentedon Aug 24, 2026
Correction to the issue body: defect 1 does not reliably mask defect 2. Which one surfaces depends on the order PowerShell binds the splatted parameters, so both are independently reachable.
The probe in the body hit
LandingPagePath; the test added in #149, splatting the same values, hits the other one first:That also confirms defect 2 concretely:
[IO.Path]::Combine($moduleOutDir, $locale)with $moduleOutDir unset collapses to the bare locale nameen-USrather than throwing, producing a relative path that does not exist. All three defects need fixing; none is shadowed by another.tablackburn commentedon Aug 24, 2026
Adding a fourth acceptance criterion, from Copilot's review of #170:
That is correct, and it sharpens defect 3. Fixing the function is not enough: the three defects can all be repaired at the function level and
GenerateUpdatableHelpwould still be broken, because the task never passes-Moduleand never passes the module output directory that defect 2 needs. The baseline in #170 passes-Moduleexplicitly, so it cannot catch that.Added to Done when: the
GenerateUpdatableHelptask is exercised, not only the function, so a missing argument in the task wiring fails a test. The task chain isInit -> Clean -> StageFiles -> GenerateMarkdown -> GenerateMAML -> GenerateUpdatableHelp, and psake cannot nest, so this needs the child-process patterntests/build.tests.ps1already uses rather than a direct call.This makes #152 a port plus four pieces of work: three function-level defects and the task wiring that hides them.
feat: Migrate updatable help to the PlatyPS 1.x cabinet pipeline (psa…