Private/Get-HDTConsoleBootImage.ps1

function Get-HDTConsoleBootImage {
    <#
        .SYNOPSIS
            Reads the boot image manifest a share carries, and reports a share
            that has none.

        .DESCRIPTION
            Update-HDTBootImage writes Boot\<name>.manifest.json beside the .wim
            and the .iso it produced, recording the build id, the time, the
            machine, the engine version and the SHA-256 and size of both
            artifacts. That file is where the console's build date and hashes
            come from: re-deriving them would mean hashing a 500 MB ISO every
            time a window opens, and the manifest exists precisely so nobody has
            to.

            THREE ANSWERS, NOT TWO. 'Ok' is a manifest that was read. 'Missing'
            is a share whose image has never been built - a legitimate state for
            a share an administrator is still setting up, so it names
            Update-HDTBootImage rather than reading as a fault. 'Error' is a
            manifest that is there and cannot be read, which is a fault and says
            so. Collapsing the last two would tell an admin to rebuild an image
            when the real problem is a truncated file on the share.

            IT NEVER THROWS. Every failure becomes a status, because the boot
            image is one panel of a window that still has a share, task sequences
            and operating systems to show.

            THE ISO CLAIM IS EVALUATED, NOT REPEATED. HashMatch is the
            comparison of the standalone WIM's hash against the hash of the
            boot.wim inside the ISO, both of which the manifest records. An older
            manifest written before that key existed answers $false rather than
            throwing - Get-HDTConsoleJsonProperty is what makes a missing key a
            default instead of a PropertyNotFoundException.

        .PARAMETER Root
            The workspace root.

        .PARAMETER BootImage
            The projected bootImage block from Import-HDTWorkspaceDocument. Its
            Name is what the manifest is called.

        .PARAMETER FileSystem
            An IFileSystem.

        .INPUTS
            None. This command does not accept pipeline input.

        .OUTPUTS
            System.Management.Automation.PSCustomObject - see
            Get-HDTConsoleWorkspace's OUTPUTS.

        .EXAMPLE
            Get-HDTConsoleBootImage -Root 'C:\HDTLab\Share' -BootImage $workspace.BootImage -FileSystem $fs
    #>

    [CmdletBinding()]
    [OutputType([pscustomobject])]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string] $Root,

        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        [object] $BootImage,

        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        [object] $FileSystem
    )

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

    $manifestName = '{0}.manifest.json' -f $BootImage.Name
    $manifestPath = Get-HDTWorkspacePath -Root $Root -Kind Boot -ChildPath $manifestName

    $result = [pscustomobject] @{
        Name             = [string] $BootImage.Name
        Architecture     = [string] $BootImage.Architecture
        Language         = [string] $BootImage.Language
        ManifestPath     = $manifestPath
        Status           = 'Missing'
        Error            = ''
        BuildId          = ''
        BuiltUtc         = $null
        BuiltOn          = ''
        EngineVersion    = ''
        WimPath          = ''
        WimSha256        = ''
        WimSizeBytes     = [long] 0
        IsoPath          = ''
        IsoSha256        = ''
        IsoSizeBytes     = [long] 0
        IsoBootWimSha256 = ''
        HashMatch        = $false
    }

    if (-not $FileSystem.TestPath($manifestPath)) {
        $result.Error = ("no boot image has been built for this share yet. Run Update-HDTBootImage against '{0}' to build one." -f
            [System.IO.Path]::Combine($Root, 'workspace.yaml'))

        return $result
    }

    try {
        $manifest = ConvertFrom-Json -InputObject ([string] $FileSystem.ReadAllText($manifestPath))

        $artifact = Get-HDTConsoleJsonProperty -InputObject $manifest -Name 'artifacts' -Default $null
        $wim = Get-HDTConsoleJsonProperty -InputObject $artifact -Name 'wim' -Default $null
        $iso = Get-HDTConsoleJsonProperty -InputObject $artifact -Name 'iso' -Default $null

        $result.BuildId = [string] (Get-HDTConsoleJsonProperty -InputObject $manifest -Name 'buildId')
        $result.BuiltOn = [string] (Get-HDTConsoleJsonProperty -InputObject $manifest -Name 'builtOn')
        $result.EngineVersion = [string] (Get-HDTConsoleJsonProperty -InputObject $manifest -Name 'engineVersion')

        # THE BUILD DATE IS NOT NECESSARILY A STRING BY THE TIME IT GETS HERE.
        # ConvertFrom-Json coerces an ISO-8601 value to a [datetime] on its own,
        # and it does not do it the same way on both engines - Windows PowerShell
        # 5.1 and pwsh 7 disagree about the resulting Kind. Formatting it back to
        # a string and re-parsing loses the offset and shifts the build time by
        # the viewer's zone, twice; the first draft of this file did exactly that
        # and reported an image built at 07:13 UTC as 04:13 UTC on a UTC+3 desk.
        # So the DateTime is taken as a DateTime when that is what arrived, and
        # only a genuine string is parsed - AssumeUniversal because the manifest
        # writes Z, AdjustToUniversal so the result is Kind=Utc either way.
        $builtUtcValue = Get-HDTConsoleJsonProperty -InputObject $manifest -Name 'builtUtc' -Default $null

        if ($builtUtcValue -is [datetime]) {
            $result.BuiltUtc = ([datetime] $builtUtcValue).ToUniversalTime()
        } elseif (-not [string]::IsNullOrWhiteSpace([string] $builtUtcValue)) {
            $result.BuiltUtc = [datetime]::Parse([string] $builtUtcValue, [cultureinfo]::InvariantCulture,
                ([System.Globalization.DateTimeStyles]::AssumeUniversal -bor
                    [System.Globalization.DateTimeStyles]::AdjustToUniversal))
        }

        $result.WimPath = [string] (Get-HDTConsoleJsonProperty -InputObject $wim -Name 'path')
        $result.WimSha256 = [string] (Get-HDTConsoleJsonProperty -InputObject $wim -Name 'sha256')
        $result.WimSizeBytes = [long] (Get-HDTConsoleJsonProperty -InputObject $wim -Name 'sizeBytes' -Default 0)

        $result.IsoPath = [string] (Get-HDTConsoleJsonProperty -InputObject $iso -Name 'path')
        $result.IsoSha256 = [string] (Get-HDTConsoleJsonProperty -InputObject $iso -Name 'sha256')
        $result.IsoSizeBytes = [long] (Get-HDTConsoleJsonProperty -InputObject $iso -Name 'sizeBytes' -Default 0)

        $result.IsoBootWimSha256 = [string] (Get-HDTConsoleJsonProperty -InputObject $artifact -Name 'isoBootWimSha256')

        $result.HashMatch = ((-not [string]::IsNullOrWhiteSpace($result.WimSha256)) -and
            ([string]::Equals($result.WimSha256, $result.IsoBootWimSha256, [System.StringComparison]::OrdinalIgnoreCase)))

        $result.Status = 'Ok'
    } catch {
        $result.Status = 'Error'
        $result.Error = ("the boot image manifest could not be read: {0}" -f [string] $_.Exception.Message)
    }

    return $result
}