Conversation
Test-WcdNetworkAdapterActive was one regex behind 22 lines of function and comment-based help. The issue counted one caller; there are two - the Where-Object in Get-WcdActiveNetworkAdapters and the Wi-Fi branch in Set-WcdNetworkDiagnostics - so the regex is now written at both, and the function is gone. The virtual-adapter exclusion list is the real domain knowledge in this file and was buried between two functions that did nothing. It is now $script:WcdIrrelevantAdapterPattern at the top, where someone whose VPN client turned up in the summary will find it. tests/Config-Network.Tests.ps1 gains three tests that hit the exclusion directly rather than through a whole diagnostic run, plus one that pins the active-and-relevant combination the inlining touched. Also adds CLAUDE.md: how to run pwsh and Pester on Linux without sudo, and which two tests fail there on purpose. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CONTEXT.md names Form Factor, Environment and Optional Tool. The interface
already honoured them; the implementation drifted two lines in, so someone
grepping for Environment found the parameter and stopped, because the value
was called $usageResult from the moment it was read.
Locals in Resolve-WcdExecutionOptions:
$deviceResult -> $formFactorResult
$usageResult -> $environmentResult $usageLabel -> $environmentLabel
$engineerResult -> $optionalToolResult $engineerTypes -> $optionalToolNames
$engineerYesNo -> $optionalToolAnswer
$T keys, in both language tables:
PromptUsageDesc1/2 -> PromptEnvironmentDesc1/2
PromptEngineer, PromptEngineerDesc1/2 -> PromptOptionalTool, ...Desc1/2
Engineer{BoxTitle,CombineHint,ChoicePrompt,AtLeastOne,InvalidChoice,Selection}
-> OptionalTool...
The issue lists only the first four Engineer keys; renaming the other four too
is what lets the guard pattern anchor on them rather than leave half the drift
in place. The EN display strings still say "Engineering workstation" - that is
correct English for the technician, and only the keys were wrong.
$moduleStatus loses the three French identifiers: Etapes/Echecs/Avertissements
-> Steps/Failures/Warnings. Format-WcdModuleLine was the only reader.
The guard is the actual deliverable. tests/Help.Tests.ps1 now builds the stale
pattern once in BeforeAll and uses it twice: once to sweep src/*.ps1, once to
assert it does NOT flag Config-DeviceManager, Get-WcdPnPDevices,
Set-WcdDeviceManagerStatus, or the FR display strings 'Echecs: {0}' and
'Avertissements: {0}'. Verified by reintroducing $deviceResult, the Etapes
hashtable key and $ModuleStatus.Etapes one at a time - each fails the test.
132 passed / 2 failed locally; both failures are the known Windows-only ones
documented in CLAUDE.md. PSScriptAnalyzer clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A key present in one table and absent from the other does not throw:
PowerShell returns $null and the checklist renders a blank label, in one
language only. Nobody notices until a technician runs the tool in French.
tests/Translations.Tests.ps1 extracts the single $T assignment from
src/Invoke-WcdConfiguration.ps1 with the AST parser - the orchestrator cannot
be dot-sourced, it would run the whole run and exit - and replays it once per
-ScriptUI value. Both tables are pure literals, so replaying them builds two
hashtables and does nothing else.
The comparison recurses: Checklist, Labels, Remedy and ComputerNameRejected
are nested, and a flat .Keys comparison misses exactly the cases that hurt.
It runs both directions, so a key added to FR alone fails too.
Also asserts no value is empty - a present-but-blank key renders the same
blank line an absent one does.
Verified by deleting one key at a time and re-running: a top-level FR key
(WaitEnterFinish), a nested FR key (Checklist.Erp) and a nested EN key
(Remedy.RequiresAdmin) each fail the test. No divergence exists today, so
nothing needed fixing.
Not covered: format placeholders differing between the two languages, e.g. an
EN string with {0} whose FR translation dropped it. Same class of silent bug,
but out of scope here - add it when one bites.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ion edits (#17) Adding a Module was four small edits according to the manual and six in practice, across three files. Miss one and nothing fails: a missing progress-plan entry dropped the Module from the run silently, because $moduleStepPlan.ContainsKey treated "not planned" and "planned as empty" differently and an absent Module ran with a $null step list. A missing $T.Checklist key rendered a blank label. Every src/Config-*.ps1 now exports Get-Wcd<Name>Descriptor, returning Name, Order, RowOrder, Steps, Rows and Invoke. The orchestrator globs Config-*.ps1, dot-sources each, calls its descriptor and builds everything from what comes back. The $modules array and the switch ($modName) dispatch are gone. Get-WcdModuleProgressPlan and Get-WcdTechnicalStepLabels now assemble descriptors instead of hardcoding twelve Modules. Absorbing the non-uniformity the issue lists: - Form-Factor Steps: a Step carries Planned = $false rather than being absent, so Config-Power still plans five on a Laptop and two on a Desktop while the three battery/lid labels survive for the diagnostic. - Manifest Steps: Config-Applications and Config-Printer read $Config, which the descriptor receives. - Conditional Modules: Steps = @() still means "skip this Module", and the Module's rows are emitted anyway - a declined rename owes the technician a Manual Step saying so. - Rows that are not one-per-Module: Config-Disk gives two, Config-WindowsUpdate folds two Steps into one. A row is either @{ Label; Steps } or a fixed @{ Label; Kind; Detail }, with MissingKind / MissingDetail / OmitWhenMissing for the cases that need them. - Run order: Order and RowOrder are separate, because the checklist is not in run order - it never was. Both live in the Module's own file. Two things stay in the Diagnostic because they belong to no Module, now named rather than inline: Resolve-WcdRestartEntry (raised from Results across Config-Identity and Config-WindowsUpdate) and the trailing Manual Steps. Descriptors take -Translations rather than reading $script:T, so a Module stays testable on its own. The signature is uniform across all twelve, which means some descriptors declare parameters they do not read; PSReviewUnusedParameter is suppressed per function with that justification rather than repo-wide. A descriptor that is missing or malformed stops the run at startup naming what is wrong - Test-WcdModuleDescriptor holds the contract, and tests/ModuleDescriptor.Tests.ps1 runs all twelve Modules through it under three execution profiles, checks Order/RowOrder uniqueness, checks every row references a Step the Module declares, and checks the guard itself rejects each missing field. A Module that fails to dot-source still degrades to an ERROR row and lets the run continue, as before. Verified byte-identical against main: a harness dot-sources both trees and dumps the progress plan, every checklist row (Step, Label, Kind, Detail) and the rendered line for 7 scenarios x 2 languages - laptop/desktop, workstation/vdi, declined apps, identity applied, update reboot pending, no printers, winget missing, nothing ran, unelevated power. 554 lines, zero diff. Dropped: $T.ModuleNotFound in both tables (a globbed file cannot be missing), and the dead PrinterAdd / PrinterSkip step labels, which no Module emitted. Manual chapter 7 rewritten: the registration section is one code block in one file, with a table explaining each field. The row label in both $T tables is called out as the one thing still outside - and #20's parity test now catches forgetting the second one. 153 passed / 2 failed locally; both are the known Windows-only failures. PSScriptAnalyzer clean. Manual recompiled. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rLeft (#18) Every Module wrote the same twelve lines around the one line that does the work, and the Result shape was a literal repeated across twelve files. Its optional fields - Severity, RemedyKey, RemedyArgs, Applied, RebootPending - had no schema anywhere: a typo in a property name was not an error, the Diagnostic read $null, and the row quietly rendered as a plain success. Invoke-WcdStep owns the progress events, both log lines, the try/catch and the Result shape. The five awkward cases the issue lists are absorbed by one mechanism, not five parameters: the Action returns nothing when the Step simply worked, or a hashtable merged over the Result. @{ Severity; Error; RemedyKey } did not run, and why - reported, not failed @{ Severity; Error } severity read off the machine, not a throw @{ Applied }, @{ RebootPending } extra fields the Diagnostic reads @{ RemedyKey; RemedyArgs } format arguments for the remediation @{ Log } a log line other than -SuccessLog Anything the Action emits that is not a hashtable is ignored, so an Action calling a command that writes to the pipeline still means "it worked". The one extra parameter is -OnFailure, which classifies a caught error into a failure fragment. Config-TaskbarLeft had the registry-GPO classification written out twice; it is now one scriptblock both Steps share. Without it that Module would need the -Raw escape hatch the issue warns about. Progress kinds come from Complete-WcdProgressStep, which already decided that mapping - the runner does not repeat it. Config-Power and Config-TaskbarLeft converted. Their tests pass unchanged, but those cover only 2 of the 8 paths, so the Results were also compared against main directly: a harness dot-sources both trees and dumps every Result property, every log line (timestamps stripped) and every progress event for laptop and desktop, elevated and unelevated, powercfg throwing, and the registry write succeeding, denied and failing generically. 58 lines, zero diff - including property order, which the JSON report reproduces and which needed [ordered] on the Power fragment to preserve. tests/WcdHelpers.Tests.ps1 gains 12 tests: success, throw, pipeline noise ignored, did-not-run-with-reason, OnFailure classifying, OnFailure not being allowed to declare success, severity chosen by the Action, extra fields, RemedyArgs, the Log override staying out of the Result, no log line when there is nothing to say, and Step/Success/Error staying first. The remaining 10 Modules are left alone deliberately, and the manual now says why: Config-Applications and Config-Printer loop over manifest entries, Config-Network branches on what the adapters reported, and a runner half its callers escape from is not a runner. 165 passed / 2 failed locally; both are the known Windows-only failures. PSScriptAnalyzer clean. Manual recompiled. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Sep 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements #17, #18, #20, #21 and #22, in the order their dependencies impose:
#22 → #21 → #20 → #17 → #18. One commit per issue.
#19 is not implemented — see the note at the bottom.
What each commit does
82d9d82Test-WcdNetworkAdapterActivefolded into its callers; the virtual-adapter exclusion list promoted to a named script-scope variable at the top of the file, where someone whose VPN client turned up in the summary will find it.91d5921Resolve-WcdExecutionOptionsand both$Ttables;$moduleStatusloses the three French identifiers. The extended stale-vocabulary pattern is the actual deliverable.c5923f1tests/Translations.Tests.ps1asserts the EN and FR tables expose identical keys, recursing intoChecklist,Labels,RemedyandComputerNameRejected.5da3ed2Config-*.ps1exports a descriptor. The$modulesarray and theswitch ($modName)dispatch are gone; the orchestrator globssrc/Config-*.ps1.d7e1394Invoke-WcdStepowns the progress events, both log lines, the try/catch and the Result shape.Config-PowerandConfig-TaskbarLeftconverted; the other ten left alone deliberately.Two issues disagreed with the code, and the code won
Test-WcdNetworkAdapterActivehas one caller. It has two —Config-Network.ps1:113and:519. The regex is inlined at both.Modules run Identity → Power → Decimal → Taskbar → …, the checklist reads
Identity → Restart → Taskbar → Language → Keyboard → Decimal → Power → ….
So a descriptor carries
OrderandRowOrder. Both live in the Module'sown file, so it is still one registration point rather than six.
How "byte-identical" was verified
Not by reading the diff. Two harnesses dot-source both trees and compare:
Step,Label,Kind,Detail) and the rendered line, for 7 scenarios × 2 languages:laptop/desktop, workstation/vdi, declined apps, identity applied, update
reboot pending, no printers, winget missing, nothing ran, unelevated power.
554 lines, zero diff.
every progress event, for 8 paths: laptop/desktop × elevated/unelevated,
powercfg throwing, registry write succeeding / denied / failing generically.
58 lines, zero diff — including property order, which the JSON report
reproduces and which needed
[ordered]on the Power fragment to preserve.Each new guard was also verified by breaking the thing it guards:
$deviceResult, theEtapeshashtable key and$ModuleStatus.Etapesone at a time; each fails the test.fails, in both directions.
Test-WcdModuleDescriptoris itself tested against each missing field.Also in here
CLAUDE.md: how to runpwshand Pester on Linux withoutsudo, and whichtwo tests fail there on purpose (
New-WinUserLanguageListandGet-CimInstancecannot be mocked away — Pester'sMockneeds the command toexist first). A clean local run is 165 passed / 2 failed; CI on Windows is
still the authority.
a new section on when
Invoke-WcdStepis the wrong tool.docs/manual.pdfrecompiled.
$T.ModuleNotFoundin both tables (a globbed file cannot bemissing) and the
PrinterAdd/PrinterSkipstep labels, which no Moduleemitted.
.gitignorepicks updocs/research/, which was already uncommitted in theworking tree.
#19 is deliberately not here
Its own author writes that it "should be closed as
wontfixwithoutembarrassment if it keeps losing to more useful work", and that it is worth
doing for tidiness rather than for extensibility. #20 is what actually stops the
bug, and it landed. Closing #19 as
wontfixfor the reason the issue alreadygives.
🤖 Generated with Claude Code