Public/Get-HDTWizardSkip.ps1

function Get-HDTWizardSkip {
    <#
        .SYNOPSIS
            Resolves MDT's Skip* properties for the Welcome screen, from the
            boot image.
 
        .DESCRIPTION
            W2 of the WPF-first direction (.planning/WPF-FIRST.md), and MDT's
            Skip* mechanism under the HDT prefix so an MDT admin recognises it
            instantly:
 
              HDTSkipWelcome the Welcome screen entirely
              HDTSkipStaticIp the network pane; DHCP is used
              HDTSkipDeployRoot the share box; bootstrap.json's value is used
              HDTSkipCredential the account pane; the embedded credential wins
 
            THEY COME FROM bootstrap.json AND NOT FROM rules.yaml, and that is a
            CORRECTION to WPF-FIRST recorded in the document itself. rules.yaml
            lives ON THE SHARE, and the Welcome screen is what makes the share
            reachable - configuring the network and collecting the credential is
            its whole job. A HDTSkipWelcome on the share is a rule the machine
            cannot read until after the screen it was meant to skip has already
            been shown.
 
            MDT HAS THE SAME SPLIT FOR THE SAME REASON. SkipBDDWelcome is in
            Bootstrap.ini, which is inside the boot image; every other Skip*
            property is in CustomSettings.ini, on the share. The later panes -
            task sequence, computer name, summary - run after connecting and do
            resolve through the ordinary rules engine.
 
            THE DEFAULT IS SKIPPED, AND IT IS THE MOST IMPORTANT LINE HERE.
            WPF-FIRST: "THE UNATTENDED PATH IS THE DEFAULT, NOT THE EXCEPTION."
            A wizard that appeared by default would make every image already
            built start waiting for a human who is not there, and the E2E suite
            that proves zero-keystroke deployment would be proving something
            else. So an unset welcome rule means SKIPPED unless something
            genuinely has to be asked.
 
            THE ONLY THING DECIDABLE BEFORE THE SHARE IS REACHABLE is whether
            the image can authenticate at all, and DESIGN 6.3 already settled
            how that is spelled: promptForCredential. An image that carries a
            working credential asks nothing and shows nothing; an image built
            with -PromptForCredential is the one that stops for a person.
 
            AN EXPLICIT RULE ALWAYS WINS over any of that. A site that wants the
            screen on an image that could have skipped it sets welcome to false,
            and gets it.
 
            A BOOT WITH NO BOOTSTRAP DOCUMENT SKIPS. Failing towards the
            unattended path is the safe direction: the deployment either
            proceeds or fails loudly, rather than sitting on a screen in a room
            nobody is in.
 
        .PARAMETER Bootstrap
            What Get-HDTBootstrapConfiguration returned. Null is not an error.
 
        .INPUTS
            None. This command does not accept pipeline input.
 
        .OUTPUTS
            System.Management.Automation.PSCustomObject with Welcome, StaticIp,
            DeployRoot, Credential (all booleans - true means SKIP), Pane
            (Name/Visible for each pane control) and Source (Rule/Value/Source
            provenance for each rule).
 
        .EXAMPLE
            $skip = Get-HDTWizardSkip -Bootstrap (Get-HDTBootstrapConfiguration -Path 'X:\HDT\bootstrap.json')
            if (-not $skip.Welcome) { Show-HDTWizard -XamlPath $p -Pane $skip.Pane }
 
            The whole contract: ask first, and show only what is left to ask.
    #>

    [CmdletBinding()]
    [OutputType([pscustomobject])]
    param(
        [Parameter(Position = 0)]
        [AllowNull()]
        [object] $Bootstrap
    )

    Set-StrictMode -Version Latest
    $ErrorActionPreference = 'Stop'

    # A skip key that is absent, or present and null, means THE IMAGE SAID
    # NOTHING - which is a different fact from false and is what the defaults
    # below are for. Every image built before this existed is in that state.
    $stated = {
        param([string] $Name)

        if ($null -eq $Bootstrap) { return $null }
        if ($null -eq $Bootstrap.PSObject.Properties['Skip']) { return $null }
        if ($null -eq $Bootstrap.Skip) { return $null }
        if ($null -eq $Bootstrap.Skip.PSObject.Properties[$Name]) { return $null }
        if ($null -eq $Bootstrap.Skip.$Name) { return $null }

        return [bool] $Bootstrap.Skip.$Name
    }

    $prompt = $false
    if ($null -ne $Bootstrap -and $null -ne $Bootstrap.PSObject.Properties['PromptForCredential']) {
        $prompt = [bool] $Bootstrap.PromptForCredential
    }

    # THE DEFAULTS, and each one is an argument rather than a preference:
    #
    # welcome skipped unless the image cannot authenticate unaided. See
    # the header - this is what keeps every existing image
    # unattended.
    # staticIp shown. A machine whose network is wrong is the commonest
    # reason a technician is looking at this screen at all.
    # deployRoot shown, prefilled. Inferring it away is fine right up until
    # the prefilled share is the unreachable one.
    # credential SHOWN, even when the image carries an account. The pane is
    # then a statement rather than a question, and WHICH ACCOUNT
    # this machine deploys as is the fact a technician most wants
    # to confirm before pressing Next. Hiding it is something the
    # image asks for, never something inferred from having an
    # answer.
    $rule = @(
        @{ Name = 'Welcome'; Rule = 'HDTSkipWelcome'; Default = (-not $prompt) }
        @{ Name = 'StaticIp'; Rule = 'HDTSkipStaticIp'; Default = $false }
        @{ Name = 'DeployRoot'; Rule = 'HDTSkipDeployRoot'; Default = $false }
        @{ Name = 'Credential'; Rule = 'HDTSkipCredential'; Default = $false }
    )

    $result = [ordered] @{
        Welcome    = $true
        StaticIp   = $false
        DeployRoot = $false
        Credential = $true
        Pane       = @()
        Source     = @()
    }

    foreach ($current in $rule) {
        $name = [string] $current.Name

        $value = & $stated $name
        $source = 'bootstrap.json'

        if ($null -eq $value) {
            $value = [bool] $current.Default
            $source = 'default'
        }

        $result[$name] = [bool] $value

        $result['Source'] += [pscustomobject] @{
            Rule   = [string] $current.Rule
            Value  = [bool] $value
            Source = $source
        }
    }

    # THE PANE CONTROLS THE HOST COLLAPSES, by name. Visible is the inverse of
    # skipped, so the host never has to reason about the word "skip" - it sets
    # visibility and nothing else.
    # THE HINT IS A PANE LIKE ANY OTHER, so the host needs no new mechanism: it
    # sets visibility by name and still never reasons about why.
    #
    # It appears only when the image carried NO SHARE - the case that used to be
    # a refusal in Get-HDTBootstrapConfiguration and is now a question, because
    # the person who can answer it is standing in front of the machine. An empty
    # box on its own reads as optional; this says it is not.
    #
    # NO BOOTSTRAP AT ALL IS NOT "NO SHARE". Nothing is known then, and the
    # whole screen is skipped anyway - rendering the hint would state a fact
    # nobody established.
    $shareStated = $false
    if ($null -ne $Bootstrap -and $null -ne $Bootstrap.PSObject.Properties['DeployRoot']) {
        $shareStated = -not [string]::IsNullOrWhiteSpace([string] $Bootstrap.DeployRoot)
    }

    $hintSkipped = $true
    if ($null -ne $Bootstrap -and -not $shareStated) { $hintSkipped = $false }

    foreach ($pane in @(
            @{ Name = 'HDTNetworkPane'; Skipped = $result['StaticIp'] }
            @{ Name = 'HDTDeployRootPane'; Skipped = $result['DeployRoot'] }
            @{ Name = 'HDTDeployRootHint'; Skipped = $hintSkipped }
            @{ Name = 'HDTCredentialPane'; Skipped = $result['Credential'] })) {

        $result['Pane'] += [pscustomobject] @{
            Name    = [string] $pane.Name
            Visible = (-not [bool] $pane.Skipped)
        }
    }

    return [pscustomobject] $result
}