-
Notifications
You must be signed in to change notification settings - Fork 93
/
Invoke-AzureStorageBlobUpload.ps1
153 lines (128 loc) · 6.69 KB
/
Invoke-AzureStorageBlobUpload.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
function Invoke-AzureStorageBlobUpload {
<#
.SYNOPSIS
Upload and commit .intunewin file into Azure Storage blob container.
.DESCRIPTION
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
.NOTES
Author: Nickolaj Andersen
Contact: @NickolajA
Created: 2020-01-04
Updated: 2024-11-15
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 expired access token
1.0.5 - (2024-06-04) Added retry logic for chunk uploads and finalization steps to enhance reliability (thanks to @tjgruber)
1.0.6 - (2024-11-15) Refactor date handling for token to fix locale-specific parsing issues (thanks to @tjgruber)
#>
param(
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$StorageUri,
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$FilePath,
[parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string]$Resource
)
$ChunkSizeInBytes = 1024l * 1024l * 6l;
$RetryCount = 5
$RetryDelay = 10
# Start the timer for SAS URI renewal
$SASRenewalTimer = [System.Diagnostics.Stopwatch]::StartNew()
# Find the file size and open the file
$FileSize = (Get-Item -Path $FilePath).Length
$ChunkCount = [System.Math]::Ceiling($FileSize / $ChunkSizeInBytes)
$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 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"
# Convert ExpiresOn to DateTimeOffset in UTC
$ExpiresOnUTC = [DateTimeOffset]::Parse(
$Global:AccessToken.ExpiresOn.ToString(),
[System.Globalization.CultureInfo]::InvariantCulture,
[System.Globalization.DateTimeStyles]::AssumeUniversal
).ToUniversalTime()
# Get the current UTC time as DateTimeOffset
$UTCDateTime = [DateTimeOffset]::UtcNow
# Calculate the TimeSpan between expiration and current time
$TimeSpan = $ExpiresOnUTC - $UTCDateTime
# Calculate the token expiration time in minutes
$TokenExpireMinutes = [System.Math]::Round($TimeSpan.TotalMinutes)
# Determine if refresh of access token is required when expiration count is less than or equal to minimum age
if ($TokenExpireMinutes -le 10) {
Write-Verbose -Message "Existing token found but is soon about to expire, refreshing token"
Connect-MSIntuneGraph -TenantID $Global:AccessTokenTenantID -Refresh
}
# Convert and calculate required chunk elements for content upload
$ChunkID = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($Chunk.ToString("0000")))
$ChunkIDs += $ChunkID
$Start = $Chunk * $ChunkSizeInBytes
$Length = [System.Math]::Min($ChunkSizeInBytes, $FileSize - $Start)
$Bytes = $BinaryReader.ReadBytes($Length)
# Increment chunk to get the current chunk
$CurrentChunk = $Chunk + 1
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)'"
$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"
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."
}
}
}
# Stop timer
$SASRenewalTimer.Stop()
# Complete write status progress bar
Write-Progress -Completed -Activity "Uploading File to Azure Storage blob"
# Finalize the upload of the content file to Azure Storage blob
$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()
}