Modules/businessdev.ALbuild.Containers/Private/Repair-BcArtifactCache.ps1

function Repair-BcArtifactCache {
    <#
    .SYNOPSIS
        Removes an incomplete/corrupt BC artifact from the shared cache so the container re-downloads it.
 
    .DESCRIPTION
        The BC container downloads its artifact into the mounted cache (host BcArtifactCacheFolder ->
        C:\dl) on first run and reuses it thereafter. An interrupted download - a killed container, an
        agent restart, a full disk, a dropped network - leaves the artifact folder present but INCOMPLETE
        (e.g. without 'manifest.json'). The image then finds the folder, assumes the artifact is cached,
        skips the re-download, and exits immediately with
        "Cannot find path 'C:\dl\<type>\<version>\<country>\manifest.json'".
 
        This validates the cached app (country) and platform artifacts for the given URL and deletes any
        that are incomplete, so the next container start performs a clean re-download. Validity:
          * app/country folder - must contain a PARSEABLE 'manifest.json' (exactly what the image reads).
          * platform folder - a parseable 'manifest.json' OR a 'lastused' marker with real content
                                 (older BC platform artifacts ship no manifest.json but always get a
                                 'lastused' file on a completed download).
        A folder that does not exist is fine (nothing cached yet - the container will download it).
 
    .PARAMETER CacheFolder
        The host artifact cache mounted into the container at C:\dl (BcArtifactCacheFolder).
 
    .PARAMETER ArtifactUrl
        The artifact URL of the form https://.../{type}/{version}/{country}.
 
    .OUTPUTS
        System.String[] - the cache folders that were removed (empty when everything is valid/absent).
    #>

    [CmdletBinding(SupportsShouldProcess)]
    [OutputType([string[]])]
    param(
        [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $CacheFolder,
        [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $ArtifactUrl
    )

    if (-not (Test-Path -LiteralPath $CacheFolder)) { return @() }

    # Parse .../{type}/{version}/{country} (query strings / SAS tokens are ignored by AbsolutePath).
    try { $segments = ([uri]$ArtifactUrl).AbsolutePath.Trim('/').Split('/') }
    catch { Write-ALbuildLog -Level Verbose "Could not parse artifact URL '$ArtifactUrl' for cache validation; skipping."; return @() }
    if ($segments.Count -lt 3) { Write-ALbuildLog -Level Verbose "Artifact URL '$ArtifactUrl' is not {type}/{version}/{country}; skipping cache validation."; return @() }
    $type = $segments[0]; $version = $segments[1]; $country = $segments[2]
    $versionRoot = Join-Path (Join-Path $CacheFolder $type) $version

    # A folder is complete when it has a parseable manifest.json; a platform folder may instead carry
    # only the 'lastused' completion marker (older BC platform artifacts have no manifest.json).
    $isComplete = {
        param([string] $Folder, [bool] $AllowLastUsedMarker)
        $manifest = Join-Path $Folder 'manifest.json'
        if (Test-Path -LiteralPath $manifest) {
            try { $null = Get-Content -LiteralPath $manifest -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop; return $true }
            catch { return $false }   # present but truncated/corrupt JSON
        }
        if ($AllowLastUsedMarker -and (Test-Path -LiteralPath (Join-Path $Folder 'lastused'))) {
            return @(Get-ChildItem -LiteralPath $Folder -Force -ErrorAction SilentlyContinue |
                    Where-Object { $_.Name -ne 'lastused' }).Count -gt 0
        }
        return $false
    }

    $removed = [System.Collections.Generic.List[string]]::new()
    $targets = @(
        [PSCustomObject]@{ Folder = (Join-Path $versionRoot $country); AllowLastUsedMarker = $false }   # app: image requires manifest.json
        [PSCustomObject]@{ Folder = (Join-Path $versionRoot 'platform'); AllowLastUsedMarker = $true }
    )
    foreach ($t in $targets) {
        if (-not (Test-Path -LiteralPath $t.Folder)) { continue }
        if (& $isComplete $t.Folder $t.AllowLastUsedMarker) { continue }
        Write-ALbuildLog -Level Warning "Cached BC artifact at '$($t.Folder)' is incomplete/corrupt (no valid manifest); removing it so the container re-downloads a clean copy."
        if ($PSCmdlet.ShouldProcess($t.Folder, 'Remove incomplete cached BC artifact')) {
            try { Remove-Item -LiteralPath $t.Folder -Recurse -Force -ErrorAction Stop; $removed.Add($t.Folder) }
            catch { Write-ALbuildLog -Level Warning "Could not remove '$($t.Folder)': $($_.Exception.Message). A stale artifact may still cause the container to fail; clear it manually." }
        }
    }
    if ($removed.Count -eq 0) { Write-ALbuildLog -Level Verbose "Cached BC artifact for '$type/$version/$country' is valid (or not yet present)." }
    return $removed.ToArray()
}