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 :
- Current - most current release
- LTS - most current supported release
- 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
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 .
Works only in combination with channel . Not applicable for current and LTS channels and will be ignored if those channels are used .
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 :
- latest - most latest build on specific channel
- 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
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 {
throw
}
}
}
}
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
}
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 " "
}
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 " }
{ $_ -eq " current " } { return " current " }
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 )
}
}
}
}
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
#quality is not supported for LTS or current channel
if ( ! [ string ] :: IsNullOrEmpty ( $Quality ) -and ( @ ( " LTS " , " current " ) -contains $Channel ) ) {
$Quality = " "
Say-Warning " Specifying quality for current or LTS channel is not supported, the quality will be ignored. "
}
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 = @ ( )
# 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. "
Say " To check the list of dependencies, go to https://docs.microsoft.com/dotnet/core/install/windows#dependencies "
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-02-20 06:07:00 +08:00
# MIInoQYJKoZIhvcNAQcCoIInkjCCJ44CAQExDzANBglghkgBZQMEAgEFADB5Bgor
2020-06-13 01:46:25 +08:00
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
2022-02-20 06:07:00 +08:00
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAuo3sRWvfrJ+Bd
# sIQ2zLeO20Ij33Vb5ljtEhxAYYSEc6CCDYEwggX/MIID56ADAgECAhMzAAACUosz
# qviV8znbAAAAAAJSMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
2020-06-13 01:46:25 +08:00
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
2022-02-20 06:07:00 +08:00
# bmcgUENBIDIwMTEwHhcNMjEwOTAyMTgzMjU5WhcNMjIwOTAxMTgzMjU5WjB0MQsw
2020-06-13 01:46:25 +08:00
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
2022-02-20 06:07:00 +08:00
# AQDQ5M+Ps/X7BNuv5B/0I6uoDwj0NJOo1KrVQqO7ggRXccklyTrWL4xMShjIou2I
# sbYnF67wXzVAq5Om4oe+LfzSDOzjcb6ms00gBo0OQaqwQ1BijyJ7NvDf80I1fW9O
# L76Kt0Wpc2zrGhzcHdb7upPrvxvSNNUvxK3sgw7YTt31410vpEp8yfBEl/hd8ZzA
# v47DCgJ5j1zm295s1RVZHNp6MoiQFVOECm4AwK2l28i+YER1JO4IplTH44uvzX9o
# RnJHaMvWzZEpozPy4jNO2DDqbcNs4zh7AWMhE1PWFVA+CHI/En5nASvCvLmuR/t8
# q4bc8XR8QIZJQSp+2U6m2ldNAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUNZJaEUGL2Guwt7ZOAu4efEYXedEw
2021-02-02 16:40:17 +08:00
# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1
2022-02-20 06:07:00 +08:00
# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDY3NTk3MB8GA1UdIwQYMBaAFEhu
2021-02-02 16:40:17 +08:00
# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu
# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w
# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx
2022-02-20 06:07:00 +08:00
# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAFkk3
# uSxkTEBh1NtAl7BivIEsAWdgX1qZ+EdZMYbQKasY6IhSLXRMxF1B3OKdR9K/kccp
# kvNcGl8D7YyYS4mhCUMBR+VLrg3f8PUj38A9V5aiY2/Jok7WZFOAmjPRNNGnyeg7
# l0lTiThFqE+2aOs6+heegqAdelGgNJKRHLWRuhGKuLIw5lkgx9Ky+QvZrn/Ddi8u
# TIgWKp+MGG8xY6PBvvjgt9jQShlnPrZ3UY8Bvwy6rynhXBaV0V0TTL0gEx7eh/K1
# o8Miaru6s/7FyqOLeUS4vTHh9TgBL5DtxCYurXbSBVtL1Fj44+Od/6cmC9mmvrti
# yG709Y3Rd3YdJj2f3GJq7Y7KdWq0QYhatKhBeg4fxjhg0yut2g6aM1mxjNPrE48z
# 6HWCNGu9gMK5ZudldRw4a45Z06Aoktof0CqOyTErvq0YjoE4Xpa0+87T/PVUXNqf
# 7Y+qSU7+9LtLQuMYR4w3cSPjuNusvLf9gBnch5RqM7kaDtYWDgLyB42EfsxeMqwK
# WwA+TVi0HrWRqfSx2olbE56hJcEkMjOSKz3sRuupFCX3UroyYf52L+2iVTrda8XW
# esPG62Mnn3T8AuLfzeJFuAbfOSERx7IFZO92UPoXE1uEjL5skl1yTZB3MubgOA4F
# 8KoRNhviFAEST+nG8c8uIsbZeb08SeYQMqjVEmkwggd6MIIFYqADAgECAgphDpDS
2021-02-02 16:40:17 +08:00
# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
2020-08-29 03:01:15 +08:00
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
2021-02-02 16:40:17 +08:00
# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0
# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla
# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT
# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG
# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S
# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz
# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7
# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u
# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33
# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl
# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP
# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB
# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF
# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM
# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ
# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud
# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO
# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0
# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p
# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB
# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw
# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA
# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY
# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj
# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd
# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ
# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf
# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ
# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j
# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B
# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96
# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7
# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I
2022-02-20 06:07:00 +08:00
# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIZdjCCGXICAQEwgZUwfjELMAkG
2021-02-02 16:40:17 +08:00
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
2022-02-20 06:07:00 +08:00
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAlKLM6r4lfM52wAAAAACUjAN
2021-02-02 16:40:17 +08:00
# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
2022-02-20 06:07:00 +08:00
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgBzhh8wf+
# /kA/CamFNwS1K9Nt0wsjATKS8Y66iHQTKrIwQgYKKwYBBAGCNwIBDDE0MDKgFIAS
2021-02-02 16:40:17 +08:00
# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN
2022-02-20 06:07:00 +08:00
# BgkqhkiG9w0BAQEFAASCAQAruzFwahFXWsJ9eny7iY/KyWolnqJ9+cvDD1QaJ0qJ
# tHlTLP8pr0GPPtitwuk+3JME7wkx80+AZXl/ocO39tdhy1Ucd6yiyV3M1Ub/Eo4D
# 37L1saazNomWeeMD6oX3FboVe1Ql17MX1SscTW5QRx8ytYgUOUIbrxFybODcdVn9
# YG7mlkgsJr2lhUhTnHtq7jV/jEu6Sk9o+g15Pbu90VncsCoNerg9aEziui6/W3/g
# Wvt4WhfCPanppzWVmsvTUp5MAt5cpdNBr9b8CV/alI3TGnNwC8RuWMCK6CS4963l
# f4AiLZW/fgD8UQYtl+tAyYfSerpX0sDB039jymj1FkqHoYIXADCCFvwGCisGAQQB
# gjcDAwExghbsMIIW6AYJKoZIhvcNAQcCoIIW2TCCFtUCAQMxDzANBglghkgBZQME
2021-08-26 17:44:10 +08:00
# AgEFADCCAVEGCyqGSIb3DQEJEAEEoIIBQASCATwwggE4AgEBBgorBgEEAYRZCgMB
2022-02-20 06:07:00 +08:00
# MDEwDQYJYIZIAWUDBAIBBQAEIEcy5EvHdubbPngOS1BSZ6xt9xSMrt3OLtUyfQaT
# IyRBAgZh+unPYkgYEzIwMjIwMjA4MTAyMjE5LjM1OVowBIACAfSggdCkgc0wgcox
2021-02-02 16:40:17 +08:00
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
2021-08-26 17:44:10 +08:00
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1p
# Y3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMxJjAkBgNVBAsTHVRoYWxlcyBUU1Mg
2022-02-20 06:07:00 +08:00
# RVNOOkREOEMtRTMzNy0yRkFFMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFt
# cCBTZXJ2aWNloIIRVzCCBwwwggT0oAMCAQICEzMAAAGcD6ZNYdKeSygAAQAAAZww
2021-08-26 17:44:10 +08:00
# DQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0
# b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh
# dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcN
2022-02-20 06:07:00 +08:00
# MjExMjAyMTkwNTE5WhcNMjMwMjI4MTkwNTE5WjCByjELMAkGA1UEBhMCVVMxEzAR
2021-08-26 17:44:10 +08:00
# BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p
# Y3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2Eg
2022-02-20 06:07:00 +08:00
# T3BlcmF0aW9uczEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046REQ4Qy1FMzM3LTJG
# QUUxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0G
# CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDbUioMGV1JFj+s612s02mKu23KPUNs
# 71OjDeJGtxkTF9rSWTiuA8XgYkAAi/5+2Ff7Ck7JcKQ9H/XD1OKwg1/bH3E1qO1z
# 8XRy0PlpGhmyilgE7KsOvW8PIZCf243KdldgOrxrL8HKiQodOwStyT5lLWYpMsuT
# 2fH8k8oihje4TlpWiFPaCKLnFDaAB0Ccy6vIdtHjYB1Ie3iOZPisquL+vNdCx7gO
# hB8iiTmTdsU8OSUpC8tBTeTIYPzmhaxQZd4moNk6qeCJyi7fiW4fyXdHrZ3otmgx
# xa5pXz5pUUr+cEjV+cwIYBMkaY5kHM9c6dEGkgHn0ZDJvdt/54FOdSG61WwHh4+e
# vUhwvXaB4LCMZIdCt5acOfNvtDjV3CHyFOp5AU/qgAwGftHU9brv4EUwcuteEAKH
# 46NufE20l/WjlNUh7gAvt2zKMjO4zXRxCUTh/prBQwXJiUZeFSrEXiOfkuvSlBni
# yAYYZp5kOnaxfCKdGYjvr4QLA93vQJ6p2Ox3IHvOdCPaCr8LsKVcFpyp8MEhhJTM
# +1LwqHJqFDF5O1Z9mjbYvm3R9vPhkG+RDLKoTpr7mTgkaTljd9xvm94Obp8BD9Hk
# 4mPi51mtgLiuN8/6aZVESVZXtvSuNkD5DnIJQerIy5jaRKW/W2rCe9ngNDJadS7R
# 96GGRl7IIE37lwIDAQABo4IBNjCCATIwHQYDVR0OBBYEFLtpCWdTXY5dtddkspy+
# oxjCA/qyMB8GA1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRY
# MFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01p
# Y3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEF
# BQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9w
# a2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAo
# MSkuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwgwDQYJKoZI
# hvcNAQELBQADggIBAKcAKqYjGEczTWMs9z0m7Yo23sgqVF3LyK6gOMz7TCHAJN+F
# vbvZkQ53VkvrZUd1sE6a9ToGldcJnOmBc6iuhBlpvdN1BLBRO8QSTD1433VTj4XC
# Qd737wND1+eqKG3BdjrzbDksEwfG4v57PgrN/T7s7PkEjUGXfIgFQQkr8TQi+/HZ
# Z9kRlNccgeACqlfb4uGPxn5sdhQPoxdMvmC3qG9DONJ5UsS9KtO+bey+ohUTDa9L
# vEToc4Qzy5fuHj2H1JsmCaKG78nXpfWpwBLBxZYSpfml29onN8jcG7KD8nGSS/76
# PDlb2GMQsvv+Ra0JgL6FtGRGgYmHCpM6zVrf4V/a+SoHcC+tcdGYk2aKU5KOlv+f
# FE3n024V+z54tDAKR9z78rejdCBWqfvy5cBUQ9c5+3unHD08BEp7qP2rgpoD856v
# NDgEwO77n7EWT76nl/IyrbK2kjbHLzUMphFpXKnV1fYWJI2+E/0LHvXFGGqF4OvM
# BRxbrJVn03T2Dy5db6s5TzJzSaQvCrXYqA4HKvstQWkqkpvBHTX8M09+/vyRbVXN
# xrPdeXw6oD2Q4DksykCFfn8N2j2LdixE9wG5iilv69dzsvHIN/g9A9+thkAQCVb9
# DUSOTaMIGgsOqDYFjhT6ze9lkhHHGv/EEIkxj9l6S4hqUQyWerFkaUWDXcnZMIIH
# cTCCBVmgAwIBAgITMwAAABXF52ueAptJmQAAAAAAFTANBgkqhkiG9w0BAQsFADCB
# iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl
# ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMp
# TWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEw
# OTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UE
# CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z
# b2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQ
# Q0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOThpkzntHIh
# C3miy9ckeb0O1YLT/e6cBwfSqWxOdcjKNVf2AX9sSuDivbk+F2Az/1xPx2b3lVNx
# WuJ+Slr+uDZnhUYjDLWNE893MsAQGOhgfWpSg0S3po5GawcU88V29YZQ3MFEyHFc
# UTE3oAo4bo3t1w/YJlN8OWECesSq/XJprx2rrPY2vjUmZNqYO7oaezOtgFt+jBAc
# nVL+tuhiJdxqD89d9P6OU8/W7IVWTe/dvI2k45GPsjksUZzpcGkNyjYtcI4xyDUo
# veO0hyTD4MmPfrVUj9z6BVWYbWg7mka97aSueik3rMvrg0XnRm7KMtXAhjBcTyzi
# YrLNueKNiOSWrAFKu75xqRdbZ2De+JKRHh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9
# fvzZnkXftnIv231fgLrbqn427DZM9ituqBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdH
# GO2n6Jl8P0zbr17C89XYcz1DTsEzOUyOArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7X
# KHYC4jMYctenIPDC+hIK12NvDMk2ZItboKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiE
# R9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/
# eKtFtvUeh17aj54WcmnGrnu3tz5q4i6tAgMBAAGjggHdMIIB2TASBgkrBgEEAYI3
# FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQWBBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAd
# BgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEE
# AYI3TIN9AQEwQTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29t
# L3BraW9wcy9Eb2NzL1JlcG9zaXRvcnkuaHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMI
# MBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMB
# Af8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1Ud
# HwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3By
# b2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggrBgEFBQcBAQRO
# MEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2Vy
# dHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MA0GCSqGSIb3DQEBCwUAA4IC
# AQCdVX38Kq3hLB9nATEkW+Geckv8qW/qXBS2Pk5HZHixBpOXPTEztTnXwnE2P9pk
# bHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gng
# ugnue99qb74py27YP0h1AdkY3m2CDPVtI1TkeFN1JFe53Z/zjj3G82jfZfakVqr3
# lbYoVSfQJL1AoL8ZthISEV09J+BAljis9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHC
# gRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTpkbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6
# MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEU
# BHG/ZPkkvnNtyo4JvbMBV0lUZNlz138eW0QBjloZkWsNn6Qo3GcZKCS6OEuabvsh
# VGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJsWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+
# fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrp
# NPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0dFtq0Z4+7X6gMTN9vMvpe784cETRkPHI
# qzqKOghif9lwY1NNje6CbaUFEMFxBmoQtB1VM1izoXBm8qGCAs4wggI3AgEBMIH4
# oYHQpIHNMIHKMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G
# A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUw
# IwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMSYwJAYDVQQLEx1U
# aGFsZXMgVFNTIEVTTjpERDhDLUUzMzctMkZBRTElMCMGA1UEAxMcTWljcm9zb2Z0
# IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUAzdlp6t3ws/bnErbm
# 9c0M+9dvU0CggYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu
# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv
# cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAN
# BgkqhkiG9w0BAQUFAAIFAOWsqGYwIhgPMjAyMjAyMDgxNjI5MjZaGA8yMDIyMDIw
# OTE2MjkyNlowdzA9BgorBgEEAYRZCgQBMS8wLTAKAgUA5ayoZgIBADAKAgEAAgIi
# 7QIB/zAHAgEAAgITmjAKAgUA5a355gIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgor
# BgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUA
# A4GBAKFiWA9Ov0L2rGOdPitNu60BAeztCagpqKBsGnKaaSmjA/XcSXrtIvcUBBsw
# WlflwqnKvPDv3ihqUXsxY84IZZaMecGWzN1mXLh00VftiZuzoLMDlSEtvzmxeWYg
# 4nFHTR5oMqxy06auAM08mVl3P4MywHz4Yp21OIs2KSWQg3DSMYIEDTCCBAkCAQEw
# gZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT
# B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UE
# AxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAGcD6ZNYdKeSygA
# AQAAAZwwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0B
# CRABBDAvBgkqhkiG9w0BCQQxIgQg8oNFH9aqIpzxu29p4VzI2Hh6Hzv2T5HQOlhP
# W5ksqHkwgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCA3D0WFII0syjoRd/Xe
# EIG0WUIKzzuy6P6hORrb0nqmvDCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD
2021-08-26 17:44:10 +08:00
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
2022-02-20 06:07:00 +08:00
# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w
# IFBDQSAyMDEwAhMzAAABnA+mTWHSnksoAAEAAAGcMCIEIOSl7GUcmDL8AU+F0I3A
# iZQESHSkml1QPlOc3M8uqdF0MA0GCSqGSIb3DQEBCwUABIICAHV226l2xxnkjTdh
# Q6wi7u8betBvFNfYYn5cXwy+NoU30JjQ3ynnmNSB+TQVlmcthi8duP8dud+NU5xc
# +1jBTcJTHjWk/TDZUYpB/xNqsgUTJ8fnebVZhioPJhmFJMeQztT4NTD1ZbeJWO4O
# EABDpRqyns2eQptCrKcPIypjvdDeYfkwjgnUEyyaTZRHPZ2kNOLY6kO9feKfntFW
# 8YUIaas2IzI5GcGm+kw1efHveG0WvUpeGuFKsTUA6Jb18mOyamafu32ftD5t5LXV
# +GMgnaJLgdogAAGwK0GiB47YNIvPlKD6bGnyTR3KNYheI1GI38w0knOecCC4NW0k
# 21qROWJdnkKLKTtBX344yTnJbbKoIPzGgyWCzSINS2SD4JHzgTu8dsmMMJWwcEZh
# BLyrzmd/vWrjUFQFXVf6RZmJJVvF28s7LWrpXPVTR8Al75j8KqyZKou4fZvyaHtx
# S6K0jIqavLWqWd4wvx9seCtA0Pz1GWrBZADIRHG8d8cFoY95Z99z84NM81qXB246
# IsxU2iW+96zMJriMNEKaLHzlgFoWsoXLDQhuQItjwIvzwmwygwx0MxqoFYm+lDWG
# 8/KtSh47aZicAlwqNVGy3MZHDnX69kysQsrWoe2wDhJ3eHJ4zRL2WHVagz26L7xF
# oSkLphLv0GiFseJhPvXGQPypnyN8
2020-06-13 01:46:25 +08:00
# SIG # End signature block