Public/Resolve-HDTBootstrapRule.ps1

function Resolve-HDTBootstrapRule {
    <#
        .SYNOPSIS
            Chooses the deployment share from the machine's own facts, before it
            has connected to one.
 
        .DESCRIPTION
            ONE BOOT IMAGE, MANY SHARES - MDT's Bootstrap.ini, and the thing HDT
            could not do. The machine has already been gathered by the time this
            runs: it knows its default gateway, its MAC, its model and its UUID,
            and none of that needed a share. Those facts are what the rules match
            on, exactly as MDT's Priority=DefaultGateway, MACAddress does.
 
            THE FALLBACK IS WHAT THE IMAGE WAS BUILT WITH, which is MDT's
            [Default] DeployRoot. An image whose rules match nothing still
            deploys, from the share it was built for; an image with no rules at
            all behaves exactly as it did before this existed. Both of those
            matter more than the feature does.
 
            IT RESOLVES, IT DOES NOT CONNECT. What comes back is a decision and
            its provenance - which rule chose it, or that none did. Reaching the
            share, resolving a volume-relative path and authenticating are the
            caller's, and stay where they were.
 
            THE PROVENANCE IS THE POINT, as everywhere else in the variable
            engine: "the single biggest debugging pain in MDT is not knowing why
            HDTComputerName ended up as it did" applies twice over to a share,
            because a machine that reached the wrong one fails in a way that
            looks like a network fault.
 
        .PARAMETER RuleDocument
            What Import-HDTBootstrapRuleDocument returned, or $null when the
            image carries no such file - which is most of them.
 
        .PARAMETER Fact
            The gathered facts, from Get-HDTMachineFact.
 
        .PARAMETER DeployRoot
            What bootstrap.json says. Used when no rule chooses one.
 
        .OUTPUTS
            System.Management.Automation.PSCustomObject
 
              DeployRoot the share to connect to
              Source 'Rule' or 'BootImage'
              RuleName which rule chose it, '' when none did
              UserName the account to open it with, composed as
                                DOMAIN\user - empty when no rule set one
              Password that account's password, empty likewise
              CredentialSource 'Rule' or 'BootImage'
              Variable everything the bootstrap rules resolved
              Provenance where each of those came from
 
        .EXAMPLE
            Resolve-HDTBootstrapRule -RuleDocument $document -Fact $fact -DeployRoot '\\SERVER\HdtShare'
 
        .LINK
            Import-HDTBootstrapRuleDocument
    #>

    [CmdletBinding()]
    [OutputType([pscustomobject])]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [AllowNull()]
        [object] $RuleDocument,

        [Parameter(Mandatory = $true, Position = 1)]
        [ValidateNotNull()]
        [System.Collections.IDictionary] $Fact,

        [Parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [string] $DeployRoot
    )

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

    $empty = [ordered] @{}

    if ($null -eq $RuleDocument) {
        return [pscustomobject] @{
            DeployRoot       = $DeployRoot
            Source           = 'BootImage'
            RuleName         = ''
            UserName         = ''
            Password         = ''
            CredentialSource = 'BootImage'
            Variable         = $empty
            Provenance       = $empty
        }
    }

    # THE SAME ENGINE, WITH TWO OF ITS FIVE SOURCES. There is no command line
    # here, no machine override to read (that file is on the share) and no
    # sequence to take defaults from - so a bootstrap resolution is rules over
    # facts, which is the same first-match-wins walk with the same %Var%
    # expansion and the same provenance record.
    $resolved = Resolve-HDTVariable -RuleDocument $RuleDocument -Fact $Fact

    $chosen = $DeployRoot
    $source = 'BootImage'
    $ruleName = ''

    if ($resolved.Variable.Contains('HDTDeployRoot')) {
        $candidate = [string] $resolved.Variable['HDTDeployRoot']

        # AN EMPTY ANSWER IS NOT AN ANSWER. A rule that resolved to '' would
        # otherwise disconnect the image from the share it was built for, which
        # is the one failure this feature must not be able to cause.
        if (-not [string]::IsNullOrWhiteSpace($candidate)) {
            $chosen = $candidate
            $source = 'Rule'

            if ($resolved.Provenance.Contains('HDTDeployRoot')) {
                $ruleName = [string] $resolved.Provenance['HDTDeployRoot'].Rule
            }
        }
    }

    # THE ACCOUNT, COMPOSED HERE SO THE PAYLOAD DECIDES NOTHING. MDT's
    # Bootstrap.ini carries UserID, UserDomain and UserPassword as three keys
    # and joins them at the point of use; a payload that did the joining would
    # be a payload with a rule in it.
    #
    # A DOMAIN IS OPTIONAL, because an account local to the file server has
    # none - 'CONTOSO\svc' and 'svc' are both answers, and 'svc' with an empty
    # domain must not become '\svc'.
    $userName = ''
    $password = ''
    $credentialSource = 'BootImage'

    if ($resolved.Variable.Contains('HDTUserId')) {
        $userId = [string] $resolved.Variable['HDTUserId']

        if (-not [string]::IsNullOrWhiteSpace($userId)) {
            $userName = $userId
            $credentialSource = 'Rule'

            if ($resolved.Variable.Contains('HDTUserDomain')) {
                $domain = [string] $resolved.Variable['HDTUserDomain']
                if (-not [string]::IsNullOrWhiteSpace($domain)) {
                    $userName = '{0}\{1}' -f $domain, $userId
                }
            }

            if ($resolved.Variable.Contains('HDTUserPassword')) {
                $password = [string] $resolved.Variable['HDTUserPassword']
            }
        }
    }

    return [pscustomobject] @{
        DeployRoot       = $chosen
        Source           = $source
        RuleName         = $ruleName
        UserName         = $userName
        Password         = $password
        CredentialSource = $credentialSource
        Variable         = $resolved.Variable
        Provenance       = $resolved.Provenance
    }
}