Runtime/OSDAppVisualStudioCode.ps1

# Shared by the module, PreInstall and Runner. Loading this file performs no I/O.
function Get-OSDAppVisualStudioCodePackageUri {
    param(
        [Parameter(Mandatory)][string]$Version,
        [Parameter(Mandatory)][string]$Commit,
        [Parameter(Mandatory)][ValidateSet('x64','arm64')][string]$Architecture
    )
    if ($Version -notmatch '^\d+\.\d+\.\d+$' -or $Commit -notmatch '^[a-f0-9]{40}$') {
        throw 'Visual Studio Code stable release version or commit is invalid.'
    }
    "https://vscode.download.prss.microsoft.com/dbazure/download/stable/$Commit/VSCodeSetup-$Architecture-$Version.exe"
}

function Get-OSDAppVisualStudioCodeRelease {
    [CmdletBinding()]
    param([Parameter(Mandatory)][ValidateSet('x64','arm64')][string]$Architecture)
    # These platforms select the system installer, never the per-user installer or ZIP.
    $release = Invoke-RestMethod -Uri "https://update.code.visualstudio.com/api/update/win32-$Architecture/stable/latest" -TimeoutSec 60 -ErrorAction Stop
    $version = [string]$release.productVersion
    $commit = [string]$release.version
    $expectedUri = Get-OSDAppVisualStudioCodePackageUri -Version $version -Commit $commit -Architecture $Architecture
    if ([string]$release.url -cne $expectedUri -or [string]$release.sha256hash -notmatch '^[a-fA-F0-9]{64}$') {
        throw 'Visual Studio Code release has an unexpected system-installer URL or missing SHA-256.'
    }
    [pscustomobject]@{ Version=$version; Commit=$commit; Architecture=$Architecture; PackageUri=$expectedUri; Sha256=[string]$release.sha256hash }
}

function Assert-OSDAppVisualStudioCodeGeneration {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string]$Root,
        [Parameter(Mandatory)][ValidateSet('x64','arm64')][string]$Architecture,
        [psobject]$Release,
        [switch]$VerifyPublisher
    )
    $info = Get-Content -LiteralPath (Join-Path $Root 'CacheInfo.json') -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop
    $expectedUri = Get-OSDAppVisualStudioCodePackageUri -Version ([string]$info.Version) -Commit ([string]$info.Commit) -Architecture $Architecture
    if ([string]$info.Id -ne 'VisualStudioCode' -or [string]$info.Architecture -ne $Architecture -or
        [string]$info.PackageUri -cne $expectedUri -or [string]$info.Sha256 -notmatch '^[a-fA-F0-9]{64}$' -or
        [long]$info.Size -le 0) { throw 'Visual Studio Code cache metadata is invalid or incompatible.' }
    if ($Release -and ([string]$info.Version -ne [string]$Release.Version -or
        [string]$info.Commit -ne [string]$Release.Commit -or [string]$info.Sha256 -ne [string]$Release.Sha256 -or
        [string]$info.PackageUri -cne [string]$Release.PackageUri)) { throw 'Visual Studio Code cache does not match the current official release.' }
    $package = Join-Path $Root 'Package.exe'
    if ((Get-Item -LiteralPath $package -ErrorAction Stop).Length -ne [long]$info.Size -or
        (Get-FileHash -LiteralPath $package -Algorithm SHA256 -ErrorAction Stop).Hash -ne [string]$info.Sha256) {
        throw 'Visual Studio Code package size or SHA-256 does not match the recorded release.'
    }
    # Inno Setup's bootstrapper architecture need not equal its payload architecture.
    # The exact official platform URL and release digest identify the intended payload.
    if ($VerifyPublisher) {
        $signature = Get-AuthenticodeSignature -LiteralPath $package -ErrorAction Stop
        if ([string]$signature.Status -ne 'Valid' -or $null -eq $signature.SignerCertificate -or
            [string]$signature.SignerCertificate.Subject -notmatch '(?:^|,\s*)O=(?:Microsoft Corporation|"Microsoft Corporation")(?=,|$)') {
            throw 'Visual Studio Code installer must have a valid Microsoft Corporation Authenticode signature.'
        }
    }
    $info
}

function Sync-OSDAppVisualStudioCodeGeneration {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string]$Root,
        [Parameter(Mandatory)][ValidateSet('x64','arm64')][string]$Architecture,
        [Parameter(Mandatory)][scriptblock]$Download,
        [ValidateRange(0, [double]::MaxValue)][double]$MinimumFreeSpaceGB = 1
    )
    $downloadAction = $Download
    Restore-OSDAppCacheGeneration -Root $Root -Validate {
        param($candidate)
        Assert-OSDAppVisualStudioCodeGeneration -Root $candidate -Architecture $Architecture -VerifyPublisher
    } | Out-Null
    $release = Get-OSDAppVisualStudioCodeRelease -Architecture $Architecture
    $current = $false
    try { Assert-OSDAppVisualStudioCodeGeneration -Root $Root -Architecture $Architecture -Release $release -VerifyPublisher | Out-Null; $current = $true }
    catch { Write-Verbose "Visual Studio Code cache requires acquisition: $($_.Exception.Message)" }
    if (-not $current) {
        # The update API publishes a SHA-256, but no byte count. Preserve the normal
        # free-space reserve; record actual length and validate the digest after download.
        Invoke-OSDAppBuiltInCacheTransaction -Root $Root -MinimumFreeSpaceGB $MinimumFreeSpaceGB -Build {
            param($candidate)
            $package = Join-Path $candidate 'Package.exe'
            & $downloadAction $release.PackageUri $package | Out-Null
            [ordered]@{
                Id='VisualStudioCode'; Cached=$true; Version=$release.Version; Commit=$release.Commit;
                VersionSource='MicrosoftUpdateService'; Architecture=$Architecture;
                PackageUri=$release.PackageUri; Sha256=$release.Sha256; Size=(Get-Item -LiteralPath $package -ErrorAction Stop).Length;
                SourcePolicy='Evergreen'; SyncMethod='ReleaseSha256'; SyncedAt=(Get-Date).ToUniversalTime().ToString('o')
            } | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $candidate 'CacheInfo.json') -Encoding UTF8
        } -Validate {
            param($candidate)
            Assert-OSDAppVisualStudioCodeGeneration -Root $candidate -Architecture $Architecture -Release $release -VerifyPublisher
        } | Out-Null
    }
    [pscustomobject]@{ Id='VisualStudioCode'; Version=$release.Version; Architecture=$Architecture; Root=$Root; Updated=(-not $current) }
}