Fix four setup and engine defects found in the CM1 lab freeze test - #213
Merged
Sev7eNup merged 1 commit intoAug 15, 2026
Merged
Conversation
All four reproduce on a real installation and are verified fixed on the same
machine, not only in unit tests.
Unattended install skipped all provisioning once a host already had NodePilot.
AnswerMode came from IsUpdateSelected(), which reads ModePage.SelectedValueIndex
- and /ANSWERFILE skips the mode page, so the index kept its hard default of 0
('update'). An answer file saying "mode": "install" therefore dropped every
provisioning key on any second install, SCCM re-deploy or golden-image refresh.
The file decides on that path now; update mode accepts no provisioning keys, so
a Provision run for one performs no action and exits 0.
Database provisioning never received -CertificateHostName. The script declares
the parameter and the installer builds HostNameInCertificate from the same
answer-file key, but the provisioning call dropped it, so it derived the name
from -Server. The very normal 'localhost' against a server whose certificate
names the FQDN failed with "The target principal name is incorrect" and created
nothing.
A failed provisioning was invisible unattended. It exits 0 and reports itself
inside provision.ini, which the readiness page reads and the silent path did
not - so the run walked on to Apply and died in the SQL pre-flight telling the
operator to have a DBA create a login that was never the problem.
The uninstaller ignored serviceName, installPath and dataPath. It passed
GetServiceName('') - the literal 'NodePilot' in the uninstaller process - and
{app}, which is only where Inno put the uninstaller, and never passed -DataPath
at all. A non-default installation was "uninstalled" with exit 0 while the
service, its port, its firewall rule and every program file stayed exactly where
they were, and only the bookkeeping that said NodePilot existed was removed. It
now reads all three back from the installation marker.
waitAny/waitNofM marked whole runs Failed when the losing branches were
runScript. Standing a branch down is what the junction is for, and StepRunner
records those as Cancelled - but only if the exception reaches it. The
PowerShell engines converted the cancellation into an ordinary failed result,
and one Failed step fails the execution, so a correct run came back red with
every junction, log and returnData green. Branches built from delay were
unaffected because they let the exception through. All four sites now rethrow on
caller cancellation, including the two catch-alls that re-wrapped the throw as
"Isolated execution failed: Script execution cancelled". A timeout stays a
failure.
Tests: the junction contract end to end (loser Cancelled, run Succeeded), both
PowerShell engines' cancel-versus-timeout split, and static contracts for the
provisioning gate, the provisioning verdict, the certificate host name and the
three uninstall parameters.
Sev7eNup
deleted the
fix/setup-provisioning-uninstall-and-junction-cancel
branch
August 15, 2026 22:16
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.
Found during the pre-freeze live test on the CM1 lab server. All four reproduce on a real installation, and all four are verified fixed on the same machine — not only in unit tests.
What was wrong
Unattended install skipped all provisioning once a host already had NodePilot.
AnswerModecame fromIsUpdateSelected(), which readsModePage.SelectedValueIndex— and/ANSWERFILEskips the mode page, so the index kept its hard default of0(update). An answer file saying"mode": "install"therefore dropped everyprovisioning.*key on any second install, SCCM re-deploy or golden-image refresh. Measured before the fix, with a marker written by a perfectly ordinary previous install:LocalMachine\MyDatabase provisioning never received
-CertificateHostName. The script declares the parameter and the installer buildsHostNameInCertificatefrom the same answer-file key, but the provisioning call dropped it, so it derived the name from-Server. Same server, database and principal, only the parameter differs:-Server localhost-Server localhost -CertificateHostName CM1.corp.contoso.comdb_ownergrantedA failed provisioning was invisible unattended. It exits 0 and reports itself inside
provision.ini, which the readiness page reads and the silent path did not — so the run walked on toApplyand died in the SQL pre-flight telling the operator to have a DBA create a login that was never the problem.The uninstaller ignored
serviceName,installPathanddataPath. It passedGetServiceName('')— the literalNodePilotin the uninstaller process — and{app}, which is only where Inno put the uninstaller, and never passed-DataPathat all. A non-default installation was "uninstalled" with exit 0 while this stayed behind:/PURGEDATA=1{app}Windows believed NodePilot was gone while it kept running and serving.
waitAny/waitNofMmarked whole runsFailedwhen the losing branches wererunScript. Standing a branch down is what the junction is for, andStepRunnerrecords those asCancelled— but only if the exception reaches it. The PowerShell engines converted the cancellation into an ordinary failed result, and oneFailedstep fails the execution. Same junction semantics, two branch activity types, run back to back:delayCancelledrunScriptFailedEvery other step in the failing run was green. This also skewed
GET /{id}/coverage, which counts junction-race cancellations underskippedCount.The fixes
/ANSWERFILEpath. Update mode accepts no provisioning keys, so aProvisionrun for one performs no action and exits 0 — the same reasoning the surrounding code already used for running it unconditionally.-CertificateHostNamethrough toProvision-NodePilotDatabase.ps1; an absent key keeps the script's own fallback.provision.database.status, exactly as the readiness page does.ServiceName,InstallPathandDataPathback fromHKLM\SOFTWARE\NodePilot\Server, falling back to the old guesses only when the marker is gone.Isolated execution failed: Script execution cancelled, which was the same red step under a new name. A timeout stays a failure withTimedOutset. Remote/WinRM is covered too.Tests
WorkflowEngineTests— the junction contract end to end: loserCancelled, noFailedstep, executionSucceeded.RunspaceEngineAsyncTests/ProcessIsolationEngineTests— cancel throws, timeout still returns a failed result. Both previously asserted the defective behaviour.Test-DeploymentTemplates.ps1— static contracts for the provisioning gate, the provisioning verdict, the certificate host name, and the three uninstall parameters, plus negative contracts against the exact old code. Three existing contracts were anchored on the gate line that was itself the bug and moved with it.Ran: full
NodePilot.Engine.Tests(1832 passed),Test-DeploymentTemplates.ps1,Test-SetupAdapter.ps1(213 assertions). Everything else is covered by CI.Verified live on CM1
Rebuilt and re-run against the failing cases: provisioning now runs with a previous installation present (InitSession → Provision → Apply, certificate generated, database created via
localhost), a deliberately broken provisioning aborts naming the real TLS cause, a non-default install uninstalls completely with/PURGEDATA=1, and the junction runbook that reportedFailednow reportsSucceededwith its losersCancelled.Still open from the same test round and deliberately not in this PR: the update path does not refresh the installation marker, and Inno's
{app}does not follow the answer file'sinstallPath.