Skip to content

Commit

Permalink
Merge pull request #7 from tjgruber/6-sporadic-upload-chunk-failure-t…
Browse files Browse the repository at this point in the history
…o-azure-storage-blob

6 sporadic upload chunk failure to azure storage blob
  • Loading branch information
tjgruber authored Jun 4, 2024
2 parents 7a4a51d + 73130ef commit d59204e
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 31 deletions.
67 changes: 55 additions & 12 deletions Private/Invoke-AzureStorageBlobUpload.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,21 @@ function Invoke-AzureStorageBlobUpload {
Upload and commit .intunewin file into Azure Storage blob container.
This is a modified function that was originally developed by Dave Falkus and is available here:
https://github.com/microsoftgraph/powershell-intune-samples/blob/master/LOB_Application/Win32_Application_Add.ps1
https://github.com/microsoftgraph/powershell-intune-samples/blob/master/LOB_Application/Win32_Application_Add.ps1
.NOTES
Author: Nickolaj Andersen
Contact: @NickolajA
Created: 2020-01-04
Updated: 2023-09-04
Updated: 2024-06-04
Version history:
1.0.0 - (2020-01-04) Function created
1.0.1 - (2020-09-20) Fixed an issue where the System.IO.BinaryReader wouldn't open a file path containing whitespaces
1.0.2 - (2021-03-15) Fixed an issue where SAS Uri renewal wasn't working correctly
1.0.3 - (2022-09-03) Added access token refresh functionality when a token is about to expire, to prevent uploads from failing due to an expire access token
1.0.4 - (2023-09-04) Updated with Test-AccessToken function
#>
1.0.3 - (2022-09-03) Added access token refresh functionality when a token is about to expire, to prevent uploads from failing due to an expired access token
1.0.5 - (2024-06-04) Added retry logic for chunk uploads and finalization steps to enhance reliability (thanks to @tjgruber)
#>
param(
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
Expand All @@ -36,6 +36,8 @@ function Invoke-AzureStorageBlobUpload {
[string]$Resource
)
$ChunkSizeInBytes = 1024l * 1024l * 6l;
$RetryCount = 5
$RetryDelay = 10

# Start the timer for SAS URI renewal
$SASRenewalTimer = [System.Diagnostics.Stopwatch]::StartNew()
Expand All @@ -46,7 +48,7 @@ function Invoke-AzureStorageBlobUpload {
$BinaryReader = New-Object -TypeName System.IO.BinaryReader([System.IO.File]::Open($FilePath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite))
$Position = $BinaryReader.BaseStream.Seek(0, [System.IO.SeekOrigin]::Begin)

# Upload each chunk and dheck whether a SAS URI renewal is required after each chunk is uploaded and renew if needed
# Upload each chunk and check whether a SAS URI renewal is required after each chunk is uploaded and renew if needed
$ChunkIDs = @()
for ($Chunk = 0; $Chunk -lt $ChunkCount; $Chunk++) {
Write-Verbose -Message "SAS Uri renewal timer has elapsed for: $($SASRenewalTimer.Elapsed.Minutes) minute $($SASRenewalTimer.Elapsed.Seconds) seconds"
Expand Down Expand Up @@ -75,12 +77,38 @@ function Invoke-AzureStorageBlobUpload {

Write-Progress -Activity "Uploading file to Azure Storage blob" -Status "Uploading chunk $($CurrentChunk) of $($ChunkCount)" -PercentComplete ($CurrentChunk / $ChunkCount * 100)
Write-Verbose -Message "Uploading file to Azure Storage blob, processing chunk '$($CurrentChunk)' of '$($ChunkCount)'"
$UploadResponse = Invoke-AzureStorageBlobUploadChunk -StorageUri $StorageUri -ChunkID $ChunkID -Bytes $Bytes


$UploadSuccess = $false
for ($i = 0; $i -lt $RetryCount; $i++) {
try {
$UploadResponse = Invoke-AzureStorageBlobUploadChunk -StorageUri $StorageUri -ChunkID $ChunkID -Bytes $Bytes
$UploadSuccess = $true
break
} catch {
Write-Warning "Failed to upload chunk. Attempt $($i + 1) of $RetryCount. Error: $_"
Start-Sleep -Seconds $RetryDelay
Write-Warning "Retrying upload of chunk '$($CurrentChunk)' of '$($ChunkCount)'"
}
}

if (-not $UploadSuccess) {
Write-Error "Failed to upload chunk after $RetryCount attempts. Aborting upload."
return
}

if (($CurrentChunk -lt $ChunkCount) -and ($SASRenewalTimer.ElapsedMilliseconds -ge 450000)) {
Write-Verbose -Message "SAS Uri renewal is required, attempting to renew"
$RenewedSASUri = Invoke-AzureStorageBlobUploadRenew -Resource $Resource
$SASRenewalTimer.Restart()
try {
$RenewedSASUri = Invoke-AzureStorageBlobUploadRenew -Resource $Resource
if ($null -ne $RenewedSASUri) {
$StorageUri = $RenewedSASUri
$SASRenewalTimer.Restart()
} else {
Write-Warning "SAS Uri renewal failed, continuing with existing Uri"
}
} catch {
Write-Warning "SAS Uri renewal attempt failed with error: $_. Continuing with existing Uri."
}
}
}

Expand All @@ -91,9 +119,24 @@ function Invoke-AzureStorageBlobUpload {
Write-Progress -Completed -Activity "Uploading File to Azure Storage blob"

# Finalize the upload of the content file to Azure Storage blob
Invoke-AzureStorageBlobUploadFinalize -StorageUri $StorageUri -ChunkID $ChunkIDs
$FinalizeSuccess = $false
for ($i = 0; $i -lt $RetryCount; $i++) {
try {
Invoke-AzureStorageBlobUploadFinalize -StorageUri $StorageUri -ChunkID $ChunkIDs
$FinalizeSuccess = $true
break
} catch {
Write-Warning "Failed to finalize Azure Storage blob upload. Attempt $($i + 1) of $RetryCount. Error: $_"
Start-Sleep -Seconds $RetryDelay
}
}

if (-not $FinalizeSuccess) {
Write-Error "Failed to finalize upload after $RetryCount attempts. Aborting upload."
return
}

# Close and dispose binary reader object
$BinaryReader.Close()
$BinaryReader.Dispose()
}
}
28 changes: 15 additions & 13 deletions Private/Invoke-AzureStorageBlobUploadChunk.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ function Invoke-AzureStorageBlobUploadChunk {
1.0.0 - (2020-01-04) Function created
1.0.1 - (2021-04-02) Added UseBasicParsing to support conditions where IE first run experience have not been completed
1.0.2 - (2024-01-10) Fixed issue described in #128 - thanks to @jaspain for finding the solution
#>
1.0.3 - (2024-06-03) Added exception throwing on failure to support retry logic in the upload process (thanks to @tjgruber)
#>
param(
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
Expand All @@ -33,18 +34,19 @@ function Invoke-AzureStorageBlobUploadChunk {
[ValidateNotNullOrEmpty()]
[System.Object]$Bytes
)
$Uri = "$($StorageUri)&comp=block&blockid=$($ChunkID)"
$ISOEncoding = [System.Text.Encoding]::GetEncoding("iso-8859-1")
$EncodedBytes = $ISOEncoding.GetString($Bytes)
$Uri = "$($StorageUri)&comp=block&blockid=$($ChunkID)"
$ISOEncoding = [System.Text.Encoding]::GetEncoding("iso-8859-1")
$EncodedBytes = $ISOEncoding.GetString($Bytes)
$Headers = @{
"content-type" = "text/plain; charset=iso-8859-1"
"x-ms-blob-type" = "BlockBlob"
}
"content-type" = "text/plain; charset=iso-8859-1"
"x-ms-blob-type" = "BlockBlob"
}

try {
$WebResponse = Invoke-WebRequest $Uri -Method "Put" -Headers $Headers -Body $EncodedBytes -UseBasicParsing -ErrorAction Stop
}
catch {
try {
$WebResponse = Invoke-WebRequest $Uri -Method "Put" -Headers $Headers -Body $EncodedBytes -UseBasicParsing -ErrorAction Stop
return $WebResponse
} catch {
Write-Warning -Message "Failed to upload chunk to Azure Storage blob. Error message: $($_.Exception.Message)"
}
}
throw $_
}
}
6 changes: 4 additions & 2 deletions Private/Invoke-AzureStorageBlobUploadFinalize.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ function Invoke-AzureStorageBlobUploadFinalize {
Version history:
1.0.0 - (2020-01-04) Function created
1.0.1 - (2024-05-29) Added content-type header to the REST request to ensure correct handling of the request body (thanks to @tjgruber)
1.0.2 - (2024-06-03) Added exception throwing on failure to support retry logic in the finalization process (thanks to @tjgruber)
#>
param(
[parameter(Mandatory = $true)]
Expand Down Expand Up @@ -45,8 +46,9 @@ function Invoke-AzureStorageBlobUploadFinalize {

try {
$WebResponse = Invoke-RestMethod -Uri $Uri -Method "Put" -Body $XML -Headers $Headers -ErrorAction Stop
}
catch {
return $WebResponse
} catch {
Write-Warning -Message "Failed to finalize Azure Storage blob upload. Error message: $($_.Exception.Message)"
throw $_
}
}
25 changes: 21 additions & 4 deletions Private/Invoke-AzureStorageBlobUploadRenew.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,34 @@ function Invoke-AzureStorageBlobUploadRenew {
Author: Nickolaj Andersen
Contact: @NickolajA
Created: 2020-01-04
Updated: 2021-03-15
Updated: 2024-06-03
Version history:
1.0.0 - (2020-01-04) Function created
1.0.1 - (2021-03-15) Fixed an issue where SAS Uri renewal wasn't working correctly
#>
1.0.2 - (2024-06-03) Added loop to check the status of the SAS URI renewal
#>
param(
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$Resource
)
$RenewSASURIRequest = Invoke-IntuneGraphRequest -APIVersion "Beta" -Resource "$($Resource)/renewUpload" -Method "POST" -Body "{}"
$FilesProcessingRequest = Wait-IntuneWin32AppFileProcessing -Stage "AzureStorageUriRenewal" -Resource $Resource
}

# Loop to wait for the renewal process to complete and check the status
$attempts = 0
$maxAttempts = 3
$waitTime = 5 # seconds

while ($attempts -lt $maxAttempts) {
$FilesProcessingRequest = Invoke-IntuneGraphRequest -APIVersion "Beta" -Resource "$($Resource)" -Method "GET"
if ($FilesProcessingRequest.uploadState -eq "azureStorageUriRenewalSuccess") {
return $FilesProcessingRequest.azureStorageUri
} elseif ($FilesProcessingRequest.uploadState -eq "azureStorageUriRenewalFailed") {
throw "SAS Uri renewal failed"
}
$attempts++
Start-Sleep -Seconds $waitTime
}
throw "SAS Uri renewal did not complete in the expected time"
}

0 comments on commit d59204e

Please sign in to comment.