Public/Get-HDTWizardCredential.ps1

function Get-HDTWizardCredential {
    <#
        .SYNOPSIS
            Composes MDT's Bootstrap.ini credential quartet and proves it by
            connecting.
 
        .DESCRIPTION
            W2 of the WPF-first direction. The four
            fields MDT's Bootstrap.ini carries, and what HDT does with each:
 
              DeployRoot the share, and therefore THE SERVER
              UserID the account
              UserDomain the domain - or, left blank, the SERVER, which is
                             how Windows names a local account
              UserPassword never prefilled, never logged
 
            WHY UserDomain IS ITS OWN FIELD. HDT collapsed it into the username
            string until now ('LAP-AMMSO01\svc-hdt-deploy'), which is fine for a
            machine and wrong for a human: a technician must be able to say
            "this account is LOCAL to that server" without knowing the
            convention is to type the server's name where a domain goes. Blank
            means local, and this command turns that into the name Windows
            needs.
 
            A UserID THAT ALREADY CARRIES A DOMAIN IS LEFT ALONE. Technicians
            type what they know, and 'CONTOSO\svc' in the user box must not
            become 'CONTOSO\CONTOSO\svc'.
 
            IT CONNECTS, AND THAT IS THE POINT. A form that only collects text
            moves the failure thirty seconds downstream into a log nobody is
            reading, on a machine that has already started. Connecting here
            makes a wrong password a red line on the page the technician is
            looking at, with the cursor still in the box.
 
            IT NEVER THROWS FOR A BAD CREDENTIAL. The wizard has to stay on
            screen so the answer can be retyped, so every refusal comes back as
            Connected = $false with a Message. It throws only for the things a
            technician cannot fix by typing.
 
            IT ALWAYS DISCONNECTS. The mapping this makes is a test, not the
            deployment's, and a wizard that left one behind would hand the
            payload a connection it did not open and cannot reason about.
 
            THE PASSWORD APPEARS IN NOTHING IT RETURNS except the PSCredential
            itself - not the composed name, not the message, not the provider's
            operation journal. A deployment log is copied around far more freely
            than a password is.
 
        .PARAMETER DeployRoot
            The share, \\server\share. The server is taken from it.
 
        .PARAMETER UserId
            MDT's UserID.
 
        .PARAMETER UserDomain
            MDT's UserDomain. Blank means the account is local to the server.
 
        .PARAMETER Password
            MDT's UserPassword.
 
        .PARAMETER ContentProvider
            An IContentProvider to validate with. Built from the composed
            credential when omitted.
 
        .PARAMETER FileSystem
            An IFileSystem, for the provider built when none is injected.
 
        .OUTPUTS
            System.Management.Automation.PSCustomObject with UserName, UserId,
            UserDomain, Server, IsLocalAccount, Connected, GuestRefused,
            Credential and Message.
 
        .EXAMPLE
            Get-HDTWizardCredential -DeployRoot '\\HDT01\HDTShare' -UserId 'svc' -UserDomain '' -Password $secure
 
            A local account on HDT01, composed as HDT01\svc and proven by
            connecting.
    #>

    [CmdletBinding()]
    [OutputType([pscustomobject])]
    param(
        [Parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [string] $DeployRoot,

        [Parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [string] $UserId,

        [Parameter()]
        [AllowEmptyString()]
        [string] $UserDomain = '',

        [Parameter()]
        [AllowNull()]
        [securestring] $Password,

        [Parameter()]
        [AllowNull()]
        [object] $ContentProvider,

        [Parameter()]
        [AllowNull()]
        [object] $FileSystem
    )

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

    $result = [ordered] @{
        UserName       = ''
        UserId         = $UserId
        UserDomain     = $UserDomain
        Server         = ''
        IsLocalAccount = $false
        Connected      = $false
        GuestRefused   = $false
        Credential     = $null
        Message        = ''
    }

    # -- 1. the share, and the server it names ------------------------------

    if (-not ([string] $DeployRoot).StartsWith('\\')) {
        $result['Message'] = ("'{0}' is not a share. A deployment share is a UNC path, \\server\share - a local path needs no account at all." -f $DeployRoot)
        return [pscustomobject] $result
    }

    $part = @(([string] $DeployRoot).TrimStart('\').Split('\') | Where-Object { $_ })
    if ($part.Count -ge 1) { $result['Server'] = [string] $part[0] }

    # -- 2. the account -----------------------------------------------------

    if ([string]::IsNullOrWhiteSpace($UserId)) {
        $result['Message'] = 'Enter the user name of the deployment account (MDT calls this UserID).'
        return [pscustomobject] $result
    }

    $plain = ''
    if ($null -ne $Password) {
        $plain = [string] (New-Object -TypeName System.Management.Automation.PSCredential `
                -ArgumentList 'placeholder', $Password).GetNetworkCredential().Password
    }

    if ([string]::IsNullOrEmpty($plain)) {
        $result['Message'] = 'Enter the password for the deployment account.'
        return [pscustomobject] $result
    }

    # A user that already carries its own domain wins over the domain box.
    if ($UserId.Contains('\')) {
        $result['UserName'] = $UserId
        $result['UserDomain'] = [string] @($UserId.Split('\'))[0]
    } elseif ([string]::IsNullOrWhiteSpace($UserDomain)) {
        # BLANK MEANS LOCAL, and local is spelled SERVER\user.
        $result['UserName'] = '{0}\{1}' -f $result['Server'], $UserId
        $result['IsLocalAccount'] = $true
    } else {
        $result['UserName'] = '{0}\{1}' -f $UserDomain, $UserId
    }

    $result['Credential'] = New-Object -TypeName System.Management.Automation.PSCredential `
        -ArgumentList ([string] $result['UserName']), $Password

    # -- 3. prove it --------------------------------------------------------

    $provider = $ContentProvider
    if ($null -eq $provider) {
        $providerArgument = @{
            Provider   = 'Smb'
            Root       = $DeployRoot
            Credential = $result['Credential']
        }
        if ($null -ne $FileSystem) { $providerArgument['FileSystem'] = $FileSystem }

        $provider = New-HDTContentProvider @providerArgument
    }

    try {
        [void] $provider.Connect()

        $result['Connected'] = $true
        $result['Message'] = ("Connected to {0} as {1}." -f $DeployRoot, $result['UserName'])
    } catch {
        $message = [string] $_.Exception.Message

        $result['Connected'] = $false
        $result['Message'] = $message

        # DESIGN 6.3's refusal, reported as what it is from the technician's
        # seat: an account problem, fixed on this page.
        if ($message -match 'HDTSecurityError' -or $message -match 'guest') {
            $result['GuestRefused'] = $true
        }
    } finally {
        # THE MAPPING THIS MADE IS A TEST, NOT THE DEPLOYMENT'S.
        try { [void] $provider.Disconnect() } catch { $null = $_ }
    }

    return [pscustomobject] $result
}