mirror of
https://github.com/actions/setup-dotnet.git
synced 2026-05-27 17:28:17 +01:00
Update externals/install-dotnet.ps1 and externals/install-dotnet.sh to the v2026.05.19 release of dotnet/install-scripts. This uptakes the changes from dotnet/install-scripts#697 which preserves archive links during installation (hardlinks on Windows, symlinks on Linux) as part of dotnet/sdk#52182. On Windows, .NET 11.0+ is now installed via tar.gz archives which preserves hardlinks, significantly reducing disk space usage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1700 lines
73 KiB
PowerShell
1700 lines
73 KiB
PowerShell
#
|
|
# 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.
|
|
|
|
Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:
|
|
- The SDK needs to be installed without user interaction and without admin rights.
|
|
- The SDK installation doesn't need to persist across multiple CI runs.
|
|
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.
|
|
|
|
.PARAMETER Channel
|
|
Default: LTS
|
|
Download from the Channel specified. Possible values:
|
|
- STS - the most recent Standard Term Support release
|
|
- LTS - the most recent Long Term Support release
|
|
- 2-part version in a format A.B - represents a specific release
|
|
examples: 2.0, 1.0
|
|
- 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
|
|
Warning: Value "Current" is deprecated for the Channel parameter. Use "STS" instead.
|
|
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, preview, GA.
|
|
Works only in combination with channel. Not applicable for STS and LTS channels and will be ignored if those channels are used.
|
|
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.
|
|
.PARAMETER Version
|
|
Default: latest
|
|
Represents a build version on specific channel. Possible values:
|
|
- latest - the 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
|
|
.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.
|
|
.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 deterministically 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://builds.dotnet.microsoft.com/dotnet
|
|
For internal use only.
|
|
Allows using a different storage to download SDK archives from.
|
|
.PARAMETER UncachedFeed
|
|
For internal use only.
|
|
Allows using a different storage to download SDK archives from.
|
|
.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.
|
|
.PARAMETER ProxyBypassList
|
|
If set with ProxyAddress, will provide the list of comma separated urls that will bypass the proxy
|
|
.PARAMETER SkipNonVersionedFiles
|
|
Default: false
|
|
Skips installing non-versioned files if they already exist, such as dotnet.exe.
|
|
.PARAMETER JSonFile
|
|
Determines the SDK version from a user specified global.json file
|
|
Note: global.json must have a value for 'SDK:Version'
|
|
.PARAMETER DownloadTimeout
|
|
Determines timeout duration in seconds for downloading of the SDK file
|
|
Default: 1200 seconds (20 minutes)
|
|
.PARAMETER KeepZip
|
|
If set, downloaded archive file is kept. Applies to both .zip and .tar.gz formats.
|
|
.PARAMETER ZipPath
|
|
Use that path to store the downloaded archive, generated by default. Applies to both .zip and .tar.gz formats.
|
|
.EXAMPLE
|
|
dotnet-install.ps1 -Version 7.0.401
|
|
Installs the .NET SDK version 7.0.401
|
|
.EXAMPLE
|
|
dotnet-install.ps1 -Channel 8.0 -Quality GA
|
|
Installs the latest GA (general availability) version of the .NET 8.0 SDK
|
|
#>
|
|
[cmdletbinding()]
|
|
param(
|
|
[string]$Channel = "LTS",
|
|
[string]$Quality,
|
|
[string]$Version = "Latest",
|
|
[switch]$Internal,
|
|
[string]$JSonFile,
|
|
[Alias('i')][string]$InstallDir = "<auto>",
|
|
[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,
|
|
[string]$AzureFeed,
|
|
[string]$UncachedFeed,
|
|
[string]$FeedCredential,
|
|
[string]$ProxyAddress,
|
|
[switch]$ProxyUseDefaultCredentials,
|
|
[string[]]$ProxyBypassList = @(),
|
|
[switch]$SkipNonVersionedFiles,
|
|
[int]$DownloadTimeout = 1200,
|
|
[switch]$KeepZip,
|
|
[string]$ZipPath = [System.IO.Path]::combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName()),
|
|
[switch]$Help
|
|
)
|
|
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = "Stop"
|
|
$ProgressPreference = "SilentlyContinue"
|
|
|
|
function Say($str) {
|
|
try {
|
|
Write-Host "dotnet-install: $str"
|
|
}
|
|
catch {
|
|
# Some platforms cannot utilize Write-Host (Azure Functions, for instance). Fall back to Write-Output
|
|
Write-Output "dotnet-install: $str"
|
|
}
|
|
}
|
|
|
|
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 verbose 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"
|
|
}
|
|
}
|
|
|
|
function Say-Verbose($str) {
|
|
try {
|
|
Write-Verbose "dotnet-install: $str"
|
|
}
|
|
catch {
|
|
# Some platforms cannot utilize Write-Verbose (Azure Functions, for instance). Fall back to Write-Output
|
|
Write-Output "dotnet-install: $str"
|
|
}
|
|
}
|
|
|
|
function Measure-Action($name, $block) {
|
|
$time = Measure-Command $block
|
|
$totalSeconds = $time.TotalSeconds
|
|
Say-Verbose "Action '$name' took $totalSeconds seconds"
|
|
}
|
|
|
|
function Get-Remote-File-Size($zipUri) {
|
|
try {
|
|
$response = Invoke-WebRequest -UseBasicParsing -Uri $zipUri -Method Head
|
|
$fileSize = $response.Headers["Content-Length"]
|
|
if ((![string]::IsNullOrEmpty($fileSize))) {
|
|
Say "Remote file $zipUri size is $fileSize bytes."
|
|
|
|
return $fileSize
|
|
}
|
|
}
|
|
catch {
|
|
Say-Verbose "Content-Length header was not extracted for $zipUri."
|
|
}
|
|
|
|
return $null
|
|
}
|
|
|
|
function Say-Invocation($Invocation) {
|
|
$command = $Invocation.MyCommand;
|
|
$args = (($Invocation.BoundParameters.Keys | foreach { "-$_ `"$($Invocation.BoundParameters[$_])`"" }) -join " ")
|
|
Say-Verbose "$command $args"
|
|
}
|
|
|
|
function Invoke-With-Retry([ScriptBlock]$ScriptBlock, [System.Threading.CancellationToken]$cancellationToken = [System.Threading.CancellationToken]::None, [int]$MaxAttempts = 3, [int]$SecondsBetweenAttempts = 1) {
|
|
$Attempts = 0
|
|
$local:startTime = $(get-date)
|
|
|
|
while ($true) {
|
|
try {
|
|
return & $ScriptBlock
|
|
}
|
|
catch {
|
|
$Attempts++
|
|
if (($Attempts -lt $MaxAttempts) -and -not $cancellationToken.IsCancellationRequested) {
|
|
Start-Sleep $SecondsBetweenAttempts
|
|
}
|
|
else {
|
|
$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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function Get-Machine-Architecture() {
|
|
Say-Invocation $MyInvocation
|
|
|
|
# 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
|
|
if ( $ENV:PROCESSOR_ARCHITEW6432 -ne $null ) {
|
|
return $ENV:PROCESSOR_ARCHITEW6432
|
|
}
|
|
|
|
try {
|
|
if ( ((Get-CimInstance -ClassName CIM_OperatingSystem).OSArchitecture) -like "ARM*") {
|
|
if ( [Environment]::Is64BitOperatingSystem ) {
|
|
return "arm64"
|
|
}
|
|
return "arm"
|
|
}
|
|
}
|
|
catch {
|
|
# Machine doesn't support Get-CimInstance
|
|
}
|
|
|
|
return $ENV:PROCESSOR_ARCHITECTURE
|
|
}
|
|
|
|
function Get-CLIArchitecture-From-Architecture([string]$Architecture) {
|
|
Say-Invocation $MyInvocation
|
|
|
|
if ($Architecture -eq "<auto>") {
|
|
$Architecture = Get-Machine-Architecture
|
|
}
|
|
|
|
switch ($Architecture.ToLowerInvariant()) {
|
|
{ ($_ -eq "amd64") -or ($_ -eq "x64") } { return "x64" }
|
|
{ $_ -eq "x86" } { return "x86" }
|
|
{ $_ -eq "arm" } { return "arm" }
|
|
{ $_ -eq "arm64" } { return "arm64" }
|
|
default { throw "Architecture '$Architecture' not supported. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues" }
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
return $FeedCredential
|
|
}
|
|
function Get-NormalizedQuality([string]$Quality) {
|
|
Say-Invocation $MyInvocation
|
|
|
|
if ([string]::IsNullOrEmpty($Quality)) {
|
|
return ""
|
|
}
|
|
|
|
switch ($Quality) {
|
|
{ @("daily", "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, 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.Contains("Current")) {
|
|
Say-Warning 'Value "Current" is deprecated for -Channel option. Use "STS" instead.'
|
|
}
|
|
|
|
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 "sts" } { return "STS" }
|
|
{ $_ -eq "current" } { return "STS" }
|
|
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." }
|
|
}
|
|
}
|
|
|
|
function Test-TarAvailable {
|
|
if ($env:DOTNET_INSTALL_SKIP_TAR -eq "1") {
|
|
Say-Verbose "Skipping tar detection due to DOTNET_INSTALL_SKIP_TAR environment variable."
|
|
return $false
|
|
}
|
|
$tarCommand = Get-Command -Name "tar" -ErrorAction SilentlyContinue
|
|
return $null -ne $tarCommand
|
|
}
|
|
|
|
function Get-FileExtension-For-Version([string]$VersionOrChannel) {
|
|
if (-not $script:TarAvailable) {
|
|
return ".zip"
|
|
}
|
|
if ($VersionOrChannel -match '^(\d+)\.') {
|
|
$majorVersion = [int]$Matches[1]
|
|
if ($majorVersion -ge 11) {
|
|
# Windows tarballs are only available starting with 11.0 preview 3
|
|
if ($VersionOrChannel -match 'preview\.(\d+)') {
|
|
$previewNum = [int]$Matches[1]
|
|
if ($majorVersion -eq 11 -and $previewNum -lt 3) {
|
|
Say-Verbose "Version '$VersionOrChannel' predates tar.gz availability; using zip."
|
|
return ".zip"
|
|
}
|
|
}
|
|
Say-Verbose "Using tar.gz archive format for version/channel '$VersionOrChannel'."
|
|
return ".tar.gz"
|
|
}
|
|
}
|
|
return ".zip"
|
|
}
|
|
|
|
# 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
|
|
function Get-Version-From-LatestVersion-File-Content([string]$VersionText) {
|
|
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.
|
|
}
|
|
}
|
|
|
|
function GetHTTPResponse([Uri] $Uri, [bool]$HeaderOnly, [bool]$DisableRedirect, [bool]$DisableFeedCredential) {
|
|
$cts = New-Object System.Threading.CancellationTokenSource
|
|
|
|
$downloadScript = {
|
|
|
|
$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))) {
|
|
if ($null -ne $DefaultProxy.GetProxy($Uri)) {
|
|
$ProxyAddress = $DefaultProxy.GetProxy($Uri).OriginalString
|
|
}
|
|
else {
|
|
$ProxyAddress = $null
|
|
}
|
|
$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...")
|
|
}
|
|
}
|
|
|
|
$HttpClientHandler = New-Object System.Net.Http.HttpClientHandler
|
|
if ($ProxyAddress) {
|
|
$HttpClientHandler.Proxy = New-Object System.Net.WebProxy -Property @{
|
|
Address = $ProxyAddress;
|
|
UseDefaultCredentials = $ProxyUseDefaultCredentials;
|
|
BypassList = $ProxyBypassList;
|
|
}
|
|
}
|
|
if ($DisableRedirect) {
|
|
$HttpClientHandler.AllowAutoRedirect = $false
|
|
}
|
|
$HttpClient = New-Object System.Net.Http.HttpClient -ArgumentList $HttpClientHandler
|
|
|
|
# Default timeout for HttpClient is 100s. For a 50 MB download this assumes 500 KB/s average, any less will time out
|
|
# Defaulting to 20 minutes allows it to work over much slower connections.
|
|
$HttpClient.Timeout = New-TimeSpan -Seconds $DownloadTimeout
|
|
|
|
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");
|
|
$Response = $Task.GetAwaiter().GetResult();
|
|
|
|
if (($null -eq $Response) -or ((-not $HeaderOnly) -and (-not ($Response.IsSuccessStatusCode)))) {
|
|
# 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"]
|
|
|
|
if (404 -eq [int] $Response.StatusCode) {
|
|
$cts.Cancel()
|
|
}
|
|
}
|
|
|
|
throw $DownloadException
|
|
}
|
|
|
|
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
|
|
}
|
|
finally {
|
|
if ($null -ne $HttpClient) {
|
|
$HttpClient.Dispose()
|
|
}
|
|
}
|
|
}
|
|
|
|
try {
|
|
return Invoke-With-Retry $downloadScript $cts.Token
|
|
}
|
|
finally {
|
|
if ($null -ne $cts) {
|
|
$cts.Dispose()
|
|
}
|
|
}
|
|
}
|
|
|
|
function Get-Version-From-LatestVersion-File([string]$AzureFeed, [string]$Channel) {
|
|
Say-Invocation $MyInvocation
|
|
|
|
$VersionFileUrl = $null
|
|
if ($Runtime -eq "dotnet") {
|
|
$VersionFileUrl = "$AzureFeed/Runtime/$Channel/latest.version"
|
|
}
|
|
elseif ($Runtime -eq "aspnetcore") {
|
|
$VersionFileUrl = "$AzureFeed/aspnetcore/Runtime/$Channel/latest.version"
|
|
}
|
|
elseif ($Runtime -eq "windowsdesktop") {
|
|
$VersionFileUrl = "$AzureFeed/WindowsDesktop/$Channel/latest.version"
|
|
}
|
|
elseif (-not $Runtime) {
|
|
$VersionFileUrl = "$AzureFeed/Sdk/$Channel/latest.version"
|
|
}
|
|
else {
|
|
throw "Invalid value for `$Runtime"
|
|
}
|
|
|
|
Say-Verbose "Constructed latest.version URL: $VersionFileUrl"
|
|
|
|
try {
|
|
$Response = GetHTTPResponse -Uri $VersionFileUrl
|
|
}
|
|
catch {
|
|
Say-Verbose "Failed to download latest.version file."
|
|
throw
|
|
}
|
|
$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." }
|
|
}
|
|
|
|
$VersionInfo = Get-Version-From-LatestVersion-File-Content $VersionText
|
|
|
|
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 {
|
|
Say-Error "Json file unreadable: '$JSonFile'"
|
|
throw
|
|
}
|
|
if ($JSonContent) {
|
|
try {
|
|
$JSonContent.PSObject.Properties | ForEach-Object {
|
|
$PropertyName = $_.Name
|
|
if ($PropertyName -eq "version") {
|
|
$Version = $_.Value
|
|
Say-Verbose "Version = $Version"
|
|
}
|
|
}
|
|
}
|
|
catch {
|
|
Say-Error "Unable to parse the SDK node in '$JSonFile'"
|
|
throw
|
|
}
|
|
}
|
|
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) {
|
|
if ($Version.ToLowerInvariant() -eq "latest") {
|
|
$LatestVersionInfo = Get-Version-From-LatestVersion-File -AzureFeed $AzureFeed -Channel $Channel
|
|
return $LatestVersionInfo.Version
|
|
}
|
|
else {
|
|
return $Version
|
|
}
|
|
}
|
|
else {
|
|
return Parse-Jsonfile-For-Version $JSonFile
|
|
}
|
|
}
|
|
|
|
function Get-Download-Link([string]$AzureFeed, [string]$SpecificVersion, [string]$CLIArchitecture) {
|
|
Say-Invocation $MyInvocation
|
|
|
|
# If anything fails in this lookup it will default to $SpecificVersion
|
|
$SpecificProductVersion = Get-Product-Version -AzureFeed $AzureFeed -SpecificVersion $SpecificVersion
|
|
|
|
# For .NET 11.0+, tar.gz is the preferred archive format on Windows
|
|
$ext = Get-FileExtension-For-Version $SpecificVersion
|
|
|
|
if ($Runtime -eq "dotnet") {
|
|
$PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/dotnet-runtime-$SpecificProductVersion-win-$CLIArchitecture$ext"
|
|
}
|
|
elseif ($Runtime -eq "aspnetcore") {
|
|
$PayloadURL = "$AzureFeed/aspnetcore/Runtime/$SpecificVersion/aspnetcore-runtime-$SpecificProductVersion-win-$CLIArchitecture$ext"
|
|
}
|
|
elseif ($Runtime -eq "windowsdesktop") {
|
|
# The windows desktop runtime is part of the core runtime layout prior to 5.0
|
|
$PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/windowsdesktop-runtime-$SpecificProductVersion-win-$CLIArchitecture$ext"
|
|
if ($SpecificVersion -match '^(\d+)\.(.*)$') {
|
|
$majorVersion = [int]$Matches[1]
|
|
if ($majorVersion -ge 5) {
|
|
$PayloadURL = "$AzureFeed/WindowsDesktop/$SpecificVersion/windowsdesktop-runtime-$SpecificProductVersion-win-$CLIArchitecture$ext"
|
|
}
|
|
}
|
|
}
|
|
elseif (-not $Runtime) {
|
|
$PayloadURL = "$AzureFeed/Sdk/$SpecificVersion/dotnet-sdk-$SpecificProductVersion-win-$CLIArchitecture$ext"
|
|
}
|
|
else {
|
|
throw "Invalid value for `$Runtime"
|
|
}
|
|
|
|
Say-Verbose "Constructed primary named payload URL: $PayloadURL"
|
|
|
|
return $PayloadURL, $SpecificProductVersion
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
function Get-Product-Version([string]$AzureFeed, [string]$SpecificVersion, [string]$PackageDownloadLink) {
|
|
Say-Invocation $MyInvocation
|
|
|
|
# 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."
|
|
}
|
|
}
|
|
catch {
|
|
Say-Verbose "Could not read productVersion.txt at $productVersionTxtUrl (Exception: '$($_.Exception.Message)'. )"
|
|
}
|
|
}
|
|
|
|
# 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
|
|
}
|
|
|
|
$productVersion = Get-ProductVersionFromDownloadLink $PackageDownloadLink $SpecificVersion
|
|
return $productVersion
|
|
}
|
|
|
|
function Get-Product-Version-Url([string]$AzureFeed, [string]$SpecificVersion, [string]$PackageDownloadLink, [bool]$Flattened) {
|
|
Say-Invocation $MyInvocation
|
|
|
|
$majorVersion = $null
|
|
if ($SpecificVersion -match '^(\d+)\.(.*)') {
|
|
$majorVersion = $Matches[1] -as [int]
|
|
}
|
|
|
|
$pvFileName = 'productVersion.txt'
|
|
if ($Flattened) {
|
|
if (-not $Runtime) {
|
|
$pvFileName = 'sdk-productVersion.txt'
|
|
}
|
|
elseif ($Runtime -eq "dotnet") {
|
|
$pvFileName = 'runtime-productVersion.txt'
|
|
}
|
|
else {
|
|
$pvFileName = "$Runtime-productVersion.txt"
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
function Get-User-Share-Path() {
|
|
Say-Invocation $MyInvocation
|
|
|
|
$InstallRoot = $env:DOTNET_INSTALL_DIR
|
|
if (!$InstallRoot) {
|
|
$InstallRoot = "$env:LocalAppData\Microsoft\dotnet"
|
|
}
|
|
elseif ($InstallRoot -like "$env:ProgramFiles\dotnet\?*") {
|
|
Say-Warning "The install root specified by the environment variable DOTNET_INSTALL_DIR points to the sub folder of $env:ProgramFiles\dotnet which is the default dotnet install root using .NET SDK installer. It is better to keep aligned with .NET SDK installer."
|
|
}
|
|
return $InstallRoot
|
|
}
|
|
|
|
function Resolve-Installation-Path([string]$InstallDir) {
|
|
Say-Invocation $MyInvocation
|
|
|
|
if ($InstallDir -eq "<auto>") {
|
|
return Get-User-Share-Path
|
|
}
|
|
return $InstallDir
|
|
}
|
|
|
|
function Test-User-Write-Access([string]$InstallDir) {
|
|
try {
|
|
$tempFileName = [guid]::NewGuid().ToString()
|
|
$tempFilePath = Join-Path -Path $InstallDir -ChildPath $tempFileName
|
|
New-Item -Path $tempFilePath -ItemType File -Force
|
|
Remove-Item $tempFilePath -Force
|
|
return $true
|
|
}
|
|
catch {
|
|
return $false
|
|
}
|
|
}
|
|
|
|
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) {
|
|
# example path with regex: shared/1.0.0-beta-12345/somepath
|
|
$match = [regex]::match($path, "/\d+\.\d+[^/]+/")
|
|
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
|
|
if ($null -ne $dir) {
|
|
$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
|
|
if (($null -eq $PathWithVersion) -Or ($DirectoriesToUnpack -contains $PathWithVersion)) {
|
|
$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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch {
|
|
Say-Error "Failed to extract package. Exception: $_"
|
|
throw;
|
|
}
|
|
finally {
|
|
if ($null -ne $Zip) {
|
|
$Zip.Dispose()
|
|
}
|
|
}
|
|
}
|
|
|
|
function Extract-Dotnet-Package-Tar([string]$TarPath, [string]$OutPath) {
|
|
Say-Invocation $MyInvocation
|
|
|
|
New-Item -ItemType Directory -Force -Path $OutPath | Out-Null
|
|
|
|
# Build an exclude list of versioned directories that already exist locally.
|
|
# This matches the zip extraction behavior: if shared/Microsoft.NETCore.App/11.0.0/
|
|
# already exists, we skip all files under that version to avoid overwriting a
|
|
# previously installed version.
|
|
$excludeArgs = @()
|
|
$versionRegex = '.*/\d+\.\d+[^/]+/'
|
|
|
|
# Read the tarball's file listing to discover versioned directory prefixes
|
|
$tarListing = & tar -tzf $TarPath 2>$null
|
|
$versionedDirs = @{}
|
|
foreach ($entry in $tarListing) {
|
|
$normalizedEntry = $entry.Replace('\', '/')
|
|
$match = [regex]::Match($normalizedEntry, $versionRegex)
|
|
if ($match.Success) {
|
|
$versionedPrefix = $normalizedEntry.Substring(0, $match.Index + $match.Length)
|
|
if (-not $versionedDirs.ContainsKey($versionedPrefix)) {
|
|
$localDir = Join-Path -Path $OutPath -ChildPath $versionedPrefix
|
|
$versionedDirs[$versionedPrefix] = (Test-Path $localDir -PathType Container)
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach ($dir in $versionedDirs.GetEnumerator()) {
|
|
if ($dir.Value) {
|
|
# This versioned directory already exists — exclude it from extraction
|
|
$pattern = $dir.Key + '*'
|
|
$excludeArgs += '--exclude'
|
|
$excludeArgs += $pattern
|
|
Say-Verbose "Excluding pre-existing versioned directory: $($dir.Key)"
|
|
}
|
|
}
|
|
|
|
# Handle non-versioned file override logic.
|
|
# If OverrideNonVersionedFiles is false, exclude non-versioned files that already exist.
|
|
if (-not $OverrideNonVersionedFiles) {
|
|
foreach ($entry in $tarListing) {
|
|
$normalizedEntry = $entry.Replace('\', '/')
|
|
# Skip directory entries (end with /)
|
|
if ($normalizedEntry.EndsWith('/')) { continue }
|
|
$match = [regex]::Match($normalizedEntry, $versionRegex)
|
|
if (-not $match.Success) {
|
|
# Non-versioned file — exclude if it already exists locally
|
|
$localPath = Join-Path -Path $OutPath -ChildPath $normalizedEntry
|
|
if (Test-Path $localPath) {
|
|
$excludeArgs += '--exclude'
|
|
$excludeArgs += $normalizedEntry
|
|
Say-Verbose "Excluding pre-existing non-versioned file: $normalizedEntry"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
try {
|
|
# Extract directly to install root, preserving hard links
|
|
$tarOutput = & tar -xzf $TarPath -C $OutPath @excludeArgs 2>&1
|
|
if ($LASTEXITCODE -ne 0) {
|
|
$tarOutputText = ($tarOutput | Out-String).Trim()
|
|
if ([string]::IsNullOrWhiteSpace($tarOutputText)) {
|
|
throw "Tar extraction failed with exit code $LASTEXITCODE."
|
|
}
|
|
throw "Tar extraction failed with exit code $LASTEXITCODE. tar output: $tarOutputText"
|
|
}
|
|
}
|
|
catch {
|
|
Say-Error "Failed to extract tar package. Exception: $_"
|
|
throw
|
|
}
|
|
}
|
|
|
|
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()
|
|
|
|
ValidateRemoteLocalFileSizes -LocalFileOutPath $OutPath -SourceUri $Source
|
|
}
|
|
finally {
|
|
if ($null -ne $Stream) {
|
|
$Stream.Dispose()
|
|
}
|
|
}
|
|
}
|
|
|
|
function ValidateRemoteLocalFileSizes([string]$LocalFileOutPath, $SourceUri) {
|
|
try {
|
|
$remoteFileSize = Get-Remote-File-Size -zipUri $SourceUri
|
|
$fileSize = [long](Get-Item $LocalFileOutPath).Length
|
|
Say "Downloaded file $SourceUri size is $fileSize bytes."
|
|
|
|
if ((![string]::IsNullOrEmpty($remoteFileSize)) -and !([string]::IsNullOrEmpty($fileSize)) ) {
|
|
if ($remoteFileSize -ne $fileSize) {
|
|
Say "The remote and local file sizes are not equal. Remote file size is $remoteFileSize bytes and local size is $fileSize bytes. The local package may be corrupted."
|
|
}
|
|
else {
|
|
Say "The remote and local file sizes are equal."
|
|
}
|
|
}
|
|
else {
|
|
Say "Either downloaded or local package size can not be measured. One of them may be corrupted."
|
|
}
|
|
}
|
|
catch {
|
|
Say "Either downloaded or local package size can not be measured. One of them may be corrupted."
|
|
}
|
|
}
|
|
|
|
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."
|
|
}
|
|
}
|
|
|
|
function Prepend-Sdk-InstallRoot-To-Path([string]$InstallRoot) {
|
|
$BinPath = Get-Absolute-Path $(Join-Path -Path $InstallRoot -ChildPath "")
|
|
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"
|
|
}
|
|
}
|
|
|
|
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'"
|
|
}
|
|
}
|
|
|
|
function Get-AkaMSDownloadLink([string]$Channel, [string]$Quality, [bool]$Internal, [string]$Product, [string]$Architecture, [string]$FileExtension = ".zip") {
|
|
Say-Invocation $MyInvocation
|
|
|
|
#quality is not supported for LTS or STS channel
|
|
if (![string]::IsNullOrEmpty($Quality) -and (@("LTS", "STS") -contains $Channel)) {
|
|
$Quality = ""
|
|
Say-Warning "Specifying quality for STS 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$FileExtension"
|
|
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
|
|
|
|
}
|
|
|
|
function Get-AkaMsLink-And-Version([string] $NormalizedChannel, [string] $NormalizedQuality, [bool] $Internal, [string] $ProductName, [string] $Architecture) {
|
|
|
|
# When tar is available, try .tar.gz first via aka.ms, then fall back to .zip.
|
|
# This handles symbolic channels (STS/LTS) and numeric channels where tar.gz
|
|
# may not yet be available (e.g. pre-11.0 versions).
|
|
$extensionsToTry = @(".zip")
|
|
if ($script:TarAvailable) {
|
|
$extensionsToTry = @(".tar.gz", ".zip")
|
|
}
|
|
|
|
$AkaMsDownloadLink = $null
|
|
foreach ($ext in $extensionsToTry) {
|
|
$AkaMsDownloadLink = Get-AkaMSDownloadLink -Channel $NormalizedChannel -Quality $NormalizedQuality -Internal $Internal -Product $ProductName -Architecture $Architecture -FileExtension $ext
|
|
if (-not [string]::IsNullOrEmpty($AkaMsDownloadLink)) {
|
|
break
|
|
}
|
|
Say-Verbose "aka.ms link resolution with '$ext' extension failed, trying next format."
|
|
}
|
|
|
|
if ([string]::IsNullOrEmpty($AkaMsDownloadLink)) {
|
|
if (-not [string]::IsNullOrEmpty($NormalizedQuality)) {
|
|
# if quality is specified - exit with error - there is no fallback approach
|
|
Say-Error "Failed to locate the latest version in the channel '$NormalizedChannel' with '$NormalizedQuality' quality for '$ProductName', os: 'win', architecture: '$Architecture'."
|
|
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."
|
|
return ($null, $null, $null)
|
|
}
|
|
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
|
|
$pathParts = $AkaMsDownloadLink.Split('/')
|
|
if ($pathParts.Length -ge 2) {
|
|
$SpecificVersion = $pathParts[$pathParts.Length - 2]
|
|
Say-Verbose "Version: '$SpecificVersion'."
|
|
}
|
|
else {
|
|
Say-Error "Failed to extract the version from download link '$AkaMsDownloadLink'."
|
|
return ($null, $null, $null)
|
|
}
|
|
|
|
#retrieve effective (product) version
|
|
$EffectiveVersion = Get-Product-Version -SpecificVersion $SpecificVersion -PackageDownloadLink $AkaMsDownloadLink
|
|
Say-Verbose "Product version: '$EffectiveVersion'."
|
|
|
|
return ($AkaMsDownloadLink, $SpecificVersion, $EffectiveVersion);
|
|
}
|
|
}
|
|
|
|
function Get-Feeds-To-Use() {
|
|
$feeds = @(
|
|
"https://builds.dotnet.microsoft.com/dotnet"
|
|
"https://ci.dot.net/public"
|
|
)
|
|
|
|
if (-not [string]::IsNullOrEmpty($AzureFeed)) {
|
|
$feeds = @($AzureFeed)
|
|
}
|
|
|
|
if (-not [string]::IsNullOrEmpty($UncachedFeed)) {
|
|
$feeds = @($UncachedFeed)
|
|
}
|
|
|
|
Write-Verbose "Initialized feeds: $feeds"
|
|
|
|
return $feeds
|
|
}
|
|
|
|
function Resolve-AssetName-And-RelativePath([string] $Runtime) {
|
|
|
|
if ($Runtime -eq "dotnet") {
|
|
$assetName = ".NET Core Runtime"
|
|
$dotnetPackageRelativePath = "shared\Microsoft.NETCore.App"
|
|
}
|
|
elseif ($Runtime -eq "aspnetcore") {
|
|
$assetName = "ASP.NET Core Runtime"
|
|
$dotnetPackageRelativePath = "shared\Microsoft.AspNetCore.App"
|
|
}
|
|
elseif ($Runtime -eq "windowsdesktop") {
|
|
$assetName = ".NET Core Windows Desktop Runtime"
|
|
$dotnetPackageRelativePath = "shared\Microsoft.WindowsDesktop.App"
|
|
}
|
|
elseif (-not $Runtime) {
|
|
$assetName = ".NET Core SDK"
|
|
$dotnetPackageRelativePath = "sdk"
|
|
}
|
|
else {
|
|
throw "Invalid value for `$Runtime"
|
|
}
|
|
|
|
return ($assetName, $dotnetPackageRelativePath)
|
|
}
|
|
|
|
function Prepare-Install-Directory {
|
|
$diskSpaceWarning = "Failed to check the disk space. Installation will continue, but it may fail if you do not have enough disk space.";
|
|
|
|
if ($PSVersionTable.PSVersion.Major -lt 7) {
|
|
Say-Verbose $diskSpaceWarning
|
|
return
|
|
}
|
|
|
|
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 $diskSpaceWarning
|
|
}
|
|
|
|
# The check is relevant for PS version >= 7, the result can be irrelevant for older versions. See https://github.com/PowerShell/PowerShell/issues/12442.
|
|
if ( ($null -ne $diskInfo) -and ($diskInfo.Free / 1MB -le 100)) {
|
|
throw "There is not enough disk space on drive ${installDrive}:"
|
|
}
|
|
}
|
|
|
|
if ($Help) {
|
|
Get-Help $PSCommandPath -Examples
|
|
exit
|
|
}
|
|
|
|
Say-Verbose "Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:"
|
|
Say-Verbose "- The SDK needs to be installed without user interaction and without admin rights."
|
|
Say-Verbose "- The SDK installation doesn't need to persist across multiple CI runs."
|
|
Say-Verbose "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"
|
|
}
|
|
|
|
$OverrideNonVersionedFiles = !$SkipNonVersionedFiles
|
|
|
|
Measure-Action "Product discovery" {
|
|
$script:CLIArchitecture = Get-CLIArchitecture-From-Architecture $Architecture
|
|
$script:NormalizedQuality = Get-NormalizedQuality $Quality
|
|
Say-Verbose "Normalized quality: '$NormalizedQuality'"
|
|
$script:NormalizedChannel = Get-NormalizedChannel $Channel
|
|
Say-Verbose "Normalized channel: '$NormalizedChannel'"
|
|
$script:NormalizedProduct = Get-NormalizedProduct $Runtime
|
|
Say-Verbose "Normalized product: '$NormalizedProduct'"
|
|
$script:FeedCredential = ValidateFeedCredential $FeedCredential
|
|
$script:TarAvailable = Test-TarAvailable
|
|
Say-Verbose "Tar available: '$TarAvailable'"
|
|
}
|
|
|
|
$InstallRoot = Resolve-Installation-Path $InstallDir
|
|
if (-not (Test-User-Write-Access $InstallRoot)) {
|
|
Say-Error "The current user doesn't have write access to the installation root '$InstallRoot' to install .NET. Please try specifying a different installation directory using the -InstallDir parameter, or ensure the selected directory has the appropriate permissions."
|
|
throw
|
|
}
|
|
Say-Verbose "InstallRoot: $InstallRoot"
|
|
$ScriptName = $MyInvocation.MyCommand.Name
|
|
($assetName, $dotnetPackageRelativePath) = Resolve-AssetName-And-RelativePath -Runtime $Runtime
|
|
|
|
$feeds = Get-Feeds-To-Use
|
|
$DownloadLinks = @()
|
|
|
|
if ($Version.ToLowerInvariant() -ne "latest" -and -not [string]::IsNullOrEmpty($Quality)) {
|
|
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."
|
|
}
|
|
|
|
# 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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# 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) {
|
|
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: $_"
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($DownloadLinks.count -eq 0) {
|
|
throw "Failed to resolve the exact version number."
|
|
}
|
|
|
|
if ($DryRun) {
|
|
PrintDryRunOutput $MyInvocation $DownloadLinks
|
|
return
|
|
}
|
|
|
|
Measure-Action "Installation directory preparation" { Prepare-Install-Directory }
|
|
|
|
Say-Verbose "Zip path: $ZipPath"
|
|
|
|
$DownloadSucceeded = $false
|
|
$DownloadedLink = $null
|
|
$ErrorMessages = @()
|
|
|
|
foreach ($link in $DownloadLinks) {
|
|
Say-Verbose "Downloading `"$($link.type)`" link $($link.downloadLink)"
|
|
|
|
try {
|
|
Measure-Action "Package download" { DownloadFile -Source $link.downloadLink -OutPath $ZipPath }
|
|
Say-Verbose "Download succeeded."
|
|
$DownloadSucceeded = $true
|
|
$DownloadedLink = $link
|
|
break
|
|
}
|
|
catch {
|
|
$StatusCode = $null
|
|
$ErrorMessage = $null
|
|
|
|
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
|
|
}
|
|
|
|
Say-Verbose "Download failed with status code $StatusCode. Error message: $ErrorMessage"
|
|
$ErrorMessages += "Downloading from `"$($link.type)`" link has failed with error:`nUri: $($link.downloadLink)`nStatusCode: $StatusCode`nError: $ErrorMessage"
|
|
}
|
|
|
|
# This link failed. Clean up before trying the next one.
|
|
SafeRemoveFile -Path $ZipPath
|
|
}
|
|
|
|
if (-not $DownloadSucceeded) {
|
|
foreach ($ErrorMessage in $ErrorMessages) {
|
|
Say-Error $ErrorMessages
|
|
}
|
|
|
|
throw "Could not find `"$assetName`" with version = $($DownloadLinks[0].effectiveVersion)`nRefer to: https://aka.ms/dotnet-os-lifecycle for information on .NET support"
|
|
}
|
|
|
|
Say "Extracting the archive."
|
|
if ($DownloadedLink.downloadLink.EndsWith(".tar.gz")) {
|
|
Measure-Action "Package extraction" { Extract-Dotnet-Package-Tar -TarPath $ZipPath -OutPath $InstallRoot }
|
|
}
|
|
else {
|
|
Measure-Action "Package extraction" { 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.
|
|
if ($DownloadedLink.effectiveVersion -Match "rtm" -or $DownloadedLink.effectiveVersion -Match "servicing") {
|
|
$ReleaseVersion = $DownloadedLink.effectiveVersion.Split("-")[0]
|
|
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) {
|
|
Say-Verbose "Checking installation: version = $($DownloadedLink.effectiveVersion)"
|
|
$isAssetInstalled = Is-Dotnet-Package-Installed -InstallRoot $InstallRoot -RelativePathToPackage $dotnetPackageRelativePath -SpecificVersion $DownloadedLink.effectiveVersion
|
|
}
|
|
|
|
# Version verification failed. More likely something is wrong either with the downloaded content or with the verification algorithm.
|
|
if (!$isAssetInstalled) {
|
|
Say-Error "Failed to verify the version of installed `"$assetName`".`nInstallation source: $($DownloadedLink.downloadLink).`nInstallation location: $InstallRoot.`nReport the bug at https://github.com/dotnet/install-scripts/issues."
|
|
throw "`"$assetName`" with version = $($DownloadedLink.effectiveVersion) failed to install with an unknown error."
|
|
}
|
|
|
|
if (-not $KeepZip) {
|
|
SafeRemoveFile -Path $ZipPath
|
|
}
|
|
|
|
Measure-Action "Setting up shell environment" { Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot }
|
|
|
|
Say "Note that the script does not ensure your Windows version is supported during the installation."
|
|
Say "To check the list of supported versions, go to https://learn.microsoft.com/dotnet/core/install/windows#supported-versions"
|
|
Say "Installed version is $($DownloadedLink.effectiveVersion)"
|
|
Say "Installation finished"
|
|
|
|
# SIG # Begin signature block
|
|
# MIIncQYJKoZIhvcNAQcCoIInYjCCJ14CAQExDzANBglghkgBZQMEAgEFADB5Bgor
|
|
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
|
|
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDmNI7+9b4lSWsX
|
|
# 2kYVvqQHpZeXB6Kt5BepNOcmIqEK1aCCDMkwggYEMIID7KADAgECAhMzAAACHPrN
|
|
# xZvoL37EAAAAAAIcMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNVBAYTAlVTMR4wHAYD
|
|
# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBD
|
|
# b2RlIFNpZ25pbmcgUENBIDIwMjQwHhcNMjYwNDE2MTg1OTQxWhcNMjcwNDE1MTg1
|
|
# OTQxWjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
|
|
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYD
|
|
# VQQDExVNaWNyb3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IB
|
|
# DwAwggEKAoIBAQDVsZfgOKmM31HPfoWOoNEiw0SlCiIxUMC0I9NMWbucKOw/e9lP
|
|
# oAoehQVu6SG65V4EPzrYsnBnFPNoi4/HoOdjhz1qkrEt4I6tEcxXU6oOeY9zGveC
|
|
# /3iBeuhLYxM3M/PkcUoebF+Nednm8OkdSPoDu8imViHPQq/8CQUu0WRR4rE+dMRf
|
|
# rpVqfmNi2qWCX94T4MsepijGVkwE//tJg0ryAiYdHT34LSnlG/RSBZmQRGWZ5g8j
|
|
# qnKjRParSqMft1gvjuUTVgtWNZfgcLFSK5Wa0myrq8OPcgTGGsRgun+tnSS+IxDT
|
|
# xVsAPH1OzvPjwomguByhUe/OcvUN0D5Wmp7xAgMBAAGjggGqMIIBpjAOBgNVHQ8B
|
|
# Af8EBAMCB4AwHwYDVR0lBBgwFgYKKwYBBAGCN0wIAQYIKwYBBQUHAwMwHQYDVR0O
|
|
# BBYEFNoH7a2YDjOSwpkp6DHcmUS7J+0yMFQGA1UdEQRNMEukSTBHMS0wKwYDVQQL
|
|
# EyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxFjAUBgNVBAUT
|
|
# DTIzMDAxMis1MDc1NjkwHwYDVR0jBBgwFoAUf1k/VCHarU/vBeXmo9ctBpQSCDEw
|
|
# YAYDVR0fBFkwVzBVoFOgUYZPaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9w
|
|
# cy9jcmwvTWljcm9zb2Z0JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNy
|
|
# bDBtBggrBgEFBQcBAQRhMF8wXQYIKwYBBQUHMAKGUWh0dHA6Ly93d3cubWljcm9z
|
|
# b2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwQ29kZSUyMFNpZ25pbmcl
|
|
# MjBQQ0ElMjAyMDI0LmNydDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IC
|
|
# AQAUnEqhaRXe0T3hIJjvdQErEkrA/7bByjn6t5IArODkkRjzkYwtKMc2yYj2quaN
|
|
# rLutWw2YZcngKPy1b71YyDJQTy4NDRwaSh9Tw5thrk3NmcPrAHia5vtcBJ1CgtKK
|
|
# 7mQbIcQ22d/N3813ayCDDFewu1+jsZmX+r/aTEqaOM4TVxVtRSkuCy8nAXKuChOK
|
|
# Li/zA4XuH8iEYqIsj2YoNaeSxVmeGiERXpKdo3dDmYi0kO5w2D8VS4c3+9h6gElY
|
|
# BaAAg/dYErBg27qT3vv0zRDJhJufvCNylA8S7/+8H5E/PV5cng6na9VV/w9OV3qu
|
|
# uND6zdGa2EX38Glp50F9AIQk3p2xXmcvorDeM4XJ7UlWYBi6g80J1SSOQnInCYFE
|
|
# msfUNn3+1AaTJKSJL83quKArTac2pKhu0Yzzzrzo6HrsRiQKzpnRBb1/dMa6P3hz
|
|
# 75XbMRBctNsFhZC07WCmjExdLg2eHW5uV0TY8D5+6wozJf7vF3+WHkYPO85Z+BC6
|
|
# U4FkNbYNycZ9cE4j1tXRdyDCfml6c0HWPHjNVDObrv9lKt3qUqFpX38VCqVCyNOO
|
|
# 1UcXfQiVjJw32U2WUKZjt/neJKHEBsm9kFsLuWzkQ53+qcaSaytmsCnk2gOglrlD
|
|
# 5d3kKyvvAw+rzm0lT8K38P6PLxfZQHhu4W8dV7Av8N2ZmDCCBr0wggSloAMCAQIC
|
|
# EzMAAAA5O7Y3Gb8GHWcAAAAAADkwDQYJKoZIhvcNAQEMBQAwgYgxCzAJBgNVBAYT
|
|
# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD
|
|
# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBS
|
|
# b290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDExMB4XDTI0MDgwODIwNTQxOFoX
|
|
# DTM2MDMyMjIyMTMwNFowVzELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFU1pY3Jvc29m
|
|
# dCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQ
|
|
# Q0EgMjAyNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANgBnB7jOMeq
|
|
# lRYHNa265v4IY9fH8TKhemHfPINe1gpLaV3dhg324WwH06LcHbpnsBukCDNitryo
|
|
# 0dtS/EW6I/yEL/bLSY8hKpbfQuWusBPr9qazYcDxCW/qnjb5JsI1s8bNOg3bVATv
|
|
# QVL4tcf03aTycsz8QeCdM0l/yHRObJ9QqazM1r6VPEOJ7LL+uEEb73w6QCuhs89a
|
|
# 1uv1zerOYMnsneRRwCbpyW11IcggU0cRKDDq1pjVJzIbIF6+oiXXbReOsgeI8zu1
|
|
# FyQfK0fVkaya8SmVHQ/tOf23mZ4W9k0Ri22QW9p3UgSC5OUDktKxxcCmGL6tXLfO
|
|
# GSWHIIV4YrTJTT6PNty5REojHJuZHArkF9VnHTERWoTjAzfI3kP+5b4alUdhgAZ7
|
|
# ttOu1bVnXfHaqPYl2rPs20ji03LOVWsh/radgE17es5hL+t6lV0eVHrVhsssROWJ
|
|
# uz2MXMCt7iw7lFPG9LXKGjsmonn2gotGdHIuEg5JnJMJVmixd5LRlkmgYRZKzhxS
|
|
# CwyoGIq0PhaA7Y+VPct5pCHkijcIIDm0nlkK+0KyepolcqGm0T/GYQRMhHJlGOOm
|
|
# VQop36wUVUYklUy++vDWeEgEo4s7hxN6mIbf2MSIQ/iIfMZgJxC69oukMUXCrOC3
|
|
# SkE/xIkgpfl22MM1itkZ35nNXkMolU1lAgMBAAGjggFOMIIBSjAOBgNVHQ8BAf8E
|
|
# BAMCAYYwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFH9ZP1Qh2q1P7wXl5qPX
|
|
# LQaUEggxMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMA8GA1UdEwEB/wQFMAMB
|
|
# Af8wHwYDVR0jBBgwFoAUci06AjGQQ7kUBU7h6qfHMdEjiTQwWgYDVR0fBFMwUTBP
|
|
# oE2gS4ZJaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMv
|
|
# TWljUm9vQ2VyQXV0MjAxMV8yMDExXzAzXzIyLmNybDBeBggrBgEFBQcBAQRSMFAw
|
|
# TgYIKwYBBQUHMAKGQmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMv
|
|
# TWljUm9vQ2VyQXV0MjAxMV8yMDExXzAzXzIyLmNydDANBgkqhkiG9w0BAQwFAAOC
|
|
# AgEAFJQfOChP7onn6fLIMKrSlN1WYKwDFgAddymOUO3FrM8d7B/W/iQ6DxXsDn7D
|
|
# 5W4wMwYeLystcEqfkjz4NURRgazyMu5yRzQh4LqjA4tStTcJh1opExo7nn5PuPBY
|
|
# nbu0+THSuVHTe0VTTPVhily/piFrDo3axQ9P4C+Ol5yet+2gTfekICS5xS+cYfSI
|
|
# vgn0JksVBVMYVI5QFu/qhnLhsEFEUzG8fvv0hjgkO+lkpV9ty6GkN4vdnd7ya6Q6
|
|
# aR9y34aiM1qmxaxBi6OUnyNl6fkuun/diTFnYDLTppOkr/mg5WSfCiDVMNCxtj4w
|
|
# PKC5OmHm1DQIt/MNokbbH3UGsFP1QbzsLocuSqLCvH09Io3fDPTmscR9Y75G4qX7
|
|
# RTX8AdBPo0I6OEojf39zuFZt0qOHm65YWQE69cZM2ueE1MB05dNNgHK9gTE7zKvK
|
|
# /fg8B2qjW88MT/WF5V5uvZGtqa9FSL2RazArA+rDPuf6JGYz4HpgMZHB4S6szWSK
|
|
# YBv0VisCzfxgeU+dquXW9bd0auYlOB58DPcOYKdc3Se94g+xL4pcEhbB54JOgAkw
|
|
# YTu/9dLeH2pDqeJZAABVDWRQCaXfO5LgyKwKCLYXpigrZYCjUSBcr+Ve8PFWMhVT
|
|
# Ql0v4q8J/AUmQN5W4n101cY2L4A7GTQG1h32HHAvfQESWP0xghn+MIIZ+gIBATBu
|
|
# MFcxCzAJBgNVBAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
|
|
# KDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMjQCEzMAAAIc
|
|
# +s3Fm+gvfsQAAAAAAhwwDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwG
|
|
# CisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZI
|
|
# hvcNAQkEMSIEIB5Na9P++kVnSeIy27N4IyRD4N0sjfWHRS1ht0NHdoK/MEIGCisG
|
|
# AQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3
|
|
# Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEBBQAEggEAx05EI+Kk4hoAKG/8oVdr
|
|
# UIq/+sv+E0vpSyWQMTq/yrkfnPWqT2TxNVtcG3ZcQT5/36cjMe1umi1zzy178VRP
|
|
# HKH2jxMOzMqtJPsAI/yAd46EOyTBwayS+27NOvz2r3v8WV0YjuplJb1teHmJtoIf
|
|
# UVC1va6H668UMrR2Mm5l8GvtcrGmDLjq3FLTLeidco7aj7xtkwUhyXvCS/HzoF9G
|
|
# yrYW3NKqIy+KbNXTD8RbwDxKvx3ED9C1SfZi3fi/t1a2wjNsxVqf4XNsO2+JvuuO
|
|
# SGTZOoo/3jqtS22IcV5Z3In0D3+C/rigq0179KhwPtztz4+J9MU5gtGmVayF1MH1
|
|
# M6GCF7AwghesBgorBgEEAYI3AwMBMYIXnDCCF5gGCSqGSIb3DQEHAqCCF4kwgheF
|
|
# AgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFaBgsqhkiG9w0BCRABBKCCAUkEggFFMIIB
|
|
# QQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFlAwQCAQUABCDhOUY7jtQW2mTHjZaf
|
|
# zhylI7IWKlybklUpfwDq0GnW8AIGaeuhCndSGBMyMDI2MDUxNzAwNDk0OC4wNzFa
|
|
# MASAAgH0oIHZpIHWMIHTMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv
|
|
# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0
|
|
# aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0
|
|
# ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjoyQTFBLTA1RTAtRDk0NzElMCMG
|
|
# A1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaCCEf4wggcoMIIFEKAD
|
|
# AgECAhMzAAACEKvN5BYY7zmwAAEAAAIQMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNV
|
|
# BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w
|
|
# HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29m
|
|
# dCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTI1MDgxNDE4NDgxMloXDTI2MTExMzE4
|
|
# NDgxMlowgdMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD
|
|
# VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTAr
|
|
# BgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEnMCUG
|
|
# A1UECxMeblNoaWVsZCBUU1MgRVNOOjJBMUEtMDVFMC1EOTQ3MSUwIwYDVQQDExxN
|
|
# aWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIICIjANBgkqhkiG9w0BAQEFAAOC
|
|
# Ag8AMIICCgKCAgEAjcc4q057ZwIgpKu4pTXWLejvYEduRf+1mIpbiJEMFWWmU2xp
|
|
# ip+zK7xFxKGB1CclUXBU0/ZQZ6LG8H0gI7yvosrsPEI1DPB/XccGCvswKbAKckng
|
|
# OuGTEPGk7K/vEZa9h0Xt02b7m2n9MdIjkLrFl0pDriKyz0QHGpdh93X6+NApfE1T
|
|
# L24Vo0xkeoFGpL3rX9gXhIOF59EMnTd2o45FW/oxMgY9q0y0jGO0HrCLTCZr50e7
|
|
# TZRSNYAy2lyKbvKI2MKlN1wLzJvZbbc//L3s1q3J6KhS0KC2VNEImYdFgVkJej4z
|
|
# ZqHfScTbx9hjFgFpVkJl4xH5VJ8tyJdXE9+vU0k9AaT2QP1Zm3WQmXedSoLjjI7L
|
|
# WznuHwnoGIXLiJMQzPqKqRIFL3wzcrDrZeWgtAdBPbipglZ5CQns6Baj5Mb6a/EZ
|
|
# C9G3faJYK5QVHeE6eLoSEwp1dz5WurLXNPsp0VWplpl/FJb8jrRT/jOoHu85qRcd
|
|
# YpgByU9W7IWPdrthmyfqeAw0omVWN5JxcogYbLo2pANJHlsMdWnxIpN5YwHbGEPC
|
|
# uosBHPk2Xd9+E/pZPQUR6v+D85eEN5A/ZM/xiPpxa8dJZ87BpTvui7/2uflUMJf2
|
|
# Yc9ZLPgEdhQQo0LwMDSTDT48y3sV7Pdo+g5q+MqnJztN/6qt1cgUTe9u+ykCAwEA
|
|
# AaOCAUkwggFFMB0GA1UdDgQWBBSe42+FrpdF2avbUhlk86BLSH5kejAfBgNVHSME
|
|
# GDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBfBgNVHR8EWDBWMFSgUqBQhk5odHRw
|
|
# Oi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBUaW1l
|
|
# LVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmwwbAYIKwYBBQUHAQEEYDBeMFwGCCsG
|
|
# AQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01p
|
|
# Y3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNydDAMBgNVHRMB
|
|
# Af8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMA4GA1UdDwEB/wQEAwIHgDAN
|
|
# BgkqhkiG9w0BAQsFAAOCAgEAvs4rO3oo8czOrxPqnnSEkUVq718QzlrIiy7/EW7J
|
|
# mQXsJoFxHWUF0Ux0PDyKFDRXPJVv29F7kpJkBJJmcQg5HQV7blUXIMWQ1qX0KdtF
|
|
# QXI/MRL77Z+pK5x1jX+tbRkA7a5Ft7vWuRoAEi02HpFH5m/Akh/dfsbx8wOpecJb
|
|
# YvuHuy4aG0/tGzOWFCxMMNhGAIJ4qdV87JnY/uMBmiodlm+Gz357XWW5tg3HrtNZ
|
|
# XuQ0tWUv26ud4nGKJo/oLZHP75p4Rpt7dMdYKUF9AuVFBwxYZYpvgk12tfK+/yOw
|
|
# q84/fjXVCdM83Qnawtbenbk/lnbc9KsZom+GnvA4itAMUpSXFWrcRkqdUQLN+JrG
|
|
# 6fPBoV8+D8U2Q2F4XkiCR6EU9JzYKwTuvL6t3nFuxnkLdNjbTg2/yv2j3WaDuCK5
|
|
# lSPgsndIiH6Bku2Ui3A0aUo6D9z9v+XEuBs9ioVJaOjf/z+Urqg7ESnxG0/T1dKc
|
|
# i7vLQ2XNgWFYO+/OlDjtGoma1ijX4m14N9qgrXTuWEGwgC7hhBgp3id/LAOf9BST
|
|
# WA5lBrilsEoexXBrOn/1wM3rjG0hIsxvF5/YOK78mVRGY6Y7zYJ+uXt4OTOFBwad
|
|
# Pv8MklreQZLPnQPtiwop4rlLUYaPCiD4YUqRNbLp8Sgyo9g0iAcZYznTuc+8Q8ZI
|
|
# rgwwggdxMIIFWaADAgECAhMzAAAAFcXna54Cm0mZAAAAAAAVMA0GCSqGSIb3DQEB
|
|
# CwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
|
|
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYD
|
|
# VQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAe
|
|
# Fw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMyMjVaMHwxCzAJBgNVBAYTAlVTMRMw
|
|
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
|
|
# aWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0
|
|
# YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5OGm
|
|
# TOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51yMo1V/YBf2xK4OK9uT4XYDP/XE/H
|
|
# ZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY6GB9alKDRLemjkZrBxTzxXb1hlDc
|
|
# wUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9cmmvHaus9ja+NSZk2pg7uhp7M62A
|
|
# W36MEBydUv626GIl3GoPz130/o5Tz9bshVZN7928jaTjkY+yOSxRnOlwaQ3KNi1w
|
|
# jjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDuaRr3tpK56KTesy+uDRedGbsoy1cCG
|
|
# MFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74kpEeHT39IM9zfUGaRnXNxF803RKJ
|
|
# 1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2K26oElHovwUDo9Fzpk03dJQcNIIP
|
|
# 8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5TI4CvEJoLhDqhFFG4tG9ahhaYQFz
|
|
# ymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZki1ugpoMhXV8wdJGUlNi5UPkLiWHz
|
|
# NgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9QBXpsxREdcu+N+VLEhReTwDwV2xo3
|
|
# xwgVGD94q0W29R6HXtqPnhZyacaue7e3PmriLq0CAwEAAaOCAd0wggHZMBIGCSsG
|
|
# AQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUCBBYEFCqnUv5kxJq+gpE8RjUpzxD/
|
|
# LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJlpxtTNRnpcjBcBgNVHSAEVTBTMFEG
|
|
# DCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jvc29m
|
|
# dC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9yeS5odG0wEwYDVR0lBAwwCgYIKwYB
|
|
# BQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8G
|
|
# A1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186aGMQw
|
|
# VgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9j
|
|
# cmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUF
|
|
# BwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3Br
|
|
# aS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwDQYJKoZIhvcNAQEL
|
|
# BQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/ypb+pcFLY+TkdkeLEGk5c9MTO1OdfC
|
|
# cTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulmZzpTTd2YurYeeNg2LpypglYAA7AF
|
|
# vonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM9W0jVOR4U3UkV7ndn/OOPcbzaN9l
|
|
# 9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECWOKz3+SmJw7wXsFSFQrP8DJ6LGYnn
|
|
# 8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4FOmRsqlb30mjdAy87JGA0j3mSj5m
|
|
# O0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3UwxTSwethQ/gpY3UA8x1RtnWN0SCyx
|
|
# TkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPXfx5bRAGOWhmRaw2fpCjcZxkoJLo4
|
|
# S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVXVAmxaQFEfnyhYWxz/gq77EFmPWn9
|
|
# y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGConsXHRWJjXD+57XQKBqJC4822rpM
|
|
# +Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU5nR0W2rRnj7tfqAxM328y+l7vzhw
|
|
# RNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEGahC0HVUzWLOhcGbyoYIDWTCCAkEC
|
|
# AQEwggEBoYHZpIHWMIHTMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv
|
|
# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0
|
|
# aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0
|
|
# ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjoyQTFBLTA1RTAtRDk0NzElMCMG
|
|
# A1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIa
|
|
# AxUAOsyf2b6riPKnnXlIgIL2f53PUsKggYMwgYCkfjB8MQswCQYDVQQGEwJVUzET
|
|
# MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV
|
|
# TWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1T
|
|
# dGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQsFAAIFAO2zHvgwIhgPMjAyNjA1MTYx
|
|
# NjUxMDRaGA8yMDI2MDUxNzE2NTEwNFowdzA9BgorBgEEAYRZCgQBMS8wLTAKAgUA
|
|
# 7bMe+AIBADAKAgEAAgIhcwIB/zAHAgEAAgISqjAKAgUA7bRweAIBADA2BgorBgEE
|
|
# AYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYag
|
|
# MA0GCSqGSIb3DQEBCwUAA4IBAQBjb6eELCy7c+QKNzPmI4O0riW248wi0uSI0DKx
|
|
# /ZRwZgOShcyQUcjn6Q/SGMTQ9cqeJe/IhyZbzUCTATtImhIybXreibtT2cAMO4M6
|
|
# 3gGHvd96iu8Q49zOYR182At7wmlUTeMrsj51LpZkWCUy4ZSeOSri83Aong5MKGMF
|
|
# Kt2xnKiSYL2tXIm0zcTpX8xjXMMWFb7wa0CBnHn5ZMYJVtuntxE1B4wJyMZktBS6
|
|
# WkP9XpGCXB1X7VhYM3vvtBFq2vA5neloI7waPPPTU+0hK6IX7+c1U776yDnJW4jc
|
|
# OUJsj8rjwrS+Gc/gPt98V3kMoGvxG0eGEzdjkmblUlhT1FwpMYIEDTCCBAkCAQEw
|
|
# gZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT
|
|
# B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UE
|
|
# AxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAIQq83kFhjvObAA
|
|
# AQAAAhAwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0B
|
|
# CRABBDAvBgkqhkiG9w0BCQQxIgQgn7/AUpcQFhG3OcHi0Aeq56NC1uclpcmcCkGu
|
|
# +HK5un4wgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCDD1SHufsjzY59S1iHU
|
|
# QY9hnsKSrJPg5a9Mc4YnGmPHxjCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD
|
|
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
|
|
# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w
|
|
# IFBDQSAyMDEwAhMzAAACEKvN5BYY7zmwAAEAAAIQMCIEIC5b102d224evVIH68x9
|
|
# lEf8Au5vChR0bDNP4SViDwl6MA0GCSqGSIb3DQEBCwUABIICAHMgBiQOozDAIR+S
|
|
# O28IjquhCukrxIucbxjLUxu4ltpxavA+cedbwkdNPAMFAYEaLHeEYutciK6N5f4R
|
|
# VIJX/OSx2ZL6ac/xB5A/7rdoaHV7PK+F1y807INXMA2HPnJGDVbi7HD7nJp26Fuz
|
|
# P2K3aGE3kK06C2jXsjdqWsAZv32k1daX+OqFGJdOsmmua/qlF0AMWTXL7rsBStOH
|
|
# 5YI/KXtAsd9vvUbqcCMGeGmnRk6V7NmsKbQIkZ3kDk6YfKoZWmSknCKHF6LzCPkI
|
|
# bZSI8PNhcn19VUl9VS4WXGyWeGG5zBkOXnImAIKlaLMxR8Z9/kQVx84TrsDBl6Yh
|
|
# 34t6ISVDkQtre8SBG6fdjyWKmCOSntciVUlEom8ZwkadixIaf8nOoUJSYq5fl+4J
|
|
# HkcUaY79lJKL5DiENwdC4Lt6eNabRo3SPbQE7IQWbWx5h3et1QDitsZ7Gg9yAHaZ
|
|
# LS/t/SIAy+OXLXNxyttcyrbDvFI8A8tFE9P1w+3MC1n2cveqd6Wu07Z4l74Mj0LX
|
|
# yR+0h/KjfE6SPaL+N20E4CjM0bxRKZ+CuFj/A3tykEADdzFtsEfzUzTDkD3cJpOj
|
|
# fojWQNX1ly8Tu3G/WdAFqzrln2y8jCbVdPIqo1ntbu+BbYom1VQqAihYQB855+pG
|
|
# D8bOWbnrvhUua7gDFFDA762iZMxD
|
|
# SIG # End signature block
|