Public/New-HDTCimProvider.ps1

function New-HDTCimProvider {
    <#
        .SYNOPSIS
            Creates the real ICimProvider adapter over Get-CimInstance.
 
        .DESCRIPTION
            The one place in HDT that calls Get-CimInstance. PROJECT constraint 4
            forbids engine logic from touching CIM directly, so Get-HDTMachineFact
            and everything after it receives this object and can be swapped for
            New-HDTFakeCimProvider in a test with no machine attached.
 
            Adapters stay branch-free because they are not unit tested.
The two branches here are not logic: one dispatches
            the contract's two overloads, which a ScriptMethod must do positionally
            because it cannot be overloaded, and the other attaches the class name
            to an error message Get-CimInstance leaves out. Neither inspects data.
 
            It is a [pscustomobject] carrying ScriptMethod members rather than a
            PowerShell class. Classes dot-sourced into the module are the known
            flaky path across -Force re-imports (see 01-03); a pscustomobject
            duck-types to the same contract, reloads cleanly, and behaves
            identically under pwsh 7 and Windows PowerShell 5.1.
 
        .OUTPUTS
            System.Management.Automation.PSCustomObject with a GetInstance
            ScriptMethod. Note that Get-Member -MemberType Method does NOT list a
            ScriptMethod - use -MemberType Method, ScriptMethod.
 
        .EXAMPLE
            $cim = New-HDTCimProvider
            $cim.GetInstance('Win32_ComputerSystem')[0].Model
 
            The one-argument form, which defaults to root/cimv2.
 
        .EXAMPLE
            $cim = New-HDTCimProvider
            $cim.GetInstance('root/cimv2/security/microsofttpm', 'Win32_Tpm')[0].SpecVersion
 
            The two-argument form, which fact gathering needs because Win32_Tpm
            lives outside root/cimv2.
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '',
        Justification = 'Builds a stateless service adapter object; it changes no state.')]
    [CmdletBinding()]
    [OutputType([object])]
    param()

    $provider = [pscustomobject] @{}

    $provider | Add-Member -MemberType ScriptMethod -Name GetInstance -Value {
        param([string] $First, [string] $Second)

        # A ScriptMethod cannot be overloaded, and it binds optional arguments
        # positionally. When $Second is empty the caller used the one-argument
        # form, so $First is the class name and the namespace is the default.
        if ([string]::IsNullOrEmpty($Second)) {
            $namespace = 'root/cimv2'
            $class = $First
        } else {
            $namespace = $First
            $class = $Second
        }

        try {
            # The unary comma is mandatory: a ScriptMethod returning an array
            # collapses a single-element array to a scalar without it, and the
            # ICimProvider contract requires an array even for Win32_BaseBoard.
            return , ([object[]] @(Get-CimInstance -Namespace $namespace -ClassName $class -ErrorAction Stop))
        } catch {
            # Get-CimInstance says only "Invalid class " - it does NOT name the
            # class. The ICimProvider contract requires the class name, and 01-03
            # proved that assertion goes red when it is removed, because a vaguer
            # message hides a typo in a fact gatherer. This is a rethrow with the
            # argument attached, not a branch on data, so the adapter stays dumb.
            throw [System.ArgumentException]::new(
                "Invalid class '$class' in namespace '$namespace': $($_.Exception.Message)", $_.Exception)
        }
    }

    # WMI IS HOW WinPE CONFIGURES A NETWORK, and that is not a preference.
    # SPIKES S14: Get-NetIPAddress does not exist in a WinPE image built from
    # the ADK - the NetTCPIP module is not there, and it is what killed the
    # first SMB probe. Win32_NetworkAdapterConfiguration's EnableStatic,
    # SetGateways and SetDNSServerSearchOrder are guaranteed, because WinPE-WMI
    # is one of the six components Get-HDTBootImageComponent always injects.
    #
    # RETURNS THE ReturnValue AND NOTHING ELSE. Deciding that 0 and 1 mean
    # success, and what any other code should say to a technician, is
    # Set-HDTStaticAddress's job - this adapter reads no data and judges none.
    $provider | Add-Member -MemberType ScriptMethod -Name InvokeMethod -Value {
        param([object] $Instance, [string] $MethodName, [hashtable] $Argument)

        $outcome = Invoke-CimMethod -InputObject $Instance -MethodName $MethodName `
            -Arguments $Argument -ErrorAction Stop

        return [int] $outcome.ReturnValue
    }

    return $provider
}