Private/Invoke-AzBlobUpload.ps1

function Invoke-AzBlobUpload {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$SasUri,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$FilePath
    )

    $chunkSize  = 1024 * 1024 * 6   # 6 MB — matches reference
    $isoEncoding = [System.Text.Encoding]::GetEncoding('iso-8859-1')
    $fileInfo   = Get-Item -Path $FilePath
    $totalSize  = $fileInfo.Length
    $chunkCount = [System.Math]::Ceiling($totalSize / $chunkSize)
    $chunkIds   = @()
    $chunkIndex = 0

    Write-Verbose " File size: $([math]::Round($totalSize / 1MB, 1)) MB — uploading in $chunkCount chunk(s)..."

    $binaryReader = New-Object System.IO.BinaryReader(
        [System.IO.File]::Open($FilePath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
    )
    $null = $binaryReader.BaseStream.Seek(0, [System.IO.SeekOrigin]::Begin)

    try {
        for ($i = 0; $i -lt $chunkCount; $i++) {
            $start  = $i * $chunkSize
            $length = [System.Math]::Min($chunkSize, $totalSize - $start)
            $bytes  = $binaryReader.ReadBytes($length)

            # Block ID: 4-digit zero-padded index → ASCII bytes → base64 (no URL-encoding — matches reference)
            $chunkId  = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($i.ToString('0000')))
            $chunkIds += $chunkId

            # Convert bytes via ISO-8859-1 (1:1 byte→char mapping preserves binary content)
            $encodedBytes = $isoEncoding.GetString($bytes)
            $headers = @{
                'x-ms-blob-type' = 'BlockBlob'
                'content-type'   = 'text/plain; charset=iso-8859-1'
            }

            $blockUri = "$SasUri&comp=block&blockid=$chunkId"
            Invoke-WebRequest -Uri $blockUri -Method Put -Headers $headers `
                -Body $encodedBytes -UseBasicParsing | Out-Null

            $currentChunk = $i + 1
            Write-Progress -Activity 'Uploading to Azure Storage' `
                -Status "Uploading chunk $currentChunk of $chunkCount" `
                -PercentComplete ($currentChunk / $chunkCount * 100)
        }
    }
    finally {
        $binaryReader.Close()
        $binaryReader.Dispose()
    }

    # ── Commit: Put Block List ─────────────────────────────────────────────────────
    $blockListXml = '<?xml version="1.0" encoding="utf-8"?><BlockList>'
    foreach ($id in $chunkIds) { $blockListXml += "<Latest>$id</Latest>" }
    $blockListXml += '</BlockList>'

    $finalizeHeaders = @{ 'content-type' = 'text/plain; charset=UTF-8' }
    $commitUri = "$SasUri&comp=blocklist"
    Invoke-RestMethod -Uri $commitUri -Method Put -Body $blockListXml `
        -Headers $finalizeHeaders -ErrorAction Stop | Out-Null

    Write-Progress -Activity 'Uploading to Azure Storage' -Completed
    Write-Host ' ✓ Upload complete.' -ForegroundColor Green
}