Private/Save-OSDAppDownload.ps1

function Save-OSDAppDownload {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [uri]$Uri,

        [Parameter(Mandatory)]
        [string]$DestinationPath,

        [Parameter(Mandatory)]
        [string]$Activity,

        [int]$ProgressId = 20,

        [int]$ParentProgressId = -1
    )

    Add-Type -AssemblyName System.Net.Http -ErrorAction SilentlyContinue

    $handler = New-Object System.Net.Http.HttpClientHandler
    $handler.AllowAutoRedirect = $true
    $client = New-Object System.Net.Http.HttpClient($handler)
    $client.Timeout = [TimeSpan]::FromHours(1)

    $directory = Split-Path -Path $DestinationPath -Parent
    if ($directory) {
        New-Item -ItemType Directory -Path $directory -Force | Out-Null
    }

    $tempPath = "$DestinationPath.download"
    if (Test-Path -LiteralPath $tempPath) {
        Remove-Item -LiteralPath $tempPath -Force
    }

    $stopwatch = [System.Diagnostics.Stopwatch]::StartNew()

    try {
        $response = $client.GetAsync($Uri, [System.Net.Http.HttpCompletionOption]::ResponseHeadersRead).GetAwaiter().GetResult()
        $response.EnsureSuccessStatusCode()

        $totalBytes = $response.Content.Headers.ContentLength
        $source = $response.Content.ReadAsStreamAsync().GetAwaiter().GetResult()

        $target = New-Object System.IO.FileStream(
            $tempPath,
            [System.IO.FileMode]::Create,
            [System.IO.FileAccess]::Write,
            [System.IO.FileShare]::None
        )

        try {
            $buffer = New-Object byte[] (1024 * 1024)
            [int64]$downloaded = 0
            $lastPercent = -1
            $lastProgressSecond = -1

            while (($read = $source.Read($buffer, 0, $buffer.Length)) -gt 0) {
                $target.Write($buffer, 0, $read)
                $downloaded += $read

                $elapsedSeconds = [math]::Max($stopwatch.Elapsed.TotalSeconds, 0.001)
                $speedBytesPerSecond = $downloaded / $elapsedSeconds
                $speedMBps = [math]::Round($speedBytesPerSecond / 1MB, 1)
                $downloadedGB = [math]::Round($downloaded / 1GB, 2)
                $elapsed = $stopwatch.Elapsed.ToString('hh\:mm\:ss')

                if ($totalBytes -and $totalBytes -gt 0) {
                    $percent = [math]::Min(100, [int](($downloaded / $totalBytes) * 100))
                    $totalGB = [math]::Round($totalBytes / 1GB, 2)
                    $remainingSeconds = if ($speedBytesPerSecond -gt 0) { [math]::Max(0, ($totalBytes - $downloaded) / $speedBytesPerSecond) } else { 0 }
                    $remaining = [TimeSpan]::FromSeconds($remainingSeconds).ToString('hh\:mm\:ss')

                    if ($percent -ne $lastPercent -or [int]$stopwatch.Elapsed.TotalSeconds -ne $lastProgressSecond) {
                        $status = "$downloadedGB GB / $totalGB GB · $percent% · $speedMBps MB/s · $elapsed elapsed · ~$remaining remaining"
                        Write-Progress -Id $ProgressId -ParentId $ParentProgressId -Activity $Activity -Status $status -PercentComplete $percent
                        $lastPercent = $percent
                        $lastProgressSecond = [int]$stopwatch.Elapsed.TotalSeconds
                    }
                }
                elseif ([int]$stopwatch.Elapsed.TotalSeconds -ne $lastProgressSecond) {
                    $status = "$downloadedGB GB · $speedMBps MB/s · $elapsed elapsed"
                    Write-Progress -Id $ProgressId -ParentId $ParentProgressId -Activity $Activity -Status $status -PercentComplete -1
                    $lastProgressSecond = [int]$stopwatch.Elapsed.TotalSeconds
                }
            }
        }
        finally {
            $target.Dispose()
            $source.Dispose()
            Write-Progress -Id $ProgressId -Activity $Activity -Completed
        }

        if (Test-Path -LiteralPath $DestinationPath) {
            Remove-Item -LiteralPath $DestinationPath -Force
        }
        Move-Item -LiteralPath $tempPath -Destination $DestinationPath -Force
    }
    catch {
        if (Test-Path -LiteralPath $tempPath) {
            Remove-Item -LiteralPath $tempPath -Force -ErrorAction SilentlyContinue
        }
        throw
    }
    finally {
        $stopwatch.Stop()
        $client.Dispose()
        $handler.Dispose()
    }

    Get-Item -LiteralPath $DestinationPath
}