2020-06-13 01:46:25 +08:00
#
# Copyright (c) .NET Foundation and contributors. All rights reserved.
# Licensed under the MIT license. See LICENSE file in the project root for full license information.
#
<#
. SYNOPSIS
Installs dotnet cli
. DESCRIPTION
Installs dotnet cli . If dotnet installation already exists in the given directory
it will update it only if the requested version differs from the one already installed .
. PARAMETER Channel
Default : LTS
Download from the Channel specified . Possible values :
2022-11-16 00:00:14 +08:00
- STS - the most recent Standard Term Support release
- LTS - the most recent Long Term Support release
2020-06-13 01:46:25 +08:00
- 2 -part version in a format A . B - represents a specific release
examples : 2.0 , 1.0
2021-06-08 17:27:57 +08:00
- 3 -part version in a format A . B . Cxx - represents a specific SDK release
examples : 5.0 . 1xx , 5.0 . 2xx
Supported since 5.0 release
2022-11-16 00:00:14 +08:00
Warning : Value " Current " is deprecated for the Channel parameter . Use " STS " instead .
2021-06-08 17:27:57 +08:00
Note : The version parameter overrides the channel parameter when any version other than 'latest' is used .
. PARAMETER Quality
Download the latest build of specified quality in the channel . The possible values are : daily , signed , validated , preview , GA .
2022-11-16 00:00:14 +08:00
Works only in combination with channel . Not applicable for STS and LTS channels and will be ignored if those channels are used .
2021-06-08 17:27:57 +08:00
For SDK use channel in A . B . Cxx format : using quality together with channel in A . B format is not supported .
Supported since 5.0 release .
Note : The version parameter overrides the channel parameter when any version other than 'latest' is used , and therefore overrides the quality .
2020-06-13 01:46:25 +08:00
. PARAMETER Version
Default : latest
Represents a build version on specific channel . Possible values :
2022-07-29 07:22:35 +08:00
- latest - the latest build on specific channel
2020-06-13 01:46:25 +08:00
- 3 -part version in a format A . B . C - represents specific version of build
examples : 2.0 . 0 -preview2 - 006120 , 1.1 . 0
2021-06-08 17:27:57 +08:00
. PARAMETER Internal
Download internal builds . Requires providing credentials via -FeedCredential parameter .
. PARAMETER FeedCredential
Token to access Azure feed . Used as a query string to append to the Azure feed .
This parameter typically is not specified .
2020-06-13 01:46:25 +08:00
. PARAMETER InstallDir
Default : % LocalAppData % \ Microsoft \ dotnet
Path to where to install dotnet . Note that binaries will be placed directly in a given directory .
. PARAMETER Architecture
Default : < auto > - this value represents currently running OS architecture
Architecture of dotnet binaries to be installed .
Possible values are : < auto > , amd64 , x64 , x86 , arm64 , arm
. PARAMETER SharedRuntime
This parameter is obsolete and may be removed in a future version of this script .
The recommended alternative is '-Runtime dotnet' .
Installs just the shared runtime bits , not the entire SDK .
. PARAMETER Runtime
Installs just a shared runtime , not the entire SDK .
Possible values :
- dotnet - the Microsoft . NETCore . App shared runtime
- aspnetcore - the Microsoft . AspNetCore . App shared runtime
- windowsdesktop - the Microsoft . WindowsDesktop . App shared runtime
. PARAMETER DryRun
If set it will not perform installation but instead display what command line to use to consistently install
currently requested version of dotnet cli . In example if you specify version 'latest' it will display a link
with specific version so that this command can be used deterministicly in a build script .
It also displays binaries location if you prefer to install or download it yourself .
. PARAMETER NoPath
By default this script will set environment variable PATH for the current process to the binaries folder inside installation folder .
If set it will display binaries location but not set any environment variable .
. PARAMETER Verbose
Displays diagnostics information .
. PARAMETER AzureFeed
Default : https : / / dotnetcli . azureedge . net / dotnet
2022-02-20 06:07:00 +08:00
For internal use only .
Allows using a different storage to download SDK archives from .
This parameter is only used if $NoCdn is false .
2020-06-13 01:46:25 +08:00
. PARAMETER UncachedFeed
2022-02-20 06:07:00 +08:00
For internal use only .
Allows using a different storage to download SDK archives from .
This parameter is only used if $NoCdn is true .
2020-06-13 01:46:25 +08:00
. PARAMETER ProxyAddress
If set , the installer will use the proxy when making web requests
. PARAMETER ProxyUseDefaultCredentials
Default : false
Use default credentials , when using proxy address .
2020-08-29 03:01:15 +08:00
. PARAMETER ProxyBypassList
If set with ProxyAddress , will provide the list of comma separated urls that will bypass the proxy
2020-06-13 01:46:25 +08:00
. PARAMETER SkipNonVersionedFiles
Default : false
Skips installing non-versioned files if they already exist , such as dotnet . exe .
. PARAMETER NoCdn
Disable downloading from the Azure CDN , and use the uncached feed directly .
. PARAMETER JSonFile
Determines the SDK version from a user specified global . json file
Note : global . json must have a value for 'SDK:Version'
2021-08-26 17:44:10 +08:00
. PARAMETER DownloadTimeout
Determines timeout duration in seconds for dowloading of the SDK file
Default : 1200 seconds ( 20 minutes )
2020-06-13 01:46:25 +08:00
#>
[ cmdletbinding ( ) ]
param (
[ string ] $Channel = " LTS " ,
2021-06-08 17:27:57 +08:00
[ string ] $Quality ,
2020-06-13 01:46:25 +08:00
[ string ] $Version = " Latest " ,
2021-06-08 17:27:57 +08:00
[ switch ] $Internal ,
2020-06-13 01:46:25 +08:00
[ string ] $JSonFile ,
2021-08-26 17:44:10 +08:00
[ Alias ( 'i' ) ] [ string ] $InstallDir = " <auto> " ,
2020-06-13 01:46:25 +08:00
[ string ] $Architecture = " <auto> " ,
[ string ] $Runtime ,
[ Obsolete ( " This parameter may be removed in a future version of this script. The recommended alternative is '-Runtime dotnet'. " ) ]
[ switch ] $SharedRuntime ,
[ switch ] $DryRun ,
[ switch ] $NoPath ,
2022-02-20 06:07:00 +08:00
[ string ] $AzureFeed ,
[ string ] $UncachedFeed ,
2020-06-13 01:46:25 +08:00
[ string ] $FeedCredential ,
[ string ] $ProxyAddress ,
[ switch ] $ProxyUseDefaultCredentials ,
2020-08-29 03:01:15 +08:00
[ string[] ] $ProxyBypassList = @ ( ) ,
2020-06-13 01:46:25 +08:00
[ switch ] $SkipNonVersionedFiles ,
2021-08-26 17:44:10 +08:00
[ switch ] $NoCdn ,
[ int ] $DownloadTimeout = 1200
2020-06-13 01:46:25 +08:00
)
Set-StrictMode -Version Latest
$ErrorActionPreference = " Stop "
$ProgressPreference = " SilentlyContinue "
function Say($str ) {
2021-02-02 16:40:17 +08:00
try {
2020-08-29 03:01:15 +08:00
Write-Host " dotnet-install: $str "
}
2021-02-02 16:40:17 +08:00
catch {
2020-08-29 03:01:15 +08:00
# Some platforms cannot utilize Write-Host (Azure Functions, for instance). Fall back to Write-Output
Write-Output " dotnet-install: $str "
}
2020-06-13 01:46:25 +08:00
}
2021-02-02 16:40:17 +08:00
function Say-Warning($str ) {
try {
Write-Warning " dotnet-install: $str "
}
catch {
# Some platforms cannot utilize Write-Warning (Azure Functions, for instance). Fall back to Write-Output
Write-Output " dotnet-install: Warning: $str "
}
}
# Writes a line with error style settings.
# Use this function to show a human-readable comment along with an exception.
function Say-Error($str ) {
try {
# Write-Error is quite oververbose for the purpose of the function, let's write one line with error style settings.
$Host . UI . WriteErrorLine ( " dotnet-install: $str " )
}
catch {
Write-Output " dotnet-install: Error: $str "
}
}
2020-06-13 01:46:25 +08:00
function Say-Verbose($str ) {
2021-02-02 16:40:17 +08:00
try {
2020-08-29 03:01:15 +08:00
Write-Verbose " dotnet-install: $str "
}
2021-02-02 16:40:17 +08:00
catch {
2020-08-29 03:01:15 +08:00
# Some platforms cannot utilize Write-Verbose (Azure Functions, for instance). Fall back to Write-Output
Write-Output " dotnet-install: $str "
}
2020-06-13 01:46:25 +08:00
}
function Say-Invocation($Invocation ) {
$command = $Invocation . MyCommand ;
$args = ( ( $Invocation . BoundParameters . Keys | foreach { " - $_ `" $( $Invocation . BoundParameters [ $_ ] ) `" " } ) -join " " )
Say-Verbose " $command $args "
}
2021-08-26 17:44:10 +08:00
function Invoke-With-Retry([ScriptBlock]$ScriptBlock , [ System.Threading.CancellationToken ] $cancellationToken = [ System.Threading.CancellationToken ] :: None , [ int ] $MaxAttempts = 3 , [ int ] $SecondsBetweenAttempts = 1 ) {
2020-06-13 01:46:25 +08:00
$Attempts = 0
2022-07-29 07:22:35 +08:00
$local : startTime = $ ( get-date )
2020-06-13 01:46:25 +08:00
while ( $true ) {
try {
2021-02-02 16:40:17 +08:00
return & $ScriptBlock
2020-06-13 01:46:25 +08:00
}
catch {
$Attempts + +
2021-08-26 17:44:10 +08:00
if ( ( $Attempts -lt $MaxAttempts ) -and -not $cancellationToken . IsCancellationRequested ) {
2020-06-13 01:46:25 +08:00
Start-Sleep $SecondsBetweenAttempts
}
else {
2022-07-29 07:22:35 +08:00
$local : elapsedTime = $ ( get-date ) - $local : startTime
if ( ( $local : elapsedTime . TotalSeconds - $DownloadTimeout ) -gt 0 -and -not $cancellationToken . IsCancellationRequested ) {
throw New-Object System . TimeoutException ( " Failed to reach the server: connection timeout: default timeout is $DownloadTimeout second(s) " ) ;
}
throw ;
2020-06-13 01:46:25 +08:00
}
}
}
}
function Get-Machine-Architecture ( ) {
Say-Invocation $MyInvocation
2020-07-16 01:32:15 +08:00
# On PS x86, PROCESSOR_ARCHITECTURE reports x86 even on x64 systems.
# To get the correct architecture, we need to use PROCESSOR_ARCHITEW6432.
# PS x64 doesn't define this, so we fall back to PROCESSOR_ARCHITECTURE.
# Possible values: amd64, x64, x86, arm64, arm
2022-02-20 06:07:00 +08:00
if ( $ENV:PROCESSOR_ARCHITEW6432 -ne $null ) {
2020-07-16 01:32:15 +08:00
return $ENV:PROCESSOR_ARCHITEW6432
}
2022-08-31 23:40:05 +08:00
try {
if ( ( ( Get-CimInstance -ClassName CIM_OperatingSystem ) . OSArchitecture ) -like " ARM* " ) {
if ( [ Environment ] :: Is64BitOperatingSystem )
{
return " arm64 "
}
return " arm "
}
}
catch {
# Machine doesn't support Get-CimInstance
2022-07-29 07:22:35 +08:00
}
2020-06-13 01:46:25 +08:00
return $ENV:PROCESSOR_ARCHITECTURE
}
function Get-CLIArchitecture-From-Architecture([string]$Architecture ) {
Say-Invocation $MyInvocation
2022-02-20 06:07:00 +08:00
if ( $Architecture -eq " <auto> " ) {
$Architecture = Get-Machine -Architecture
}
2021-06-08 17:27:57 +08:00
switch ( $Architecture . ToLowerInvariant ( ) ) {
2020-06-13 01:46:25 +08:00
{ ( $_ -eq " amd64 " ) -or ( $_ -eq " x64 " ) } { return " x64 " }
{ $_ -eq " x86 " } { return " x86 " }
{ $_ -eq " arm " } { return " arm " }
{ $_ -eq " arm64 " } { return " arm64 " }
2021-02-02 16:40:17 +08:00
default { throw " Architecture ' $Architecture ' not supported. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues " }
2020-06-13 01:46:25 +08:00
}
}
2022-02-20 06:07:00 +08:00
function ValidateFeedCredential([string ] $FeedCredential )
{
if ( $Internal -and [ string ] :: IsNullOrWhitespace ( $FeedCredential ) ) {
$message = " Provide credentials via -FeedCredential parameter. "
if ( $DryRun ) {
Say-Warning " $message "
} else {
throw " $message "
}
}
#FeedCredential should start with "?", for it to be added to the end of the link.
#adding "?" at the beginning of the FeedCredential if needed.
if ( ( ! [ string ] :: IsNullOrWhitespace ( $FeedCredential ) ) -and ( $FeedCredential [ 0 ] -ne '?' ) ) {
$FeedCredential = " ? " + $FeedCredential
}
2021-06-08 17:27:57 +08:00
2022-02-20 06:07:00 +08:00
return $FeedCredential
}
2021-06-08 17:27:57 +08:00
function Get-NormalizedQuality([string]$Quality ) {
Say-Invocation $MyInvocation
if ( [ string ] :: IsNullOrEmpty ( $Quality ) ) {
return " "
}
switch ( $Quality ) {
{ @ ( " daily " , " signed " , " validated " , " preview " ) -contains $_ } { return $Quality . ToLowerInvariant ( ) }
#ga quality is available without specifying quality, so normalizing it to empty
{ $_ -eq " ga " } { return " " }
default { throw " ' $Quality ' is not a supported value for -Quality option. Supported values are: daily, signed, validated, preview, ga. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues. " }
}
}
function Get-NormalizedChannel([string]$Channel ) {
Say-Invocation $MyInvocation
if ( [ string ] :: IsNullOrEmpty ( $Channel ) ) {
return " "
}
2022-11-16 00:00:14 +08:00
if ( $Channel . Contains ( " Current " ) ) {
Say-Warning 'Value "Current" is deprecated for -Channel option. Use "STS" instead.'
}
2021-06-08 17:27:57 +08:00
if ( $Channel . StartsWith ( 'release/' ) ) {
Say-Warning 'Using branch name with -Channel option is no longer supported with newer releases. Use -Quality option with a channel in X.Y format instead, such as "-Channel 5.0 -Quality Daily."'
}
switch ( $Channel ) {
{ $_ -eq " lts " } { return " LTS " }
2022-11-16 00:00:14 +08:00
{ $_ -eq " sts " } { return " STS " }
{ $_ -eq " current " } { return " STS " }
2021-06-08 17:27:57 +08:00
default { return $Channel . ToLowerInvariant ( ) }
}
}
function Get-NormalizedProduct([string]$Runtime ) {
Say-Invocation $MyInvocation
switch ( $Runtime ) {
{ $_ -eq " dotnet " } { return " dotnet-runtime " }
{ $_ -eq " aspnetcore " } { return " aspnetcore-runtime " }
{ $_ -eq " windowsdesktop " } { return " windowsdesktop-runtime " }
{ [ string ] :: IsNullOrEmpty ( $_ ) } { return " dotnet-sdk " }
default { throw " ' $Runtime ' is not a supported value for -Runtime option, supported values are: dotnet, aspnetcore, windowsdesktop. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues. " }
}
}
2020-06-13 01:46:25 +08:00
# The version text returned from the feeds is a 1-line or 2-line string:
# For the SDK and the dotnet runtime (2 lines):
# Line 1: # commit_hash
# Line 2: # 4-part version
# For the aspnetcore runtime (1 line):
# Line 1: # 4-part version
2022-02-20 06:07:00 +08:00
function Get-Version-From-LatestVersion-File-Content([string]$VersionText ) {
2020-06-13 01:46:25 +08:00
Say-Invocation $MyInvocation
$Data = -split $VersionText
$VersionInfo = @ {
CommitHash = $ ( if ( $Data . Count -gt 1 ) { $Data [ 0 ] } )
Version = $Data [ -1 ] # last line is always the version number.
}
return $VersionInfo
}
function Load-Assembly([string ] $Assembly ) {
try {
Add-Type -Assembly $Assembly | Out-Null
}
catch {
# On Nano Server, Powershell Core Edition is used. Add-Type is unable to resolve base class assemblies because they are not GAC'd.
# Loading the base class assemblies is not unnecessary as the types will automatically get resolved.
}
}
2021-06-08 17:27:57 +08:00
function GetHTTPResponse([Uri ] $Uri , [ bool ] $HeaderOnly , [ bool ] $DisableRedirect , [ bool ] $DisableFeedCredential )
2020-06-13 01:46:25 +08:00
{
2021-08-26 17:44:10 +08:00
$cts = New-Object System . Threading . CancellationTokenSource
$downloadScript = {
2020-06-13 01:46:25 +08:00
$HttpClient = $null
try {
# HttpClient is used vs Invoke-WebRequest in order to support Nano Server which doesn't support the Invoke-WebRequest cmdlet.
Load-Assembly -Assembly System . Net . Http
if ( -not $ProxyAddress ) {
try {
# Despite no proxy being explicitly specified, we may still be behind a default proxy
$DefaultProxy = [ System.Net.WebRequest ] :: DefaultWebProxy ;
if ( $DefaultProxy -and ( -not $DefaultProxy . IsBypassed ( $Uri ) ) ) {
2022-02-20 06:07:00 +08:00
if ( $null -ne $DefaultProxy . GetProxy ( $Uri ) ) {
$ProxyAddress = $DefaultProxy . GetProxy ( $Uri ) . OriginalString
} else {
$ProxyAddress = $null
}
2020-06-13 01:46:25 +08:00
$ProxyUseDefaultCredentials = $true
}
} catch {
# Eat the exception and move forward as the above code is an attempt
# at resolving the DefaultProxy that may not have been a problem.
$ProxyAddress = $null
Say-Verbose ( " Exception ignored: $_ .Exception.Message - moving forward... " )
}
}
2021-06-08 17:27:57 +08:00
$HttpClientHandler = New-Object System . Net . Http . HttpClientHandler
2020-06-13 01:46:25 +08:00
if ( $ProxyAddress ) {
2020-08-29 03:01:15 +08:00
$HttpClientHandler . Proxy = New-Object System . Net . WebProxy -Property @ {
Address = $ProxyAddress ;
UseDefaultCredentials = $ProxyUseDefaultCredentials ;
BypassList = $ProxyBypassList ;
}
2021-06-08 17:27:57 +08:00
}
if ( $DisableRedirect )
{
$HttpClientHandler . AllowAutoRedirect = $false
2020-06-13 01:46:25 +08:00
}
2021-06-08 17:27:57 +08:00
$HttpClient = New-Object System . Net . Http . HttpClient -ArgumentList $HttpClientHandler
2020-06-13 01:46:25 +08:00
# Default timeout for HttpClient is 100s. For a 50 MB download this assumes 500 KB/s average, any less will time out
2021-08-26 17:44:10 +08:00
# Defaulting to 20 minutes allows it to work over much slower connections.
$HttpClient . Timeout = New-TimeSpan -Seconds $DownloadTimeout
2021-06-08 17:27:57 +08:00
if ( $HeaderOnly ) {
$completionOption = [ System.Net.Http.HttpCompletionOption ] :: ResponseHeadersRead
}
else {
$completionOption = [ System.Net.Http.HttpCompletionOption ] :: ResponseContentRead
}
if ( $DisableFeedCredential ) {
$UriWithCredential = $Uri
}
else {
$UriWithCredential = " ${Uri} ${FeedCredential} "
}
$Task = $HttpClient . GetAsync ( " $UriWithCredential " , $completionOption ) . ConfigureAwait ( " false " ) ;
2021-02-02 16:40:17 +08:00
$Response = $Task . GetAwaiter ( ) . GetResult ( ) ;
2021-06-08 17:27:57 +08:00
if ( ( $null -eq $Response ) -or ( ( -not $HeaderOnly ) -and ( -not ( $Response . IsSuccessStatusCode ) ) ) ) {
2021-02-02 16:40:17 +08:00
# The feed credential is potentially sensitive info. Do not log FeedCredential to console output.
$DownloadException = [ System.Exception ] " Unable to download $Uri . "
if ( $null -ne $Response ) {
$DownloadException . Data [ " StatusCode " ] = [ int ] $Response . StatusCode
$DownloadException . Data [ " ErrorMessage " ] = " Unable to download $Uri . Returned HTTP status code: " + $DownloadException . Data [ " StatusCode " ]
2021-08-26 17:44:10 +08:00
if ( 404 -eq [ int ] $Response . StatusCode )
{
$cts . Cancel ( )
}
2020-06-13 01:46:25 +08:00
}
2021-02-02 16:40:17 +08:00
throw $DownloadException
2020-06-13 01:46:25 +08:00
}
2021-02-02 16:40:17 +08:00
return $Response
}
catch [ System.Net.Http.HttpRequestException ] {
$DownloadException = [ System.Exception ] " Unable to download $Uri . "
# Pick up the exception message and inner exceptions' messages if they exist
$CurrentException = $PSItem . Exception
$ErrorMsg = $CurrentException . Message + " `r `n "
while ( $CurrentException . InnerException ) {
$CurrentException = $CurrentException . InnerException
$ErrorMsg + = $CurrentException . Message + " `r `n "
}
# Check if there is an issue concerning TLS.
if ( $ErrorMsg -like " *SSL/TLS* " ) {
$ErrorMsg + = " Ensure that TLS 1.2 or higher is enabled to use this script. `r `n "
}
$DownloadException . Data [ " ErrorMessage " ] = $ErrorMsg
throw $DownloadException
2020-06-13 01:46:25 +08:00
}
finally {
2021-08-26 17:44:10 +08:00
if ( $null -ne $HttpClient ) {
2020-06-13 01:46:25 +08:00
$HttpClient . Dispose ( )
}
}
2021-08-26 17:44:10 +08:00
}
try {
return Invoke-With -Retry $downloadScript $cts . Token
}
finally
{
if ( $null -ne $cts )
{
$cts . Dispose ( )
}
}
2020-06-13 01:46:25 +08:00
}
2022-02-20 06:07:00 +08:00
function Get-Version-From-LatestVersion-File([string]$AzureFeed , [ string ] $Channel ) {
2020-06-13 01:46:25 +08:00
Say-Invocation $MyInvocation
$VersionFileUrl = $null
if ( $Runtime -eq " dotnet " ) {
2022-02-20 06:07:00 +08:00
$VersionFileUrl = " $AzureFeed /Runtime/ $Channel /latest.version "
2020-06-13 01:46:25 +08:00
}
elseif ( $Runtime -eq " aspnetcore " ) {
2022-02-20 06:07:00 +08:00
$VersionFileUrl = " $AzureFeed /aspnetcore/Runtime/ $Channel /latest.version "
2020-06-13 01:46:25 +08:00
}
elseif ( $Runtime -eq " windowsdesktop " ) {
2022-02-20 06:07:00 +08:00
$VersionFileUrl = " $AzureFeed /WindowsDesktop/ $Channel /latest.version "
2020-06-13 01:46:25 +08:00
}
elseif ( -not $Runtime ) {
2022-02-20 06:07:00 +08:00
$VersionFileUrl = " $AzureFeed /Sdk/ $Channel /latest.version "
2020-06-13 01:46:25 +08:00
}
else {
throw " Invalid value for `$ Runtime "
}
2021-06-08 17:27:57 +08:00
Say-Verbose " Constructed latest.version URL: $VersionFileUrl "
2020-06-13 01:46:25 +08:00
try {
$Response = GetHTTPResponse -Uri $VersionFileUrl
}
catch {
2022-02-20 06:07:00 +08:00
Say-Verbose " Failed to download latest.version file. "
2021-02-02 16:40:17 +08:00
throw
2020-06-13 01:46:25 +08:00
}
$StringContent = $Response . Content . ReadAsStringAsync ( ) . Result
switch ( $Response . Content . Headers . ContentType ) {
{ ( $_ -eq " application/octet-stream " ) } { $VersionText = $StringContent }
{ ( $_ -eq " text/plain " ) } { $VersionText = $StringContent }
{ ( $_ -eq " text/plain; charset=UTF-8 " ) } { $VersionText = $StringContent }
default { throw " `` $Response .Content.Headers.ContentType `` is an unknown .version file content type. " }
}
2022-02-20 06:07:00 +08:00
$VersionInfo = Get-Version -From -LatestVersion -File -Content $VersionText
2020-06-13 01:46:25 +08:00
return $VersionInfo
}
function Parse-Jsonfile-For-Version([string]$JSonFile ) {
Say-Invocation $MyInvocation
If ( -Not ( Test-Path $JSonFile ) ) {
throw " Unable to find ' $JSonFile ' "
}
try {
$JSonContent = Get-Content ( $JSonFile ) -Raw | ConvertFrom-Json | Select-Object -expand " sdk " -ErrorAction SilentlyContinue
}
catch {
2021-02-02 16:40:17 +08:00
Say-Error " Json file unreadable: ' $JSonFile ' "
throw
2020-06-13 01:46:25 +08:00
}
if ( $JSonContent ) {
try {
$JSonContent . PSObject . Properties | ForEach-Object {
$PropertyName = $_ . Name
if ( $PropertyName -eq " version " ) {
$Version = $_ . Value
Say-Verbose " Version = $Version "
}
}
}
catch {
2021-02-02 16:40:17 +08:00
Say-Error " Unable to parse the SDK node in ' $JSonFile ' "
throw
2020-06-13 01:46:25 +08:00
}
}
else {
throw " Unable to find the SDK node in ' $JSonFile ' "
}
If ( $Version -eq $null ) {
throw " Unable to find the SDK:version node in ' $JSonFile ' "
}
return $Version
}
function Get-Specific-Version-From-Version([string]$AzureFeed , [ string ] $Channel , [ string ] $Version , [ string ] $JSonFile ) {
Say-Invocation $MyInvocation
if ( -not $JSonFile ) {
2021-06-08 17:27:57 +08:00
if ( $Version . ToLowerInvariant ( ) -eq " latest " ) {
2022-02-20 06:07:00 +08:00
$LatestVersionInfo = Get-Version -From -LatestVersion -File -AzureFeed $AzureFeed -Channel $Channel
2021-02-02 16:40:17 +08:00
return $LatestVersionInfo . Version
}
else {
return $Version
2020-06-13 01:46:25 +08:00
}
}
else {
return Parse-Jsonfile -For -Version $JSonFile
}
}
function Get-Download-Link([string]$AzureFeed , [ string ] $SpecificVersion , [ string ] $CLIArchitecture ) {
Say-Invocation $MyInvocation
2021-02-02 16:40:17 +08:00
# If anything fails in this lookup it will default to $SpecificVersion
$SpecificProductVersion = Get-Product -Version -AzureFeed $AzureFeed -SpecificVersion $SpecificVersion
2020-06-13 01:46:25 +08:00
if ( $Runtime -eq " dotnet " ) {
2021-02-02 16:40:17 +08:00
$PayloadURL = " $AzureFeed /Runtime/ $SpecificVersion /dotnet-runtime- $SpecificProductVersion -win- $CLIArchitecture .zip "
2020-06-13 01:46:25 +08:00
}
elseif ( $Runtime -eq " aspnetcore " ) {
2021-02-02 16:40:17 +08:00
$PayloadURL = " $AzureFeed /aspnetcore/Runtime/ $SpecificVersion /aspnetcore-runtime- $SpecificProductVersion -win- $CLIArchitecture .zip "
2020-06-13 01:46:25 +08:00
}
elseif ( $Runtime -eq " windowsdesktop " ) {
2021-02-05 16:21:42 +08:00
# The windows desktop runtime is part of the core runtime layout prior to 5.0
2021-02-02 16:40:17 +08:00
$PayloadURL = " $AzureFeed /Runtime/ $SpecificVersion /windowsdesktop-runtime- $SpecificProductVersion -win- $CLIArchitecture .zip "
2021-02-05 16:21:42 +08:00
if ( $SpecificVersion -match '^(\d+)\.(.*)$' )
{
$majorVersion = [ int ] $Matches [ 1 ]
if ( $majorVersion -ge 5 )
{
$PayloadURL = " $AzureFeed /WindowsDesktop/ $SpecificVersion /windowsdesktop-runtime- $SpecificProductVersion -win- $CLIArchitecture .zip "
}
}
2020-06-13 01:46:25 +08:00
}
elseif ( -not $Runtime ) {
2021-02-02 16:40:17 +08:00
$PayloadURL = " $AzureFeed /Sdk/ $SpecificVersion /dotnet-sdk- $SpecificProductVersion -win- $CLIArchitecture .zip "
2020-06-13 01:46:25 +08:00
}
else {
throw " Invalid value for `$ Runtime "
}
Say-Verbose " Constructed primary named payload URL: $PayloadURL "
2021-02-02 16:40:17 +08:00
return $PayloadURL , $SpecificProductVersion
2020-06-13 01:46:25 +08:00
}
function Get-LegacyDownload-Link([string]$AzureFeed , [ string ] $SpecificVersion , [ string ] $CLIArchitecture ) {
Say-Invocation $MyInvocation
if ( -not $Runtime ) {
$PayloadURL = " $AzureFeed /Sdk/ $SpecificVersion /dotnet-dev-win- $CLIArchitecture . $SpecificVersion .zip "
}
elseif ( $Runtime -eq " dotnet " ) {
$PayloadURL = " $AzureFeed /Runtime/ $SpecificVersion /dotnet-win- $CLIArchitecture . $SpecificVersion .zip "
}
else {
return $null
}
Say-Verbose " Constructed legacy named payload URL: $PayloadURL "
return $PayloadURL
}
2021-06-08 17:27:57 +08:00
function Get-Product-Version([string]$AzureFeed , [ string ] $SpecificVersion , [ string ] $PackageDownloadLink ) {
2021-02-02 16:40:17 +08:00
Say-Invocation $MyInvocation
2021-06-08 17:27:57 +08:00
# Try to get the version number, using the productVersion.txt file located next to the installer file.
$ProductVersionTxtURLs = ( Get-Product -Version -Url $AzureFeed $SpecificVersion $PackageDownloadLink -Flattened $true ) ,
( Get-Product -Version -Url $AzureFeed $SpecificVersion $PackageDownloadLink -Flattened $false )
Foreach ( $ProductVersionTxtURL in $ProductVersionTxtURLs ) {
Say-Verbose " Checking for the existence of $ProductVersionTxtURL "
try {
$productVersionResponse = GetHTTPResponse ( $productVersionTxtUrl )
if ( $productVersionResponse . StatusCode -eq 200 ) {
$productVersion = $productVersionResponse . Content . ReadAsStringAsync ( ) . Result . Trim ( )
if ( $productVersion -ne $SpecificVersion )
{
Say " Using alternate version $productVersion found in $ProductVersionTxtURL "
}
return $productVersion
}
else {
Say-Verbose " Got StatusCode $( $productVersionResponse . StatusCode ) when trying to get productVersion.txt at $productVersionTxtUrl . "
2021-02-05 16:21:42 +08:00
}
2021-06-08 17:27:57 +08:00
}
catch {
Say-Verbose " Could not read productVersion.txt at $productVersionTxtUrl (Exception: ' $( $_ . Exception . Message ) '. ) "
2021-02-05 16:21:42 +08:00
}
2021-02-02 16:40:17 +08:00
}
2021-06-08 17:27:57 +08:00
# Getting the version number with productVersion.txt has failed. Try parsing the download link for a version number.
if ( [ string ] :: IsNullOrEmpty ( $PackageDownloadLink ) )
{
Say-Verbose " Using the default value ' $SpecificVersion ' as the product version. "
return $SpecificVersion
2021-02-02 16:40:17 +08:00
}
2021-06-08 17:27:57 +08:00
$productVersion = Get-ProductVersionFromDownloadLink $PackageDownloadLink $SpecificVersion
return $productVersion
}
2021-02-02 16:40:17 +08:00
2021-06-08 17:27:57 +08:00
function Get-Product-Version-Url([string]$AzureFeed , [ string ] $SpecificVersion , [ string ] $PackageDownloadLink , [ bool ] $Flattened ) {
Say-Invocation $MyInvocation
2021-02-02 16:40:17 +08:00
2021-06-08 17:27:57 +08:00
$majorVersion = $null
if ( $SpecificVersion -match '^(\d+)\.(.*)' ) {
$majorVersion = $Matches [ 1 ] -as [ int ]
}
2021-02-02 16:40:17 +08:00
2021-06-08 17:27:57 +08:00
$pvFileName = 'productVersion.txt'
if ( $Flattened ) {
if ( -not $Runtime ) {
$pvFileName = 'sdk-productVersion.txt'
}
elseif ( $Runtime -eq " dotnet " ) {
$pvFileName = 'runtime-productVersion.txt'
2021-02-02 16:40:17 +08:00
}
else {
2021-06-08 17:27:57 +08:00
$pvFileName = " $Runtime -productVersion.txt "
2021-02-02 16:40:17 +08:00
}
}
2021-06-08 17:27:57 +08:00
if ( [ string ] :: IsNullOrEmpty ( $PackageDownloadLink ) ) {
if ( $Runtime -eq " dotnet " ) {
$ProductVersionTxtURL = " $AzureFeed /Runtime/ $SpecificVersion / $pvFileName "
}
elseif ( $Runtime -eq " aspnetcore " ) {
$ProductVersionTxtURL = " $AzureFeed /aspnetcore/Runtime/ $SpecificVersion / $pvFileName "
}
elseif ( $Runtime -eq " windowsdesktop " ) {
# The windows desktop runtime is part of the core runtime layout prior to 5.0
$ProductVersionTxtURL = " $AzureFeed /Runtime/ $SpecificVersion / $pvFileName "
if ( $majorVersion -ne $null -and $majorVersion -ge 5 ) {
$ProductVersionTxtURL = " $AzureFeed /WindowsDesktop/ $SpecificVersion / $pvFileName "
}
}
elseif ( -not $Runtime ) {
$ProductVersionTxtURL = " $AzureFeed /Sdk/ $SpecificVersion / $pvFileName "
}
else {
throw " Invalid value ' $Runtime ' specified for `$ Runtime "
}
}
else {
$ProductVersionTxtURL = $PackageDownloadLink . Substring ( 0 , $PackageDownloadLink . LastIndexOf ( " / " ) ) + " / $pvFileName "
}
Say-Verbose " Constructed productVersion link: $ProductVersionTxtURL "
return $ProductVersionTxtURL
}
function Get-ProductVersionFromDownloadLink([string]$PackageDownloadLink , [ string ] $SpecificVersion )
{
Say-Invocation $MyInvocation
#product specific version follows the product name
#for filename 'dotnet-sdk-3.1.404-win-x64.zip': the product version is 3.1.400
$filename = $PackageDownloadLink . Substring ( $PackageDownloadLink . LastIndexOf ( " / " ) + 1 )
$filenameParts = $filename . Split ( '-' )
if ( $filenameParts . Length -gt 2 )
{
$productVersion = $filenameParts [ 2 ]
Say-Verbose " Extracted product version ' $productVersion ' from download link ' $PackageDownloadLink '. "
}
else {
Say-Verbose " Using the default value ' $SpecificVersion ' as the product version. "
$productVersion = $SpecificVersion
}
return $productVersion
2021-02-02 16:40:17 +08:00
}
2020-06-13 01:46:25 +08:00
function Get-User-Share-Path ( ) {
Say-Invocation $MyInvocation
$InstallRoot = $env:DOTNET_INSTALL_DIR
if ( ! $InstallRoot ) {
$InstallRoot = " $env:LocalAppData \Microsoft\dotnet "
}
return $InstallRoot
}
function Resolve-Installation-Path([string]$InstallDir ) {
Say-Invocation $MyInvocation
if ( $InstallDir -eq " <auto> " ) {
return Get-User -Share -Path
}
return $InstallDir
}
function Is-Dotnet-Package-Installed([string]$InstallRoot , [ string ] $RelativePathToPackage , [ string ] $SpecificVersion ) {
Say-Invocation $MyInvocation
$DotnetPackagePath = Join-Path -Path $InstallRoot -ChildPath $RelativePathToPackage | Join-Path -ChildPath $SpecificVersion
Say-Verbose " Is-Dotnet-Package-Installed: DotnetPackagePath= $DotnetPackagePath "
return Test-Path $DotnetPackagePath -PathType Container
}
function Get-Absolute-Path([string]$RelativeOrAbsolutePath ) {
# Too much spam
# Say-Invocation $MyInvocation
return $ExecutionContext . SessionState . Path . GetUnresolvedProviderPathFromPSPath ( $RelativeOrAbsolutePath )
}
function Get-Path-Prefix-With-Version($path ) {
2022-02-20 06:07:00 +08:00
# example path with regex: shared/1.0.0-beta-12345/somepath
$match = [ regex ] :: match ( $path , " /\d+\.\d+[^/]+/ " )
2020-06-13 01:46:25 +08:00
if ( $match . Success ) {
return $entry . FullName . Substring ( 0 , $match . Index + $match . Length )
}
return $null
}
function Get-List-Of-Directories-And-Versions-To-Unpack-From-Dotnet-Package([System.IO.Compression.ZipArchive]$Zip , [ string ] $OutPath ) {
Say-Invocation $MyInvocation
$ret = @ ( )
foreach ( $entry in $Zip . Entries ) {
$dir = Get-Path -Prefix -With -Version $entry . FullName
2022-02-20 06:07:00 +08:00
if ( $null -ne $dir ) {
2020-06-13 01:46:25 +08:00
$path = Get-Absolute -Path $ ( Join-Path -Path $OutPath -ChildPath $dir )
if ( -Not ( Test-Path $path -PathType Container ) ) {
$ret + = $dir
}
}
}
$ret = $ret | Sort-Object | Get-Unique
$values = ( $ret | foreach { " $_ " } ) -join " ; "
Say-Verbose " Directories to unpack: $values "
return $ret
}
# Example zip content and extraction algorithm:
# Rule: files if extracted are always being extracted to the same relative path locally
# .\
# a.exe # file does not exist locally, extract
# b.dll # file exists locally, override only if $OverrideFiles set
# aaa\ # same rules as for files
# ...
# abc\1.0.0\ # directory contains version and exists locally
# ... # do not extract content under versioned part
# abc\asd\ # same rules as for files
# ...
# def\ghi\1.0.1\ # directory contains version and does not exist locally
# ... # extract content
function Extract-Dotnet-Package([string]$ZipPath , [ string ] $OutPath ) {
Say-Invocation $MyInvocation
Load-Assembly -Assembly System . IO . Compression . FileSystem
Set-Variable -Name Zip
try {
$Zip = [ System.IO.Compression.ZipFile ] :: OpenRead ( $ZipPath )
$DirectoriesToUnpack = Get-List -Of -Directories -And -Versions -To -Unpack -From -Dotnet -Package -Zip $Zip -OutPath $OutPath
foreach ( $entry in $Zip . Entries ) {
$PathWithVersion = Get-Path -Prefix -With -Version $entry . FullName
2022-02-20 06:07:00 +08:00
if ( ( $null -eq $PathWithVersion ) -Or ( $DirectoriesToUnpack -contains $PathWithVersion ) ) {
2020-06-13 01:46:25 +08:00
$DestinationPath = Get-Absolute -Path $ ( Join-Path -Path $OutPath -ChildPath $entry . FullName )
$DestinationDir = Split-Path -Parent $DestinationPath
$OverrideFiles = $OverrideNonVersionedFiles -Or ( -Not ( Test-Path $DestinationPath ) )
if ( ( -Not $DestinationPath . EndsWith ( " \ " ) ) -And $OverrideFiles ) {
New-Item -ItemType Directory -Force -Path $DestinationDir | Out-Null
[ System.IO.Compression.ZipFileExtensions ] :: ExtractToFile ( $entry , $DestinationPath , $OverrideNonVersionedFiles )
}
}
}
}
2022-07-29 07:22:35 +08:00
catch
{
Say-Error " Failed to extract package. Exception: $_ "
throw ;
}
2020-06-13 01:46:25 +08:00
finally {
2022-02-20 06:07:00 +08:00
if ( $null -ne $Zip ) {
2020-06-13 01:46:25 +08:00
$Zip . Dispose ( )
}
}
}
function DownloadFile($Source , [ string ] $OutPath ) {
if ( $Source -notlike " http* " ) {
# Using System.IO.Path.GetFullPath to get the current directory
# does not work in this context - $pwd gives the current directory
if ( ! [ System.IO.Path ] :: IsPathRooted ( $Source ) ) {
$Source = $ ( Join-Path -Path $pwd -ChildPath $Source )
}
$Source = Get-Absolute -Path $Source
Say " Copying file from $Source to $OutPath "
Copy-Item $Source $OutPath
return
}
$Stream = $null
try {
$Response = GetHTTPResponse -Uri $Source
$Stream = $Response . Content . ReadAsStreamAsync ( ) . Result
$File = [ System.IO.File ] :: Create ( $OutPath )
$Stream . CopyTo ( $File )
$File . Close ( )
}
finally {
2022-02-20 06:07:00 +08:00
if ( $null -ne $Stream ) {
2020-06-13 01:46:25 +08:00
$Stream . Dispose ( )
}
}
}
2021-02-02 16:40:17 +08:00
function SafeRemoveFile($Path ) {
try {
if ( Test-Path $Path ) {
Remove-Item $Path
Say-Verbose " The temporary file `" $Path `" was removed. "
}
else
{
Say-Verbose " The temporary file `" $Path `" does not exist, therefore is not removed. "
}
}
catch
{
Say-Warning " Failed to remove the temporary file: `" $Path `" , remove it manually. "
}
}
2022-02-20 06:07:00 +08:00
function Prepend-Sdk-InstallRoot-To-Path([string]$InstallRoot ) {
$BinPath = Get-Absolute -Path $ ( Join-Path -Path $InstallRoot -ChildPath " " )
2020-06-13 01:46:25 +08:00
if ( -Not $NoPath ) {
$SuffixedBinPath = " $BinPath ; "
if ( -Not $env:path . Contains ( $SuffixedBinPath ) ) {
Say " Adding to current process PATH: `" $BinPath `" . Note: This change will not be visible if PowerShell was run as a child process. "
$env:path = $SuffixedBinPath + $env:path
} else {
Say-Verbose " Current process PATH already contains `" $BinPath `" "
}
}
else {
Say " Binaries of dotnet can be found in $BinPath "
}
}
2022-02-20 06:07:00 +08:00
function PrintDryRunOutput($Invocation , $DownloadLinks )
{
Say " Payload URLs: "
for ( $linkIndex = 0 ; $linkIndex -lt $DownloadLinks . count ; $linkIndex + + ) {
Say " URL # $linkIndex - $( $DownloadLinks [ $linkIndex ] . type ) : $( $DownloadLinks [ $linkIndex ] . downloadLink ) "
}
$RepeatableCommand = " .\ $ScriptName -Version `" $SpecificVersion `" -InstallDir `" $InstallRoot `" -Architecture `" $CLIArchitecture `" "
if ( $Runtime -eq " dotnet " ) {
$RepeatableCommand + = " -Runtime `" dotnet `" "
}
elseif ( $Runtime -eq " aspnetcore " ) {
$RepeatableCommand + = " -Runtime `" aspnetcore `" "
}
foreach ( $key in $Invocation . BoundParameters . Keys ) {
if ( -not ( @ ( " Architecture " , " Channel " , " DryRun " , " InstallDir " , " Runtime " , " SharedRuntime " , " Version " , " Quality " , " FeedCredential " ) -contains $key ) ) {
$RepeatableCommand + = " - $key `" $( $Invocation . BoundParameters [ $key ] ) `" "
}
}
if ( $Invocation . BoundParameters . Keys -contains " FeedCredential " ) {
$RepeatableCommand + = " -FeedCredential `" <feedCredential> `" "
}
Say " Repeatable invocation: $RepeatableCommand "
if ( $SpecificVersion -ne $EffectiveVersion )
{
Say " NOTE: Due to finding a version manifest with this runtime, it would actually install with version ' $EffectiveVersion ' "
}
}
2021-06-08 17:27:57 +08:00
function Get-AkaMSDownloadLink([string]$Channel , [ string ] $Quality , [ bool ] $Internal , [ string ] $Product , [ string ] $Architecture ) {
Say-Invocation $MyInvocation
2022-11-16 00:00:14 +08:00
#quality is not supported for LTS or STS channel
if ( ! [ string ] :: IsNullOrEmpty ( $Quality ) -and ( @ ( " LTS " , " STS " ) -contains $Channel ) ) {
2021-06-08 17:27:57 +08:00
$Quality = " "
2022-11-16 00:00:14 +08:00
Say-Warning " Specifying quality for STS or LTS channel is not supported, the quality will be ignored. "
2021-06-08 17:27:57 +08:00
}
Say-Verbose " Retrieving primary payload URL from aka.ms link for channel: ' $Channel ', quality: ' $Quality ' product: ' $Product ', os: 'win', architecture: ' $Architecture '. "
#construct aka.ms link
$akaMsLink = " https://aka.ms/dotnet "
if ( $Internal ) {
$akaMsLink + = " /internal "
}
$akaMsLink + = " / $Channel "
if ( -not [ string ] :: IsNullOrEmpty ( $Quality ) ) {
$akaMsLink + = " / $Quality "
}
$akaMsLink + = " / $Product -win- $Architecture .zip "
Say-Verbose " Constructed aka.ms link: ' $akaMsLink '. "
$akaMsDownloadLink = $null
for ( $maxRedirections = 9 ; $maxRedirections -ge 0 ; $maxRedirections - - )
{
#get HTTP response
#do not pass credentials as a part of the $akaMsLink and do not apply credentials in the GetHTTPResponse function
#otherwise the redirect link would have credentials as well
#it would result in applying credentials twice to the resulting link and thus breaking it, and in echoing credentials to the output as a part of redirect link
$Response = GetHTTPResponse -Uri $akaMsLink -HeaderOnly $true -DisableRedirect $true -DisableFeedCredential $true
Say-Verbose " Received response: `n $Response "
if ( [ string ] :: IsNullOrEmpty ( $Response ) ) {
Say-Verbose " The link ' $akaMsLink ' is not valid: failed to get redirect location. The resource is not available. "
return $null
}
#if HTTP code is 301 (Moved Permanently), the redirect link exists
if ( $Response . StatusCode -eq 301 )
{
try {
$akaMsDownloadLink = $Response . Headers . GetValues ( " Location " ) [ 0 ]
if ( [ string ] :: IsNullOrEmpty ( $akaMsDownloadLink ) ) {
Say-Verbose " The link ' $akaMsLink ' is not valid: server returned 301 (Moved Permanently), but the headers do not contain the redirect location. "
return $null
}
Say-Verbose " The redirect location retrieved: ' $akaMsDownloadLink '. "
# This may yet be a link to another redirection. Attempt to retrieve the page again.
$akaMsLink = $akaMsDownloadLink
continue
}
catch {
Say-Verbose " The link ' $akaMsLink ' is not valid: failed to get redirect location. "
return $null
}
}
elseif ( ( ( $Response . StatusCode -lt 300 ) -or ( $Response . StatusCode -ge 400 ) ) -and ( -not [ string ] :: IsNullOrEmpty ( $akaMsDownloadLink ) ) )
{
# Redirections have ended.
return $akaMsDownloadLink
}
Say-Verbose " The link ' $akaMsLink ' is not valid: failed to retrieve the redirection location. "
return $null
}
Say-Verbose " Aka.ms links have redirected more than the maximum allowed redirections. This may be caused by a cyclic redirection of aka.ms links. "
return $null
}
2022-02-20 06:07:00 +08:00
function Get-AkaMsLink-And-Version([string ] $NormalizedChannel , [ string ] $NormalizedQuality , [ bool ] $Internal , [ string ] $ProductName , [ string ] $Architecture ) {
$AkaMsDownloadLink = Get-AkaMSDownloadLink -Channel $NormalizedChannel -Quality $NormalizedQuality -Internal $Internal -Product $ProductName -Architecture $Architecture
2021-06-08 17:27:57 +08:00
if ( [ string ] :: IsNullOrEmpty ( $AkaMsDownloadLink ) ) {
if ( -not [ string ] :: IsNullOrEmpty ( $NormalizedQuality ) ) {
# if quality is specified - exit with error - there is no fallback approach
2022-02-20 06:07:00 +08:00
Say-Error " Failed to locate the latest version in the channel ' $NormalizedChannel ' with ' $NormalizedQuality ' quality for ' $ProductName ', os: 'win', architecture: ' $Architecture '. "
2021-06-08 17:27:57 +08:00
Say-Error " Refer to: https://aka.ms/dotnet-os-lifecycle for information on .NET Core support. "
throw " aka.ms link resolution failure "
}
Say-Verbose " Falling back to latest.version file approach. "
2022-02-20 06:07:00 +08:00
return ( $null , $null , $null )
2021-06-08 17:27:57 +08:00
}
else {
Say-Verbose " Retrieved primary named payload URL from aka.ms link: ' $AkaMsDownloadLink '. "
Say-Verbose " Downloading using legacy url will not be attempted. "
#get version from the path
2022-02-20 06:07:00 +08:00
$pathParts = $AkaMsDownloadLink . Split ( '/' )
2021-06-08 17:27:57 +08:00
if ( $pathParts . Length -ge 2 ) {
$SpecificVersion = $pathParts [ $pathParts . Length - 2 ]
Say-Verbose " Version: ' $SpecificVersion '. "
}
else {
2022-02-20 06:07:00 +08:00
Say-Error " Failed to extract the version from download link ' $AkaMsDownloadLink '. "
return ( $null , $null , $null )
2021-06-08 17:27:57 +08:00
}
#retrieve effective (product) version
2022-02-20 06:07:00 +08:00
$EffectiveVersion = Get-Product -Version -SpecificVersion $SpecificVersion -PackageDownloadLink $AkaMsDownloadLink
2021-06-08 17:27:57 +08:00
Say-Verbose " Product version: ' $EffectiveVersion '. "
2022-02-20 06:07:00 +08:00
return ( $AkaMsDownloadLink , $SpecificVersion , $EffectiveVersion ) ;
2021-06-08 17:27:57 +08:00
}
}
2022-02-20 06:07:00 +08:00
function Get-Feeds-To-Use ( )
{
$feeds = @ (
" https://dotnetcli.azureedge.net/dotnet " ,
" https://dotnetbuilds.azureedge.net/public "
)
2021-06-08 17:27:57 +08:00
2022-02-20 06:07:00 +08:00
if ( -not [ string ] :: IsNullOrEmpty ( $AzureFeed ) ) {
$feeds = @ ( $AzureFeed )
}
2020-06-13 01:46:25 +08:00
2022-02-20 06:07:00 +08:00
if ( $NoCdn ) {
$feeds = @ (
" https://dotnetcli.blob.core.windows.net/dotnet " ,
" https://dotnetbuilds.blob.core.windows.net/public "
)
2020-06-13 01:46:25 +08:00
2022-02-20 06:07:00 +08:00
if ( -not [ string ] :: IsNullOrEmpty ( $UncachedFeed ) ) {
$feeds = @ ( $UncachedFeed )
}
2020-06-13 01:46:25 +08:00
}
2022-02-20 06:07:00 +08:00
return $feeds
}
function Resolve-AssetName-And-RelativePath([string ] $Runtime ) {
2020-06-13 01:46:25 +08:00
if ( $Runtime -eq " dotnet " ) {
2022-02-20 06:07:00 +08:00
$assetName = " .NET Core Runtime "
$dotnetPackageRelativePath = " shared\Microsoft.NETCore.App "
2020-06-13 01:46:25 +08:00
}
elseif ( $Runtime -eq " aspnetcore " ) {
2022-02-20 06:07:00 +08:00
$assetName = " ASP.NET Core Runtime "
$dotnetPackageRelativePath = " shared\Microsoft.AspNetCore.App "
2021-06-08 17:27:57 +08:00
}
2022-02-20 06:07:00 +08:00
elseif ( $Runtime -eq " windowsdesktop " ) {
$assetName = " .NET Core Windows Desktop Runtime "
$dotnetPackageRelativePath = " shared\Microsoft.WindowsDesktop.App "
2020-06-13 01:46:25 +08:00
}
2022-02-20 06:07:00 +08:00
elseif ( -not $Runtime ) {
$assetName = " .NET Core SDK "
$dotnetPackageRelativePath = " sdk "
2021-06-08 17:27:57 +08:00
}
2022-02-20 06:07:00 +08:00
else {
throw " Invalid value for `$ Runtime "
2021-02-02 16:40:17 +08:00
}
2022-02-20 06:07:00 +08:00
return ( $assetName , $dotnetPackageRelativePath )
2020-06-13 01:46:25 +08:00
}
2022-02-20 06:07:00 +08:00
function Prepare-Install-Directory {
New-Item -ItemType Directory -Force -Path $InstallRoot | Out-Null
$installDrive = $ ( ( Get-Item $InstallRoot -Force ) . PSDrive . Name ) ;
$diskInfo = $null
try {
$diskInfo = Get-PSDrive -Name $installDrive
}
catch {
Say-Warning " Failed to check the disk space. Installation will continue, but it may fail if you do not have enough disk space. "
}
if ( ( $null -ne $diskInfo ) -and ( $diskInfo . Free / 1 MB -le 100 ) ) {
throw " There is not enough disk space on drive ${installDrive} : "
}
2020-06-13 01:46:25 +08:00
}
2022-02-20 06:07:00 +08:00
Say " Note that the intended use of this script is for Continuous Integration (CI) scenarios, where: "
Say " - The SDK needs to be installed without user interaction and without admin rights. "
Say " - The SDK installation doesn't need to persist across multiple CI runs. "
Say " To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer. `r `n "
if ( $SharedRuntime -and ( -not $Runtime ) ) {
$Runtime = " dotnet "
2020-06-13 01:46:25 +08:00
}
2022-02-20 06:07:00 +08:00
$OverrideNonVersionedFiles = ! $SkipNonVersionedFiles
$CLIArchitecture = Get-CLIArchitecture -From -Architecture $Architecture
$NormalizedQuality = Get-NormalizedQuality $Quality
Say-Verbose " Normalized quality: ' $NormalizedQuality ' "
$NormalizedChannel = Get-NormalizedChannel $Channel
Say-Verbose " Normalized channel: ' $NormalizedChannel ' "
$NormalizedProduct = Get-NormalizedProduct $Runtime
Say-Verbose " Normalized product: ' $NormalizedProduct ' "
$FeedCredential = ValidateFeedCredential $FeedCredential
$InstallRoot = Resolve-Installation -Path $InstallDir
Say-Verbose " InstallRoot: $InstallRoot "
$ScriptName = $MyInvocation . MyCommand . Name
( $assetName , $dotnetPackageRelativePath ) = Resolve-AssetName -And -RelativePath -Runtime $Runtime
$feeds = Get-Feeds -To -Use
$DownloadLinks = @ ( )
2022-07-29 07:22:35 +08:00
if ( $Version . ToLowerInvariant ( ) -ne " latest " -and -not [ string ] :: IsNullOrEmpty ( $Quality ) ) {
2022-11-16 00:00:14 +08:00
throw " Quality and Version options are not allowed to be specified simultaneously. See https:// learn.microsoft.com/dotnet/core/tools/dotnet-install-script#options for details. "
2022-07-29 07:22:35 +08:00
}
2022-02-20 06:07:00 +08:00
# aka.ms links can only be used if the user did not request a specific version via the command line or a global.json file.
if ( [ string ] :: IsNullOrEmpty ( $JSonFile ) -and ( $Version -eq " latest " ) ) {
( $DownloadLink , $SpecificVersion , $EffectiveVersion ) = Get-AkaMsLink -And -Version $NormalizedChannel $NormalizedQuality $Internal $NormalizedProduct $CLIArchitecture
if ( $null -ne $DownloadLink ) {
$DownloadLinks + = New-Object PSObject -Property @ { downloadLink = " $DownloadLink " ; specificVersion = " $SpecificVersion " ; effectiveVersion = " $EffectiveVersion " ; type = 'aka.ms' }
Say-Verbose " Generated aka.ms link $DownloadLink with version $EffectiveVersion "
if ( -Not $DryRun ) {
Say-Verbose " Checking if the version $EffectiveVersion is already installed "
if ( Is-Dotnet -Package -Installed -InstallRoot $InstallRoot -RelativePathToPackage $dotnetPackageRelativePath -SpecificVersion $EffectiveVersion )
{
Say " $assetName with version ' $EffectiveVersion ' is already installed. "
Prepend-Sdk -InstallRoot -To -Path -InstallRoot $InstallRoot
return
}
}
}
2020-06-13 01:46:25 +08:00
}
2022-02-20 06:07:00 +08:00
# Primary and legacy links cannot be used if a quality was specified.
# If we already have an aka.ms link, no need to search the blob feeds.
if ( [ string ] :: IsNullOrEmpty ( $NormalizedQuality ) -and 0 -eq $DownloadLinks . count )
2021-02-02 16:40:17 +08:00
{
2022-02-20 06:07:00 +08:00
foreach ( $feed in $feeds ) {
try {
$SpecificVersion = Get-Specific -Version -From -Version -AzureFeed $feed -Channel $Channel -Version $Version -JSonFile $JSonFile
$DownloadLink , $EffectiveVersion = Get-Download -Link -AzureFeed $feed -SpecificVersion $SpecificVersion -CLIArchitecture $CLIArchitecture
$LegacyDownloadLink = Get-LegacyDownload -Link -AzureFeed $feed -SpecificVersion $SpecificVersion -CLIArchitecture $CLIArchitecture
$DownloadLinks + = New-Object PSObject -Property @ { downloadLink = " $DownloadLink " ; specificVersion = " $SpecificVersion " ; effectiveVersion = " $EffectiveVersion " ; type = 'primary' }
Say-Verbose " Generated primary link $DownloadLink with version $EffectiveVersion "
if ( -not [ string ] :: IsNullOrEmpty ( $LegacyDownloadLink ) ) {
$DownloadLinks + = New-Object PSObject -Property @ { downloadLink = " $LegacyDownloadLink " ; specificVersion = " $SpecificVersion " ; effectiveVersion = " $EffectiveVersion " ; type = 'legacy' }
Say-Verbose " Generated legacy link $LegacyDownloadLink with version $EffectiveVersion "
}
if ( -Not $DryRun ) {
Say-Verbose " Checking if the version $EffectiveVersion is already installed "
if ( Is-Dotnet -Package -Installed -InstallRoot $InstallRoot -RelativePathToPackage $dotnetPackageRelativePath -SpecificVersion $EffectiveVersion )
{
Say " $assetName with version ' $EffectiveVersion ' is already installed. "
Prepend-Sdk -InstallRoot -To -Path -InstallRoot $InstallRoot
return
}
}
}
catch
{
Say-Verbose " Failed to acquire download links from feed $feed . Exception: $_ "
}
}
2021-02-02 16:40:17 +08:00
}
2022-02-20 06:07:00 +08:00
if ( $DownloadLinks . count -eq 0 ) {
throw " Failed to resolve the exact version number. "
2020-06-13 01:46:25 +08:00
}
2022-02-20 06:07:00 +08:00
if ( $DryRun ) {
PrintDryRunOutput $MyInvocation $DownloadLinks
return
2021-06-08 17:27:57 +08:00
}
2022-02-20 06:07:00 +08:00
Prepare-Install -Directory
2020-06-13 01:46:25 +08:00
$ZipPath = [ System.IO.Path ] :: combine ( [ System.IO.Path ] :: GetTempPath ( ) , [ System.IO.Path ] :: GetRandomFileName ( ) )
Say-Verbose " Zip path: $ZipPath "
2022-02-20 06:07:00 +08:00
$DownloadSucceeded = $false
$DownloadedLink = $null
$ErrorMessages = @ ( )
2021-02-02 16:40:17 +08:00
2022-02-20 06:07:00 +08:00
foreach ( $link in $DownloadLinks )
{
Say-Verbose " Downloading `" $( $link . type ) `" link $( $link . downloadLink ) "
2021-02-02 16:40:17 +08:00
2022-02-20 06:07:00 +08:00
try {
DownloadFile -Source $link . downloadLink -OutPath $ZipPath
Say-Verbose " Download succeeded. "
$DownloadSucceeded = $true
$DownloadedLink = $link
break
2021-02-02 16:40:17 +08:00
}
2022-02-20 06:07:00 +08:00
catch {
$StatusCode = $null
$ErrorMessage = $null
2021-02-02 16:40:17 +08:00
2022-02-20 06:07:00 +08:00
if ( $PSItem . Exception . Data . Contains ( " StatusCode " ) ) {
$StatusCode = $PSItem . Exception . Data [ " StatusCode " ]
}
if ( $PSItem . Exception . Data . Contains ( " ErrorMessage " ) ) {
$ErrorMessage = $PSItem . Exception . Data [ " ErrorMessage " ]
} else {
$ErrorMessage = $PSItem . Exception . Message
}
2021-02-02 16:40:17 +08:00
2022-02-20 06:07:00 +08:00
Say-Verbose " Download failed with status code $StatusCode . Error message: $ErrorMessage "
$ErrorMessages + = " Downloading from `" $( $link . type ) `" link has failed with error: `n Uri: $( $link . downloadLink ) `n StatusCode: $StatusCode `n Error: $ErrorMessage "
2021-02-02 16:40:17 +08:00
}
2022-02-20 06:07:00 +08:00
# This link failed. Clean up before trying the next one.
2021-02-02 16:40:17 +08:00
SafeRemoveFile -Path $ZipPath
2020-06-13 01:46:25 +08:00
}
2022-02-20 06:07:00 +08:00
if ( -not $DownloadSucceeded ) {
foreach ( $ErrorMessage in $ErrorMessages ) {
Say-Error $ErrorMessages
2021-02-02 16:40:17 +08:00
}
2022-02-20 06:07:00 +08:00
throw " Could not find `" $assetName `" with version = $( $DownloadLinks [ 0 ] . effectiveVersion ) `n Refer to: https://aka.ms/dotnet-os-lifecycle for information on .NET support "
2020-06-13 01:46:25 +08:00
}
2022-02-20 06:07:00 +08:00
Say " Extracting the archive. "
2020-06-13 01:46:25 +08:00
Extract-Dotnet -Package -ZipPath $ZipPath -OutPath $InstallRoot
# Check if the SDK version is installed; if not, fail the installation.
$isAssetInstalled = $false
# if the version contains "RTM" or "servicing"; check if a 'release-type' SDK version is installed.
2022-02-20 06:07:00 +08:00
if ( $DownloadedLink . effectiveVersion -Match " rtm " -or $DownloadedLink . effectiveVersion -Match " servicing " ) {
$ReleaseVersion = $DownloadedLink . effectiveVersion . Split ( " - " ) [ 0 ]
2020-06-13 01:46:25 +08:00
Say-Verbose " Checking installation: version = $ReleaseVersion "
$isAssetInstalled = Is-Dotnet -Package -Installed -InstallRoot $InstallRoot -RelativePathToPackage $dotnetPackageRelativePath -SpecificVersion $ReleaseVersion
}
# Check if the SDK version is installed.
if ( ! $isAssetInstalled ) {
2022-02-20 06:07:00 +08:00
Say-Verbose " Checking installation: version = $( $DownloadedLink . effectiveVersion ) "
$isAssetInstalled = Is-Dotnet -Package -Installed -InstallRoot $InstallRoot -RelativePathToPackage $dotnetPackageRelativePath -SpecificVersion $DownloadedLink . effectiveVersion
2020-06-13 01:46:25 +08:00
}
2021-02-02 16:40:17 +08:00
# Version verification failed. More likely something is wrong either with the downloaded content or with the verification algorithm.
2020-06-13 01:46:25 +08:00
if ( ! $isAssetInstalled ) {
2022-02-20 06:07:00 +08:00
Say-Error " Failed to verify the version of installed `" $assetName `" . `n Installation source: $( $DownloadedLink . downloadLink ) . `n Installation location: $InstallRoot . `n Report the bug at https://github.com/dotnet/install-scripts/issues. "
throw " `" $assetName `" with version = $( $DownloadedLink . effectiveVersion ) failed to install with an unknown error. "
2020-06-13 01:46:25 +08:00
}
2021-02-02 16:40:17 +08:00
SafeRemoveFile -Path $ZipPath
2020-06-13 01:46:25 +08:00
2022-02-20 06:07:00 +08:00
Prepend-Sdk -InstallRoot -To -Path -InstallRoot $InstallRoot
2020-06-13 01:46:25 +08:00
2021-02-02 16:40:17 +08:00
Say " Note that the script does not resolve dependencies during installation. "
2022-11-16 00:00:14 +08:00
Say " To check the list of dependencies, go to https://learn.microsoft.com/dotnet/core/install/windows#dependencies "
2022-07-29 07:22:35 +08:00
Say " Installed version is $( $DownloadedLink . effectiveVersion ) "
2020-06-13 01:46:25 +08:00
Say " Installation finished "
2021-08-26 17:44:10 +08:00
2020-06-13 01:46:25 +08:00
# SIG # Begin signature block
2022-11-16 00:00:14 +08:00
# MIInzgYJKoZIhvcNAQcCoIInvzCCJ7sCAQExDzANBglghkgBZQMEAgEFADB5Bgor
2020-06-13 01:46:25 +08:00
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
2022-11-16 00:00:14 +08:00
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCB7pzZ0nuEMd30h
# n1EcAYUQN+1clltqaLf9611TDrw/laCCDYUwggYDMIID66ADAgECAhMzAAACzfNk
# v/jUTF1RAAAAAALNMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
2020-06-13 01:46:25 +08:00
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
2022-11-16 00:00:14 +08:00
# bmcgUENBIDIwMTEwHhcNMjIwNTEyMjA0NjAyWhcNMjMwNTExMjA0NjAyWjB0MQsw
2020-06-13 01:46:25 +08:00
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
2022-11-16 00:00:14 +08:00
# AQDrIzsY62MmKrzergm7Ucnu+DuSHdgzRZVCIGi9CalFrhwtiK+3FIDzlOYbs/zz
# HwuLC3hir55wVgHoaC4liQwQ60wVyR17EZPa4BQ28C5ARlxqftdp3H8RrXWbVyvQ
# aUnBQVZM73XDyGV1oUPZGHGWtgdqtBUd60VjnFPICSf8pnFiit6hvSxH5IVWI0iO
# nfqdXYoPWUtVUMmVqW1yBX0NtbQlSHIU6hlPvo9/uqKvkjFUFA2LbC9AWQbJmH+1
# uM0l4nDSKfCqccvdI5l3zjEk9yUSUmh1IQhDFn+5SL2JmnCF0jZEZ4f5HE7ykDP+
# oiA3Q+fhKCseg+0aEHi+DRPZAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQU0WymH4CP7s1+yQktEwbcLQuR9Zww
# VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh
# dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzQ3MDUzMDAfBgNVHSMEGDAW
# gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v
# d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw
# MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov
# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx
# XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB
# AE7LSuuNObCBWYuttxJAgilXJ92GpyV/fTiyXHZ/9LbzXs/MfKnPwRydlmA2ak0r
# GWLDFh89zAWHFI8t9JLwpd/VRoVE3+WyzTIskdbBnHbf1yjo/+0tpHlnroFJdcDS
# MIsH+T7z3ClY+6WnjSTetpg1Y/pLOLXZpZjYeXQiFwo9G5lzUcSd8YVQNPQAGICl
# 2JRSaCNlzAdIFCF5PNKoXbJtEqDcPZ8oDrM9KdO7TqUE5VqeBe6DggY1sZYnQD+/
# LWlz5D0wCriNgGQ/TWWexMwwnEqlIwfkIcNFxo0QND/6Ya9DTAUykk2SKGSPt0kL
# tHxNEn2GJvcNtfohVY/b0tuyF05eXE3cdtYZbeGoU1xQixPZAlTdtLmeFNly82uB
# VbybAZ4Ut18F//UrugVQ9UUdK1uYmc+2SdRQQCccKwXGOuYgZ1ULW2u5PyfWxzo4
# BR++53OB/tZXQpz4OkgBZeqs9YaYLFfKRlQHVtmQghFHzB5v/WFonxDVlvPxy2go
# a0u9Z+ZlIpvooZRvm6OtXxdAjMBcWBAsnBRr/Oj5s356EDdf2l/sLwLFYE61t+ME
# iNYdy0pXL6gN3DxTVf2qjJxXFkFfjjTisndudHsguEMk8mEtnvwo9fOSKT6oRHhM
# 9sZ4HTg/TTMjUljmN3mBYWAWI5ExdC1inuog0xrKmOWVMIIHejCCBWKgAwIBAgIK
# YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm
# aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw
# OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD
# VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG
# 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la
# UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc
# 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D
# dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+
# lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk
# kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6
# A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd
# X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL
# 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd
# sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3
# T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS
# 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI
# bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL
# BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD
# uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv
# c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf
# MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf
# MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF
# BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h
# cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA
# YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn
# 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7
# v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b
# pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/
# KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy
# CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp
# mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi
# hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb
# BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS
# oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL
# gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX
# cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCGZ8wghmbAgEBMIGVMH4x
2022-08-31 23:40:05 +08:00
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
2022-11-16 00:00:14 +08:00
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p
# Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAALN82S/+NRMXVEAAAAA
# As0wDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw
# HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEINK7
# cJe0KVfbcXchjID30U/cUg7pWAQUa3+n8JuhjLCLMEIGCisGAQQBgjcCAQwxNDAy
# oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20wDQYJKoZIhvcNAQEBBQAEggEAODLxcflOtjpIXXIhbYyQ0wFeBx0NrmoMU/Ri
# e7CRrAieAbG4iLJzs4DhUov5iuMHY6AAbLWAH54QlSkd4XNp6POsE7lSzN9yjlVw
# V/e0XCaYeXIbtd75hGd5P7wAhM4m2ViDI4IRHyQtjysW0U0F6YiqNlFm7Fzo5Si6
# l2XxvuEDSdyJcEN70wHQajx6bKfnI/oMY59iGjDXvDP/6cQV9NI0gPHFTwPKA7vg
# PySyVFEG7dQMoEwAWy9GHbcS//RulgUwBhWcrtUP411XLSO6is2VTknwbdglc9HZ
# zViuS4C1ujHlPrlMzm8Y5iGVIQCna5w2NU/kGsSK5+dMkovomKGCFykwghclBgor
# BgEEAYI3AwMBMYIXFTCCFxEGCSqGSIb3DQEHAqCCFwIwghb+AgEDMQ8wDQYJYIZI
# AWUDBAIBBQAwggFZBgsqhkiG9w0BCRABBKCCAUgEggFEMIIBQAIBAQYKKwYBBAGE
# WQoDATAxMA0GCWCGSAFlAwQCAQUABCDRz6ce9oWlH6+o0BtjmAjtvEMN1hfhIA5v
# +wTZHvB4XgIGY2PeyIloGBMyMDIyMTExMDE1MDUxNi43MzRaMASAAgH0oIHYpIHV
# MIHSMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQL
# EyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsT
# HVRoYWxlcyBUU1MgRVNOOkEyNDAtNEI4Mi0xMzBFMSUwIwYDVQQDExxNaWNyb3Nv
# ZnQgVGltZS1TdGFtcCBTZXJ2aWNloIIReDCCBycwggUPoAMCAQICEzMAAAG4CNTB
# uHngUUkAAQAAAbgwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNV
# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
# c29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAg
# UENBIDIwMTAwHhcNMjIwOTIwMjAyMjE2WhcNMjMxMjE0MjAyMjE2WjCB0jELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9z
# b2Z0IElyZWxhbmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMg
# VFNTIEVTTjpBMjQwLTRCODItMTMwRTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt
# U3RhbXAgU2VydmljZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJwb
# sfwRHERn5C95QPGn37tJ5vOiY9aWjeIDxpgaXaYGiqsw0G0cvCK3YulrqemEf2Ck
# GSdcOJAF++EqhOSqrO13nGcjqw6hFNnsGwKANyzddwnOO0jz1lfBIIu77TbfNvna
# WbwSRu0DTGHA7n7PR0MYJ9bC/HopStpbFf606LKcTWnwaUuEdAhx6FAqg1rkgugi
# uuaaxKyxRkdjFZLKFXEXL9p01PtwS0fG6vZiRVnEKgeal2TeLvdAIqapBwltPYif
# gqnp7Z4VJMcPo0TWmRNVFOcHRNwWHehN9xg6ugIGXPo7hMpWrPgg4moHO2epc0T3
# 6rgm9hlDrl28bG5TakmV7NJ98kbF5lgtlrowT6ecwEVtuLd4a0gzYqhanW7zaFZn
# Dft5yMexy59ifETdzpwArj2nJAyIsiq1PY3XPm2mUMLlACksqelHKfWihK/Fehw/
# mziovBVwkkr/G0F19OWgR+MBUKifwpOyQiLAxrqvVnfCY4QjJCZiHIuS15HCQ/TI
# t/Qj4x1WvRa1UqjnmpLu4/yBYWZsdvZoq8SXI7iOs7muecAJeEkYlM6iOkMighzE
# hjQK9ThPpoAtluXbL7qIHGrfFlHmX/4soc7jj1j8uB31U34gJlB2XphjMaT+E+O9
# SImk/6GRV9Sm8C88Fnmm2VdwMluCNAUzPFjfvHx3AgMBAAGjggFJMIIBRTAdBgNV
# HQ4EFgQUxP1HJTeFwzNYo1njfucXuUfQaW4wHwYDVR0jBBgwFoAUn6cVXQBeYl2D
# 9OXSZacbUzUZ6XIwXwYDVR0fBFgwVjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3Nv
# ZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUy
# MDIwMTAoMSkuY3JsMGwGCCsGAQUFBwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDov
# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1l
# LVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcnQwDAYDVR0TAQH/BAIwADAWBgNVHSUB
# Af8EDDAKBggrBgEFBQcDCDAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQAD
# ggIBAJ9uk8miwpMoKw3D996piEzbegAGxkABHYn2vP2hbqnkS9U97s/6QlyZOhGF
# sVudaiLeRZZTsaG5hR0oCuBINZ/lelo5xzHc+mBOpBXpxSaW1hqoxaCLsVH1EBtz
# 7in25Hjy+ejuBcilH6EZ0ZtNxmWGIQz8R0AuS0Tj4VgJXHIlXP9dVOiyGo9Velrk
# +FGx/BC+iEuCaKd/IsypHPiCUCh52DGc91s2S7ldQx1H4CljOAtanDfbvSejASWL
# o/s3w0XMAbDurWNns0XidAF2RnL1PaxoOyz9VYakNGK4F3/uJRZnVgbsCYuwNX1B
# mSwM1ZbPSnggNSGTZx/FQ20Jj/ulrK0ryAbvNbNb4kkaS4a767ifCqvUOFLlUT8P
# N43hhldxI6yHPMOWItJpEHIZBiTNKblBsYbIrghb1Ym9tfSsLa5ZJDzVZNndRfhU
# qJOyXF+CVm9OtVmFDG9kIwM6QAX8Q0if721z4VOzZNvD8ktg1lI+XjXgXDJVs3h4
# 7sMu9GXSYzky+7dtgmc3iRPkda3YVRdmPJtNFN0NLybcssE7vhFCij75eDGQBFq0
# A4KVG6uBdr6UTWwE0VKHxBz2BpGvn7BCs+5yxnF+HV6CUickDqqPi/II7Zssd9Eb
# P9uzj4luldXDAPrWGtdGq+wK0odlGNVuCMxsL3hn8+KiO9UiMIIHcTCCBVmgAwIB
# AgITMwAAABXF52ueAptJmQAAAAAAFTANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UE
# BhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAc
# BgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0
# IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEwOTMwMTgyMjI1
# WhcNMzAwOTMwMTgzMjI1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu
# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv
# cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCC
# AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOThpkzntHIhC3miy9ckeb0O
# 1YLT/e6cBwfSqWxOdcjKNVf2AX9sSuDivbk+F2Az/1xPx2b3lVNxWuJ+Slr+uDZn
# hUYjDLWNE893MsAQGOhgfWpSg0S3po5GawcU88V29YZQ3MFEyHFcUTE3oAo4bo3t
# 1w/YJlN8OWECesSq/XJprx2rrPY2vjUmZNqYO7oaezOtgFt+jBAcnVL+tuhiJdxq
# D89d9P6OU8/W7IVWTe/dvI2k45GPsjksUZzpcGkNyjYtcI4xyDUoveO0hyTD4MmP
# frVUj9z6BVWYbWg7mka97aSueik3rMvrg0XnRm7KMtXAhjBcTyziYrLNueKNiOSW
# rAFKu75xqRdbZ2De+JKRHh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9fvzZnkXftnIv
# 231fgLrbqn427DZM9ituqBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdHGO2n6Jl8P0zb
# r17C89XYcz1DTsEzOUyOArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7XKHYC4jMYcten
# IPDC+hIK12NvDMk2ZItboKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiER9vcG9H9stQc
# xWv2XFJRXRLbJbqvUAV6bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/eKtFtvUeh17a
# j54WcmnGrnu3tz5q4i6tAgMBAAGjggHdMIIB2TASBgkrBgEEAYI3FQEEBQIDAQAB
# MCMGCSsGAQQBgjcVAgQWBBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAdBgNVHQ4EFgQU
# n6cVXQBeYl2D9OXSZacbUzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEEAYI3TIN9AQEw
# QTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9E
# b2NzL1JlcG9zaXRvcnkuaHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMIMBkGCSsGAQQB
# gjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/
# MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJ
# oEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01p
# Y1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYB
# BQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9v
# Q2VyQXV0XzIwMTAtMDYtMjMuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQCdVX38Kq3h
# LB9nATEkW+Geckv8qW/qXBS2Pk5HZHixBpOXPTEztTnXwnE2P9pkbHzQdTltuw8x
# 5MKP+2zRoZQYIu7pZmc6U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gngugnue99qb74p
# y27YP0h1AdkY3m2CDPVtI1TkeFN1JFe53Z/zjj3G82jfZfakVqr3lbYoVSfQJL1A
# oL8ZthISEV09J+BAljis9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHCgRlCGVJ1ijbC
# HcNhcy4sa3tuPywJeBTpkbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6MhrZlvSP9pEB
# 9s7GdP32THJvEKt1MMU0sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEUBHG/ZPkkvnNt
# yo4JvbMBV0lUZNlz138eW0QBjloZkWsNn6Qo3GcZKCS6OEuabvshVGtqRRFHqfG3
# rsjoiV5PndLQTHa1V1QJsWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+fpO+y/g75LcV
# v7TOPqUxUYS8vwLBgqJ7Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrpNPgkNWcr4A24
# 5oyZ1uEi6vAnQj0llOZ0dFtq0Z4+7X6gMTN9vMvpe784cETRkPHIqzqKOghif9lw
# Y1NNje6CbaUFEMFxBmoQtB1VM1izoXBm8qGCAtQwggI9AgEBMIIBAKGB2KSB1TCB
# 0jELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl
# ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEtMCsGA1UECxMk
# TWljcm9zb2Z0IElyZWxhbmQgT3BlcmF0aW9ucyBMaW1pdGVkMSYwJAYDVQQLEx1U
# aGFsZXMgVFNTIEVTTjpBMjQwLTRCODItMTMwRTElMCMGA1UEAxMcTWljcm9zb2Z0
# IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUAcGteVqFx/IbTKXHL
# euXCPRPMD7uggYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu
2022-08-31 23:40:05 +08:00
# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv
# cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAN
2022-11-16 00:00:14 +08:00
# BgkqhkiG9w0BAQUFAAIFAOcW7qowIhgPMjAyMjExMTAxMTI5NDZaGA8yMDIyMTEx
# MTExMjk0NlowdDA6BgorBgEEAYRZCgQBMSwwKjAKAgUA5xbuqgIBADAHAgEAAgIE
# qTAHAgEAAgIRVjAKAgUA5xhAKgIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEE
# AYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GB
# AGsU2HTQg158bHX+QngoY7NVfCbGRaLjQOi8geKi26qQWAxll9QLFg4+epiG2nZB
# eQvhxeNmIzounhWfJ+gfhFMi8aBT5z4dLK9iBtmpG1Y14RmSS4andiUlS6bVNVNe
# WGObqHijMVeMOphiTaAfzR6zSASDaG0CfVm9bNBOnZZsMYIEDTCCBAkCAQEwgZMw
# fDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl
# ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMd
# TWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAG4CNTBuHngUUkAAQAA
# AbgwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRAB
# BDAvBgkqhkiG9w0BCQQxIgQg578XwPrBwneU95xu1sHFncHeCC0UPQ7QK7PvSSby
# VpwwgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCAo69Y4oHA7Q4pS+Y1NsBfr
# pIYTeWsPeGTami0X0PD7HzCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
# EwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3Nv
# ZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBD
# QSAyMDEwAhMzAAABuAjUwbh54FFJAAEAAAG4MCIEIJVlMK4mQfdJaAZx7Unfka/I
# D4Wbw5edh/SR7TTptzRqMA0GCSqGSIb3DQEBCwUABIICAA1QlR3ywR7e+jqZ++NC
# xsIwREiwVS70CEkbH8XpPRS0mFS0SHcCfpwymGfdep3D0CWk0PIfMhXq0SD97iBI
# rOLdHglVBkMYTjGEBHyBzv/LevAZUuzoc5aqyIF4Ywa5KS4PGbMSuRK5CKAojOzH
# A/vp2/KYuADmf9kOOgOfDVicyfoqZ+3W+QaUI/k0KKV4dPLF55+y18C+Td6sR60Y
# AkcvGZObuj/OkREhdTJ1qJ2E/4RKG8gtGY1DfluLon7+UvS/ciWDWrJnHMmkxM11
# cYuRIvLArIdq/YS2bcSnY6AVO2zYjj7gCqDN9GuCurstUKC5uxVl3VNxntC0u3Le
# BoI/R5uMYlTXodW8ukLNL6zHrQ4wI4udgW77KJref+3E1PEpZBRMxwose7Vt8lDc
# sW1vdM+eZzUXRLhDR8a0Nai7+PaNoukoGf4pvwsu8Mkeji5a0hWtU9lUVRv6nzue
# 3L2olhsbiHhAET7N6Rj0kzEhbUgfVUJrGvNlWOfN7MDr+OpArGXMPLtovbKTLtXF
# v/GrJo9wQuyqUmY6KQSRDZgOw1CcoZpJcy40HG/aOlJwk03N13OZD5H3KfHwEphR
# YnbGwGq9zUId5druSr5s40Yyl3idAkqmI5SXAm9v/gRq2X9vMU0a7KqXet9wO62F
# TqxV+7Qp48Vw6hW1g+Q7oWoc
2020-06-13 01:46:25 +08:00
# SIG # End signature block