Skip to content

Instantly share code, notes, and snippets.

@MovGP0
Created February 18, 2026 08:44
Show Gist options
  • Select an option

  • Save MovGP0/f3992b36c9c9b497652a35d006262bdc to your computer and use it in GitHub Desktop.

Select an option

Save MovGP0/f3992b36c9c9b497652a35d006262bdc to your computer and use it in GitHub Desktop.
Chunked file download via curl
param(
[string]$Url,
[string]$OutputPath,
[int]$ChunkSizeMb = 1,
[int]$MaxRetries = 16
)
$ErrorActionPreference = 'Stop'
if ($ChunkSizeMb -lt 1)
{
throw 'ChunkSizeMb must be >= 1.'
}
$head = Invoke-WebRequest -Uri $Url -Method Head
$contentLengthHeader = $head.Headers['Content-Length']
if ($contentLengthHeader -is [System.Array])
{
$contentLengthHeader = $contentLengthHeader[0]
}
$totalBytes = [int64]$contentLengthHeader
if ($totalBytes -le 0)
{
throw 'Unable to determine Content-Length.'
}
$chunkSizeBytes = [int64]$ChunkSizeMb * 1MB
$partsDir = Join-Path (Get-Location) '.temp\parts'
if (-not (Test-Path -LiteralPath $partsDir))
{
New-Item -ItemType Directory -Path $partsDir -Force | Out-Null
}
$outputDir = Split-Path -Parent $OutputPath
if (-not (Test-Path -LiteralPath $outputDir))
{
New-Item -ItemType Directory -Path $outputDir -Force | Out-Null
}
$chunkCount = [int][Math]::Ceiling($totalBytes / [double]$chunkSizeBytes)
$ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36'
Write-Host ("Total bytes: {0}" -f $totalBytes)
Write-Host ("Chunk size: {0}" -f $chunkSizeBytes)
Write-Host ("Chunk count: {0}" -f $chunkCount)
for ($index = 0; $index -lt $chunkCount; $index++)
{
$start = [int64]$index * $chunkSizeBytes
$end = [Math]::Min($start + $chunkSizeBytes - 1, $totalBytes - 1)
$expectedBytes = $end - $start + 1
$partPath = Join-Path $partsDir ("part-{0:D5}.bin" -f $index)
$isValidExistingPart = $false
if (Test-Path -LiteralPath $partPath)
{
$existingLength = (Get-Item -LiteralPath $partPath).Length
if ($existingLength -eq $expectedBytes)
{
$isValidExistingPart = $true
}
else
{
Remove-Item -LiteralPath $partPath -Force
}
}
if (-not $isValidExistingPart)
{
$downloaded = $false
for ($attempt = 1; $attempt -le $MaxRetries; $attempt++)
{
& curl.exe -L --fail --silent --show-error --connect-timeout 10 --max-time 60 `
-H ("Range: bytes={0}-{1}" -f $start, $end) `
-H 'Accept-Encoding: identity' `
-H ("Referer: {0}" -f $Url) `
-H ("User-Agent: {0}" -f $ua) `
-o $partPath `
$Url
$exitCode = $LASTEXITCODE
if ($exitCode -eq 0 -and (Test-Path -LiteralPath $partPath))
{
$actualLength = (Get-Item -LiteralPath $partPath).Length
if ($actualLength -eq $expectedBytes)
{
$downloaded = $true
break
}
}
if (Test-Path -LiteralPath $partPath)
{
Remove-Item -LiteralPath $partPath -Force -ErrorAction SilentlyContinue
}
Write-Host ("Retry chunk {0}/{1} range {2}-{3} attempt {4}/{5}" -f ($index + 1), $chunkCount, $start, $end, $attempt, $MaxRetries)
Start-Sleep -Seconds 1
}
if (-not $downloaded)
{
throw ("Failed chunk {0}/{1} range {2}-{3}" -f ($index + 1), $chunkCount, $start, $end)
}
}
if ((($index + 1) % 25) -eq 0 -or ($index + 1) -eq $chunkCount)
{
$percent = [Math]::Round((($index + 1) * 100.0) / $chunkCount, 2)
Write-Host ("Downloaded chunks: {0}/{1} ({2}%)" -f ($index + 1), $chunkCount, $percent)
}
}
$combinedPath = "$OutputPath.download"
if (Test-Path -LiteralPath $combinedPath)
{
Remove-Item -LiteralPath $combinedPath -Force
}
$outStream = [System.IO.File]::Open($combinedPath, [System.IO.FileMode]::CreateNew, [System.IO.FileAccess]::Write, [System.IO.FileShare]::Read)
try
{
for ($index = 0; $index -lt $chunkCount; $index++)
{
$partPath = Join-Path $partsDir ("part-{0:D5}.bin" -f $index)
$inStream = [System.IO.File]::Open($partPath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::Read)
try
{
$inStream.CopyTo($outStream)
}
finally
{
$inStream.Dispose()
}
}
}
finally
{
$outStream.Dispose()
}
$finalSize = (Get-Item -LiteralPath $combinedPath).Length
if ($finalSize -ne $totalBytes)
{
throw ("Combined file size mismatch. Expected {0}, got {1}" -f $totalBytes, $finalSize)
}
Move-Item -LiteralPath $combinedPath -Destination $OutputPath -Force
Write-Host ("Completed: {0}" -f $OutputPath)
Write-Host ("Size: {0}" -f $finalSize)
chunked-download.ps1 -Url 'https://ndr-progressive.ard-mcdn.de/progressive_geo/2025/1206/TV-20251206-2206-4911.1080.mp4'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment