Public/New-HDTEnvironmentProvider.ps1

function New-HDTEnvironmentProvider {
    <#
        .SYNOPSIS
            Creates the real IEnvironmentProvider adapter over the process
            environment.
 
        .DESCRIPTION
            The one place in HDT that reads environment variables. PROJECT
            constraint 4 forbids engine logic from using $env: directly, so
            Get-HDTMachineFact receives this object and can be swapped for
            New-HDTFakeEnvironmentProvider - which is the only way a UEFI
            machine, a BIOS machine and a machine with firmware_type unset can
            all be proven from one desk.
 
            The fact gatherer reads firmware_type for HDTIsUEFI, and
            PROCESSOR_ARCHITEW6432 / PROCESSOR_ARCHITECTURE for HDTArchitecture.
 
            GetVariable returns $null for a variable that is not set, which
            [System.Environment]::GetEnvironmentVariable already does, and
            compares names case-insensitively, which Windows already does. The
            adapter therefore stays branch-free apart from recording.
 
            Every lookup is recorded in $Operations, before it can throw, exactly
            as the fakes record (tests/helpers/README.md section 4).
 
            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).
 
        .OUTPUTS
            System.Management.Automation.PSCustomObject with a GetVariable
            ScriptMethod. Note that Get-Member -MemberType Method does NOT list a
            ScriptMethod - use -MemberType Method, ScriptMethod.
 
        .EXAMPLE
            $environment = New-HDTEnvironmentProvider
            $environment.GetVariable('firmware_type')
 
            Returns 'UEFI' on a UEFI machine, which is what HDTIsUEFI is derived
            from.
    #>

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

    $provider = [pscustomobject] @{
        Operations = [System.Collections.ArrayList]::new()
    }

    $provider | 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
            })
    }

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

    $provider | Add-Member -MemberType ScriptMethod -Name GetVariable -Value {
        param([string] $Name)

        $this.Record('GetVariable', @($Name))

        return [System.Environment]::GetEnvironmentVariable($Name)
    }

    return $provider
}