Public/Start-HDTWizardDeployment.ps1
|
function Start-HDTWizardDeployment { <# .SYNOPSIS Turns what the technician answered into the variable set the engine runs on - or into a refusal. .DESCRIPTION W5 OF .planning/WPF-FIRST.md: "summary, and a Deploy button - the wizard hands the engine a resolved variable set". The button already says Deploy on the last page (Step-HDTWizardPage's caption); this is what it hands over. THE HANDOFF WAS SITTING IN THE ENTRY POINT. Start-HDTDeployment.ps1 read the answer, decided whether it counted as consent, put the typed values back through Resolve-HDTVariable and copied the result into a case-insensitive dictionary - four decisions in a file whose own test asserts it holds no deployment logic. They are here now, where a test can reach them without a booted machine. THE SECOND RESOLUTION IS THE POINT, AND IT IS NOT A PATCH. A typed value cannot simply be written over the resolved set: that would set values with no provenance and no precedence, which is the whole thing DESIGN 3.1 exists to prevent. The answers go back through the resolver AS THE Wizard SOURCE, so a typed name beats the rule that guessed one, a rule still wins where a box was left empty, and Provenance says which of those happened for every name. AND ONLY WHEN THERE IS SOMETHING TO APPLY. A wizard that asked nothing - every page skipped because the rules already answered - resolves once, and the first resolution is handed back untouched. A DISMISSED WINDOW IS NOT CONSENT TO PARTITION A DISK. The allow-list is the same three answers the shell holds, held again here because this is the last gate before a disk is wiped: anything that is not exactly 'Next' or 'CommandPrompt' is a Cancel. Nothing is resolved for a refusal, because the answer to "what would this have deployed" is "nothing". IT OPENS NO PROMPT AND ENDS NO MACHINE. 'CommandPrompt' is reported, and what to do about it belongs to the caller: in WinPE that is opening the prompt, restoring the console the wizard hid, and leaving the machine running. .PARAMETER Answer What Show-HDTWizardShell returned: an Action and the collected Value. .PARAMETER ResolveArgument The arguments the first resolution used. It is COPIED before Wizard is added, because the caller keeps it for logging and for a retry - a command that quietly added to it would resolve the next attempt with this attempt's answers. .INPUTS None. This command does not accept pipeline input. .OUTPUTS System.Management.Automation.PSCustomObject with Action ('Deploy', 'Cancel' or 'CommandPrompt'), Variable, Applied and Resolved. .EXAMPLE $deploy = Start-HDTWizardDeployment -Answer $answer -ResolveArgument $resolveArgument if ($deploy.Action -ne 'Deploy') { throw 'HDTDeploymentCancelled: ...' } What the payload does with it. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Resolves variables and reports what was asked for; it changes no state and starts nothing. A confirmation prompt in WinPE, behind a window that has just closed, would hang a deployment.')] [CmdletBinding()] [OutputType([pscustomobject])] param( [Parameter(Mandatory = $true, Position = 0)] [ValidateNotNull()] [object] $Answer, [Parameter(Mandatory = $true)] [ValidateNotNull()] [System.Collections.IDictionary] $ResolveArgument ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' $empty = [System.Collections.Specialized.OrderedDictionary]::new([System.StringComparer]::OrdinalIgnoreCase) $action = '' if ($null -ne $Answer.PSObject.Properties['Action']) { $action = [string] $Answer.Action } # THE ALLOW-LIST, AND EVERYTHING ELSE IS A CANCEL. if ($action -eq 'CommandPrompt') { return [pscustomobject] @{ Action = 'CommandPrompt' Variable = $empty Applied = [string[]] @() Resolved = $null } } if ($action -ne 'Next') { return [pscustomobject] @{ Action = 'Cancel' Variable = $empty Applied = [string[]] @() Resolved = $null } } # -- what the technician typed ------------------------------------------ $typed = $null if ($null -ne $Answer.PSObject.Properties['Value']) { $typed = $Answer.Value } $applied = @() if ($null -ne $typed -and $typed -is [System.Collections.IDictionary]) { $applied = @(@($typed.Keys) | ForEach-Object { [string] $_ } | Sort-Object) } # A COPY, NEVER THE CALLER'S OWN HASHTABLE. See the parameter. $argument = @{} foreach ($key in @($ResolveArgument.Keys)) { $argument[$key] = $ResolveArgument[$key] } if (@($applied).Count -gt 0) { $argument['Wizard'] = $typed } $resolved = Resolve-HDTVariable @argument # THE ENGINE'S BAG IS ITS OWN, and case-insensitive: a step reading # %hdtcomputername% and a rule setting HDTComputerName are the same name. $variable = [System.Collections.Specialized.OrderedDictionary]::new([System.StringComparer]::OrdinalIgnoreCase) foreach ($name in @($resolved.Variable.Keys)) { $variable[[string] $name] = $resolved.Variable[$name] } return [pscustomobject] @{ Action = 'Deploy' Variable = $variable Applied = [string[]] @($applied) Resolved = $resolved } } |