private/WinPEDrivers/CloudWinPEDriver/Get-CloudWinPEDriverOemVMware.ps1

#Requires -PSEdition Core

function Get-CloudWinPEDriverOemVMware {
    <#
    .SYNOPSIS
        Discovers the latest VMware Tools ISO metadata from the VMware packages index.
 
    .DESCRIPTION
        Internal helper called by Update-OSDeployWinPEDriversCatalog. Fetches the VMware packages
        directory listing for the latest Windows Tools ISOs (both amd64 and arm64),
        parses filenames for version and build ID, and reads Last-Modified and Size from
        the index table. Returns a two-element array: [amd64, arm64].
 
    .OUTPUTS
        [PSCustomObject[]] Two entries ordered [amd64, arm64]: Architecture, ReadmeUri,
        Id, Version, ReleaseDate, FileName, FileSizeMB, DownloadUri, Checksums.
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject[]])]
    param ()

    try {
        $searchUri = $global:OSDeployModule.WinPEDrivers.'vmware'.UpdateUri
        if ([string]::IsNullOrWhiteSpace($searchUri)) {
            throw "vmware UpdateUri is not defined in winpedrivers.json."
        }

        Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Fetching VMware packages directory listing"
        $response = Invoke-WebRequest -Uri $searchUri -UseBasicParsing -ErrorAction Stop
        $html = $response.Content

        # Parse both ISO entries from an Apache autoindex table
        # amd64: VMware-tools-windows-13.0.10-25056151.iso
        # arm64: VMware-tools-windows-arm-13.0.10-25056151.iso
        $amd64Match = [regex]::Match($html,
            '(VMware-tools-windows-(\d+\.\d+\.\d+)-(\d+)\.iso)')
        $arm64Match = [regex]::Match($html,
            '(VMware-tools-windows-arm-(\d+\.\d+\.\d+)-(\d+)\.iso)')

        if (-not $amd64Match.Success) {
            Write-Warning "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Could not find amd64 ISO filename on '$searchUri'. Page format may have changed."
            return
        }
        if (-not $arm64Match.Success) {
            Write-Warning "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] Could not find arm64 ISO filename on '$searchUri'. Page format may have changed."
            return
        }

        $amd64File    = $amd64Match.Groups[1].Value
        $amd64Version = $amd64Match.Groups[2].Value
        $amd64Id      = $amd64Match.Groups[3].Value

        $arm64File    = $arm64Match.Groups[1].Value
        $arm64Version = $arm64Match.Groups[2].Value
        $arm64Id      = $arm64Match.Groups[3].Value

        Write-Verbose "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] amd64='$amd64File' arm64='$arm64File'"

        # Parse Last-Modified and Size from the directory listing row for each file
        # Apache autoindex rows: <td align="right">2026-01-20 10:30 </td><td align="right"> 113M</td>
        $amd64Meta   = Get-VMwareListingRow -Html $html -FileName $amd64File
        $arm64Meta   = Get-VMwareListingRow -Html $html -FileName $arm64File

        $baseUri = $searchUri.TrimEnd('/')

        @(
            [PSCustomObject]@{
                Architecture = 'amd64'
            ReadmeUri    = $searchUri
                PackageId    = $amd64Id
                Version      = $amd64Version
                ReleaseDate  = $amd64Meta.ReleaseDate
                FileName     = $amd64File
                FileSizeMB   = $amd64Meta.FileSizeMB
                DownloadUri  = "$baseUri/$amd64File"
                Checksums    = [PSCustomObject]@{}
            },
            [PSCustomObject]@{
                Architecture = 'arm64'
                ReadmeUri    = $searchUri
                PackageId    = $arm64Id
                Version      = $arm64Version
                ReleaseDate  = $arm64Meta.ReleaseDate
                FileName     = $arm64File
                FileSizeMB   = $arm64Meta.FileSizeMB
                DownloadUri  = "$baseUri/$arm64File"
                Checksums    = [PSCustomObject]@{}
            }
        )
    }
    catch {
        Write-Warning "[$(Get-Date -format s)] [$($MyInvocation.MyCommand.Name)] $($_.Exception.Message)"
    }
}

function Get-VMwareListingRow {
    param ([string]$Html, [string]$FileName)

    $releaseDate = ''
    $fileSizeMB  = $null

    # Match the exact ISO row in the autoindex table; avoid adjacent .sha and .sig rows.
    $rowPattern = '(?is)<tr[^>]*>\s*<td[^>]*>.*?<a href="' + [regex]::Escape($FileName) + '">' + [regex]::Escape($FileName) + '</a></td>\s*<td[^>]*>\s*(?<Date>\d{4}-\d{2}-\d{2})(?:\s+\d{2}:\d{2}Z?)?\s*</td>\s*<td[^>]*>\s*(?<Size>\d+(?:\.\d+)?)\s*(?<Unit>[MGK]B?|B)?\s*</td>'
    $rowMatch = [regex]::Match($Html, $rowPattern)
    if ($rowMatch.Success) {
        $releaseDate = $rowMatch.Groups['Date'].Value

        $rawSize = [double]$rowMatch.Groups['Size'].Value
        $unit    = $rowMatch.Groups['Unit'].Value.ToUpperInvariant()
        $fileSizeMB = switch ($unit) {
            'GB' { [math]::Round($rawSize * 1024, 2) }
            'G'  { [math]::Round($rawSize * 1024, 2) }
            'MB' { [math]::Round($rawSize, 2) }
            'M'  { [math]::Round($rawSize, 2) }
            'KB' { [math]::Round($rawSize / 1024, 2) }
            'K'  { [math]::Round($rawSize / 1024, 2) }
            'B'  { [math]::Round($rawSize / 1MB, 2) }
            default { [math]::Round($rawSize, 2) }
        }
    }

    [PSCustomObject]@{
        ReleaseDate = $releaseDate
        FileSizeMB  = $fileSizeMB
    }
}