Private/WinGet/Send-IntuneWinAzureStorageChunkedUpload.ps1
|
function Send-IntuneWinAzureStorageChunkedUpload { [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$FilePath, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$UploadUri, [Parameter()] [ValidateRange(1, [int]::MaxValue)] [int]$ChunkSizeBytes = 6MB, [Parameter()] [scriptblock]$RenewUploadUri, [Parameter()] [ValidateRange(1, [int]::MaxValue)] [int]$MaxRetries = 3, [Parameter()] [ValidateRange(1, [int]::MaxValue)] [int]$ChunkUploadTimeoutSec = 300, [Parameter()] [ValidateRange(1, [int]::MaxValue)] [int]$CommitTimeoutSec = 120 ) function Get-UploadUriForLogging { param( [Parameter(Mandatory)] [string]$Uri ) return ($Uri -split '\?', 2)[0] } $fileSize = [int64](Get-Item -Path $FilePath).Length $chunkSize = [int64]$ChunkSizeBytes $chunkCount = [int][Math]::Ceiling($fileSize / $chunkSize) $currentUploadUri = $UploadUri $chunkIds = [System.Collections.Generic.List[string]]::new([Math]::Max($chunkCount, 0)) Write-Debug "Starting Azure Storage chunked upload for '$FilePath' ($fileSize bytes) to '$(Get-UploadUriForLogging -Uri $UploadUri)' using ChunkSizeBytes=$ChunkSizeBytes across $chunkCount chunk(s)." $reader = [System.IO.BinaryReader]::new([System.IO.File]::Open($FilePath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::Read)) try { for ($index = 0; $index -lt $chunkCount; $index++) { $chunkId = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($index.ToString('0000'))) $chunkIds.Add($chunkId) $chunkLength = Get-IntuneWinUploadChunkLength -FileSize $fileSize -ChunkIndex $index -ChunkSizeBytes $ChunkSizeBytes $chunkBytes = $reader.ReadBytes($chunkLength) $chunkUri = "$currentUploadUri&comp=block&blockid=$chunkId" Write-Debug "Uploading Azure Storage chunk $($index + 1)/$chunkCount for '$FilePath' (ChunkId='$chunkId', Bytes=$chunkLength)." for ($attempt = 1; $attempt -le $MaxRetries; $attempt++) { try { Invoke-IntuneWinAzureStorageBlockUpload -Uri $chunkUri -Body $chunkBytes -TimeoutSec $ChunkUploadTimeoutSec break } catch { $isAuthFailure = $_.Exception.Message -match 'AuthenticationFailed|403|authorized' if ($isAuthFailure -and $RenewUploadUri -and $attempt -lt $MaxRetries) { Write-Debug "Azure Storage upload authorization expired for chunk $($index + 1)/$chunkCount. Renewing upload URI before retrying." $currentUploadUri = & $RenewUploadUri $chunkUri = "$currentUploadUri&comp=block&blockid=$chunkId" continue } if ($attempt -eq $MaxRetries) { throw } Write-Debug "Azure Storage upload retry for chunk $($index + 1)/$chunkCount after failure '$($_.Exception.Message)' (attempt $attempt of $MaxRetries)." Start-Sleep -Seconds (5 * $attempt) } } } } finally { $reader.Close() $reader.Dispose() } $blockListXml = [System.Text.StringBuilder]::new() [void]$blockListXml.Append('<?xml version="1.0" encoding="utf-8"?><BlockList>') foreach ($chunkId in $chunkIds) { [void]$blockListXml.Append("<Latest>$chunkId</Latest>") } [void]$blockListXml.Append('</BlockList>') $blockListBody = $blockListXml.ToString() for ($attempt = 1; $attempt -le $MaxRetries; $attempt++) { try { Write-Debug "Committing Azure Storage block list for '$FilePath' with $($chunkIds.Count) chunk(s)." Invoke-RestMethod -Method PUT -Uri "$currentUploadUri&comp=blocklist" -Body $blockListBody -Headers @{ 'Content-Type' = 'text/plain; charset=UTF-8' } -TimeoutSec $CommitTimeoutSec -ErrorAction Stop | Out-Null break } catch { $isAuthFailure = $_.Exception.Message -match 'AuthenticationFailed|403|authorized' if ($isAuthFailure -and $RenewUploadUri -and $attempt -lt $MaxRetries) { Write-Debug "Azure Storage block list commit authorization expired. Renewing upload URI before retrying." $currentUploadUri = & $RenewUploadUri continue } if ($attempt -eq $MaxRetries) { throw } Write-Debug "Azure Storage block list commit retry after failure '$($_.Exception.Message)' (attempt $attempt of $MaxRetries)." Start-Sleep -Seconds (5 * $attempt) } } Write-Debug "Completed Azure Storage chunked upload for '$FilePath'. FinalUploadUri='$(Get-UploadUriForLogging -Uri $currentUploadUri)', ChunkCount=$chunkCount, FileSize=$fileSize." return [PSCustomObject]@{ ChunkCount = $chunkCount FileSize = $fileSize UploadUri = $currentUploadUri } } |