private/Get-OSDeployCoreESD.ps1

#Requires -PSEdition Core
#Requires -Version 7.4

function Get-OSDeployCoreESD {
    <#
    .SYNOPSIS
        Returns verified ESD files from the OSDeployCore OSDCloud OS cache.
 
    .DESCRIPTION
        Resolves the latest OS catalog XML from the OSDeploy module's
        catalogs\operatingsystem directory, then checks whether the corresponding
        en-US Enterprise ESD files for both x64 (amd64) and ARM64 have already been
        downloaded to C:\ProgramData\OSDeployCore\OSDCloud\OS\<Version>.
 
        For each architecture, if the file is present its SHA256 checksum is verified
        against the catalog value. Files that are missing or that fail the checksum
        check are excluded from output and a warning is written. Only files that exist
        and whose checksum matches are returned.
 
        This function is read-only: it does not download, delete, or prompt the user.
 
    .OUTPUTS
        System.IO.FileInfo. One FileInfo object per verified ESD file found in the
        cache (up to two objects: x64 and ARM64).
 
    .EXAMPLE
        Get-OSDeployCoreESD
 
        Returns FileInfo objects for any en-US Enterprise ESD files that are already
        cached and SHA256-verified against the latest catalog.
 
    .EXAMPLE
        $esds = Get-OSDeployCoreESD
        $esds | Select-Object Name, Length
 
        Lists the name and size of each verified cached ESD.
 
    .NOTES
        Run Update-OSDeployCoreESD to download missing or outdated ESD files.
 
    Dependencies:
      Module Functions: Update-OSDeployCoreESD, Write-OSDeployCoreProgress
      .NET Classes: [System.IO.FileInfo], [System.IO.Path]
    #>

    [CmdletBinding()]
    [OutputType([System.IO.FileInfo[]])]
    param ()

    Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Start"

    # -------------------------------------------------------------------------
    # Resolve catalog files (latest first)
    # -------------------------------------------------------------------------
    $catalogDir = Join-Path $script:OSDeployModuleBase 'catalogs\operatingsystem'
    $latestXml  = Get-ChildItem -Path $catalogDir -Filter '*.xml' -File |
        Sort-Object Name -Descending |
        Select-Object -First 1

    if (-not $latestXml) {
        Write-Warning "[$($MyInvocation.MyCommand.Name)] No OS catalog XML files found in '$catalogDir'."
        return
    }

    Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Using catalog: $($latestXml.Name)"
    Write-OSDeployCoreProgress "Checking Operating System Catalog at $($latestXml.FullName)"

    # -------------------------------------------------------------------------
    # Parse the catalog
    # -------------------------------------------------------------------------
    [xml]$catalog = Get-Content -Path $latestXml.FullName -Raw
    $allFiles     = $catalog.MCT.Catalogs.Catalog.PublishedMedia.Files.File

    # -------------------------------------------------------------------------
    # Derive OS folder name from the catalog filename
    # e.g. '26200.8457-win11-25h2.xml' → 'Windows 11 25H2'
    # -------------------------------------------------------------------------
    $catalogBase = [System.IO.Path]::GetFileNameWithoutExtension($latestXml.Name)
    if ($catalogBase -notmatch '^\d+\.\d+-win(\d+)-(.+)$') {
        Write-Warning "[$($MyInvocation.MyCommand.Name)] Cannot parse OS version from catalog name '$($latestXml.Name)'. Expected format: '<build>-win<version>-<release>.xml' (e.g. '26200.8457-win11-25h2.xml')."
        return
    }
    $osFolderName = "Windows $($Matches[1]) $($Matches[2].ToUpper())"
    Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] OS folder name: $osFolderName"

    # -------------------------------------------------------------------------
    # Locate the download directory
    # -------------------------------------------------------------------------
    $downloadDir = Join-Path $script:OSDeployCorePath 'OSDCloud' 'OS' $osFolderName
    Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Download directory: $downloadDir"

    # -------------------------------------------------------------------------
    # Target both architectures regardless of host architecture
    # -------------------------------------------------------------------------
    $targets = @(
        [pscustomobject]@{ Architecture = 'x64';   Edition = 'Enterprise'; LanguageCode = 'en-us' }
        [pscustomobject]@{ Architecture = 'ARM64'; Edition = 'Enterprise'; LanguageCode = 'en-us' }
    )

    $normalizeHash = { param([string]$hash) ($hash -replace '\s+', '').ToUpperInvariant() }
    $results       = [System.Collections.Generic.List[System.IO.FileInfo]]::new()

    foreach ($target in $targets) {
        $entry = $allFiles | Where-Object {
            $_.LanguageCode -eq $target.LanguageCode -and
            $_.Edition      -eq $target.Edition      -and
            $_.Architecture -eq $target.Architecture
        } | Select-Object -First 1

        if (-not $entry) {
            Write-Warning "[$($MyInvocation.MyCommand.Name)] No catalog entry found for $($target.Edition) $($target.Architecture) $($target.LanguageCode). Skipping."
            continue
        }

        $destPath = Join-Path $downloadDir $entry.FileName
        Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Checking: $($entry.FileName)"
        Write-OSDeployCoreProgress "Checking cache for $($entry.FileName)"

        if (-not (Test-Path -Path $destPath -PathType Leaf)) {
            Write-Warning "[$($MyInvocation.MyCommand.Name)] ESD not found in cache: $destPath"
            Write-Warning "[$($MyInvocation.MyCommand.Name)] Run Update-OSDeployCoreESD to download it."
            continue
        }

        $expectedSha256 = & $normalizeHash $entry.Sha256
        $actualHash     = (Get-FileHash -Path $destPath -Algorithm SHA256).Hash.ToUpperInvariant()

        if ($actualHash -ne $expectedSha256) {
            Write-Warning "[$($MyInvocation.MyCommand.Name)] SHA256 mismatch for '$($entry.FileName)'."
            Write-Warning "[$($MyInvocation.MyCommand.Name)] Expected : $expectedSha256"
            Write-Warning "[$($MyInvocation.MyCommand.Name)] Actual : $actualHash"
            Write-Warning "[$($MyInvocation.MyCommand.Name)] Run Update-OSDeployCoreESD to re-download it."
            continue
        }

        Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] SHA256 verified: $($entry.FileName)"
        Write-OSDeployCoreProgress "ESD cached and verified: $($entry.FileName)"
        Write-OSDeployCoreProgress " SHA256 : $actualHash"
        $results.Add((Get-Item -Path $destPath))
    }

    Write-Verbose "[$(Get-Date -Format s)] [$($MyInvocation.MyCommand.Name)] Complete. $($results.Count) file(s) verified."

    return $results.ToArray()
}