44
55. DESCRIPTION
66 Special handling is required for ARM64 due to a bug in UseDotNet@2:
7-
7+
88 [BUG]: UseDotNet@2 task installs x86 build
99 https://github.com/microsoft/azure-pipelines-tasks/issues/20300
10-
10+
1111 The downloaded dotnet-install.ps1 script is kept in the $InstallDir to avoid
1212 downloading it multiple times during the pipeline job.
13-
13+
1414 The following environment variables are set for subsequent steps in the pipeline:
15-
15+
1616 DOTNET_ROOT: Set to $InstallDir.
1717 PATH: $DOTNET_ROOT is prepended to the PATH environment variable.
1818
5858# Stop on all errors.
5959$ErrorActionPreference = ' Stop'
6060
61+ # Maximum number of retry attempts for transient install failures (e.g.
62+ # corrupt downloads, network timeouts).
63+ $maxAttempts = 3
64+ $retryDelaySec = 10
65+
66+ # ------------------------------------------------------------------------------
67+ # Invoke dotnet-install.ps1 with retry logic. On each attempt the script is
68+ # called with the supplied $Params. If a non-zero exit code is returned, or
69+ # if the optional $Verify script-block throws, the attempt is considered failed
70+ # and will be retried after a short delay.
71+
72+ function Invoke-DotNetInstall
73+ {
74+ param
75+ (
76+ [Parameter (Mandatory )]
77+ [string ]$Description ,
78+
79+ [Parameter (Mandatory )]
80+ [hashtable ]$Params ,
81+
82+ [scriptblock ]$Verify = $null
83+ )
84+
85+ for ($attempt = 1 ; $attempt -le $maxAttempts ; $attempt ++ )
86+ {
87+ try
88+ {
89+ Write-Host " $Description (attempt $attempt of $maxAttempts )"
90+
91+ $global :LASTEXITCODE = 0
92+ & " $InstallDir /dotnet-install.ps1" - Verbose:$Debug - DryRun:$DryRun @Params
93+ $installSucceeded = $?
94+
95+ if (-not $installSucceeded )
96+ {
97+ throw " dotnet-install.ps1 failed."
98+ }
99+
100+ if ($global :LASTEXITCODE -ne 0 )
101+ {
102+ throw " dotnet-install.ps1 failed with exit code $global :LASTEXITCODE "
103+ }
104+
105+ if ($Verify )
106+ {
107+ & $Verify
108+ }
109+
110+ return
111+ }
112+ catch
113+ {
114+ Write-Warning " Attempt $attempt failed: $_ "
115+
116+ if ($attempt -ge $maxAttempts )
117+ {
118+ throw " $Description failed after $maxAttempts attempts. Last error: $_ "
119+ }
120+
121+ Write-Host " Retrying in $retryDelaySec seconds..."
122+ Start-Sleep - Seconds $retryDelaySec
123+ }
124+ }
125+ }
126+
61127# ------------------------------------------------------------------------------
62128# Emit our command-line arguments.
63129
@@ -80,7 +146,7 @@ if (-not (Test-Path -Path "$InstallDir/dotnet-install.ps1" -PathType Leaf))
80146 }
81147
82148 Write-Host " Downloading dotnet-install.ps1..."
83-
149+
84150 $params =
85151 @ {
86152 Uri = " https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.ps1"
@@ -113,8 +179,6 @@ if ($Debug)
113179# ------------------------------------------------------------------------------
114180# Install the SDK.
115181
116- Write-Host " Installing .NET SDK version: $sdkVersion "
117-
118182$installParams =
119183@ {
120184 Architecture = " arm64"
@@ -128,15 +192,40 @@ if ($Debug)
128192 Write-Host ($installParams | ConvertTo-Json - Depth 1 )
129193}
130194
131- & " $InstallDir /dotnet-install.ps1" - Verbose:$Debug - DryRun:$DryRun @installParams
195+ # Verify the SDK was actually installed. dotnet-install.ps1 can silently fail
196+ # with exit code 0 when the package download is corrupt or the size cannot be
197+ # measured.
198+ $verifySdk =
199+ if (-not $DryRun )
200+ {
201+ {
202+ $dotnetExe = Join-Path $InstallDir " dotnet"
203+ $installedSdks = & $dotnetExe -- list- sdks 2>&1
204+ $installedSdksText = [string ]::Join(" `n " , @ ($installedSdks ))
205+
206+ Write-Host " Installed SDKs:`n $installedSdksText "
207+
208+ $sdkPattern = " (?m)^$ ( [regex ]::Escape($sdkVersion )) \s+\["
209+
210+ if (-not [regex ]::IsMatch($installedSdksText , $sdkPattern ))
211+ {
212+ throw " SDK $sdkVersion is not present after installation."
213+ }
214+
215+ Write-Host " Verified SDK $sdkVersion is installed."
216+ }
217+ }
218+
219+ Invoke-DotNetInstall `
220+ - Description " Installing .NET SDK version: $sdkVersion " `
221+ - Params $installParams `
222+ - Verify $verifySdk
132223
133224# ------------------------------------------------------------------------------
134225# Install the Runtimes, if any.
135226
136227foreach ($channel in $Runtimes )
137228{
138- Write-Host " Installing .NET Runtime GA channel: $channel "
139-
140229 $installParams =
141230 @ {
142231 Architecture = " arm64"
@@ -152,7 +241,36 @@ foreach ($channel in $Runtimes)
152241 Write-Host ($installParams | ConvertTo-Json - Depth 1 )
153242 }
154243
155- & " $InstallDir /dotnet-install.ps1" - Verbose:$Debug - DryRun:$DryRun @installParams
244+ # Verify the runtime was actually installed. Use the same guard against
245+ # silent corruption that we use for the SDK.
246+ $verifyRuntime =
247+ if (-not $DryRun )
248+ {
249+ # Capture $channel in a local variable so the script-block closure
250+ # binds to the current iteration value.
251+ $ch = $channel
252+ {
253+ $dotnetExe = Join-Path $InstallDir " dotnet"
254+ $installedRuntimes = & $dotnetExe -- list- runtimes 2>&1
255+ $installedRuntimesText = [string ]::Join(" `n " , @ ($installedRuntimes ))
256+
257+ Write-Host " Installed runtimes:`n $installedRuntimesText "
258+
259+ $runtimePattern = " Microsoft\.NETCore\.App $ ( [regex ]::Escape($ch )) \."
260+
261+ if (-not [regex ]::IsMatch($installedRuntimesText , $runtimePattern ))
262+ {
263+ throw " Runtime $ch is not present after installation."
264+ }
265+
266+ Write-Host " Verified runtime $ch is installed."
267+ }
268+ }
269+
270+ Invoke-DotNetInstall `
271+ - Description " Installing .NET Runtime GA channel: $channel " `
272+ - Params $installParams `
273+ - Verify $verifyRuntime
156274}
157275
158276# ------------------------------------------------------------------------------
0 commit comments