Runtime/Assert-OSDAppTransactionSpace.ps1

function Get-OSDAppPathSize {
    [CmdletBinding()]
    param([Parameter(Mandatory)][string]$Path)

    [double]$bytes = 0
    if (Test-Path -LiteralPath $Path) {
        Get-ChildItem -LiteralPath $Path -File -Recurse -Force -ErrorAction Stop |
            ForEach-Object { $bytes += $_.Length }
    }
    return $bytes
}

function Get-OSDAppAvailableDiskBytes {
    [CmdletBinding()]
    param([Parameter(Mandatory)][string]$Path)

    # Resolve PowerShell drives without requiring the destination to exist.
    $resolved = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)
    $root = [IO.Path]::GetPathRoot($resolved)
    if (-not $root -or $root.StartsWith('\\')) {
        throw "Cannot determine local cache volume free space for '$Path'."
    }
    $drive = [IO.DriveInfo]::new($root)
    if (-not $drive.IsReady) { throw "Cache volume '$root' is not ready." }
    return [double]$drive.AvailableFreeSpace
}

function Assert-OSDAppTransactionSpace {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][string]$Path,
        [Parameter(Mandatory)][string]$Operation,
        [ValidateRange(0, [double]::MaxValue)][double]$ExpectedBytes = 0,
        [ValidateRange(0, [double]::MaxValue)][double]$MinimumFreeSpaceGB = 0,
        [string]$ExistingPath
    )

    # The old generation is already included in used disk space. Renaming it
    # to a sibling backup consumes no second payload copy. Reserve only the
    # candidate, 20% growth allowance and 64 MB for metadata/working overhead.
    # Without a remote length, the old generation is an estimate, not a bound.
    $basis = 'RemoteOrSourceLength'
    if ($ExpectedBytes -le 0) {
        $basis = 'MinimumFreeSpace'
        if ($ExistingPath) {
            $ExpectedBytes = Get-OSDAppPathSize -Path $ExistingPath
            if ($ExpectedBytes -gt 0) { $basis = 'ExistingGenerationEstimate' }
        }
    }
    $required = [math]::Max(($MinimumFreeSpaceGB * 1GB), ([math]::Ceiling($ExpectedBytes * 1.2) + 64MB))
    $available = Get-OSDAppAvailableDiskBytes -Path $Path
    if ($available -lt $required) {
        throw ('Insufficient free space before {0}: path={1}; available={2:N2} GB; required={3:N2} GB; basis={4}. Existing cache content has not been replaced.' -f $Operation, $Path, ($available / 1GB), ($required / 1GB), $basis)
    }
    [pscustomobject]@{
        Path = $Path
        AvailableBytes = $available
        RequiredBytes = $required
        Basis = $basis
    }
}