Public/New-HDTSmbService.ps1

function New-HDTSmbService {
    <#
        .SYNOPSIS
            Creates the real ISmbService adapter over the SmbShare module.
 
        .DESCRIPTION
            A THIN ADAPTER, AND DELIBERATELY DUMB. It constructs arguments for
            four SmbShare cmdlets and
            projects what they return; every decision that could be got wrong -
            whether the identity that came back is a guest, whether the dialect
            is acceptable, whether a credential was supplied at all - lives in
            New-HDTSmbContentProvider, where it is unit tested against
            New-HDTFakeSmbService.
 
              NewMapping(remotePath, userName, password) New-SmbMapping
              RemoveMapping(remotePath) Remove-SmbMapping -Force
              GetConnection(serverName) Get-SmbConnection
              GetClientConfiguration() Get-SmbClientConfiguration
 
            THE SmbShare MODULE IS THE MECHANISM BECAUSE IT IS PRESENT IN WinPE.
            The boot image contents table records SmbShare as present and NetTCPIP,
            NetAdapter and DnsClient as absent, so nothing here may reach for
            those - and 'net use' is not needed as a fallback.
 
            IT CARRIES EXACTLY ONE BRANCH, and it is named rather than hidden:
            New-SmbMapping refuses an empty -UserName, so a connection made as
            the caller's own identity - which is what the loopback integration
            test does, and what -AllowAnonymous means - has to omit the two
            parameters rather than pass them empty. The rule that adapters stay
            branch-free exists because they are not unit tested; this one is
            exercised for real by
            tests/integration/SmbContentProvider.Integration.Tests.ps1 against a
            throwaway share, which is the same bargain README section 11 records
            for New-HDTScriptInvoker.
 
            THE PASSWORD IS RECORDED AS '<redacted>'. $Operations is printed
            verbatim in a failure dump, and the deployment account's password
            does not belong in one (tests/helpers/README.md section 4).
 
        .PARAMETER Journal
            The shared cross-service operation journal. When supplied, every
            recorded call is appended to it in addition to $Operations, numbered
            globally across services.
 
        .OUTPUTS
            System.Management.Automation.PSCustomObject with the four ISmbService
            ScriptMethods. Note that Get-Member -MemberType Method does NOT list
            a ScriptMethod - use -MemberType Method, ScriptMethod.
 
        .EXAMPLE
            $smb = New-HDTSmbService
            $content = New-HDTSmbContentProvider -Root '\\server\HdtShare' -Credential $credential -SmbService $smb
            $content.Connect()
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '',
        Justification = 'Builds a stateless service adapter object; it changes no state.')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '',
        Justification = 'The adapter boundary: New-SmbMapping -Password takes a plain string, and the provider above it holds the PSCredential.')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingUserNameAndPasswordParams', '',
        Justification = 'The adapter boundary: these are New-SmbMapping parameters, not a credential prompt.')]
    [CmdletBinding()]
    [OutputType([object])]
    param(
        [Parameter()]
        [AllowNull()]
        [System.Collections.ArrayList] $Journal
    )

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

    $service = [pscustomobject] @{
        Operations  = [System.Collections.ArrayList]::new()
        Journal     = $Journal
        ServiceName = 'SmbService'
    }

    $service | Add-Member -MemberType ScriptMethod -Name Record -Value {
        param([string] $Operation, [object[]] $Argument)

        [void] $this.Operations.Add([pscustomobject] @{
                Sequence  = $this.Operations.Count + 1
                Operation = $Operation
                Arguments = $Argument
            })

        if ($null -ne $this.Journal) {
            [void] $this.Journal.Add([pscustomobject] @{
                    Sequence  = $this.Journal.Count + 1
                    Service   = $this.ServiceName
                    Operation = $Operation
                    Arguments = $Argument
                })
        }
    }

    $service | Add-Member -MemberType ScriptMethod -Name GetOperationName -Value {
        return , ([string[]] @($this.Operations | ForEach-Object { $_.Operation }))
    }

    $service | Add-Member -MemberType ScriptMethod -Name NewMapping -Value {
        param([string] $RemotePath, [string] $UserName, [string] $Password)

        $this.Record('NewMapping', @($RemotePath, $UserName, '<redacted>'))

        $argument = @{
            RemotePath = $RemotePath
            Persistent = $false
        }

        # The one branch, and the reason for it is in the help above.
        if (-not [string]::IsNullOrEmpty($UserName)) {
            $argument['UserName'] = $UserName
            $argument['Password'] = $Password
        }

        New-SmbMapping @argument | Out-Null
    }

    $service | Add-Member -MemberType ScriptMethod -Name RemoveMapping -Value {
        param([string] $RemotePath)

        $this.Record('RemoveMapping', @($RemotePath))

        Remove-SmbMapping -RemotePath $RemotePath -Force | Out-Null
    }

    $service | Add-Member -MemberType ScriptMethod -Name GetConnection -Value {
        param([string] $ServerName)

        $this.Record('GetConnection', @($ServerName))

        $row = @(Get-SmbConnection -ServerName $ServerName -ErrorAction SilentlyContinue |
                ForEach-Object {
                    [pscustomobject] @{
                        ServerName = [string] $_.ServerName
                        ShareName  = [string] $_.ShareName
                        UserName   = [string] $_.UserName
                        Dialect    = [string] $_.Dialect
                        Encrypted  = [bool] $_.Encrypted
                        Signed     = [bool] $_.Signed
                    }
                })

        # The unary comma is mandatory: a ScriptMethod collapses a single-element
        # array to a scalar without it (tests/helpers/README.md F3), and one
        # connection is the normal case.
        return , ([object[]] $row)
    }

    $service | Add-Member -MemberType ScriptMethod -Name GetClientConfiguration -Value {
        $this.Record('GetClientConfiguration', @())

        $configuration = Get-SmbClientConfiguration

        return [pscustomobject] @{
            EnableInsecureGuestLogons = [bool] $configuration.EnableInsecureGuestLogons
            RequireSecuritySignature  = [bool] $configuration.RequireSecuritySignature
        }
    }

    return $service
}