diff --git a/.github/workflows/check-dist.yml b/.github/workflows/check-dist.yml
index 91e3b88..8c06ca8 100644
--- a/.github/workflows/check-dist.yml
+++ b/.github/workflows/check-dist.yml
@@ -45,7 +45,7 @@ jobs:
id: diff
# If index.js was different than expected, upload the expected version as an artifact
- - uses: actions/upload-artifact@v3
+ - uses: actions/upload-artifact@v4
if: ${{ failure() && steps.diff.conclusion == 'failure' }}
with:
name: dist
diff --git a/.github/workflows/test-dotnet.yml b/.github/workflows/test-dotnet.yml
index aea26b3..cf81aff 100644
--- a/.github/workflows/test-dotnet.yml
+++ b/.github/workflows/test-dotnet.yml
@@ -17,7 +17,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- operating-system: [ubuntu-latest, windows-latest, macOS-latest]
+ operating-system: [ubuntu-22.04, windows-latest, macOS-latest]
dotnet-version: ['2.1', '2.2', '3.0', '3.1', '5.0']
steps:
- name: Checkout
diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml
index 629d82a..a64a251 100644
--- a/.github/workflows/workflow.yml
+++ b/.github/workflows/workflow.yml
@@ -39,7 +39,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- operating-system: [ubuntu-latest, windows-latest, macOS-latest]
+ operating-system: [ubuntu-22.04, windows-latest, macOS-latest]
steps:
- name: Checkout
uses: actions/checkout@v3
@@ -62,7 +62,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- operating-system: [ubuntu-latest, windows-latest, macOS-latest]
+ operating-system: [ubuntu-22.04, windows-latest, macos-13]
steps:
- name: Checkout
uses: actions/checkout@v3
@@ -95,7 +95,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- operating-system: [ubuntu-latest, windows-latest, macOS-latest]
+ operating-system: [ubuntu-22.04, windows-latest, macOS-latest]
steps:
- name: Checkout
uses: actions/checkout@v3
@@ -120,7 +120,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- operating-system: [ubuntu-latest, windows-latest, macOS-latest]
+ operating-system: [ubuntu-22.04, windows-latest, macOS-latest]
steps:
- name: Checkout
uses: actions/checkout@v3
@@ -144,7 +144,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- operating-system: [ubuntu-latest, windows-latest, macOS-latest]
+ operating-system: [ubuntu-22.04, windows-latest, macOS-latest]
steps:
- name: Checkout
uses: actions/checkout@v3
@@ -168,7 +168,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- operating-system: [ubuntu-latest, windows-latest, macOS-latest]
+ operating-system: [ubuntu-22.04, windows-latest, macOS-latest]
steps:
- name: Checkout
uses: actions/checkout@v3
@@ -190,18 +190,20 @@ jobs:
run: __tests__/verify-dotnet.ps1 2.2 3.1
test-proxy:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-20.04
container:
- image: mcr.microsoft.com/dotnet/core/runtime-deps:3.0-bionic
+ image: ubuntu:20.04
options: --dns 127.0.0.1
services:
squid-proxy:
- image: datadog/squid:latest
+ image: ubuntu/squid:latest
ports:
- 3128:3128
env:
https_proxy: http://squid-proxy:3128
http_proxy: http://squid-proxy:3128
+ DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: true
+
steps:
- name: Checkout
uses: actions/checkout@v3
@@ -210,7 +212,7 @@ jobs:
- name: Install curl
run: |
apt update
- apt -y install curl
+ apt -y install curl libssl1.1 libssl-dev
- name: Setup dotnet 3.1.201
uses: ./
with:
@@ -222,7 +224,7 @@ jobs:
run: __tests__/verify-dotnet.sh 3.1.201
test-bypass-proxy:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-22.04
env:
https_proxy: http://no-such-proxy:3128
no_proxy: github.com,dotnetcli.blob.core.windows.net,download.visualstudio.microsoft.com,api.nuget.org,dotnetcli.azureedge.net
diff --git a/__tests__/authutil.test.ts b/__tests__/authutil.test.ts
index f2e82d8..2302c8d 100644
--- a/__tests__/authutil.test.ts
+++ b/__tests__/authutil.test.ts
@@ -1,340 +1,336 @@
-import io = require('@actions/io');
-import fs = require('fs');
-import path = require('path');
-
-const fakeSourcesDirForTesting = path.join(
- __dirname,
- 'runner',
- path.join(
- Math.random()
- .toString(36)
- .substring(7)
- ),
- 's'
-);
-
-const invalidNuGetConfig: string = ``;
-
-const emptyNuGetConfig: string = `
-
-`;
-
-const nugetorgNuGetConfig: string = `
-
-
-
-
-`;
-
-const gprnugetorgNuGetConfig: string = `
-
-
-
-
-
-`;
-
-const gprNuGetConfig: string = `
-
-
-
-
-`;
-
-const twogprNuGetConfig: string = `
-
-
-
-
-
-`;
-
-const spaceNuGetConfig: string = `
-
-
-
-
-`;
-
-const azureartifactsNuGetConfig: string = `
-
-
-
-
-`;
-
-const azureartifactsnugetorgNuGetConfig: string = `
-
-
-
-
-
-`;
-
-// We want a NuGet.config one level above the sources directory, so it doesn't trample a user's NuGet.config but is still picked up by NuGet/dotnet.
-const nugetConfigFile = path.join(fakeSourcesDirForTesting, '../nuget.config');
-
-process.env['GITHUB_REPOSITORY'] = 'OwnerName/repo';
-import * as auth from '../src/authutil';
-
-describe('authutil tests', () => {
- beforeEach(async () => {
- await io.rmRF(fakeSourcesDirForTesting);
- await io.mkdirP(fakeSourcesDirForTesting);
- }, 30000);
-
- afterAll(async () => {
- await io.rmRF(fakeSourcesDirForTesting);
- }, 30000);
-
- beforeEach(() => {
- if (fs.existsSync(nugetConfigFile)) {
- fs.unlinkSync(nugetConfigFile);
- }
- process.env['INPUT_OWNER'] = '';
- process.env['NUGET_AUTH_TOKEN'] = '';
- });
-
- it('No existing config, sets up a full NuGet.config with URL and user/PAT for GPR', async () => {
- process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
- await auth.configAuthentication(
- 'https://nuget.pkg.github.com/OwnerName/index.json',
- '',
- fakeSourcesDirForTesting
- );
- expect(fs.existsSync(nugetConfigFile)).toBe(true);
- expect(
- fs.readFileSync(nugetConfigFile, {encoding: 'utf8'})
- ).toMatchSnapshot();
- });
-
- it('No existing config, auth token environment variable not provided, throws', async () => {
- let thrown = false;
- try {
- await auth.configAuthentication(
- 'https://nuget.pkg.github.com/OwnerName/index.json',
- '',
- fakeSourcesDirForTesting
- );
- } catch {
- thrown = true;
- }
- expect(thrown).toBe(true);
- });
-
- it('No existing config, sets up a full NuGet.config with URL and other owner/PAT for GPR', async () => {
- process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
- process.env['INPUT_OWNER'] = 'otherorg';
- await auth.configAuthentication(
- 'https://nuget.pkg.github.com/otherorg/index.json',
- '',
- fakeSourcesDirForTesting
- );
- expect(fs.existsSync(nugetConfigFile)).toBe(true);
- expect(
- fs.readFileSync(nugetConfigFile, {encoding: 'utf8'})
- ).toMatchSnapshot();
- });
-
- it('Existing config (invalid), tries to parse an invalid NuGet.config and throws', async () => {
- process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
- const inputNuGetConfigPath: string = path.join(
- fakeSourcesDirForTesting,
- 'nuget.config'
- );
- fs.writeFileSync(inputNuGetConfigPath, invalidNuGetConfig);
- let thrown = false;
- try {
- await auth.configAuthentication(
- 'https://nuget.pkg.github.com/OwnerName/index.json',
- '',
- fakeSourcesDirForTesting
- );
- } catch {
- thrown = true;
- }
- expect(thrown).toBe(true);
- });
-
- it('Existing config w/ no sources, sets up a full NuGet.config with URL and user/PAT for GPR', async () => {
- process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
- const inputNuGetConfigPath: string = path.join(
- fakeSourcesDirForTesting,
- 'nuget.config'
- );
- fs.writeFileSync(inputNuGetConfigPath, emptyNuGetConfig);
- await auth.configAuthentication(
- 'https://nuget.pkg.github.com/OwnerName/index.json',
- '',
- fakeSourcesDirForTesting
- );
- expect(fs.existsSync(nugetConfigFile)).toBe(true);
- expect(
- fs.readFileSync(nugetConfigFile, {encoding: 'utf8'})
- ).toMatchSnapshot();
- });
-
- it('Existing config w/ no GPR sources, sets up a full NuGet.config with URL and user/PAT for GPR', async () => {
- process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
- const inputNuGetConfigPath: string = path.join(
- fakeSourcesDirForTesting,
- 'nuget.config'
- );
- fs.writeFileSync(inputNuGetConfigPath, nugetorgNuGetConfig);
- await auth.configAuthentication(
- 'https://nuget.pkg.github.com/OwnerName/index.json',
- '',
- fakeSourcesDirForTesting
- );
- expect(fs.existsSync(nugetConfigFile)).toBe(true);
- expect(
- fs.readFileSync(nugetConfigFile, {encoding: 'utf8'})
- ).toMatchSnapshot();
- });
-
- it('Existing config w/ only GPR source, sets up a partial NuGet.config user/PAT for GPR', async () => {
- process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
- const inputNuGetConfigPath: string = path.join(
- fakeSourcesDirForTesting,
- 'nuget.config'
- );
- fs.writeFileSync(inputNuGetConfigPath, gprNuGetConfig);
- await auth.configAuthentication(
- 'https://nuget.pkg.github.com/OwnerName/index.json',
- '',
- fakeSourcesDirForTesting
- );
- expect(fs.existsSync(nugetConfigFile)).toBe(true);
- expect(
- fs.readFileSync(nugetConfigFile, {encoding: 'utf8'})
- ).toMatchSnapshot();
- });
-
- it('Existing config w/ GPR source and NuGet.org, sets up a partial NuGet.config user/PAT for GPR', async () => {
- process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
- const inputNuGetConfigPath: string = path.join(
- fakeSourcesDirForTesting,
- 'nuget.config'
- );
- fs.writeFileSync(inputNuGetConfigPath, gprnugetorgNuGetConfig);
- await auth.configAuthentication(
- 'https://nuget.pkg.github.com/OwnerName/index.json',
- '',
- fakeSourcesDirForTesting
- );
- expect(fs.existsSync(nugetConfigFile)).toBe(true);
- expect(
- fs.readFileSync(nugetConfigFile, {encoding: 'utf8'})
- ).toMatchSnapshot();
- });
-
- it('Existing config w/ two GPR sources, sets up a partial NuGet.config user/PAT for GPR', async () => {
- process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
- const inputNuGetConfigPath: string = path.join(
- fakeSourcesDirForTesting,
- 'nuget.config'
- );
- fs.writeFileSync(inputNuGetConfigPath, twogprNuGetConfig);
- await auth.configAuthentication(
- 'https://nuget.pkg.github.com',
- '',
- fakeSourcesDirForTesting
- );
- expect(fs.existsSync(nugetConfigFile)).toBe(true);
- expect(
- fs.readFileSync(nugetConfigFile, {encoding: 'utf8'})
- ).toMatchSnapshot();
- });
-
- it('Existing config w/ spaces in key, throws for now', async () => {
- process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
- const inputNuGetConfigPath: string = path.join(
- fakeSourcesDirForTesting,
- 'nuget.config'
- );
- fs.writeFileSync(inputNuGetConfigPath, spaceNuGetConfig);
- let thrown = false;
- try {
- await auth.configAuthentication(
- 'https://nuget.pkg.github.com/OwnerName/index.json',
- '',
- fakeSourcesDirForTesting
- );
- } catch {
- thrown = true;
- }
- expect(thrown).toBe(true);
- });
-
- it('Existing config not in repo root, sets up a partial NuGet.config user/PAT for GPR', async () => {
- process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
- const inputNuGetConfigDirectory: string = path.join(
- fakeSourcesDirForTesting,
- 'subfolder'
- );
- const inputNuGetConfigPath: string = path.join(
- inputNuGetConfigDirectory,
- 'nuget.config'
- );
- fs.mkdirSync(inputNuGetConfigDirectory, {recursive: true});
- fs.writeFileSync(inputNuGetConfigPath, gprNuGetConfig);
- await auth.configAuthentication(
- 'https://nuget.pkg.github.com/OwnerName/index.json',
- 'subfolder/nuget.config',
- fakeSourcesDirForTesting
- );
- expect(fs.existsSync(nugetConfigFile)).toBe(true);
- expect(
- fs.readFileSync(nugetConfigFile, {encoding: 'utf8'})
- ).toMatchSnapshot();
- });
-
- it('Existing config w/ only Azure Artifacts source, sets up a partial NuGet.config user/PAT for GPR', async () => {
- process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
- const inputNuGetConfigPath: string = path.join(
- fakeSourcesDirForTesting,
- 'nuget.config'
- );
- fs.writeFileSync(inputNuGetConfigPath, azureartifactsNuGetConfig);
- await auth.configAuthentication(
- 'https://pkgs.dev.azure.com/amullans/_packaging/GitHubBuilds/nuget/v3/index.json',
- '',
- fakeSourcesDirForTesting
- );
- expect(fs.existsSync(nugetConfigFile)).toBe(true);
- expect(
- fs.readFileSync(nugetConfigFile, {encoding: 'utf8'})
- ).toMatchSnapshot();
- });
-
- it('Existing config w/ Azure Artifacts source and NuGet.org, sets up a partial NuGet.config user/PAT for GPR', async () => {
- process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
- const inputNuGetConfigPath: string = path.join(
- fakeSourcesDirForTesting,
- 'nuget.config'
- );
- fs.writeFileSync(inputNuGetConfigPath, azureartifactsnugetorgNuGetConfig);
- await auth.configAuthentication(
- 'https://pkgs.dev.azure.com/amullans/_packaging/GitHubBuilds/nuget/v3/index.json',
- '',
- fakeSourcesDirForTesting
- );
- expect(fs.existsSync(nugetConfigFile)).toBe(true);
- expect(
- fs.readFileSync(nugetConfigFile, {encoding: 'utf8'})
- ).toMatchSnapshot();
- });
-
- it('No existing config, sets up a full NuGet.config with URL and token for other source', async () => {
- process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
- await auth.configAuthentication(
- 'https://pkgs.dev.azure.com/amullans/_packaging/GitHubBuilds/nuget/v3/index.json',
- '',
- fakeSourcesDirForTesting
- );
- expect(fs.existsSync(nugetConfigFile)).toBe(true);
- expect(
- fs.readFileSync(nugetConfigFile, {encoding: 'utf8'})
- ).toMatchSnapshot();
- });
-});
+import io = require('@actions/io');
+import fs = require('fs');
+import path = require('path');
+
+const fakeSourcesDirForTesting = path.join(
+ __dirname,
+ 'runner',
+ path.join(Math.random().toString(36).substring(7)),
+ 's'
+);
+
+const invalidNuGetConfig: string = ``;
+
+const emptyNuGetConfig: string = `
+
+`;
+
+const nugetorgNuGetConfig: string = `
+
+
+
+
+`;
+
+const gprnugetorgNuGetConfig: string = `
+
+
+
+
+
+`;
+
+const gprNuGetConfig: string = `
+
+
+
+
+`;
+
+const twogprNuGetConfig: string = `
+
+
+
+
+
+`;
+
+const spaceNuGetConfig: string = `
+
+
+
+
+`;
+
+const azureartifactsNuGetConfig: string = `
+
+
+
+
+`;
+
+const azureartifactsnugetorgNuGetConfig: string = `
+
+
+
+
+
+`;
+
+// We want a NuGet.config one level above the sources directory, so it doesn't trample a user's NuGet.config but is still picked up by NuGet/dotnet.
+const nugetConfigFile = path.join(fakeSourcesDirForTesting, '../nuget.config');
+
+process.env['GITHUB_REPOSITORY'] = 'OwnerName/repo';
+import * as auth from '../src/authutil';
+
+describe('authutil tests', () => {
+ beforeEach(async () => {
+ await io.rmRF(fakeSourcesDirForTesting);
+ await io.mkdirP(fakeSourcesDirForTesting);
+ }, 30000);
+
+ afterAll(async () => {
+ await io.rmRF(fakeSourcesDirForTesting);
+ }, 30000);
+
+ beforeEach(() => {
+ if (fs.existsSync(nugetConfigFile)) {
+ fs.unlinkSync(nugetConfigFile);
+ }
+ process.env['INPUT_OWNER'] = '';
+ process.env['NUGET_AUTH_TOKEN'] = '';
+ });
+
+ it('No existing config, sets up a full NuGet.config with URL and user/PAT for GPR', async () => {
+ process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
+ await auth.configAuthentication(
+ 'https://nuget.pkg.github.com/OwnerName/index.json',
+ '',
+ fakeSourcesDirForTesting
+ );
+ expect(fs.existsSync(nugetConfigFile)).toBe(true);
+ expect(
+ fs.readFileSync(nugetConfigFile, {encoding: 'utf8'})
+ ).toMatchSnapshot();
+ });
+
+ it('No existing config, auth token environment variable not provided, throws', async () => {
+ let thrown = false;
+ try {
+ await auth.configAuthentication(
+ 'https://nuget.pkg.github.com/OwnerName/index.json',
+ '',
+ fakeSourcesDirForTesting
+ );
+ } catch {
+ thrown = true;
+ }
+ expect(thrown).toBe(true);
+ });
+
+ it('No existing config, sets up a full NuGet.config with URL and other owner/PAT for GPR', async () => {
+ process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
+ process.env['INPUT_OWNER'] = 'otherorg';
+ await auth.configAuthentication(
+ 'https://nuget.pkg.github.com/otherorg/index.json',
+ '',
+ fakeSourcesDirForTesting
+ );
+ expect(fs.existsSync(nugetConfigFile)).toBe(true);
+ expect(
+ fs.readFileSync(nugetConfigFile, {encoding: 'utf8'})
+ ).toMatchSnapshot();
+ });
+
+ it('Existing config (invalid), tries to parse an invalid NuGet.config and throws', async () => {
+ process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
+ const inputNuGetConfigPath: string = path.join(
+ fakeSourcesDirForTesting,
+ 'nuget.config'
+ );
+ fs.writeFileSync(inputNuGetConfigPath, invalidNuGetConfig);
+ let thrown = false;
+ try {
+ await auth.configAuthentication(
+ 'https://nuget.pkg.github.com/OwnerName/index.json',
+ '',
+ fakeSourcesDirForTesting
+ );
+ } catch {
+ thrown = true;
+ }
+ expect(thrown).toBe(true);
+ });
+
+ it('Existing config w/ no sources, sets up a full NuGet.config with URL and user/PAT for GPR', async () => {
+ process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
+ const inputNuGetConfigPath: string = path.join(
+ fakeSourcesDirForTesting,
+ 'nuget.config'
+ );
+ fs.writeFileSync(inputNuGetConfigPath, emptyNuGetConfig);
+ await auth.configAuthentication(
+ 'https://nuget.pkg.github.com/OwnerName/index.json',
+ '',
+ fakeSourcesDirForTesting
+ );
+ expect(fs.existsSync(nugetConfigFile)).toBe(true);
+ expect(
+ fs.readFileSync(nugetConfigFile, {encoding: 'utf8'})
+ ).toMatchSnapshot();
+ });
+
+ it('Existing config w/ no GPR sources, sets up a full NuGet.config with URL and user/PAT for GPR', async () => {
+ process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
+ const inputNuGetConfigPath: string = path.join(
+ fakeSourcesDirForTesting,
+ 'nuget.config'
+ );
+ fs.writeFileSync(inputNuGetConfigPath, nugetorgNuGetConfig);
+ await auth.configAuthentication(
+ 'https://nuget.pkg.github.com/OwnerName/index.json',
+ '',
+ fakeSourcesDirForTesting
+ );
+ expect(fs.existsSync(nugetConfigFile)).toBe(true);
+ expect(
+ fs.readFileSync(nugetConfigFile, {encoding: 'utf8'})
+ ).toMatchSnapshot();
+ });
+
+ it('Existing config w/ only GPR source, sets up a partial NuGet.config user/PAT for GPR', async () => {
+ process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
+ const inputNuGetConfigPath: string = path.join(
+ fakeSourcesDirForTesting,
+ 'nuget.config'
+ );
+ fs.writeFileSync(inputNuGetConfigPath, gprNuGetConfig);
+ await auth.configAuthentication(
+ 'https://nuget.pkg.github.com/OwnerName/index.json',
+ '',
+ fakeSourcesDirForTesting
+ );
+ expect(fs.existsSync(nugetConfigFile)).toBe(true);
+ expect(
+ fs.readFileSync(nugetConfigFile, {encoding: 'utf8'})
+ ).toMatchSnapshot();
+ });
+
+ it('Existing config w/ GPR source and NuGet.org, sets up a partial NuGet.config user/PAT for GPR', async () => {
+ process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
+ const inputNuGetConfigPath: string = path.join(
+ fakeSourcesDirForTesting,
+ 'nuget.config'
+ );
+ fs.writeFileSync(inputNuGetConfigPath, gprnugetorgNuGetConfig);
+ await auth.configAuthentication(
+ 'https://nuget.pkg.github.com/OwnerName/index.json',
+ '',
+ fakeSourcesDirForTesting
+ );
+ expect(fs.existsSync(nugetConfigFile)).toBe(true);
+ expect(
+ fs.readFileSync(nugetConfigFile, {encoding: 'utf8'})
+ ).toMatchSnapshot();
+ });
+
+ it('Existing config w/ two GPR sources, sets up a partial NuGet.config user/PAT for GPR', async () => {
+ process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
+ const inputNuGetConfigPath: string = path.join(
+ fakeSourcesDirForTesting,
+ 'nuget.config'
+ );
+ fs.writeFileSync(inputNuGetConfigPath, twogprNuGetConfig);
+ await auth.configAuthentication(
+ 'https://nuget.pkg.github.com',
+ '',
+ fakeSourcesDirForTesting
+ );
+ expect(fs.existsSync(nugetConfigFile)).toBe(true);
+ expect(
+ fs.readFileSync(nugetConfigFile, {encoding: 'utf8'})
+ ).toMatchSnapshot();
+ });
+
+ it('Existing config w/ spaces in key, throws for now', async () => {
+ process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
+ const inputNuGetConfigPath: string = path.join(
+ fakeSourcesDirForTesting,
+ 'nuget.config'
+ );
+ fs.writeFileSync(inputNuGetConfigPath, spaceNuGetConfig);
+ let thrown = false;
+ try {
+ await auth.configAuthentication(
+ 'https://nuget.pkg.github.com/OwnerName/index.json',
+ '',
+ fakeSourcesDirForTesting
+ );
+ } catch {
+ thrown = true;
+ }
+ expect(thrown).toBe(true);
+ });
+
+ it('Existing config not in repo root, sets up a partial NuGet.config user/PAT for GPR', async () => {
+ process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
+ const inputNuGetConfigDirectory: string = path.join(
+ fakeSourcesDirForTesting,
+ 'subfolder'
+ );
+ const inputNuGetConfigPath: string = path.join(
+ inputNuGetConfigDirectory,
+ 'nuget.config'
+ );
+ fs.mkdirSync(inputNuGetConfigDirectory, {recursive: true});
+ fs.writeFileSync(inputNuGetConfigPath, gprNuGetConfig);
+ await auth.configAuthentication(
+ 'https://nuget.pkg.github.com/OwnerName/index.json',
+ 'subfolder/nuget.config',
+ fakeSourcesDirForTesting
+ );
+ expect(fs.existsSync(nugetConfigFile)).toBe(true);
+ expect(
+ fs.readFileSync(nugetConfigFile, {encoding: 'utf8'})
+ ).toMatchSnapshot();
+ });
+
+ it('Existing config w/ only Azure Artifacts source, sets up a partial NuGet.config user/PAT for GPR', async () => {
+ process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
+ const inputNuGetConfigPath: string = path.join(
+ fakeSourcesDirForTesting,
+ 'nuget.config'
+ );
+ fs.writeFileSync(inputNuGetConfigPath, azureartifactsNuGetConfig);
+ await auth.configAuthentication(
+ 'https://pkgs.dev.azure.com/amullans/_packaging/GitHubBuilds/nuget/v3/index.json',
+ '',
+ fakeSourcesDirForTesting
+ );
+ expect(fs.existsSync(nugetConfigFile)).toBe(true);
+ expect(
+ fs.readFileSync(nugetConfigFile, {encoding: 'utf8'})
+ ).toMatchSnapshot();
+ });
+
+ it('Existing config w/ Azure Artifacts source and NuGet.org, sets up a partial NuGet.config user/PAT for GPR', async () => {
+ process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
+ const inputNuGetConfigPath: string = path.join(
+ fakeSourcesDirForTesting,
+ 'nuget.config'
+ );
+ fs.writeFileSync(inputNuGetConfigPath, azureartifactsnugetorgNuGetConfig);
+ await auth.configAuthentication(
+ 'https://pkgs.dev.azure.com/amullans/_packaging/GitHubBuilds/nuget/v3/index.json',
+ '',
+ fakeSourcesDirForTesting
+ );
+ expect(fs.existsSync(nugetConfigFile)).toBe(true);
+ expect(
+ fs.readFileSync(nugetConfigFile, {encoding: 'utf8'})
+ ).toMatchSnapshot();
+ });
+
+ it('No existing config, sets up a full NuGet.config with URL and token for other source', async () => {
+ process.env['NUGET_AUTH_TOKEN'] = 'TEST_FAKE_AUTH_TOKEN';
+ await auth.configAuthentication(
+ 'https://pkgs.dev.azure.com/amullans/_packaging/GitHubBuilds/nuget/v3/index.json',
+ '',
+ fakeSourcesDirForTesting
+ );
+ expect(fs.existsSync(nugetConfigFile)).toBe(true);
+ expect(
+ fs.readFileSync(nugetConfigFile, {encoding: 'utf8'})
+ ).toMatchSnapshot();
+ });
+});
diff --git a/dist/index.js b/dist/index.js
index 553f991..47ab8a8 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -379,7 +379,13 @@ class DotnetCoreInstaller {
}
getReleasesJsonUrl(httpClient, versionParts) {
return __awaiter(this, void 0, void 0, function* () {
- const response = yield httpClient.getJson(DotNetCoreIndexUrl);
+ let response;
+ try {
+ response = yield httpClient.getJson(DotNetCoreIndexUrl);
+ }
+ catch (error) {
+ response = yield httpClient.getJson(DotnetCoreIndexFallbackUrl);
+ }
const result = response.result || {};
let releasesInfo = result['releases-index'];
releasesInfo = releasesInfo.filter((info) => {
@@ -403,7 +409,7 @@ class DotnetCoreInstaller {
}
}
exports.DotnetCoreInstaller = DotnetCoreInstaller;
-const DotNetCoreIndexUrl = 'https://dotnetcli.blob.core.windows.net/dotnet/release-metadata/releases-index.json';
+const DotNetCoreIndexUrl = 'https://builds.dotnet.microsoft.com/dotnet/release-metadata/releases-index.json';
/***/ }),
@@ -15845,1303 +15851,1303 @@ exports.parseURL = __nccwpck_require__(33).parseURL;
/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
"use strict";
-
-const punycode = __nccwpck_require__(5477);
-const tr46 = __nccwpck_require__(2299);
-
-const specialSchemes = {
- ftp: 21,
- file: null,
- gopher: 70,
- http: 80,
- https: 443,
- ws: 80,
- wss: 443
-};
-
-const failure = Symbol("failure");
-
-function countSymbols(str) {
- return punycode.ucs2.decode(str).length;
-}
-
-function at(input, idx) {
- const c = input[idx];
- return isNaN(c) ? undefined : String.fromCodePoint(c);
-}
-
-function isASCIIDigit(c) {
- return c >= 0x30 && c <= 0x39;
-}
-
-function isASCIIAlpha(c) {
- return (c >= 0x41 && c <= 0x5A) || (c >= 0x61 && c <= 0x7A);
-}
-
-function isASCIIAlphanumeric(c) {
- return isASCIIAlpha(c) || isASCIIDigit(c);
-}
-
-function isASCIIHex(c) {
- return isASCIIDigit(c) || (c >= 0x41 && c <= 0x46) || (c >= 0x61 && c <= 0x66);
-}
-
-function isSingleDot(buffer) {
- return buffer === "." || buffer.toLowerCase() === "%2e";
-}
-
-function isDoubleDot(buffer) {
- buffer = buffer.toLowerCase();
- return buffer === ".." || buffer === "%2e." || buffer === ".%2e" || buffer === "%2e%2e";
-}
-
-function isWindowsDriveLetterCodePoints(cp1, cp2) {
- return isASCIIAlpha(cp1) && (cp2 === 58 || cp2 === 124);
-}
-
-function isWindowsDriveLetterString(string) {
- return string.length === 2 && isASCIIAlpha(string.codePointAt(0)) && (string[1] === ":" || string[1] === "|");
-}
-
-function isNormalizedWindowsDriveLetterString(string) {
- return string.length === 2 && isASCIIAlpha(string.codePointAt(0)) && string[1] === ":";
-}
-
-function containsForbiddenHostCodePoint(string) {
- return string.search(/\u0000|\u0009|\u000A|\u000D|\u0020|#|%|\/|:|\?|@|\[|\\|\]/) !== -1;
-}
-
-function containsForbiddenHostCodePointExcludingPercent(string) {
- return string.search(/\u0000|\u0009|\u000A|\u000D|\u0020|#|\/|:|\?|@|\[|\\|\]/) !== -1;
-}
-
-function isSpecialScheme(scheme) {
- return specialSchemes[scheme] !== undefined;
-}
-
-function isSpecial(url) {
- return isSpecialScheme(url.scheme);
-}
-
-function defaultPort(scheme) {
- return specialSchemes[scheme];
-}
-
-function percentEncode(c) {
- let hex = c.toString(16).toUpperCase();
- if (hex.length === 1) {
- hex = "0" + hex;
- }
-
- return "%" + hex;
-}
-
-function utf8PercentEncode(c) {
- const buf = new Buffer(c);
-
- let str = "";
-
- for (let i = 0; i < buf.length; ++i) {
- str += percentEncode(buf[i]);
- }
-
- return str;
-}
-
-function utf8PercentDecode(str) {
- const input = new Buffer(str);
- const output = [];
- for (let i = 0; i < input.length; ++i) {
- if (input[i] !== 37) {
- output.push(input[i]);
- } else if (input[i] === 37 && isASCIIHex(input[i + 1]) && isASCIIHex(input[i + 2])) {
- output.push(parseInt(input.slice(i + 1, i + 3).toString(), 16));
- i += 2;
- } else {
- output.push(input[i]);
- }
- }
- return new Buffer(output).toString();
-}
-
-function isC0ControlPercentEncode(c) {
- return c <= 0x1F || c > 0x7E;
-}
-
-const extraPathPercentEncodeSet = new Set([32, 34, 35, 60, 62, 63, 96, 123, 125]);
-function isPathPercentEncode(c) {
- return isC0ControlPercentEncode(c) || extraPathPercentEncodeSet.has(c);
-}
-
-const extraUserinfoPercentEncodeSet =
- new Set([47, 58, 59, 61, 64, 91, 92, 93, 94, 124]);
-function isUserinfoPercentEncode(c) {
- return isPathPercentEncode(c) || extraUserinfoPercentEncodeSet.has(c);
-}
-
-function percentEncodeChar(c, encodeSetPredicate) {
- const cStr = String.fromCodePoint(c);
-
- if (encodeSetPredicate(c)) {
- return utf8PercentEncode(cStr);
- }
-
- return cStr;
-}
-
-function parseIPv4Number(input) {
- let R = 10;
-
- if (input.length >= 2 && input.charAt(0) === "0" && input.charAt(1).toLowerCase() === "x") {
- input = input.substring(2);
- R = 16;
- } else if (input.length >= 2 && input.charAt(0) === "0") {
- input = input.substring(1);
- R = 8;
- }
-
- if (input === "") {
- return 0;
- }
-
- const regex = R === 10 ? /[^0-9]/ : (R === 16 ? /[^0-9A-Fa-f]/ : /[^0-7]/);
- if (regex.test(input)) {
- return failure;
- }
-
- return parseInt(input, R);
-}
-
-function parseIPv4(input) {
- const parts = input.split(".");
- if (parts[parts.length - 1] === "") {
- if (parts.length > 1) {
- parts.pop();
- }
- }
-
- if (parts.length > 4) {
- return input;
- }
-
- const numbers = [];
- for (const part of parts) {
- if (part === "") {
- return input;
- }
- const n = parseIPv4Number(part);
- if (n === failure) {
- return input;
- }
-
- numbers.push(n);
- }
-
- for (let i = 0; i < numbers.length - 1; ++i) {
- if (numbers[i] > 255) {
- return failure;
- }
- }
- if (numbers[numbers.length - 1] >= Math.pow(256, 5 - numbers.length)) {
- return failure;
- }
-
- let ipv4 = numbers.pop();
- let counter = 0;
-
- for (const n of numbers) {
- ipv4 += n * Math.pow(256, 3 - counter);
- ++counter;
- }
-
- return ipv4;
-}
-
-function serializeIPv4(address) {
- let output = "";
- let n = address;
-
- for (let i = 1; i <= 4; ++i) {
- output = String(n % 256) + output;
- if (i !== 4) {
- output = "." + output;
- }
- n = Math.floor(n / 256);
- }
-
- return output;
-}
-
-function parseIPv6(input) {
- const address = [0, 0, 0, 0, 0, 0, 0, 0];
- let pieceIndex = 0;
- let compress = null;
- let pointer = 0;
-
- input = punycode.ucs2.decode(input);
-
- if (input[pointer] === 58) {
- if (input[pointer + 1] !== 58) {
- return failure;
- }
-
- pointer += 2;
- ++pieceIndex;
- compress = pieceIndex;
- }
-
- while (pointer < input.length) {
- if (pieceIndex === 8) {
- return failure;
- }
-
- if (input[pointer] === 58) {
- if (compress !== null) {
- return failure;
- }
- ++pointer;
- ++pieceIndex;
- compress = pieceIndex;
- continue;
- }
-
- let value = 0;
- let length = 0;
-
- while (length < 4 && isASCIIHex(input[pointer])) {
- value = value * 0x10 + parseInt(at(input, pointer), 16);
- ++pointer;
- ++length;
- }
-
- if (input[pointer] === 46) {
- if (length === 0) {
- return failure;
- }
-
- pointer -= length;
-
- if (pieceIndex > 6) {
- return failure;
- }
-
- let numbersSeen = 0;
-
- while (input[pointer] !== undefined) {
- let ipv4Piece = null;
-
- if (numbersSeen > 0) {
- if (input[pointer] === 46 && numbersSeen < 4) {
- ++pointer;
- } else {
- return failure;
- }
- }
-
- if (!isASCIIDigit(input[pointer])) {
- return failure;
- }
-
- while (isASCIIDigit(input[pointer])) {
- const number = parseInt(at(input, pointer));
- if (ipv4Piece === null) {
- ipv4Piece = number;
- } else if (ipv4Piece === 0) {
- return failure;
- } else {
- ipv4Piece = ipv4Piece * 10 + number;
- }
- if (ipv4Piece > 255) {
- return failure;
- }
- ++pointer;
- }
-
- address[pieceIndex] = address[pieceIndex] * 0x100 + ipv4Piece;
-
- ++numbersSeen;
-
- if (numbersSeen === 2 || numbersSeen === 4) {
- ++pieceIndex;
- }
- }
-
- if (numbersSeen !== 4) {
- return failure;
- }
-
- break;
- } else if (input[pointer] === 58) {
- ++pointer;
- if (input[pointer] === undefined) {
- return failure;
- }
- } else if (input[pointer] !== undefined) {
- return failure;
- }
-
- address[pieceIndex] = value;
- ++pieceIndex;
- }
-
- if (compress !== null) {
- let swaps = pieceIndex - compress;
- pieceIndex = 7;
- while (pieceIndex !== 0 && swaps > 0) {
- const temp = address[compress + swaps - 1];
- address[compress + swaps - 1] = address[pieceIndex];
- address[pieceIndex] = temp;
- --pieceIndex;
- --swaps;
- }
- } else if (compress === null && pieceIndex !== 8) {
- return failure;
- }
-
- return address;
-}
-
-function serializeIPv6(address) {
- let output = "";
- const seqResult = findLongestZeroSequence(address);
- const compress = seqResult.idx;
- let ignore0 = false;
-
- for (let pieceIndex = 0; pieceIndex <= 7; ++pieceIndex) {
- if (ignore0 && address[pieceIndex] === 0) {
- continue;
- } else if (ignore0) {
- ignore0 = false;
- }
-
- if (compress === pieceIndex) {
- const separator = pieceIndex === 0 ? "::" : ":";
- output += separator;
- ignore0 = true;
- continue;
- }
-
- output += address[pieceIndex].toString(16);
-
- if (pieceIndex !== 7) {
- output += ":";
- }
- }
-
- return output;
-}
-
-function parseHost(input, isSpecialArg) {
- if (input[0] === "[") {
- if (input[input.length - 1] !== "]") {
- return failure;
- }
-
- return parseIPv6(input.substring(1, input.length - 1));
- }
-
- if (!isSpecialArg) {
- return parseOpaqueHost(input);
- }
-
- const domain = utf8PercentDecode(input);
- const asciiDomain = tr46.toASCII(domain, false, tr46.PROCESSING_OPTIONS.NONTRANSITIONAL, false);
- if (asciiDomain === null) {
- return failure;
- }
-
- if (containsForbiddenHostCodePoint(asciiDomain)) {
- return failure;
- }
-
- const ipv4Host = parseIPv4(asciiDomain);
- if (typeof ipv4Host === "number" || ipv4Host === failure) {
- return ipv4Host;
- }
-
- return asciiDomain;
-}
-
-function parseOpaqueHost(input) {
- if (containsForbiddenHostCodePointExcludingPercent(input)) {
- return failure;
- }
-
- let output = "";
- const decoded = punycode.ucs2.decode(input);
- for (let i = 0; i < decoded.length; ++i) {
- output += percentEncodeChar(decoded[i], isC0ControlPercentEncode);
- }
- return output;
-}
-
-function findLongestZeroSequence(arr) {
- let maxIdx = null;
- let maxLen = 1; // only find elements > 1
- let currStart = null;
- let currLen = 0;
-
- for (let i = 0; i < arr.length; ++i) {
- if (arr[i] !== 0) {
- if (currLen > maxLen) {
- maxIdx = currStart;
- maxLen = currLen;
- }
-
- currStart = null;
- currLen = 0;
- } else {
- if (currStart === null) {
- currStart = i;
- }
- ++currLen;
- }
- }
-
- // if trailing zeros
- if (currLen > maxLen) {
- maxIdx = currStart;
- maxLen = currLen;
- }
-
- return {
- idx: maxIdx,
- len: maxLen
- };
-}
-
-function serializeHost(host) {
- if (typeof host === "number") {
- return serializeIPv4(host);
- }
-
- // IPv6 serializer
- if (host instanceof Array) {
- return "[" + serializeIPv6(host) + "]";
- }
-
- return host;
-}
-
-function trimControlChars(url) {
- return url.replace(/^[\u0000-\u001F\u0020]+|[\u0000-\u001F\u0020]+$/g, "");
-}
-
-function trimTabAndNewline(url) {
- return url.replace(/\u0009|\u000A|\u000D/g, "");
-}
-
-function shortenPath(url) {
- const path = url.path;
- if (path.length === 0) {
- return;
- }
- if (url.scheme === "file" && path.length === 1 && isNormalizedWindowsDriveLetter(path[0])) {
- return;
- }
-
- path.pop();
-}
-
-function includesCredentials(url) {
- return url.username !== "" || url.password !== "";
-}
-
-function cannotHaveAUsernamePasswordPort(url) {
- return url.host === null || url.host === "" || url.cannotBeABaseURL || url.scheme === "file";
-}
-
-function isNormalizedWindowsDriveLetter(string) {
- return /^[A-Za-z]:$/.test(string);
-}
-
-function URLStateMachine(input, base, encodingOverride, url, stateOverride) {
- this.pointer = 0;
- this.input = input;
- this.base = base || null;
- this.encodingOverride = encodingOverride || "utf-8";
- this.stateOverride = stateOverride;
- this.url = url;
- this.failure = false;
- this.parseError = false;
-
- if (!this.url) {
- this.url = {
- scheme: "",
- username: "",
- password: "",
- host: null,
- port: null,
- path: [],
- query: null,
- fragment: null,
-
- cannotBeABaseURL: false
- };
-
- const res = trimControlChars(this.input);
- if (res !== this.input) {
- this.parseError = true;
- }
- this.input = res;
- }
-
- const res = trimTabAndNewline(this.input);
- if (res !== this.input) {
- this.parseError = true;
- }
- this.input = res;
-
- this.state = stateOverride || "scheme start";
-
- this.buffer = "";
- this.atFlag = false;
- this.arrFlag = false;
- this.passwordTokenSeenFlag = false;
-
- this.input = punycode.ucs2.decode(this.input);
-
- for (; this.pointer <= this.input.length; ++this.pointer) {
- const c = this.input[this.pointer];
- const cStr = isNaN(c) ? undefined : String.fromCodePoint(c);
-
- // exec state machine
- const ret = this["parse " + this.state](c, cStr);
- if (!ret) {
- break; // terminate algorithm
- } else if (ret === failure) {
- this.failure = true;
- break;
- }
- }
-}
-
-URLStateMachine.prototype["parse scheme start"] = function parseSchemeStart(c, cStr) {
- if (isASCIIAlpha(c)) {
- this.buffer += cStr.toLowerCase();
- this.state = "scheme";
- } else if (!this.stateOverride) {
- this.state = "no scheme";
- --this.pointer;
- } else {
- this.parseError = true;
- return failure;
- }
-
- return true;
-};
-
-URLStateMachine.prototype["parse scheme"] = function parseScheme(c, cStr) {
- if (isASCIIAlphanumeric(c) || c === 43 || c === 45 || c === 46) {
- this.buffer += cStr.toLowerCase();
- } else if (c === 58) {
- if (this.stateOverride) {
- if (isSpecial(this.url) && !isSpecialScheme(this.buffer)) {
- return false;
- }
-
- if (!isSpecial(this.url) && isSpecialScheme(this.buffer)) {
- return false;
- }
-
- if ((includesCredentials(this.url) || this.url.port !== null) && this.buffer === "file") {
- return false;
- }
-
- if (this.url.scheme === "file" && (this.url.host === "" || this.url.host === null)) {
- return false;
- }
- }
- this.url.scheme = this.buffer;
- this.buffer = "";
- if (this.stateOverride) {
- return false;
- }
- if (this.url.scheme === "file") {
- if (this.input[this.pointer + 1] !== 47 || this.input[this.pointer + 2] !== 47) {
- this.parseError = true;
- }
- this.state = "file";
- } else if (isSpecial(this.url) && this.base !== null && this.base.scheme === this.url.scheme) {
- this.state = "special relative or authority";
- } else if (isSpecial(this.url)) {
- this.state = "special authority slashes";
- } else if (this.input[this.pointer + 1] === 47) {
- this.state = "path or authority";
- ++this.pointer;
- } else {
- this.url.cannotBeABaseURL = true;
- this.url.path.push("");
- this.state = "cannot-be-a-base-URL path";
- }
- } else if (!this.stateOverride) {
- this.buffer = "";
- this.state = "no scheme";
- this.pointer = -1;
- } else {
- this.parseError = true;
- return failure;
- }
-
- return true;
-};
-
-URLStateMachine.prototype["parse no scheme"] = function parseNoScheme(c) {
- if (this.base === null || (this.base.cannotBeABaseURL && c !== 35)) {
- return failure;
- } else if (this.base.cannotBeABaseURL && c === 35) {
- this.url.scheme = this.base.scheme;
- this.url.path = this.base.path.slice();
- this.url.query = this.base.query;
- this.url.fragment = "";
- this.url.cannotBeABaseURL = true;
- this.state = "fragment";
- } else if (this.base.scheme === "file") {
- this.state = "file";
- --this.pointer;
- } else {
- this.state = "relative";
- --this.pointer;
- }
-
- return true;
-};
-
-URLStateMachine.prototype["parse special relative or authority"] = function parseSpecialRelativeOrAuthority(c) {
- if (c === 47 && this.input[this.pointer + 1] === 47) {
- this.state = "special authority ignore slashes";
- ++this.pointer;
- } else {
- this.parseError = true;
- this.state = "relative";
- --this.pointer;
- }
-
- return true;
-};
-
-URLStateMachine.prototype["parse path or authority"] = function parsePathOrAuthority(c) {
- if (c === 47) {
- this.state = "authority";
- } else {
- this.state = "path";
- --this.pointer;
- }
-
- return true;
-};
-
-URLStateMachine.prototype["parse relative"] = function parseRelative(c) {
- this.url.scheme = this.base.scheme;
- if (isNaN(c)) {
- this.url.username = this.base.username;
- this.url.password = this.base.password;
- this.url.host = this.base.host;
- this.url.port = this.base.port;
- this.url.path = this.base.path.slice();
- this.url.query = this.base.query;
- } else if (c === 47) {
- this.state = "relative slash";
- } else if (c === 63) {
- this.url.username = this.base.username;
- this.url.password = this.base.password;
- this.url.host = this.base.host;
- this.url.port = this.base.port;
- this.url.path = this.base.path.slice();
- this.url.query = "";
- this.state = "query";
- } else if (c === 35) {
- this.url.username = this.base.username;
- this.url.password = this.base.password;
- this.url.host = this.base.host;
- this.url.port = this.base.port;
- this.url.path = this.base.path.slice();
- this.url.query = this.base.query;
- this.url.fragment = "";
- this.state = "fragment";
- } else if (isSpecial(this.url) && c === 92) {
- this.parseError = true;
- this.state = "relative slash";
- } else {
- this.url.username = this.base.username;
- this.url.password = this.base.password;
- this.url.host = this.base.host;
- this.url.port = this.base.port;
- this.url.path = this.base.path.slice(0, this.base.path.length - 1);
-
- this.state = "path";
- --this.pointer;
- }
-
- return true;
-};
-
-URLStateMachine.prototype["parse relative slash"] = function parseRelativeSlash(c) {
- if (isSpecial(this.url) && (c === 47 || c === 92)) {
- if (c === 92) {
- this.parseError = true;
- }
- this.state = "special authority ignore slashes";
- } else if (c === 47) {
- this.state = "authority";
- } else {
- this.url.username = this.base.username;
- this.url.password = this.base.password;
- this.url.host = this.base.host;
- this.url.port = this.base.port;
- this.state = "path";
- --this.pointer;
- }
-
- return true;
-};
-
-URLStateMachine.prototype["parse special authority slashes"] = function parseSpecialAuthoritySlashes(c) {
- if (c === 47 && this.input[this.pointer + 1] === 47) {
- this.state = "special authority ignore slashes";
- ++this.pointer;
- } else {
- this.parseError = true;
- this.state = "special authority ignore slashes";
- --this.pointer;
- }
-
- return true;
-};
-
-URLStateMachine.prototype["parse special authority ignore slashes"] = function parseSpecialAuthorityIgnoreSlashes(c) {
- if (c !== 47 && c !== 92) {
- this.state = "authority";
- --this.pointer;
- } else {
- this.parseError = true;
- }
-
- return true;
-};
-
-URLStateMachine.prototype["parse authority"] = function parseAuthority(c, cStr) {
- if (c === 64) {
- this.parseError = true;
- if (this.atFlag) {
- this.buffer = "%40" + this.buffer;
- }
- this.atFlag = true;
-
- // careful, this is based on buffer and has its own pointer (this.pointer != pointer) and inner chars
- const len = countSymbols(this.buffer);
- for (let pointer = 0; pointer < len; ++pointer) {
- const codePoint = this.buffer.codePointAt(pointer);
-
- if (codePoint === 58 && !this.passwordTokenSeenFlag) {
- this.passwordTokenSeenFlag = true;
- continue;
- }
- const encodedCodePoints = percentEncodeChar(codePoint, isUserinfoPercentEncode);
- if (this.passwordTokenSeenFlag) {
- this.url.password += encodedCodePoints;
- } else {
- this.url.username += encodedCodePoints;
- }
- }
- this.buffer = "";
- } else if (isNaN(c) || c === 47 || c === 63 || c === 35 ||
- (isSpecial(this.url) && c === 92)) {
- if (this.atFlag && this.buffer === "") {
- this.parseError = true;
- return failure;
- }
- this.pointer -= countSymbols(this.buffer) + 1;
- this.buffer = "";
- this.state = "host";
- } else {
- this.buffer += cStr;
- }
-
- return true;
-};
-
-URLStateMachine.prototype["parse hostname"] =
-URLStateMachine.prototype["parse host"] = function parseHostName(c, cStr) {
- if (this.stateOverride && this.url.scheme === "file") {
- --this.pointer;
- this.state = "file host";
- } else if (c === 58 && !this.arrFlag) {
- if (this.buffer === "") {
- this.parseError = true;
- return failure;
- }
-
- const host = parseHost(this.buffer, isSpecial(this.url));
- if (host === failure) {
- return failure;
- }
-
- this.url.host = host;
- this.buffer = "";
- this.state = "port";
- if (this.stateOverride === "hostname") {
- return false;
- }
- } else if (isNaN(c) || c === 47 || c === 63 || c === 35 ||
- (isSpecial(this.url) && c === 92)) {
- --this.pointer;
- if (isSpecial(this.url) && this.buffer === "") {
- this.parseError = true;
- return failure;
- } else if (this.stateOverride && this.buffer === "" &&
- (includesCredentials(this.url) || this.url.port !== null)) {
- this.parseError = true;
- return false;
- }
-
- const host = parseHost(this.buffer, isSpecial(this.url));
- if (host === failure) {
- return failure;
- }
-
- this.url.host = host;
- this.buffer = "";
- this.state = "path start";
- if (this.stateOverride) {
- return false;
- }
- } else {
- if (c === 91) {
- this.arrFlag = true;
- } else if (c === 93) {
- this.arrFlag = false;
- }
- this.buffer += cStr;
- }
-
- return true;
-};
-
-URLStateMachine.prototype["parse port"] = function parsePort(c, cStr) {
- if (isASCIIDigit(c)) {
- this.buffer += cStr;
- } else if (isNaN(c) || c === 47 || c === 63 || c === 35 ||
- (isSpecial(this.url) && c === 92) ||
- this.stateOverride) {
- if (this.buffer !== "") {
- const port = parseInt(this.buffer);
- if (port > Math.pow(2, 16) - 1) {
- this.parseError = true;
- return failure;
- }
- this.url.port = port === defaultPort(this.url.scheme) ? null : port;
- this.buffer = "";
- }
- if (this.stateOverride) {
- return false;
- }
- this.state = "path start";
- --this.pointer;
- } else {
- this.parseError = true;
- return failure;
- }
-
- return true;
-};
-
-const fileOtherwiseCodePoints = new Set([47, 92, 63, 35]);
-
-URLStateMachine.prototype["parse file"] = function parseFile(c) {
- this.url.scheme = "file";
-
- if (c === 47 || c === 92) {
- if (c === 92) {
- this.parseError = true;
- }
- this.state = "file slash";
- } else if (this.base !== null && this.base.scheme === "file") {
- if (isNaN(c)) {
- this.url.host = this.base.host;
- this.url.path = this.base.path.slice();
- this.url.query = this.base.query;
- } else if (c === 63) {
- this.url.host = this.base.host;
- this.url.path = this.base.path.slice();
- this.url.query = "";
- this.state = "query";
- } else if (c === 35) {
- this.url.host = this.base.host;
- this.url.path = this.base.path.slice();
- this.url.query = this.base.query;
- this.url.fragment = "";
- this.state = "fragment";
- } else {
- if (this.input.length - this.pointer - 1 === 0 || // remaining consists of 0 code points
- !isWindowsDriveLetterCodePoints(c, this.input[this.pointer + 1]) ||
- (this.input.length - this.pointer - 1 >= 2 && // remaining has at least 2 code points
- !fileOtherwiseCodePoints.has(this.input[this.pointer + 2]))) {
- this.url.host = this.base.host;
- this.url.path = this.base.path.slice();
- shortenPath(this.url);
- } else {
- this.parseError = true;
- }
-
- this.state = "path";
- --this.pointer;
- }
- } else {
- this.state = "path";
- --this.pointer;
- }
-
- return true;
-};
-
-URLStateMachine.prototype["parse file slash"] = function parseFileSlash(c) {
- if (c === 47 || c === 92) {
- if (c === 92) {
- this.parseError = true;
- }
- this.state = "file host";
- } else {
- if (this.base !== null && this.base.scheme === "file") {
- if (isNormalizedWindowsDriveLetterString(this.base.path[0])) {
- this.url.path.push(this.base.path[0]);
- } else {
- this.url.host = this.base.host;
- }
- }
- this.state = "path";
- --this.pointer;
- }
-
- return true;
-};
-
-URLStateMachine.prototype["parse file host"] = function parseFileHost(c, cStr) {
- if (isNaN(c) || c === 47 || c === 92 || c === 63 || c === 35) {
- --this.pointer;
- if (!this.stateOverride && isWindowsDriveLetterString(this.buffer)) {
- this.parseError = true;
- this.state = "path";
- } else if (this.buffer === "") {
- this.url.host = "";
- if (this.stateOverride) {
- return false;
- }
- this.state = "path start";
- } else {
- let host = parseHost(this.buffer, isSpecial(this.url));
- if (host === failure) {
- return failure;
- }
- if (host === "localhost") {
- host = "";
- }
- this.url.host = host;
-
- if (this.stateOverride) {
- return false;
- }
-
- this.buffer = "";
- this.state = "path start";
- }
- } else {
- this.buffer += cStr;
- }
-
- return true;
-};
-
-URLStateMachine.prototype["parse path start"] = function parsePathStart(c) {
- if (isSpecial(this.url)) {
- if (c === 92) {
- this.parseError = true;
- }
- this.state = "path";
-
- if (c !== 47 && c !== 92) {
- --this.pointer;
- }
- } else if (!this.stateOverride && c === 63) {
- this.url.query = "";
- this.state = "query";
- } else if (!this.stateOverride && c === 35) {
- this.url.fragment = "";
- this.state = "fragment";
- } else if (c !== undefined) {
- this.state = "path";
- if (c !== 47) {
- --this.pointer;
- }
- }
-
- return true;
-};
-
-URLStateMachine.prototype["parse path"] = function parsePath(c) {
- if (isNaN(c) || c === 47 || (isSpecial(this.url) && c === 92) ||
- (!this.stateOverride && (c === 63 || c === 35))) {
- if (isSpecial(this.url) && c === 92) {
- this.parseError = true;
- }
-
- if (isDoubleDot(this.buffer)) {
- shortenPath(this.url);
- if (c !== 47 && !(isSpecial(this.url) && c === 92)) {
- this.url.path.push("");
- }
- } else if (isSingleDot(this.buffer) && c !== 47 &&
- !(isSpecial(this.url) && c === 92)) {
- this.url.path.push("");
- } else if (!isSingleDot(this.buffer)) {
- if (this.url.scheme === "file" && this.url.path.length === 0 && isWindowsDriveLetterString(this.buffer)) {
- if (this.url.host !== "" && this.url.host !== null) {
- this.parseError = true;
- this.url.host = "";
- }
- this.buffer = this.buffer[0] + ":";
- }
- this.url.path.push(this.buffer);
- }
- this.buffer = "";
- if (this.url.scheme === "file" && (c === undefined || c === 63 || c === 35)) {
- while (this.url.path.length > 1 && this.url.path[0] === "") {
- this.parseError = true;
- this.url.path.shift();
- }
- }
- if (c === 63) {
- this.url.query = "";
- this.state = "query";
- }
- if (c === 35) {
- this.url.fragment = "";
- this.state = "fragment";
- }
- } else {
- // TODO: If c is not a URL code point and not "%", parse error.
-
- if (c === 37 &&
- (!isASCIIHex(this.input[this.pointer + 1]) ||
- !isASCIIHex(this.input[this.pointer + 2]))) {
- this.parseError = true;
- }
-
- this.buffer += percentEncodeChar(c, isPathPercentEncode);
- }
-
- return true;
-};
-
-URLStateMachine.prototype["parse cannot-be-a-base-URL path"] = function parseCannotBeABaseURLPath(c) {
- if (c === 63) {
- this.url.query = "";
- this.state = "query";
- } else if (c === 35) {
- this.url.fragment = "";
- this.state = "fragment";
- } else {
- // TODO: Add: not a URL code point
- if (!isNaN(c) && c !== 37) {
- this.parseError = true;
- }
-
- if (c === 37 &&
- (!isASCIIHex(this.input[this.pointer + 1]) ||
- !isASCIIHex(this.input[this.pointer + 2]))) {
- this.parseError = true;
- }
-
- if (!isNaN(c)) {
- this.url.path[0] = this.url.path[0] + percentEncodeChar(c, isC0ControlPercentEncode);
- }
- }
-
- return true;
-};
-
-URLStateMachine.prototype["parse query"] = function parseQuery(c, cStr) {
- if (isNaN(c) || (!this.stateOverride && c === 35)) {
- if (!isSpecial(this.url) || this.url.scheme === "ws" || this.url.scheme === "wss") {
- this.encodingOverride = "utf-8";
- }
-
- const buffer = new Buffer(this.buffer); // TODO: Use encoding override instead
- for (let i = 0; i < buffer.length; ++i) {
- if (buffer[i] < 0x21 || buffer[i] > 0x7E || buffer[i] === 0x22 || buffer[i] === 0x23 ||
- buffer[i] === 0x3C || buffer[i] === 0x3E) {
- this.url.query += percentEncode(buffer[i]);
- } else {
- this.url.query += String.fromCodePoint(buffer[i]);
- }
- }
-
- this.buffer = "";
- if (c === 35) {
- this.url.fragment = "";
- this.state = "fragment";
- }
- } else {
- // TODO: If c is not a URL code point and not "%", parse error.
- if (c === 37 &&
- (!isASCIIHex(this.input[this.pointer + 1]) ||
- !isASCIIHex(this.input[this.pointer + 2]))) {
- this.parseError = true;
- }
-
- this.buffer += cStr;
- }
-
- return true;
-};
-
-URLStateMachine.prototype["parse fragment"] = function parseFragment(c) {
- if (isNaN(c)) { // do nothing
- } else if (c === 0x0) {
- this.parseError = true;
- } else {
- // TODO: If c is not a URL code point and not "%", parse error.
- if (c === 37 &&
- (!isASCIIHex(this.input[this.pointer + 1]) ||
- !isASCIIHex(this.input[this.pointer + 2]))) {
- this.parseError = true;
- }
-
- this.url.fragment += percentEncodeChar(c, isC0ControlPercentEncode);
- }
-
- return true;
-};
-
-function serializeURL(url, excludeFragment) {
- let output = url.scheme + ":";
- if (url.host !== null) {
- output += "//";
-
- if (url.username !== "" || url.password !== "") {
- output += url.username;
- if (url.password !== "") {
- output += ":" + url.password;
- }
- output += "@";
- }
-
- output += serializeHost(url.host);
-
- if (url.port !== null) {
- output += ":" + url.port;
- }
- } else if (url.host === null && url.scheme === "file") {
- output += "//";
- }
-
- if (url.cannotBeABaseURL) {
- output += url.path[0];
- } else {
- for (const string of url.path) {
- output += "/" + string;
- }
- }
-
- if (url.query !== null) {
- output += "?" + url.query;
- }
-
- if (!excludeFragment && url.fragment !== null) {
- output += "#" + url.fragment;
- }
-
- return output;
-}
-
-function serializeOrigin(tuple) {
- let result = tuple.scheme + "://";
- result += serializeHost(tuple.host);
-
- if (tuple.port !== null) {
- result += ":" + tuple.port;
- }
-
- return result;
-}
-
-module.exports.serializeURL = serializeURL;
-
-module.exports.serializeURLOrigin = function (url) {
- // https://url.spec.whatwg.org/#concept-url-origin
- switch (url.scheme) {
- case "blob":
- try {
- return module.exports.serializeURLOrigin(module.exports.parseURL(url.path[0]));
- } catch (e) {
- // serializing an opaque origin returns "null"
- return "null";
- }
- case "ftp":
- case "gopher":
- case "http":
- case "https":
- case "ws":
- case "wss":
- return serializeOrigin({
- scheme: url.scheme,
- host: url.host,
- port: url.port
- });
- case "file":
- // spec says "exercise to the reader", chrome says "file://"
- return "file://";
- default:
- // serializing an opaque origin returns "null"
- return "null";
- }
-};
-
-module.exports.basicURLParse = function (input, options) {
- if (options === undefined) {
- options = {};
- }
-
- const usm = new URLStateMachine(input, options.baseURL, options.encodingOverride, options.url, options.stateOverride);
- if (usm.failure) {
- return "failure";
- }
-
- return usm.url;
-};
-
-module.exports.setTheUsername = function (url, username) {
- url.username = "";
- const decoded = punycode.ucs2.decode(username);
- for (let i = 0; i < decoded.length; ++i) {
- url.username += percentEncodeChar(decoded[i], isUserinfoPercentEncode);
- }
-};
-
-module.exports.setThePassword = function (url, password) {
- url.password = "";
- const decoded = punycode.ucs2.decode(password);
- for (let i = 0; i < decoded.length; ++i) {
- url.password += percentEncodeChar(decoded[i], isUserinfoPercentEncode);
- }
-};
-
-module.exports.serializeHost = serializeHost;
-
-module.exports.cannotHaveAUsernamePasswordPort = cannotHaveAUsernamePasswordPort;
-
-module.exports.serializeInteger = function (integer) {
- return String(integer);
-};
-
-module.exports.parseURL = function (input, options) {
- if (options === undefined) {
- options = {};
- }
-
- // We don't handle blobs, so this just delegates:
- return module.exports.basicURLParse(input, { baseURL: options.baseURL, encodingOverride: options.encodingOverride });
-};
+
+const punycode = __nccwpck_require__(5477);
+const tr46 = __nccwpck_require__(2299);
+
+const specialSchemes = {
+ ftp: 21,
+ file: null,
+ gopher: 70,
+ http: 80,
+ https: 443,
+ ws: 80,
+ wss: 443
+};
+
+const failure = Symbol("failure");
+
+function countSymbols(str) {
+ return punycode.ucs2.decode(str).length;
+}
+
+function at(input, idx) {
+ const c = input[idx];
+ return isNaN(c) ? undefined : String.fromCodePoint(c);
+}
+
+function isASCIIDigit(c) {
+ return c >= 0x30 && c <= 0x39;
+}
+
+function isASCIIAlpha(c) {
+ return (c >= 0x41 && c <= 0x5A) || (c >= 0x61 && c <= 0x7A);
+}
+
+function isASCIIAlphanumeric(c) {
+ return isASCIIAlpha(c) || isASCIIDigit(c);
+}
+
+function isASCIIHex(c) {
+ return isASCIIDigit(c) || (c >= 0x41 && c <= 0x46) || (c >= 0x61 && c <= 0x66);
+}
+
+function isSingleDot(buffer) {
+ return buffer === "." || buffer.toLowerCase() === "%2e";
+}
+
+function isDoubleDot(buffer) {
+ buffer = buffer.toLowerCase();
+ return buffer === ".." || buffer === "%2e." || buffer === ".%2e" || buffer === "%2e%2e";
+}
+
+function isWindowsDriveLetterCodePoints(cp1, cp2) {
+ return isASCIIAlpha(cp1) && (cp2 === 58 || cp2 === 124);
+}
+
+function isWindowsDriveLetterString(string) {
+ return string.length === 2 && isASCIIAlpha(string.codePointAt(0)) && (string[1] === ":" || string[1] === "|");
+}
+
+function isNormalizedWindowsDriveLetterString(string) {
+ return string.length === 2 && isASCIIAlpha(string.codePointAt(0)) && string[1] === ":";
+}
+
+function containsForbiddenHostCodePoint(string) {
+ return string.search(/\u0000|\u0009|\u000A|\u000D|\u0020|#|%|\/|:|\?|@|\[|\\|\]/) !== -1;
+}
+
+function containsForbiddenHostCodePointExcludingPercent(string) {
+ return string.search(/\u0000|\u0009|\u000A|\u000D|\u0020|#|\/|:|\?|@|\[|\\|\]/) !== -1;
+}
+
+function isSpecialScheme(scheme) {
+ return specialSchemes[scheme] !== undefined;
+}
+
+function isSpecial(url) {
+ return isSpecialScheme(url.scheme);
+}
+
+function defaultPort(scheme) {
+ return specialSchemes[scheme];
+}
+
+function percentEncode(c) {
+ let hex = c.toString(16).toUpperCase();
+ if (hex.length === 1) {
+ hex = "0" + hex;
+ }
+
+ return "%" + hex;
+}
+
+function utf8PercentEncode(c) {
+ const buf = new Buffer(c);
+
+ let str = "";
+
+ for (let i = 0; i < buf.length; ++i) {
+ str += percentEncode(buf[i]);
+ }
+
+ return str;
+}
+
+function utf8PercentDecode(str) {
+ const input = new Buffer(str);
+ const output = [];
+ for (let i = 0; i < input.length; ++i) {
+ if (input[i] !== 37) {
+ output.push(input[i]);
+ } else if (input[i] === 37 && isASCIIHex(input[i + 1]) && isASCIIHex(input[i + 2])) {
+ output.push(parseInt(input.slice(i + 1, i + 3).toString(), 16));
+ i += 2;
+ } else {
+ output.push(input[i]);
+ }
+ }
+ return new Buffer(output).toString();
+}
+
+function isC0ControlPercentEncode(c) {
+ return c <= 0x1F || c > 0x7E;
+}
+
+const extraPathPercentEncodeSet = new Set([32, 34, 35, 60, 62, 63, 96, 123, 125]);
+function isPathPercentEncode(c) {
+ return isC0ControlPercentEncode(c) || extraPathPercentEncodeSet.has(c);
+}
+
+const extraUserinfoPercentEncodeSet =
+ new Set([47, 58, 59, 61, 64, 91, 92, 93, 94, 124]);
+function isUserinfoPercentEncode(c) {
+ return isPathPercentEncode(c) || extraUserinfoPercentEncodeSet.has(c);
+}
+
+function percentEncodeChar(c, encodeSetPredicate) {
+ const cStr = String.fromCodePoint(c);
+
+ if (encodeSetPredicate(c)) {
+ return utf8PercentEncode(cStr);
+ }
+
+ return cStr;
+}
+
+function parseIPv4Number(input) {
+ let R = 10;
+
+ if (input.length >= 2 && input.charAt(0) === "0" && input.charAt(1).toLowerCase() === "x") {
+ input = input.substring(2);
+ R = 16;
+ } else if (input.length >= 2 && input.charAt(0) === "0") {
+ input = input.substring(1);
+ R = 8;
+ }
+
+ if (input === "") {
+ return 0;
+ }
+
+ const regex = R === 10 ? /[^0-9]/ : (R === 16 ? /[^0-9A-Fa-f]/ : /[^0-7]/);
+ if (regex.test(input)) {
+ return failure;
+ }
+
+ return parseInt(input, R);
+}
+
+function parseIPv4(input) {
+ const parts = input.split(".");
+ if (parts[parts.length - 1] === "") {
+ if (parts.length > 1) {
+ parts.pop();
+ }
+ }
+
+ if (parts.length > 4) {
+ return input;
+ }
+
+ const numbers = [];
+ for (const part of parts) {
+ if (part === "") {
+ return input;
+ }
+ const n = parseIPv4Number(part);
+ if (n === failure) {
+ return input;
+ }
+
+ numbers.push(n);
+ }
+
+ for (let i = 0; i < numbers.length - 1; ++i) {
+ if (numbers[i] > 255) {
+ return failure;
+ }
+ }
+ if (numbers[numbers.length - 1] >= Math.pow(256, 5 - numbers.length)) {
+ return failure;
+ }
+
+ let ipv4 = numbers.pop();
+ let counter = 0;
+
+ for (const n of numbers) {
+ ipv4 += n * Math.pow(256, 3 - counter);
+ ++counter;
+ }
+
+ return ipv4;
+}
+
+function serializeIPv4(address) {
+ let output = "";
+ let n = address;
+
+ for (let i = 1; i <= 4; ++i) {
+ output = String(n % 256) + output;
+ if (i !== 4) {
+ output = "." + output;
+ }
+ n = Math.floor(n / 256);
+ }
+
+ return output;
+}
+
+function parseIPv6(input) {
+ const address = [0, 0, 0, 0, 0, 0, 0, 0];
+ let pieceIndex = 0;
+ let compress = null;
+ let pointer = 0;
+
+ input = punycode.ucs2.decode(input);
+
+ if (input[pointer] === 58) {
+ if (input[pointer + 1] !== 58) {
+ return failure;
+ }
+
+ pointer += 2;
+ ++pieceIndex;
+ compress = pieceIndex;
+ }
+
+ while (pointer < input.length) {
+ if (pieceIndex === 8) {
+ return failure;
+ }
+
+ if (input[pointer] === 58) {
+ if (compress !== null) {
+ return failure;
+ }
+ ++pointer;
+ ++pieceIndex;
+ compress = pieceIndex;
+ continue;
+ }
+
+ let value = 0;
+ let length = 0;
+
+ while (length < 4 && isASCIIHex(input[pointer])) {
+ value = value * 0x10 + parseInt(at(input, pointer), 16);
+ ++pointer;
+ ++length;
+ }
+
+ if (input[pointer] === 46) {
+ if (length === 0) {
+ return failure;
+ }
+
+ pointer -= length;
+
+ if (pieceIndex > 6) {
+ return failure;
+ }
+
+ let numbersSeen = 0;
+
+ while (input[pointer] !== undefined) {
+ let ipv4Piece = null;
+
+ if (numbersSeen > 0) {
+ if (input[pointer] === 46 && numbersSeen < 4) {
+ ++pointer;
+ } else {
+ return failure;
+ }
+ }
+
+ if (!isASCIIDigit(input[pointer])) {
+ return failure;
+ }
+
+ while (isASCIIDigit(input[pointer])) {
+ const number = parseInt(at(input, pointer));
+ if (ipv4Piece === null) {
+ ipv4Piece = number;
+ } else if (ipv4Piece === 0) {
+ return failure;
+ } else {
+ ipv4Piece = ipv4Piece * 10 + number;
+ }
+ if (ipv4Piece > 255) {
+ return failure;
+ }
+ ++pointer;
+ }
+
+ address[pieceIndex] = address[pieceIndex] * 0x100 + ipv4Piece;
+
+ ++numbersSeen;
+
+ if (numbersSeen === 2 || numbersSeen === 4) {
+ ++pieceIndex;
+ }
+ }
+
+ if (numbersSeen !== 4) {
+ return failure;
+ }
+
+ break;
+ } else if (input[pointer] === 58) {
+ ++pointer;
+ if (input[pointer] === undefined) {
+ return failure;
+ }
+ } else if (input[pointer] !== undefined) {
+ return failure;
+ }
+
+ address[pieceIndex] = value;
+ ++pieceIndex;
+ }
+
+ if (compress !== null) {
+ let swaps = pieceIndex - compress;
+ pieceIndex = 7;
+ while (pieceIndex !== 0 && swaps > 0) {
+ const temp = address[compress + swaps - 1];
+ address[compress + swaps - 1] = address[pieceIndex];
+ address[pieceIndex] = temp;
+ --pieceIndex;
+ --swaps;
+ }
+ } else if (compress === null && pieceIndex !== 8) {
+ return failure;
+ }
+
+ return address;
+}
+
+function serializeIPv6(address) {
+ let output = "";
+ const seqResult = findLongestZeroSequence(address);
+ const compress = seqResult.idx;
+ let ignore0 = false;
+
+ for (let pieceIndex = 0; pieceIndex <= 7; ++pieceIndex) {
+ if (ignore0 && address[pieceIndex] === 0) {
+ continue;
+ } else if (ignore0) {
+ ignore0 = false;
+ }
+
+ if (compress === pieceIndex) {
+ const separator = pieceIndex === 0 ? "::" : ":";
+ output += separator;
+ ignore0 = true;
+ continue;
+ }
+
+ output += address[pieceIndex].toString(16);
+
+ if (pieceIndex !== 7) {
+ output += ":";
+ }
+ }
+
+ return output;
+}
+
+function parseHost(input, isSpecialArg) {
+ if (input[0] === "[") {
+ if (input[input.length - 1] !== "]") {
+ return failure;
+ }
+
+ return parseIPv6(input.substring(1, input.length - 1));
+ }
+
+ if (!isSpecialArg) {
+ return parseOpaqueHost(input);
+ }
+
+ const domain = utf8PercentDecode(input);
+ const asciiDomain = tr46.toASCII(domain, false, tr46.PROCESSING_OPTIONS.NONTRANSITIONAL, false);
+ if (asciiDomain === null) {
+ return failure;
+ }
+
+ if (containsForbiddenHostCodePoint(asciiDomain)) {
+ return failure;
+ }
+
+ const ipv4Host = parseIPv4(asciiDomain);
+ if (typeof ipv4Host === "number" || ipv4Host === failure) {
+ return ipv4Host;
+ }
+
+ return asciiDomain;
+}
+
+function parseOpaqueHost(input) {
+ if (containsForbiddenHostCodePointExcludingPercent(input)) {
+ return failure;
+ }
+
+ let output = "";
+ const decoded = punycode.ucs2.decode(input);
+ for (let i = 0; i < decoded.length; ++i) {
+ output += percentEncodeChar(decoded[i], isC0ControlPercentEncode);
+ }
+ return output;
+}
+
+function findLongestZeroSequence(arr) {
+ let maxIdx = null;
+ let maxLen = 1; // only find elements > 1
+ let currStart = null;
+ let currLen = 0;
+
+ for (let i = 0; i < arr.length; ++i) {
+ if (arr[i] !== 0) {
+ if (currLen > maxLen) {
+ maxIdx = currStart;
+ maxLen = currLen;
+ }
+
+ currStart = null;
+ currLen = 0;
+ } else {
+ if (currStart === null) {
+ currStart = i;
+ }
+ ++currLen;
+ }
+ }
+
+ // if trailing zeros
+ if (currLen > maxLen) {
+ maxIdx = currStart;
+ maxLen = currLen;
+ }
+
+ return {
+ idx: maxIdx,
+ len: maxLen
+ };
+}
+
+function serializeHost(host) {
+ if (typeof host === "number") {
+ return serializeIPv4(host);
+ }
+
+ // IPv6 serializer
+ if (host instanceof Array) {
+ return "[" + serializeIPv6(host) + "]";
+ }
+
+ return host;
+}
+
+function trimControlChars(url) {
+ return url.replace(/^[\u0000-\u001F\u0020]+|[\u0000-\u001F\u0020]+$/g, "");
+}
+
+function trimTabAndNewline(url) {
+ return url.replace(/\u0009|\u000A|\u000D/g, "");
+}
+
+function shortenPath(url) {
+ const path = url.path;
+ if (path.length === 0) {
+ return;
+ }
+ if (url.scheme === "file" && path.length === 1 && isNormalizedWindowsDriveLetter(path[0])) {
+ return;
+ }
+
+ path.pop();
+}
+
+function includesCredentials(url) {
+ return url.username !== "" || url.password !== "";
+}
+
+function cannotHaveAUsernamePasswordPort(url) {
+ return url.host === null || url.host === "" || url.cannotBeABaseURL || url.scheme === "file";
+}
+
+function isNormalizedWindowsDriveLetter(string) {
+ return /^[A-Za-z]:$/.test(string);
+}
+
+function URLStateMachine(input, base, encodingOverride, url, stateOverride) {
+ this.pointer = 0;
+ this.input = input;
+ this.base = base || null;
+ this.encodingOverride = encodingOverride || "utf-8";
+ this.stateOverride = stateOverride;
+ this.url = url;
+ this.failure = false;
+ this.parseError = false;
+
+ if (!this.url) {
+ this.url = {
+ scheme: "",
+ username: "",
+ password: "",
+ host: null,
+ port: null,
+ path: [],
+ query: null,
+ fragment: null,
+
+ cannotBeABaseURL: false
+ };
+
+ const res = trimControlChars(this.input);
+ if (res !== this.input) {
+ this.parseError = true;
+ }
+ this.input = res;
+ }
+
+ const res = trimTabAndNewline(this.input);
+ if (res !== this.input) {
+ this.parseError = true;
+ }
+ this.input = res;
+
+ this.state = stateOverride || "scheme start";
+
+ this.buffer = "";
+ this.atFlag = false;
+ this.arrFlag = false;
+ this.passwordTokenSeenFlag = false;
+
+ this.input = punycode.ucs2.decode(this.input);
+
+ for (; this.pointer <= this.input.length; ++this.pointer) {
+ const c = this.input[this.pointer];
+ const cStr = isNaN(c) ? undefined : String.fromCodePoint(c);
+
+ // exec state machine
+ const ret = this["parse " + this.state](c, cStr);
+ if (!ret) {
+ break; // terminate algorithm
+ } else if (ret === failure) {
+ this.failure = true;
+ break;
+ }
+ }
+}
+
+URLStateMachine.prototype["parse scheme start"] = function parseSchemeStart(c, cStr) {
+ if (isASCIIAlpha(c)) {
+ this.buffer += cStr.toLowerCase();
+ this.state = "scheme";
+ } else if (!this.stateOverride) {
+ this.state = "no scheme";
+ --this.pointer;
+ } else {
+ this.parseError = true;
+ return failure;
+ }
+
+ return true;
+};
+
+URLStateMachine.prototype["parse scheme"] = function parseScheme(c, cStr) {
+ if (isASCIIAlphanumeric(c) || c === 43 || c === 45 || c === 46) {
+ this.buffer += cStr.toLowerCase();
+ } else if (c === 58) {
+ if (this.stateOverride) {
+ if (isSpecial(this.url) && !isSpecialScheme(this.buffer)) {
+ return false;
+ }
+
+ if (!isSpecial(this.url) && isSpecialScheme(this.buffer)) {
+ return false;
+ }
+
+ if ((includesCredentials(this.url) || this.url.port !== null) && this.buffer === "file") {
+ return false;
+ }
+
+ if (this.url.scheme === "file" && (this.url.host === "" || this.url.host === null)) {
+ return false;
+ }
+ }
+ this.url.scheme = this.buffer;
+ this.buffer = "";
+ if (this.stateOverride) {
+ return false;
+ }
+ if (this.url.scheme === "file") {
+ if (this.input[this.pointer + 1] !== 47 || this.input[this.pointer + 2] !== 47) {
+ this.parseError = true;
+ }
+ this.state = "file";
+ } else if (isSpecial(this.url) && this.base !== null && this.base.scheme === this.url.scheme) {
+ this.state = "special relative or authority";
+ } else if (isSpecial(this.url)) {
+ this.state = "special authority slashes";
+ } else if (this.input[this.pointer + 1] === 47) {
+ this.state = "path or authority";
+ ++this.pointer;
+ } else {
+ this.url.cannotBeABaseURL = true;
+ this.url.path.push("");
+ this.state = "cannot-be-a-base-URL path";
+ }
+ } else if (!this.stateOverride) {
+ this.buffer = "";
+ this.state = "no scheme";
+ this.pointer = -1;
+ } else {
+ this.parseError = true;
+ return failure;
+ }
+
+ return true;
+};
+
+URLStateMachine.prototype["parse no scheme"] = function parseNoScheme(c) {
+ if (this.base === null || (this.base.cannotBeABaseURL && c !== 35)) {
+ return failure;
+ } else if (this.base.cannotBeABaseURL && c === 35) {
+ this.url.scheme = this.base.scheme;
+ this.url.path = this.base.path.slice();
+ this.url.query = this.base.query;
+ this.url.fragment = "";
+ this.url.cannotBeABaseURL = true;
+ this.state = "fragment";
+ } else if (this.base.scheme === "file") {
+ this.state = "file";
+ --this.pointer;
+ } else {
+ this.state = "relative";
+ --this.pointer;
+ }
+
+ return true;
+};
+
+URLStateMachine.prototype["parse special relative or authority"] = function parseSpecialRelativeOrAuthority(c) {
+ if (c === 47 && this.input[this.pointer + 1] === 47) {
+ this.state = "special authority ignore slashes";
+ ++this.pointer;
+ } else {
+ this.parseError = true;
+ this.state = "relative";
+ --this.pointer;
+ }
+
+ return true;
+};
+
+URLStateMachine.prototype["parse path or authority"] = function parsePathOrAuthority(c) {
+ if (c === 47) {
+ this.state = "authority";
+ } else {
+ this.state = "path";
+ --this.pointer;
+ }
+
+ return true;
+};
+
+URLStateMachine.prototype["parse relative"] = function parseRelative(c) {
+ this.url.scheme = this.base.scheme;
+ if (isNaN(c)) {
+ this.url.username = this.base.username;
+ this.url.password = this.base.password;
+ this.url.host = this.base.host;
+ this.url.port = this.base.port;
+ this.url.path = this.base.path.slice();
+ this.url.query = this.base.query;
+ } else if (c === 47) {
+ this.state = "relative slash";
+ } else if (c === 63) {
+ this.url.username = this.base.username;
+ this.url.password = this.base.password;
+ this.url.host = this.base.host;
+ this.url.port = this.base.port;
+ this.url.path = this.base.path.slice();
+ this.url.query = "";
+ this.state = "query";
+ } else if (c === 35) {
+ this.url.username = this.base.username;
+ this.url.password = this.base.password;
+ this.url.host = this.base.host;
+ this.url.port = this.base.port;
+ this.url.path = this.base.path.slice();
+ this.url.query = this.base.query;
+ this.url.fragment = "";
+ this.state = "fragment";
+ } else if (isSpecial(this.url) && c === 92) {
+ this.parseError = true;
+ this.state = "relative slash";
+ } else {
+ this.url.username = this.base.username;
+ this.url.password = this.base.password;
+ this.url.host = this.base.host;
+ this.url.port = this.base.port;
+ this.url.path = this.base.path.slice(0, this.base.path.length - 1);
+
+ this.state = "path";
+ --this.pointer;
+ }
+
+ return true;
+};
+
+URLStateMachine.prototype["parse relative slash"] = function parseRelativeSlash(c) {
+ if (isSpecial(this.url) && (c === 47 || c === 92)) {
+ if (c === 92) {
+ this.parseError = true;
+ }
+ this.state = "special authority ignore slashes";
+ } else if (c === 47) {
+ this.state = "authority";
+ } else {
+ this.url.username = this.base.username;
+ this.url.password = this.base.password;
+ this.url.host = this.base.host;
+ this.url.port = this.base.port;
+ this.state = "path";
+ --this.pointer;
+ }
+
+ return true;
+};
+
+URLStateMachine.prototype["parse special authority slashes"] = function parseSpecialAuthoritySlashes(c) {
+ if (c === 47 && this.input[this.pointer + 1] === 47) {
+ this.state = "special authority ignore slashes";
+ ++this.pointer;
+ } else {
+ this.parseError = true;
+ this.state = "special authority ignore slashes";
+ --this.pointer;
+ }
+
+ return true;
+};
+
+URLStateMachine.prototype["parse special authority ignore slashes"] = function parseSpecialAuthorityIgnoreSlashes(c) {
+ if (c !== 47 && c !== 92) {
+ this.state = "authority";
+ --this.pointer;
+ } else {
+ this.parseError = true;
+ }
+
+ return true;
+};
+
+URLStateMachine.prototype["parse authority"] = function parseAuthority(c, cStr) {
+ if (c === 64) {
+ this.parseError = true;
+ if (this.atFlag) {
+ this.buffer = "%40" + this.buffer;
+ }
+ this.atFlag = true;
+
+ // careful, this is based on buffer and has its own pointer (this.pointer != pointer) and inner chars
+ const len = countSymbols(this.buffer);
+ for (let pointer = 0; pointer < len; ++pointer) {
+ const codePoint = this.buffer.codePointAt(pointer);
+
+ if (codePoint === 58 && !this.passwordTokenSeenFlag) {
+ this.passwordTokenSeenFlag = true;
+ continue;
+ }
+ const encodedCodePoints = percentEncodeChar(codePoint, isUserinfoPercentEncode);
+ if (this.passwordTokenSeenFlag) {
+ this.url.password += encodedCodePoints;
+ } else {
+ this.url.username += encodedCodePoints;
+ }
+ }
+ this.buffer = "";
+ } else if (isNaN(c) || c === 47 || c === 63 || c === 35 ||
+ (isSpecial(this.url) && c === 92)) {
+ if (this.atFlag && this.buffer === "") {
+ this.parseError = true;
+ return failure;
+ }
+ this.pointer -= countSymbols(this.buffer) + 1;
+ this.buffer = "";
+ this.state = "host";
+ } else {
+ this.buffer += cStr;
+ }
+
+ return true;
+};
+
+URLStateMachine.prototype["parse hostname"] =
+URLStateMachine.prototype["parse host"] = function parseHostName(c, cStr) {
+ if (this.stateOverride && this.url.scheme === "file") {
+ --this.pointer;
+ this.state = "file host";
+ } else if (c === 58 && !this.arrFlag) {
+ if (this.buffer === "") {
+ this.parseError = true;
+ return failure;
+ }
+
+ const host = parseHost(this.buffer, isSpecial(this.url));
+ if (host === failure) {
+ return failure;
+ }
+
+ this.url.host = host;
+ this.buffer = "";
+ this.state = "port";
+ if (this.stateOverride === "hostname") {
+ return false;
+ }
+ } else if (isNaN(c) || c === 47 || c === 63 || c === 35 ||
+ (isSpecial(this.url) && c === 92)) {
+ --this.pointer;
+ if (isSpecial(this.url) && this.buffer === "") {
+ this.parseError = true;
+ return failure;
+ } else if (this.stateOverride && this.buffer === "" &&
+ (includesCredentials(this.url) || this.url.port !== null)) {
+ this.parseError = true;
+ return false;
+ }
+
+ const host = parseHost(this.buffer, isSpecial(this.url));
+ if (host === failure) {
+ return failure;
+ }
+
+ this.url.host = host;
+ this.buffer = "";
+ this.state = "path start";
+ if (this.stateOverride) {
+ return false;
+ }
+ } else {
+ if (c === 91) {
+ this.arrFlag = true;
+ } else if (c === 93) {
+ this.arrFlag = false;
+ }
+ this.buffer += cStr;
+ }
+
+ return true;
+};
+
+URLStateMachine.prototype["parse port"] = function parsePort(c, cStr) {
+ if (isASCIIDigit(c)) {
+ this.buffer += cStr;
+ } else if (isNaN(c) || c === 47 || c === 63 || c === 35 ||
+ (isSpecial(this.url) && c === 92) ||
+ this.stateOverride) {
+ if (this.buffer !== "") {
+ const port = parseInt(this.buffer);
+ if (port > Math.pow(2, 16) - 1) {
+ this.parseError = true;
+ return failure;
+ }
+ this.url.port = port === defaultPort(this.url.scheme) ? null : port;
+ this.buffer = "";
+ }
+ if (this.stateOverride) {
+ return false;
+ }
+ this.state = "path start";
+ --this.pointer;
+ } else {
+ this.parseError = true;
+ return failure;
+ }
+
+ return true;
+};
+
+const fileOtherwiseCodePoints = new Set([47, 92, 63, 35]);
+
+URLStateMachine.prototype["parse file"] = function parseFile(c) {
+ this.url.scheme = "file";
+
+ if (c === 47 || c === 92) {
+ if (c === 92) {
+ this.parseError = true;
+ }
+ this.state = "file slash";
+ } else if (this.base !== null && this.base.scheme === "file") {
+ if (isNaN(c)) {
+ this.url.host = this.base.host;
+ this.url.path = this.base.path.slice();
+ this.url.query = this.base.query;
+ } else if (c === 63) {
+ this.url.host = this.base.host;
+ this.url.path = this.base.path.slice();
+ this.url.query = "";
+ this.state = "query";
+ } else if (c === 35) {
+ this.url.host = this.base.host;
+ this.url.path = this.base.path.slice();
+ this.url.query = this.base.query;
+ this.url.fragment = "";
+ this.state = "fragment";
+ } else {
+ if (this.input.length - this.pointer - 1 === 0 || // remaining consists of 0 code points
+ !isWindowsDriveLetterCodePoints(c, this.input[this.pointer + 1]) ||
+ (this.input.length - this.pointer - 1 >= 2 && // remaining has at least 2 code points
+ !fileOtherwiseCodePoints.has(this.input[this.pointer + 2]))) {
+ this.url.host = this.base.host;
+ this.url.path = this.base.path.slice();
+ shortenPath(this.url);
+ } else {
+ this.parseError = true;
+ }
+
+ this.state = "path";
+ --this.pointer;
+ }
+ } else {
+ this.state = "path";
+ --this.pointer;
+ }
+
+ return true;
+};
+
+URLStateMachine.prototype["parse file slash"] = function parseFileSlash(c) {
+ if (c === 47 || c === 92) {
+ if (c === 92) {
+ this.parseError = true;
+ }
+ this.state = "file host";
+ } else {
+ if (this.base !== null && this.base.scheme === "file") {
+ if (isNormalizedWindowsDriveLetterString(this.base.path[0])) {
+ this.url.path.push(this.base.path[0]);
+ } else {
+ this.url.host = this.base.host;
+ }
+ }
+ this.state = "path";
+ --this.pointer;
+ }
+
+ return true;
+};
+
+URLStateMachine.prototype["parse file host"] = function parseFileHost(c, cStr) {
+ if (isNaN(c) || c === 47 || c === 92 || c === 63 || c === 35) {
+ --this.pointer;
+ if (!this.stateOverride && isWindowsDriveLetterString(this.buffer)) {
+ this.parseError = true;
+ this.state = "path";
+ } else if (this.buffer === "") {
+ this.url.host = "";
+ if (this.stateOverride) {
+ return false;
+ }
+ this.state = "path start";
+ } else {
+ let host = parseHost(this.buffer, isSpecial(this.url));
+ if (host === failure) {
+ return failure;
+ }
+ if (host === "localhost") {
+ host = "";
+ }
+ this.url.host = host;
+
+ if (this.stateOverride) {
+ return false;
+ }
+
+ this.buffer = "";
+ this.state = "path start";
+ }
+ } else {
+ this.buffer += cStr;
+ }
+
+ return true;
+};
+
+URLStateMachine.prototype["parse path start"] = function parsePathStart(c) {
+ if (isSpecial(this.url)) {
+ if (c === 92) {
+ this.parseError = true;
+ }
+ this.state = "path";
+
+ if (c !== 47 && c !== 92) {
+ --this.pointer;
+ }
+ } else if (!this.stateOverride && c === 63) {
+ this.url.query = "";
+ this.state = "query";
+ } else if (!this.stateOverride && c === 35) {
+ this.url.fragment = "";
+ this.state = "fragment";
+ } else if (c !== undefined) {
+ this.state = "path";
+ if (c !== 47) {
+ --this.pointer;
+ }
+ }
+
+ return true;
+};
+
+URLStateMachine.prototype["parse path"] = function parsePath(c) {
+ if (isNaN(c) || c === 47 || (isSpecial(this.url) && c === 92) ||
+ (!this.stateOverride && (c === 63 || c === 35))) {
+ if (isSpecial(this.url) && c === 92) {
+ this.parseError = true;
+ }
+
+ if (isDoubleDot(this.buffer)) {
+ shortenPath(this.url);
+ if (c !== 47 && !(isSpecial(this.url) && c === 92)) {
+ this.url.path.push("");
+ }
+ } else if (isSingleDot(this.buffer) && c !== 47 &&
+ !(isSpecial(this.url) && c === 92)) {
+ this.url.path.push("");
+ } else if (!isSingleDot(this.buffer)) {
+ if (this.url.scheme === "file" && this.url.path.length === 0 && isWindowsDriveLetterString(this.buffer)) {
+ if (this.url.host !== "" && this.url.host !== null) {
+ this.parseError = true;
+ this.url.host = "";
+ }
+ this.buffer = this.buffer[0] + ":";
+ }
+ this.url.path.push(this.buffer);
+ }
+ this.buffer = "";
+ if (this.url.scheme === "file" && (c === undefined || c === 63 || c === 35)) {
+ while (this.url.path.length > 1 && this.url.path[0] === "") {
+ this.parseError = true;
+ this.url.path.shift();
+ }
+ }
+ if (c === 63) {
+ this.url.query = "";
+ this.state = "query";
+ }
+ if (c === 35) {
+ this.url.fragment = "";
+ this.state = "fragment";
+ }
+ } else {
+ // TODO: If c is not a URL code point and not "%", parse error.
+
+ if (c === 37 &&
+ (!isASCIIHex(this.input[this.pointer + 1]) ||
+ !isASCIIHex(this.input[this.pointer + 2]))) {
+ this.parseError = true;
+ }
+
+ this.buffer += percentEncodeChar(c, isPathPercentEncode);
+ }
+
+ return true;
+};
+
+URLStateMachine.prototype["parse cannot-be-a-base-URL path"] = function parseCannotBeABaseURLPath(c) {
+ if (c === 63) {
+ this.url.query = "";
+ this.state = "query";
+ } else if (c === 35) {
+ this.url.fragment = "";
+ this.state = "fragment";
+ } else {
+ // TODO: Add: not a URL code point
+ if (!isNaN(c) && c !== 37) {
+ this.parseError = true;
+ }
+
+ if (c === 37 &&
+ (!isASCIIHex(this.input[this.pointer + 1]) ||
+ !isASCIIHex(this.input[this.pointer + 2]))) {
+ this.parseError = true;
+ }
+
+ if (!isNaN(c)) {
+ this.url.path[0] = this.url.path[0] + percentEncodeChar(c, isC0ControlPercentEncode);
+ }
+ }
+
+ return true;
+};
+
+URLStateMachine.prototype["parse query"] = function parseQuery(c, cStr) {
+ if (isNaN(c) || (!this.stateOverride && c === 35)) {
+ if (!isSpecial(this.url) || this.url.scheme === "ws" || this.url.scheme === "wss") {
+ this.encodingOverride = "utf-8";
+ }
+
+ const buffer = new Buffer(this.buffer); // TODO: Use encoding override instead
+ for (let i = 0; i < buffer.length; ++i) {
+ if (buffer[i] < 0x21 || buffer[i] > 0x7E || buffer[i] === 0x22 || buffer[i] === 0x23 ||
+ buffer[i] === 0x3C || buffer[i] === 0x3E) {
+ this.url.query += percentEncode(buffer[i]);
+ } else {
+ this.url.query += String.fromCodePoint(buffer[i]);
+ }
+ }
+
+ this.buffer = "";
+ if (c === 35) {
+ this.url.fragment = "";
+ this.state = "fragment";
+ }
+ } else {
+ // TODO: If c is not a URL code point and not "%", parse error.
+ if (c === 37 &&
+ (!isASCIIHex(this.input[this.pointer + 1]) ||
+ !isASCIIHex(this.input[this.pointer + 2]))) {
+ this.parseError = true;
+ }
+
+ this.buffer += cStr;
+ }
+
+ return true;
+};
+
+URLStateMachine.prototype["parse fragment"] = function parseFragment(c) {
+ if (isNaN(c)) { // do nothing
+ } else if (c === 0x0) {
+ this.parseError = true;
+ } else {
+ // TODO: If c is not a URL code point and not "%", parse error.
+ if (c === 37 &&
+ (!isASCIIHex(this.input[this.pointer + 1]) ||
+ !isASCIIHex(this.input[this.pointer + 2]))) {
+ this.parseError = true;
+ }
+
+ this.url.fragment += percentEncodeChar(c, isC0ControlPercentEncode);
+ }
+
+ return true;
+};
+
+function serializeURL(url, excludeFragment) {
+ let output = url.scheme + ":";
+ if (url.host !== null) {
+ output += "//";
+
+ if (url.username !== "" || url.password !== "") {
+ output += url.username;
+ if (url.password !== "") {
+ output += ":" + url.password;
+ }
+ output += "@";
+ }
+
+ output += serializeHost(url.host);
+
+ if (url.port !== null) {
+ output += ":" + url.port;
+ }
+ } else if (url.host === null && url.scheme === "file") {
+ output += "//";
+ }
+
+ if (url.cannotBeABaseURL) {
+ output += url.path[0];
+ } else {
+ for (const string of url.path) {
+ output += "/" + string;
+ }
+ }
+
+ if (url.query !== null) {
+ output += "?" + url.query;
+ }
+
+ if (!excludeFragment && url.fragment !== null) {
+ output += "#" + url.fragment;
+ }
+
+ return output;
+}
+
+function serializeOrigin(tuple) {
+ let result = tuple.scheme + "://";
+ result += serializeHost(tuple.host);
+
+ if (tuple.port !== null) {
+ result += ":" + tuple.port;
+ }
+
+ return result;
+}
+
+module.exports.serializeURL = serializeURL;
+
+module.exports.serializeURLOrigin = function (url) {
+ // https://url.spec.whatwg.org/#concept-url-origin
+ switch (url.scheme) {
+ case "blob":
+ try {
+ return module.exports.serializeURLOrigin(module.exports.parseURL(url.path[0]));
+ } catch (e) {
+ // serializing an opaque origin returns "null"
+ return "null";
+ }
+ case "ftp":
+ case "gopher":
+ case "http":
+ case "https":
+ case "ws":
+ case "wss":
+ return serializeOrigin({
+ scheme: url.scheme,
+ host: url.host,
+ port: url.port
+ });
+ case "file":
+ // spec says "exercise to the reader", chrome says "file://"
+ return "file://";
+ default:
+ // serializing an opaque origin returns "null"
+ return "null";
+ }
+};
+
+module.exports.basicURLParse = function (input, options) {
+ if (options === undefined) {
+ options = {};
+ }
+
+ const usm = new URLStateMachine(input, options.baseURL, options.encodingOverride, options.url, options.stateOverride);
+ if (usm.failure) {
+ return "failure";
+ }
+
+ return usm.url;
+};
+
+module.exports.setTheUsername = function (url, username) {
+ url.username = "";
+ const decoded = punycode.ucs2.decode(username);
+ for (let i = 0; i < decoded.length; ++i) {
+ url.username += percentEncodeChar(decoded[i], isUserinfoPercentEncode);
+ }
+};
+
+module.exports.setThePassword = function (url, password) {
+ url.password = "";
+ const decoded = punycode.ucs2.decode(password);
+ for (let i = 0; i < decoded.length; ++i) {
+ url.password += percentEncodeChar(decoded[i], isUserinfoPercentEncode);
+ }
+};
+
+module.exports.serializeHost = serializeHost;
+
+module.exports.cannotHaveAUsernamePasswordPort = cannotHaveAUsernamePasswordPort;
+
+module.exports.serializeInteger = function (integer) {
+ return String(integer);
+};
+
+module.exports.parseURL = function (input, options) {
+ if (options === undefined) {
+ options = {};
+ }
+
+ // We don't handle blobs, so this just delegates:
+ return module.exports.basicURLParse(input, { baseURL: options.baseURL, encodingOverride: options.encodingOverride });
+};
/***/ }),
diff --git a/externals/install-dotnet.ps1 b/externals/install-dotnet.ps1
index 651b812..ab38847 100644
--- a/externals/install-dotnet.ps1
+++ b/externals/install-dotnet.ps1
@@ -1,1494 +1,1619 @@
-#
-# 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
- - 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.
-.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: - this value represents currently running OS architecture
- Architecture of dotnet binaries to be installed.
- Possible values are: , 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
- For internal use only.
- Allows using a different storage to download SDK archives from.
- This parameter is only used if $NoCdn is false.
-.PARAMETER UncachedFeed
- For internal use only.
- Allows using a different storage to download SDK archives from.
- This parameter is only used if $NoCdn is true.
-.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 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'
-.PARAMETER DownloadTimeout
- Determines timeout duration in seconds for dowloading of the SDK file
- Default: 1200 seconds (20 minutes)
-#>
-[cmdletbinding()]
-param(
- [string]$Channel="LTS",
- [string]$Quality,
- [string]$Version="Latest",
- [switch]$Internal,
- [string]$JSonFile,
- [Alias('i')][string]$InstallDir="",
- [string]$Architecture="",
- [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,
- [switch]$NoCdn,
- [int]$DownloadTimeout=1200
-)
-
-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 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"
- }
-}
-
-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 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 "") {
- $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", "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." }
- }
-}
-
-
-# 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
-
- if ($Runtime -eq "dotnet") {
- $PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/dotnet-runtime-$SpecificProductVersion-win-$CLIArchitecture.zip"
- }
- elseif ($Runtime -eq "aspnetcore") {
- $PayloadURL = "$AzureFeed/aspnetcore/Runtime/$SpecificVersion/aspnetcore-runtime-$SpecificProductVersion-win-$CLIArchitecture.zip"
- }
- 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.zip"
- if ($SpecificVersion -match '^(\d+)\.(.*)$')
- {
- $majorVersion = [int]$Matches[1]
- if ($majorVersion -ge 5)
- {
- $PayloadURL = "$AzureFeed/WindowsDesktop/$SpecificVersion/windowsdesktop-runtime-$SpecificProductVersion-win-$CLIArchitecture.zip"
- }
- }
- }
- elseif (-not $Runtime) {
- $PayloadURL = "$AzureFeed/Sdk/$SpecificVersion/dotnet-sdk-$SpecificProductVersion-win-$CLIArchitecture.zip"
- }
- 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"
- }
- return $InstallRoot
-}
-
-function Resolve-Installation-Path([string]$InstallDir) {
- Say-Invocation $MyInvocation
-
- if ($InstallDir -eq "") {
- 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) {
- # 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 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 {
- if ($null -ne $Stream) {
- $Stream.Dispose()
- }
- }
-}
-
-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 `"`""
- }
- 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) {
- 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
-
-}
-
-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
-
- 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://dotnetcli.azureedge.net/dotnet",
- "https://dotnetbuilds.azureedge.net/public"
- )
-
- if (-not [string]::IsNullOrEmpty($AzureFeed)) {
- $feeds = @($AzureFeed)
- }
-
- if ($NoCdn) {
- $feeds = @(
- "https://dotnetcli.blob.core.windows.net/dotnet",
- "https://dotnetbuilds.blob.core.windows.net/public"
- )
-
- if (-not [string]::IsNullOrEmpty($UncachedFeed)) {
- $feeds = @($UncachedFeed)
- }
- }
-
- 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 {
- 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 / 1MB -le 100)) {
- throw "There is not enough disk space on drive ${installDrive}:"
- }
-}
-
-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"
-}
-
-$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 = @()
-
-if ($Version.ToLowerInvariant() -ne "latest" -and -not [string]::IsNullOrEmpty($Quality)) {
- throw "Quality and Version options are not allowed to be specified simultaneously. See https://docs.microsoft.com/en-us/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
-}
-
-Prepare-Install-Directory
-
-$ZipPath = [System.IO.Path]::combine([System.IO.Path]::GetTempPath(), [System.IO.Path]::GetRandomFileName())
-Say-Verbose "Zip path: $ZipPath"
-
-$DownloadSucceeded = $false
-$DownloadedLink = $null
-$ErrorMessages = @()
-
-foreach ($link in $DownloadLinks)
-{
- Say-Verbose "Downloading `"$($link.type)`" link $($link.downloadLink)"
-
- try {
- 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."
-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."
-}
-
-SafeRemoveFile -Path $ZipPath
-
-Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot
-
-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"
-Say "Installed version is $($DownloadedLink.effectiveVersion)"
-Say "Installation finished"
-
-# SIG # Begin signature block
-# MIInnQYJKoZIhvcNAQcCoIInjjCCJ4oCAQExDzANBglghkgBZQMEAgEFADB5Bgor
-# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
-# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBPbD3vCI+sY1o7
-# t+9GwL7gEDtWJk/5Ypegl3ITSKy+X6CCDYEwggX/MIID56ADAgECAhMzAAACzI61
-# lqa90clOAAAAAALMMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
-# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
-# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
-# bmcgUENBIDIwMTEwHhcNMjIwNTEyMjA0NjAxWhcNMjMwNTExMjA0NjAxWjB0MQsw
-# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
-# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
-# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
-# AQCiTbHs68bADvNud97NzcdP0zh0mRr4VpDv68KobjQFybVAuVgiINf9aG2zQtWK
-# No6+2X2Ix65KGcBXuZyEi0oBUAAGnIe5O5q/Y0Ij0WwDyMWaVad2Te4r1Eic3HWH
-# UfiiNjF0ETHKg3qa7DCyUqwsR9q5SaXuHlYCwM+m59Nl3jKnYnKLLfzhl13wImV9
-# DF8N76ANkRyK6BYoc9I6hHF2MCTQYWbQ4fXgzKhgzj4zeabWgfu+ZJCiFLkogvc0
-# RVb0x3DtyxMbl/3e45Eu+sn/x6EVwbJZVvtQYcmdGF1yAYht+JnNmWwAxL8MgHMz
-# xEcoY1Q1JtstiY3+u3ulGMvhAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE
-# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUiLhHjTKWzIqVIp+sM2rOHH11rfQw
-# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1
-# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDcwNTI5MB8GA1UdIwQYMBaAFEhu
-# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu
-# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w
-# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3
-# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx
-# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAeA8D
-# sOAHS53MTIHYu8bbXrO6yQtRD6JfyMWeXaLu3Nc8PDnFc1efYq/F3MGx/aiwNbcs
-# J2MU7BKNWTP5JQVBA2GNIeR3mScXqnOsv1XqXPvZeISDVWLaBQzceItdIwgo6B13
-# vxlkkSYMvB0Dr3Yw7/W9U4Wk5K/RDOnIGvmKqKi3AwyxlV1mpefy729FKaWT7edB
-# d3I4+hldMY8sdfDPjWRtJzjMjXZs41OUOwtHccPazjjC7KndzvZHx/0VWL8n0NT/
-# 404vftnXKifMZkS4p2sB3oK+6kCcsyWsgS/3eYGw1Fe4MOnin1RhgrW1rHPODJTG
-# AUOmW4wc3Q6KKr2zve7sMDZe9tfylonPwhk971rX8qGw6LkrGFv31IJeJSe/aUbG
-# dUDPkbrABbVvPElgoj5eP3REqx5jdfkQw7tOdWkhn0jDUh2uQen9Atj3RkJyHuR0
-# GUsJVMWFJdkIO/gFwzoOGlHNsmxvpANV86/1qgb1oZXdrURpzJp53MsDaBY/pxOc
-# J0Cvg6uWs3kQWgKk5aBzvsX95BzdItHTpVMtVPW4q41XEvbFmUP1n6oL5rdNdrTM
-# j/HXMRk1KCksax1Vxo3qv+13cCsZAaQNaIAvt5LvkshZkDZIP//0Hnq7NnWeYR3z
-# 4oFiw9N2n3bb9baQWuWPswG0Dq9YT9kb+Cs4qIIwggd6MIIFYqADAgECAgphDpDS
-# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
-# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
-# 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
-# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIZcjCCGW4CAQEwgZUwfjELMAkG
-# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
-# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
-# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAsyOtZamvdHJTgAAAAACzDAN
-# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
-# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgNQFZgkyG
-# luNzcU2g8/R/8PaAnIpTnmBnw3/0HJQjl9wwQgYKKwYBBAGCNwIBDDE0MDKgFIAS
-# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN
-# BgkqhkiG9w0BAQEFAASCAQABpLusOOxklzXjvllIe1AgDCgkYd0BN4cT3yQ8uULV
-# e+OnVgGOLnPcffCSGZ/SQMgJndoRMBSBd0jH5JxSkSuLXJpEWs1nl4QUg93FxYLr
-# pMdFepMsN733h5JuZGcTFf7P23IOxYaVEC+mKLbkOxIJaxgDQYSgliSg9X2hwLJ2
-# frCUV4b3ZWL0R495LhGpo65B7Ik/OOeHXWcs8d7vOnE/ObPHFv3fn1QTrq+KvbhA
-# TWEmL3P9P0Jn7k6gJjrTOxpgcDenr0IE5X63oe7y32LgLlJbr1OjKQUUCPVQ16d9
-# bgkhRp0gghdSAbDKjQuEAQ+e3GTeoNWnzxPlQfMLP0droYIW/DCCFvgGCisGAQQB
-# gjcDAwExghboMIIW5AYJKoZIhvcNAQcCoIIW1TCCFtECAQMxDzANBglghkgBZQME
-# AgEFADCCAVEGCyqGSIb3DQEJEAEEoIIBQASCATwwggE4AgEBBgorBgEEAYRZCgMB
-# MDEwDQYJYIZIAWUDBAIBBQAEIGXZSvVWqDs0wjW8JLHjKJ41lgzXHpdGqPCyYSvk
-# gIH0AgZi1XtjQncYEzIwMjIwODAzMTI1NjA1Ljc2OFowBIACAfSggdCkgc0wgcox
-# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
-# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1p
-# Y3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMxJjAkBgNVBAsTHVRoYWxlcyBUU1Mg
-# RVNOOjEyQkMtRTNBRS03NEVCMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFt
-# cCBTZXJ2aWNloIIRUzCCBwwwggT0oAMCAQICEzMAAAGhAYVVmblUXYoAAQAAAaEw
-# DQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0
-# b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh
-# dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcN
-# MjExMjAyMTkwNTI0WhcNMjMwMjI4MTkwNTI0WjCByjELMAkGA1UEBhMCVVMxEzAR
-# BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p
-# Y3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2Eg
-# T3BlcmF0aW9uczEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046MTJCQy1FM0FFLTc0
-# RUIxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0G
-# CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDayTxe5WukkrYxxVuHLYW9BEWCD9kk
-# jnnHsOKwGddIPbZlLY+l5ovLDNf+BEMQKAZQI3DX91l1yCDuP9X7tOPC48ZRGXA/
-# bf9ql0FK5438gIl7cV528XeEOFwc/A+UbIUfW296Omg8Z62xaQv3jrG4U/priArF
-# /er1UA1HNuIGUyqjlygiSPwK2NnFApi1JD+Uef5c47kh7pW1Kj7RnchpFeY9MekP
-# QRia7cEaUYU4sqCiJVdDJpefLvPT9EdthlQx75ldx+AwZf2a9T7uQRSBh8tpxPdI
-# DDkKiWMwjKTrAY09A3I/jidqPuc8PvX+sqxqyZEN2h4GA0Edjmk64nkIukAK18K5
-# nALDLO9SMTxpAwQIHRDtZeTClvAPCEoy1vtPD7f+eqHqStuu+XCkfRjXEpX9+h9f
-# rsB0/BgD5CBf3ELLAa8TefMfHZWEJRTPNrbXMKizSrUSkVv/3HP/ZsJpwaz5My2R
-# byc3Ah9bT76eBJkyfT5FN9v/KQ0HnxhRMs6HHhTmNx+LztYci+vHf0D3QH1eCjZW
-# ZRjp1mOyxpPU2mDMG6gelvJse1JzRADo7YIok/J3Ccbm8MbBbm85iogFltFHecHF
-# EFwrsDGBFnNYHMhcbarQNA+gY2e2l9fAkX3MjI7Uklkoz74/P6KIqe5jcd9FPCbb
-# SbYH9OLsteeYOQIDAQABo4IBNjCCATIwHQYDVR0OBBYEFBa/IDLbY475VQyKiZSw
-# 47l0/cypMB8GA1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRY
-# MFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01p
-# Y3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEF
-# BQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9w
-# a2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAo
-# MSkuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwgwDQYJKoZI
-# hvcNAQELBQADggIBACDDIxElfXlG5YKcKrLPSS+f3JWZprwKEiASvivaHTBRlXtA
-# s+TkadcsEei+9w5vmF5tCUzTH4c0nCI7bZxnsL+S6XsiOs3Z1V4WX+IwoXUJ4zLv
-# s0+mT4vjGDtYfKQ/bsmJKar2c99m/fHv1Wm2CTcyaePvi86Jh3UyLjdRILWbtzs4
-# oImFMwwKbzHdPopxrBhgi+C1YZshosWLlgzyuxjUl+qNg1m52MJmf11loI7D9HJo
-# aQzd+rf928Y8rvULmg2h/G50o+D0UJ1Fa/cJJaHfB3sfKw9X6GrtXYGjmM3+g+Ah
-# aVsfupKXNtOFu5tnLKvAH5OIjEDYV1YKmlXuBuhbYassygPFMmNgG2Ank3drEcDc
-# ZhCXXqpRszNo1F6Gu5JCpQZXbOJM9Ue5PlJKtmImAYIGsw+pnHy/r5ggSYOp4g5Z
-# 1oU9GhVCM3V0T9adee6OUXBk1rE4dZc/UsPlj0qoiljL+lN1A5gkmmz7k5tIObVG
-# B7dJdz8J0FwXRE5qYu1AdvauVbZwGQkL1x8aK/svjEQW0NUyJ29znDHiXl5vLoRT
-# jjFpshUBi2+IY+mNqbLmj24j5eT+bjDlE3HmNtLPpLcMDYqZ1H+6U6YmaiNmac2j
-# RXDAaeEE/uoDMt2dArfJP7M+MDv3zzNNTINeuNEtDVgm9zwfgIUCXnDZuVtiMIIH
-# 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
-# qzqKOghif9lwY1NNje6CbaUFEMFxBmoQtB1VM1izoXBm8qGCAsowggIzAgEBMIH4
-# oYHQpIHNMIHKMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G
-# A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUw
-# IwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMSYwJAYDVQQLEx1U
-# aGFsZXMgVFNTIEVTTjoxMkJDLUUzQUUtNzRFQjElMCMGA1UEAxMcTWljcm9zb2Z0
-# IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUAG3F2jO4LEMVLwgKG
-# XdYMN4FBgOCggYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu
-# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv
-# cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAN
-# BgkqhkiG9w0BAQUFAAIFAOaUaAIwIhgPMjAyMjA4MDMxMTIwMzRaGA8yMDIyMDgw
-# NDExMjAzNFowczA5BgorBgEEAYRZCgQBMSswKTAKAgUA5pRoAgIBADAGAgEAAgEI
-# MAcCAQACAhIDMAoCBQDmlbmCAgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQB
-# hFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJKoZIhvcNAQEFBQADgYEA
-# bqvBFqohycksQZhSEJZTiCeQ6hwWlYWRXL1PerCFbLmK+4vgr57BkwFsu5KzBE2z
-# i1eHNrssK4BcBLYyIhDIjMSqqtrvclrB6SSDag1WcxZrz42xatvyhKXZd52a5R5Q
-# xJw66cvwkDa4UmEtVOnbkaOPyyAql72D9w/XLHY0nmUxggQNMIIECQIBATCBkzB8
-# MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk
-# bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1N
-# aWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAaEBhVWZuVRdigABAAAB
-# oTANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEE
-# MC8GCSqGSIb3DQEJBDEiBCCGB/0CqUv9yvdxWNnaciRCHPCM4WmcYpKBUNiR1Xg+
-# yjCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIOsIVPE6gYJoIIKOhHIF7UlJ
-# Cswl4IJPISvOKInfjtCEMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
-# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
-# dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB
-# IDIwMTACEzMAAAGhAYVVmblUXYoAAQAAAaEwIgQgy8rtxrFwV1rcLxTDP8W6Af+y
-# Za2AaaMVTQY3E9A2eHMwDQYJKoZIhvcNAQELBQAEggIAEJIvjnhqXrrsyJWHHG9i
-# gKBBM3d51KglP0nJ0dY1zp7uUIBTQ53LVONE1SFiqbw6akydYum6iTaI7tvFWJRW
-# dx5Fq56gt+QY2YO0nsn3zH3ulyUUkhHuMsx5N/pQT6tsEMu6tWCuWucf44JQHlyY
-# x8/C+S5QoA7DId3ugccCFpZWUMb76QWReDtalDz3XY/gNSBT2DTJ8WT78WREcYKu
-# aBO52cUXKKLtr5ZoPcdEElB/TPuctcC6Hh0+J4Y6PCNwOVPodpEmjMSV0tAN8tZp
-# T3cyf9YPnwXNdDiaikZlPSO0pXCM5+KjrBm5hnj6+J8qMc+Qc91UMh2J96kZWVmm
-# PsWE7YA4DlWrIWn2mdGLtTJP4sOlqRjigP9rdBFo0oG9c1ySKw3rN7zpGTDnFdkS
-# vxeCoLWx48BJ4bE3Siwx6cwrYScmIgyobLb1Ztu5FEmFUn8maX5oo8IY9kPsODOG
-# 3y8hPoLOOj2lRdslV9bdjtrbnqeY5Nq/oKuftbX8iD2MYFgWOqeufw3TcQiSz2uF
-# tKGolAePdRf1S7c81CC89g7tcwy1TILR9M2JdWOwosAtpFXxX6Vc1OGiRwPAyXBL
-# dvDqiTx5zb9k87hfJvwix/oXfo4fNCNdE/i/VbmsAJjcxd+eEBbJ9Oc+oPqC/5zq
-# pPtLXUsVfUWX58dPRnYeMAg=
-# SIG # End signature block
+#
+# 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, signed, validated, preview, GA.
+ Works only in combination with channel. Not applicable for STS 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.
+.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: - this value represents currently running OS architecture
+ Architecture of dotnet binaries to be installed.
+ Possible values are: , 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
+ For internal use only.
+ Allows using a different storage to download SDK archives from.
+ This parameter is only used if $NoCdn is false.
+.PARAMETER UncachedFeed
+ For internal use only.
+ Allows using a different storage to download SDK archives from.
+ This parameter is only used if $NoCdn is true.
+.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 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'
+.PARAMETER DownloadTimeout
+ Determines timeout duration in seconds for dowloading of the SDK file
+ Default: 1200 seconds (20 minutes)
+.PARAMETER KeepZip
+ If set, downloaded file is kept
+.PARAMETER ZipPath
+ Use that path to store installer, generated by default
+.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 = "",
+ [string]$Architecture = "",
+ [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,
+ [switch]$NoCdn,
+ [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 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"
+ }
+}
+
+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 -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 "") {
+ $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", "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.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." }
+ }
+}
+
+
+# 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
+
+ if ($Runtime -eq "dotnet") {
+ $PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/dotnet-runtime-$SpecificProductVersion-win-$CLIArchitecture.zip"
+ }
+ elseif ($Runtime -eq "aspnetcore") {
+ $PayloadURL = "$AzureFeed/aspnetcore/Runtime/$SpecificVersion/aspnetcore-runtime-$SpecificProductVersion-win-$CLIArchitecture.zip"
+ }
+ 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.zip"
+ if ($SpecificVersion -match '^(\d+)\.(.*)$') {
+ $majorVersion = [int]$Matches[1]
+ if ($majorVersion -ge 5) {
+ $PayloadURL = "$AzureFeed/WindowsDesktop/$SpecificVersion/windowsdesktop-runtime-$SpecificProductVersion-win-$CLIArchitecture.zip"
+ }
+ }
+ }
+ elseif (-not $Runtime) {
+ $PayloadURL = "$AzureFeed/Sdk/$SpecificVersion/dotnet-sdk-$SpecificProductVersion-win-$CLIArchitecture.zip"
+ }
+ 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 "") {
+ 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 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 `"`""
+ }
+ 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'"
+ }
+}
+
+# grab the 'stem' of the redirect and check it against all of our configured feeds,
+# if it matches, we can be sure that the redirect is valid and we should use it for
+# subsequent processing
+function Sanitize-RedirectUrl([string]$url) {
+ $urlSegments = ([System.Uri]$url).Segments;
+ $urlStem = $urlSegments[2..($urlSegments.Length - 1)] -join "";
+ Write-Verbose "Checking configured feeds for the asset at $urlStem"
+ foreach ($prospectiveFeed in $feeds) {
+ $trialUrl = "$prospectiveFeed/$urlStem";
+ Write-Verbose "Checking $trialUrl"
+ try {
+ $trialResponse = Invoke-WebRequest -Uri $trialUrl -Method HEAD
+ if ($trialResponse.StatusCode -eq 200) {
+ Write-Verbose "Found a match at $trialUrl"
+ return $trialUrl;
+ }
+ else {
+ Write-Verbose "No match at $trialUrl"
+ }
+ }
+ catch {
+ Write-Verbose "Failed to check $trialUrl"
+ }
+ }
+}
+
+function Get-AkaMSDownloadLink([string]$Channel, [string]$Quality, [bool]$Internal, [string]$Product, [string]$Architecture) {
+ 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.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.
+ $actualRedirectUrl = Sanitize-RedirectUrl $akaMsDownloadLink
+ if ($null -ne $actualRedirectUrl) {
+ $akaMsDownloadLink = $actualRedirectUrl
+ }
+
+ 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) {
+ $AkaMsDownloadLink = Get-AkaMSDownloadLink -Channel $NormalizedChannel -Quality $NormalizedQuality -Internal $Internal -Product $ProductName -Architecture $Architecture
+
+ 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://dotnetcli.azureedge.net/dotnet"
+ "https://ci.dot.net/public"
+ "https://dotnetbuilds.azureedge.net/public"
+ )
+
+ if (-not [string]::IsNullOrEmpty($AzureFeed)) {
+ $feeds = @($AzureFeed)
+ }
+
+ if ($NoCdn) {
+ $feeds = @(
+ "https://dotnetcli.blob.core.windows.net/dotnet",
+ "https://dotnetbuilds.blob.core.windows.net/public"
+ )
+
+ 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
+}
+
+$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."
+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
+# MIIoVQYJKoZIhvcNAQcCoIIoRjCCKEICAQExDzANBglghkgBZQMEAgEFADB5Bgor
+# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
+# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAYvsOYTXPcgaBF
+# C8M6oYBHzvQKaqKPOJVvd3P0sSBCw6CCDYUwggYDMIID66ADAgECAhMzAAAEA73V
+# lV0POxitAAAAAAQDMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
+# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
+# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
+# bmcgUENBIDIwMTEwHhcNMjQwOTEyMjAxMTEzWhcNMjUwOTExMjAxMTEzWjB0MQsw
+# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
+# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
+# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+# AQCfdGddwIOnbRYUyg03O3iz19XXZPmuhEmW/5uyEN+8mgxl+HJGeLGBR8YButGV
+# LVK38RxcVcPYyFGQXcKcxgih4w4y4zJi3GvawLYHlsNExQwz+v0jgY/aejBS2EJY
+# oUhLVE+UzRihV8ooxoftsmKLb2xb7BoFS6UAo3Zz4afnOdqI7FGoi7g4vx/0MIdi
+# kwTn5N56TdIv3mwfkZCFmrsKpN0zR8HD8WYsvH3xKkG7u/xdqmhPPqMmnI2jOFw/
+# /n2aL8W7i1Pasja8PnRXH/QaVH0M1nanL+LI9TsMb/enWfXOW65Gne5cqMN9Uofv
+# ENtdwwEmJ3bZrcI9u4LZAkujAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE
+# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQU6m4qAkpz4641iK2irF8eWsSBcBkw
+# VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh
+# dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzUwMjkyNjAfBgNVHSMEGDAW
+# gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v
+# d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw
+# MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov
+# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx
+# XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB
+# AFFo/6E4LX51IqFuoKvUsi80QytGI5ASQ9zsPpBa0z78hutiJd6w154JkcIx/f7r
+# EBK4NhD4DIFNfRiVdI7EacEs7OAS6QHF7Nt+eFRNOTtgHb9PExRy4EI/jnMwzQJV
+# NokTxu2WgHr/fBsWs6G9AcIgvHjWNN3qRSrhsgEdqHc0bRDUf8UILAdEZOMBvKLC
+# rmf+kJPEvPldgK7hFO/L9kmcVe67BnKejDKO73Sa56AJOhM7CkeATrJFxO9GLXos
+# oKvrwBvynxAg18W+pagTAkJefzneuWSmniTurPCUE2JnvW7DalvONDOtG01sIVAB
+# +ahO2wcUPa2Zm9AiDVBWTMz9XUoKMcvngi2oqbsDLhbK+pYrRUgRpNt0y1sxZsXO
+# raGRF8lM2cWvtEkV5UL+TQM1ppv5unDHkW8JS+QnfPbB8dZVRyRmMQ4aY/tx5x5+
+# sX6semJ//FbiclSMxSI+zINu1jYerdUwuCi+P6p7SmQmClhDM+6Q+btE2FtpsU0W
+# +r6RdYFf/P+nK6j2otl9Nvr3tWLu+WXmz8MGM+18ynJ+lYbSmFWcAj7SYziAfT0s
+# IwlQRFkyC71tsIZUhBHtxPliGUu362lIO0Lpe0DOrg8lspnEWOkHnCT5JEnWCbzu
+# iVt8RX1IV07uIveNZuOBWLVCzWJjEGa+HhaEtavjy6i7MIIHejCCBWKgAwIBAgIK
+# YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV
+# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
+# c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm
+# aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw
+# OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
+# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD
+# VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG
+# 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la
+# UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc
+# 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D
+# dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+
+# lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk
+# kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6
+# A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd
+# X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL
+# 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd
+# sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3
+# T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS
+# 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI
+# bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL
+# BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD
+# uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv
+# c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf
+# MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3
+# dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf
+# MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF
+# BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h
+# cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA
+# YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn
+# 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7
+# v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b
+# pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/
+# KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy
+# CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp
+# mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi
+# hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb
+# BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS
+# oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL
+# gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX
+# cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCGiYwghoiAgEBMIGVMH4x
+# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
+# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p
+# Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAAQDvdWVXQ87GK0AAAAA
+# BAMwDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw
+# HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEINfL
+# pWARcSI2v5ypXRaeSwvLuu7hP0XgYbvQaaOIuiKWMEIGCisGAQQBgjcCAQwxNDAy
+# oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
+# b20wDQYJKoZIhvcNAQEBBQAEggEADr/V9EQlvMcNLQduKLU/gz5PRRSoE8txgN52
+# OuBIJS4+jPp3y82+4/09umeMdQ7+pwRQiuPAvmyZG0zGRoTz3PzpouceetqHnIHn
+# ij0lT0y4hUQ0DqmZT1AA24GJmoPnM9ab2EcRTfUp7p0t1Fq5ITOEdFvvh6EPkyc/
+# spxmI5bTlE0+anj9PmnLyFYnFtrGlmSywrDpIsjqnE8+ODtTabllcpAhLrZxInqu
+# bHXIrT3cGjATJsRAg+38R5tYP7i6aI5sS9QGmeXhuvrJeFrOIqC2gxbV7iCJIrkE
+# 5OGFIBZQkxLRZxt3VYdGAjBLj+pCY7OEjXpXvkdg47Xo8aQCKqGCF7AwghesBgor
+# BgEEAYI3AwMBMYIXnDCCF5gGCSqGSIb3DQEHAqCCF4kwgheFAgEDMQ8wDQYJYIZI
+# AWUDBAIBBQAwggFaBgsqhkiG9w0BCRABBKCCAUkEggFFMIIBQQIBAQYKKwYBBAGE
+# WQoDATAxMA0GCWCGSAFlAwQCAQUABCBVg4bCpxEOAWWIN2/4kB21BawVRDfKQ35G
+# xRhhaLpK/AIGZ2KxlnK4GBMyMDI0MTIyMzE2NDIwNy43NDJaMASAAgH0oIHZpIHW
+# MIHTMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
+# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQL
+# EyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJzAlBgNVBAsT
+# Hm5TaGllbGQgVFNTIEVTTjo2QjA1LTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9z
+# b2Z0IFRpbWUtU3RhbXAgU2VydmljZaCCEf4wggcoMIIFEKADAgECAhMzAAAB9oMv
+# JmpUXSLBAAEAAAH2MA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYD
+# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
+# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w
+# IFBDQSAyMDEwMB4XDTI0MDcyNTE4MzEwNFoXDTI1MTAyMjE4MzEwNFowgdMxCzAJ
+# BgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25k
+# MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jv
+# c29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEnMCUGA1UECxMeblNoaWVs
+# ZCBUU1MgRVNOOjZCMDUtMDVFMC1EOTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGlt
+# ZS1TdGFtcCBTZXJ2aWNlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA
+# 0UJeLMR/N9WPBZhuKVFF+eWJZ68Wujdj4X6JR05cxO5CepNXo17rVazwWLkm5Aja
+# Vh19ZVjDChHzimxsoaXxNu8IDggKwpXvpAAItv4Ux50e9S2uVwfKv57p9JKG+Q7V
+# ONShujl1NCMkcgSrPdmd/8zcsmhzcNobLomrCAIORZ8IwhYy4siVQlf1NKhlyAzm
+# kWJD0N+60IiogFBzg3yISsvroOx0x1xSi2PiRIQlTXE74MggZDIDKqH/hb9FT2kK
+# /nV/aXjuo9LMrrRmn44oYYADe/rO95F+SG3uuuhf+H4IriXr0h9ptA6SwHJPS2Vm
+# bNWCjQWq5G4YkrcqbPMax7vNXUwu7T65E8fFPd1IuE9RsG4TMAV7XkXBopmPNfvL
+# 0hjxg44kpQn384V46o+zdQqy5K9dDlWm/J6vZtp5yA1PyD3w+HbGubS0niEQ1L6w
+# GOrPfzIm0FdOn+xFo48ERl+Fxw/3OvXM5CY1EqnzEznPjzJc7OJwhJVR3VQDHjBc
+# EFTOvS9E0diNu1eocw+ZCkz4Pu/oQv+gqU+bfxL8e7PFktfRDlM6FyOzjP4zuI25
+# gD8tO9zJg6g6fRpaZc439mAbkl3zCVzTLDgchv6SxQajJtvvoQaZxQf0tRiPcbr2
+# HWfMoqqd9uiQ0hTUEhG44FBSTeUPZeEenRCWadCW4G8CAwEAAaOCAUkwggFFMB0G
+# A1UdDgQWBBRIwZsJuOcJfScPWcXZuBA4B89K8jAfBgNVHSMEGDAWgBSfpxVdAF5i
+# XYP05dJlpxtTNRnpcjBfBgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jv
+# c29mdC5jb20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENB
+# JTIwMjAxMCgxKS5jcmwwbAYIKwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRw
+# Oi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRp
+# bWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBYGA1Ud
+# JQEB/wQMMAoGCCsGAQUFBwMIMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsF
+# AAOCAgEA13kBirH1cHu1WYR1ysj125omGtQ0PaQkEzwGb70xtqSoI+svQihsgdTY
+# xaPfp2IVFdgjaMaBi81wB8/nu866FfFKKdhdp3wnMZ91PpP4Ooe7Ncf6qICkgSuw
+# gdIdQvqE0h8VQ5QW5sDV4Q0Jnj4f7KHYx4NiM8C4jTw8SQtsuxWiTH2Hikf3QYB7
+# 1a7dB9zgHOkW0hgUEeWO9mh2wWqYS/Q48ASjOqYw/ha54oVOff22WaoH+/Hxd9NT
+# EU/4vlvsRIMWT0jsnNI71jVArT4Q9Bt6VShWzyqraE6SKUoZrEwBpVsI0LMg2X3h
+# OLblC1vxM3+wMyOh97aFOs7sFnuemtI2Mfj8qg16BZTJxXlpPurWrG+OBj4BoTDk
+# C9AxXYB3yEtuwMs7pRWLyxIxw/wV9THKUGm+x+VE0POLwkrSMgjulSXkpfELHWWi
+# CVslJbFIIB/4Alv+jQJSKAJuo9CErbm2qeDk/zjJYlYaVGMyKuYZ+uSRVKB2qkEP
+# cEzG1dO9zIa1Mp32J+zzW3P7suJfjw62s3hDOLk+6lMQOR04x+2o17G3LceLkkxJ
+# m41ErdiTjAmdClen9yl6HgMpGS4okjFCJX+CpOFX7gBA3PVxQWubisAQbL5HgTFB
+# tQNEzcCdh1GYw/6nzzNNt+0GQnnobBddfOAiqkzvItqXjvGyK1QwggdxMIIFWaAD
+# AgECAhMzAAAAFcXna54Cm0mZAAAAAAAVMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYD
+# VQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEe
+# MBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3Nv
+# ZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0yMTA5MzAxODIy
+# MjVaFw0zMDA5MzAxODMyMjVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo
+# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y
+# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw
+# MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA5OGmTOe0ciELeaLL1yR5
+# vQ7VgtP97pwHB9KpbE51yMo1V/YBf2xK4OK9uT4XYDP/XE/HZveVU3Fa4n5KWv64
+# NmeFRiMMtY0Tz3cywBAY6GB9alKDRLemjkZrBxTzxXb1hlDcwUTIcVxRMTegCjhu
+# je3XD9gmU3w5YQJ6xKr9cmmvHaus9ja+NSZk2pg7uhp7M62AW36MEBydUv626GIl
+# 3GoPz130/o5Tz9bshVZN7928jaTjkY+yOSxRnOlwaQ3KNi1wjjHINSi947SHJMPg
+# yY9+tVSP3PoFVZhtaDuaRr3tpK56KTesy+uDRedGbsoy1cCGMFxPLOJiss254o2I
+# 5JasAUq7vnGpF1tnYN74kpEeHT39IM9zfUGaRnXNxF803RKJ1v2lIH1+/NmeRd+2
+# ci/bfV+AutuqfjbsNkz2K26oElHovwUDo9Fzpk03dJQcNIIP8BDyt0cY7afomXw/
+# TNuvXsLz1dhzPUNOwTM5TI4CvEJoLhDqhFFG4tG9ahhaYQFzymeiXtcodgLiMxhy
+# 16cg8ML6EgrXY28MyTZki1ugpoMhXV8wdJGUlNi5UPkLiWHzNgY1GIRH29wb0f2y
+# 1BzFa/ZcUlFdEtsluq9QBXpsxREdcu+N+VLEhReTwDwV2xo3xwgVGD94q0W29R6H
+# XtqPnhZyacaue7e3PmriLq0CAwEAAaOCAd0wggHZMBIGCSsGAQQBgjcVAQQFAgMB
+# AAEwIwYJKwYBBAGCNxUCBBYEFCqnUv5kxJq+gpE8RjUpzxD/LwTuMB0GA1UdDgQW
+# BBSfpxVdAF5iXYP05dJlpxtTNRnpcjBcBgNVHSAEVTBTMFEGDCsGAQQBgjdMg30B
+# ATBBMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3Bz
+# L0RvY3MvUmVwb3NpdG9yeS5odG0wEwYDVR0lBAwwCgYIKwYBBQUHAwgwGQYJKwYB
+# BAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMB
+# Af8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBL
+# oEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMv
+# TWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggr
+# BgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNS
+# b29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwDQYJKoZIhvcNAQELBQADggIBAJ1Vffwq
+# reEsH2cBMSRb4Z5yS/ypb+pcFLY+TkdkeLEGk5c9MTO1OdfCcTY/2mRsfNB1OW27
+# DzHkwo/7bNGhlBgi7ulmZzpTTd2YurYeeNg2LpypglYAA7AFvonoaeC6Ce5732pv
+# vinLbtg/SHUB2RjebYIM9W0jVOR4U3UkV7ndn/OOPcbzaN9l9qRWqveVtihVJ9Ak
+# vUCgvxm2EhIRXT0n4ECWOKz3+SmJw7wXsFSFQrP8DJ6LGYnn8AtqgcKBGUIZUnWK
+# NsIdw2FzLixre24/LAl4FOmRsqlb30mjdAy87JGA0j3mSj5mO0+7hvoyGtmW9I/2
+# kQH2zsZ0/fZMcm8Qq3UwxTSwethQ/gpY3UA8x1RtnWN0SCyxTkctwRQEcb9k+SS+
+# c23Kjgm9swFXSVRk2XPXfx5bRAGOWhmRaw2fpCjcZxkoJLo4S5pu+yFUa2pFEUep
+# 8beuyOiJXk+d0tBMdrVXVAmxaQFEfnyhYWxz/gq77EFmPWn9y8FBSX5+k77L+Dvk
+# txW/tM4+pTFRhLy/AsGConsXHRWJjXD+57XQKBqJC4822rpM+Zv/Cuk0+CQ1Zyvg
+# DbjmjJnW4SLq8CdCPSWU5nR0W2rRnj7tfqAxM328y+l7vzhwRNGQ8cirOoo6CGJ/
+# 2XBjU02N7oJtpQUQwXEGahC0HVUzWLOhcGbyoYIDWTCCAkECAQEwggEBoYHZpIHW
+# MIHTMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
+# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQL
+# EyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJzAlBgNVBAsT
+# Hm5TaGllbGQgVFNTIEVTTjo2QjA1LTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9z
+# b2Z0IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUAFU9eSpdxs0a0
+# 6JFIuGFHIj/I+36ggYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
+# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
+# cnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAx
+# MDANBgkqhkiG9w0BAQsFAAIFAOsTx1MwIhgPMjAyNDEyMjMxMTI2MTFaGA8yMDI0
+# MTIyNDExMjYxMVowdzA9BgorBgEEAYRZCgQBMS8wLTAKAgUA6xPHUwIBADAKAgEA
+# AgIEpgIB/zAHAgEAAgIULjAKAgUA6xUY0wIBADA2BgorBgEEAYRZCgQCMSgwJjAM
+# BgorBgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEB
+# CwUAA4IBAQDkPou5w9O3fL9lm7NIu3mAwCMpezmpCbx9mCUfLb4cXznb4psGEspn
+# XaDg3PGX1yGC3GR5peByH/hiarlvYv5SZbofvP+iiYFxeLGi+usbC8FQnuWrgyqh
+# 7RV01Fm2Is7PGF3NXQaXbGkSQUZzrekeRr4zdV2nIKshANlifSPb/wAd6BLcKtYS
+# 3Kr9xUXgZeHxo6tD88GDxJ5FDsG1RxczsdCO5mVqFZUrQqz6Cs49xt7cq2XlEwMX
+# 53L40YCUrvYYiTgqvxtOzg58ksUkP1YDfeP9Rel7pGXGyzJF0Fo+FAXiY098HPcW
+# eRCGaVV55Keop55er/x0vYOQK3WYmR9ZMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UE
+# BhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAc
+# BgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0
+# IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAH2gy8malRdIsEAAQAAAfYwDQYJYIZI
+# AWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG
+# 9w0BCQQxIgQgrAY6roZynzJwSUQzsAfof3O6FxHR94SlM3Hdh+QLWTowgfoGCyqG
+# SIb3DQEJEAIvMYHqMIHnMIHkMIG9BCArYUzxlF6m5USLS4f8NXL/8aoNEVdsCZRm
+# F+LlQjG2ojCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5n
+# dG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9y
+# YXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMz
+# AAAB9oMvJmpUXSLBAAEAAAH2MCIEIOEmAHxbUTtc2fET28qfaLGRzaIhD4dw4/ak
+# m6mLo6PCMA0GCSqGSIb3DQEBCwUABIICAH4zqNf+CV7yNKXAQwNuEZwzCHs4eJC4
+# mNKMRp9e2W++JJaxtY3kLEBoGDKfz+8RYsjcLtNOg83Hd2gEmAmgvWfs/mZlhi8j
+# tM5Yj9606ukzuF2797CDwZiXz5JTIs1wIm5+rWuAny0azioafaOZtcaxPqnANbUf
+# UHnAYoZ0W6AkSnz99XrqRueh8b3Z7h2A5saVS1/MV1fjfImxH673GlexKRYORTFP
+# A9XH8vCvVTIZobKSWa6y/KG4U5dkWrzJuLbT9Kr3x6yk0bO6epG09yy86HmlnmwQ
+# bJwBedz4ZpKkvtIJ2U2PYvLti5bztm5WL/hWhDmtXBhoqKy1d/i4LDz7F4vXy9//
+# 5Wy8zHMLw4ZFwfywP8/P5pZP7nlvIrordtefS2z3Ipea0thJxwyFJSU8c7OGvPof
+# uHhYKMn+Lg1tj5QcIvT7KG0JGp/XWe/CEB9ruOkYZh9VKBlEU2CkDsWeTXFy5heV
+# eMPMxRC/GBHPypBHHSrd8BtUsKgehcetdsfQrOI3VLV9gx8SGr8ehSU2Rg0U2UtK
+# a4S5THXp0PksrjJLmykIsvBmMgl3uK4KJbu6zh3w5ACSS/OZXzrsowysGjn2QBwb
+# hoIjCDhmNLLq2gupcAhPCgqUX6Ixmg8H0SlqoRp0mZTss7ZS+ErxTQoFBbLkoX5F
+# 9HjkIOcM6/0F
+# SIG # End signature block
\ No newline at end of file
diff --git a/externals/install-dotnet.sh b/externals/install-dotnet.sh
index 3e16cc9..9f7912e 100755
--- a/externals/install-dotnet.sh
+++ b/externals/install-dotnet.sh
@@ -298,11 +298,20 @@ get_machine_architecture() {
if command -v uname > /dev/null; then
CPUName=$(uname -m)
case $CPUName in
+ armv1*|armv2*|armv3*|armv4*|armv5*|armv6*)
+ echo "armv6-or-below"
+ return 0
+ ;;
armv*l)
echo "arm"
return 0
;;
aarch64|arm64)
+ if [ "$(getconf LONG_BIT)" -lt 64 ]; then
+ # This is 32-bit OS running on 64-bit CPU (for example Raspberry Pi OS)
+ echo "arm"
+ return 0
+ fi
echo "arm64"
return 0
;;
@@ -310,6 +319,22 @@ get_machine_architecture() {
echo "s390x"
return 0
;;
+ ppc64le)
+ echo "ppc64le"
+ return 0
+ ;;
+ loongarch64)
+ echo "loongarch64"
+ return 0
+ ;;
+ riscv64)
+ echo "riscv64"
+ return 0
+ ;;
+ powerpc|ppc)
+ echo "ppc"
+ return 0
+ ;;
esac
fi
@@ -326,7 +351,13 @@ get_normalized_architecture_from_architecture() {
local architecture="$(to_lowercase "$1")"
if [[ $architecture == \ ]]; then
- echo "$(get_machine_architecture)"
+ machine_architecture="$(get_machine_architecture)"
+ if [[ "$machine_architecture" == "armv6-or-below" ]]; then
+ say_err "Architecture \`$machine_architecture\` not supported. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues"
+ return 1
+ fi
+
+ echo $machine_architecture
return 0
fi
@@ -347,6 +378,14 @@ get_normalized_architecture_from_architecture() {
echo "s390x"
return 0
;;
+ ppc64le)
+ echo "ppc64le"
+ return 0
+ ;;
+ loongarch64)
+ echo "loongarch64"
+ return 0
+ ;;
esac
say_err "Architecture \`$architecture\` not supported. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues"
@@ -384,11 +423,17 @@ get_normalized_architecture_for_specific_sdk_version() {
# args:
# version or channel - $1
is_arm64_supported() {
- #any channel or version that starts with the specified versions
- case "$1" in
- ( "1"* | "2"* | "3"* | "4"* | "5"*)
- echo false
- return 0
+ # Extract the major version by splitting on the dot
+ major_version="${1%%.*}"
+
+ # Check if the major version is a valid number and less than 6
+ case "$major_version" in
+ [0-9]*)
+ if [ "$major_version" -lt 6 ]; then
+ echo false
+ return 0
+ fi
+ ;;
esac
echo true
@@ -407,8 +452,13 @@ get_normalized_os() {
echo "$osname"
return 0
;;
+ macos)
+ osname='osx'
+ echo "$osname"
+ return 0
+ ;;
*)
- say_err "'$user_defined_os' is not a supported value for --os option, supported values are: osx, linux, linux-musl, freebsd, rhel.6. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues."
+ say_err "'$user_defined_os' is not a supported value for --os option, supported values are: osx, macos, linux, linux-musl, freebsd, rhel.6. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues."
return 1
;;
esac
@@ -451,6 +501,10 @@ get_normalized_channel() {
local channel="$(to_lowercase "$1")"
+ if [[ $channel == current ]]; then
+ say_warning 'Value "Current" is deprecated for -Channel option. Use "STS" instead.'
+ fi
+
if [[ $channel == release/* ]]; then
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.';
fi
@@ -461,6 +515,14 @@ get_normalized_channel() {
echo "LTS"
return 0
;;
+ sts)
+ echo "STS"
+ return 0
+ ;;
+ current)
+ echo "STS"
+ return 0
+ ;;
*)
echo "$channel"
return 0
@@ -526,6 +588,40 @@ is_dotnet_package_installed() {
fi
}
+# args:
+# downloaded file - $1
+# remote_file_size - $2
+validate_remote_local_file_sizes()
+{
+ eval $invocation
+
+ local downloaded_file="$1"
+ local remote_file_size="$2"
+ local file_size=''
+
+ if [[ "$OSTYPE" == "linux-gnu"* ]]; then
+ file_size="$(stat -c '%s' "$downloaded_file")"
+ elif [[ "$OSTYPE" == "darwin"* ]]; then
+ # hardcode in order to avoid conflicts with GNU stat
+ file_size="$(/usr/bin/stat -f '%z' "$downloaded_file")"
+ fi
+
+ if [ -n "$file_size" ]; then
+ say "Downloaded file size is $file_size bytes."
+
+ if [ -n "$remote_file_size" ] && [ -n "$file_size" ]; then
+ if [ "$remote_file_size" -ne "$file_size" ]; then
+ say "The remote and local file sizes are not equal. The remote file size is $remote_file_size bytes and the local size is $file_size bytes. The local package may be corrupted."
+ else
+ say "The remote and local file sizes are equal."
+ fi
+ fi
+
+ else
+ say "Either downloaded or local package size can not be measured. One of them may be corrupted."
+ fi
+}
+
# args:
# azure_feed - $1
# channel - $2
@@ -860,6 +956,37 @@ get_absolute_path() {
return 0
}
+# args:
+# override - $1 (boolean, true or false)
+get_cp_options() {
+ eval $invocation
+
+ local override="$1"
+ local override_switch=""
+
+ if [ "$override" = false ]; then
+ override_switch="-n"
+
+ # create temporary files to check if 'cp -u' is supported
+ tmp_dir="$(mktemp -d)"
+ tmp_file="$tmp_dir/testfile"
+ tmp_file2="$tmp_dir/testfile2"
+
+ touch "$tmp_file"
+
+ # use -u instead of -n if it's available
+ if cp -u "$tmp_file" "$tmp_file2" 2>/dev/null; then
+ override_switch="-u"
+ fi
+
+ # clean up
+ rm -f "$tmp_file" "$tmp_file2"
+ rm -rf "$tmp_dir"
+ fi
+
+ echo "$override_switch"
+}
+
# args:
# input_files - stdin
# root_path - $1
@@ -871,15 +998,7 @@ copy_files_or_dirs_from_list() {
local root_path="$(remove_trailing_slash "$1")"
local out_path="$(remove_trailing_slash "$2")"
local override="$3"
- local osname="$(get_current_os_name)"
- local override_switch=$(
- if [ "$override" = false ]; then
- if [ "$osname" = "linux-musl" ]; then
- printf -- "-u";
- else
- printf -- "-n";
- fi
- fi)
+ local override_switch="$(get_cp_options "$override")"
cat | uniq | while read -r file_path; do
local path="$(remove_beginning_slash "${file_path#$root_path}")"
@@ -894,14 +1013,39 @@ copy_files_or_dirs_from_list() {
done
}
+# args:
+# zip_uri - $1
+get_remote_file_size() {
+ local zip_uri="$1"
+
+ if machine_has "curl"; then
+ file_size=$(curl -sI "$zip_uri" | grep -i content-length | awk '{ num = $2 + 0; print num }')
+ elif machine_has "wget"; then
+ file_size=$(wget --spider --server-response -O /dev/null "$zip_uri" 2>&1 | grep -i 'Content-Length:' | awk '{ num = $2 + 0; print num }')
+ else
+ say "Neither curl nor wget is available on this system."
+ return
+ fi
+
+ if [ -n "$file_size" ]; then
+ say "Remote file $zip_uri size is $file_size bytes."
+ echo "$file_size"
+ else
+ say_verbose "Content-Length header was not extracted for $zip_uri."
+ echo ""
+ fi
+}
+
# args:
# zip_path - $1
# out_path - $2
+# remote_file_size - $3
extract_dotnet_package() {
eval $invocation
local zip_path="$1"
local out_path="$2"
+ local remote_file_size="$3"
local temp_out_path="$(mktemp -d "$temporary_file_template")"
@@ -911,9 +1055,13 @@ extract_dotnet_package() {
local folders_with_version_regex='^.*/[0-9]+\.[0-9]+[^/]+/'
find "$temp_out_path" -type f | grep -Eo "$folders_with_version_regex" | sort | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" false
find "$temp_out_path" -type f | grep -Ev "$folders_with_version_regex" | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" "$override_non_versioned_files"
-
+
+ validate_remote_local_file_sizes "$zip_path" "$remote_file_size"
+
rm -rf "$temp_out_path"
- rm -f "$zip_path" && say_verbose "Temporary zip file $zip_path was removed"
+ if [ -z ${keep_zip+x} ]; then
+ rm -f "$zip_path" && say_verbose "Temporary archive file $zip_path was removed"
+ fi
if [ "$failed" = true ]; then
say_err "Extraction failed"
@@ -1124,13 +1272,69 @@ downloadwget() {
return 0
}
+extract_stem() {
+ local url="$1"
+ # extract the protocol
+ proto="$(echo $1 | grep :// | sed -e's,^\(.*://\).*,\1,g')"
+ # remove the protocol
+ url="${1/$proto/}"
+ # extract the path (if any) - since we know all of our feeds have a first path segment, we can skip the first one. otherwise we'd use -f2- to get the full path
+ full_path="$(echo $url | grep / | cut -d/ -f2-)"
+ path="$(echo $full_path | cut -d/ -f2-)"
+ echo $path
+}
+
+check_url_exists() {
+ eval $invocation
+ local url="$1"
+
+ local code=""
+ if machine_has "curl"
+ then
+ code=$(curl --head -o /dev/null -w "%{http_code}" -s --fail "$url");
+ elif machine_has "wget"
+ then
+ # get the http response, grab the status code
+ server_response=$(wget -qO- --method=HEAD --server-response "$url" 2>&1)
+ code=$(echo "$server_response" | grep "HTTP/" | awk '{print $2}')
+ fi
+ if [ $code = "200" ]; then
+ return 0
+ else
+ return 1
+ fi
+}
+
+sanitize_redirect_url() {
+ eval $invocation
+
+ local url_stem
+ url_stem=$(extract_stem "$1")
+ say_verbose "Checking configured feeds for the asset at ${yellow:-}$url_stem${normal:-}"
+
+ for feed in "${feeds[@]}"
+ do
+ local trial_url="$feed/$url_stem"
+ say_verbose "Checking ${yellow:-}$trial_url${normal:-}"
+ if check_url_exists "$trial_url"; then
+ say_verbose "Found a match at ${yellow:-}$trial_url${normal:-}"
+ echo "$trial_url"
+ return 0
+ else
+ say_verbose "No match at ${yellow:-}$trial_url${normal:-}"
+ fi
+ done
+ return 1
+}
+
get_download_link_from_aka_ms() {
eval $invocation
- #quality is not supported for LTS or current channel
- if [[ ! -z "$normalized_quality" && ("$normalized_channel" == "LTS" || "$normalized_channel" == "current") ]]; then
+ #quality is not supported for LTS or STS channel
+ #STS maps to current
+ if [[ ! -z "$normalized_quality" && ("$normalized_channel" == "LTS" || "$normalized_channel" == "STS") ]]; then
normalized_quality=""
- say_warning "Specifying quality for current or LTS channel is not supported, the quality will be ignored."
+ say_warning "Specifying quality for STS or LTS channel is not supported, the quality will be ignored."
fi
say_verbose "Retrieving primary payload URL from aka.ms for channel: '$normalized_channel', quality: '$normalized_quality', product: '$normalized_product', os: '$normalized_os', architecture: '$normalized_architecture'."
@@ -1159,6 +1363,12 @@ get_download_link_from_aka_ms() {
http_codes=$( echo "$response" | awk '$1 ~ /^HTTP/ {print $2}' )
# They all need to be 301, otherwise some links are broken (except for the last, which is not a redirect but 200 or 404).
broken_redirects=$( echo "$http_codes" | sed '$d' | grep -v '301' )
+ # The response may end without final code 2xx/4xx/5xx somehow, e.g. network restrictions on www.bing.com causes redirecting to bing.com fails with connection refused.
+ # In this case it should not exclude the last.
+ last_http_code=$( echo "$http_codes" | tail -n 1 )
+ if ! [[ $last_http_code =~ ^(2|4|5)[0-9][0-9]$ ]]; then
+ broken_redirects=$( echo "$http_codes" | grep -v '301' )
+ fi
# All HTTP codes are 301 (Moved Permanently), the redirect link exists.
if [[ -z "$broken_redirects" ]]; then
@@ -1169,6 +1379,11 @@ get_download_link_from_aka_ms() {
return 1
fi
+ sanitized_redirect_url=$(sanitize_redirect_url "$aka_ms_download_link")
+ if [[ -n "$sanitized_redirect_url" ]]; then
+ aka_ms_download_link="$sanitized_redirect_url"
+ fi
+
say_verbose "The redirect location retrieved: '$aka_ms_download_link'."
return 0
else
@@ -1180,7 +1395,9 @@ get_download_link_from_aka_ms() {
get_feeds_to_use()
{
feeds=(
+ "https://builds.dotnet.microsoft.com/dotnet"
"https://dotnetcli.azureedge.net/dotnet"
+ "https://ci.dot.net/public"
"https://dotnetbuilds.azureedge.net/public"
)
@@ -1239,7 +1456,7 @@ generate_akams_links() {
normalized_version="$(to_lowercase "$version")"
if [[ "$normalized_version" != "latest" ]] && [ -n "$normalized_quality" ]; then
- say_err "Quality and Version options are not allowed to be specified simultaneously. See https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-install-script#options for details."
+ say_err "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."
return 1
fi
@@ -1406,10 +1623,11 @@ install_dotnet() {
eval $invocation
local download_failed=false
local download_completed=false
+ local remote_file_size=0
mkdir -p "$install_root"
- zip_path="$(mktemp "$temporary_file_template")"
- say_verbose "Zip path: $zip_path"
+ zip_path="${zip_path:-$(mktemp "$temporary_file_template")}"
+ say_verbose "Archive path: $zip_path"
for link_index in "${!download_links[@]}"
do
@@ -1433,7 +1651,7 @@ install_dotnet() {
say "Failed to download $link_type link '$download_link': $download_error_msg"
;;
esac
- rm -f "$zip_path" 2>&1 && say_verbose "Temporary zip file $zip_path was removed"
+ rm -f "$zip_path" 2>&1 && say_verbose "Temporary archive file $zip_path was removed"
else
download_completed=true
break
@@ -1446,8 +1664,10 @@ install_dotnet() {
return 1
fi
- say "Extracting zip from $download_link"
- extract_dotnet_package "$zip_path" "$install_root" || return 1
+ remote_file_size="$(get_remote_file_size "$download_link")"
+
+ say "Extracting archive from $download_link"
+ extract_dotnet_package "$zip_path" "$install_root" "$remote_file_size" || return 1
# Check if the SDK version is installed; if not, fail the installation.
# if the version contains "RTM" or "servicing"; check if a 'release-type' SDK version is installed.
@@ -1597,25 +1817,42 @@ do
override_non_versioned_files=false
non_dynamic_parameters+=" $name"
;;
+ --keep-zip|-[Kk]eep[Zz]ip)
+ keep_zip=true
+ non_dynamic_parameters+=" $name"
+ ;;
+ --zip-path|-[Zz]ip[Pp]ath)
+ shift
+ zip_path="$1"
+ ;;
-?|--?|-h|--help|-[Hh]elp)
- script_name="$(basename "$0")"
+ script_name="dotnet-install.sh"
echo ".NET Tools Installer"
- echo "Usage: $script_name [-c|--channel ] [-v|--version ] [-p|--prefix ]"
+ echo "Usage:"
+ echo " # Install a .NET SDK of a given Quality from a given Channel"
+ echo " $script_name [-c|--channel ] [-q|--quality ]"
+ echo " # Install a .NET SDK of a specific public version"
+ echo " $script_name [-v|--version ]"
echo " $script_name -h|-?|--help"
echo ""
echo "$script_name is a simple command line interface for obtaining dotnet cli."
+ echo " Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:"
+ echo " - The SDK needs to be installed without user interaction and without admin rights."
+ echo " - The SDK installation doesn't need to persist across multiple CI runs."
+ echo " 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."
echo ""
echo "Options:"
echo " -c,--channel Download from the channel specified, Defaults to \`$channel\`."
echo " -Channel"
echo " Possible values:"
- echo " - Current - most current release"
- echo " - LTS - most current supported release"
+ echo " - STS - the most recent Standard Term Support release"
+ echo " - LTS - the most recent Long Term Support release"
echo " - 2-part version in a format A.B - represents a specific release"
echo " examples: 2.0; 1.0"
echo " - 3-part version in a format A.B.Cxx - represents a specific SDK release"
echo " examples: 5.0.1xx, 5.0.2xx."
echo " Supported since 5.0 release"
+ echo " Warning: Value 'Current' is deprecated for the Channel parameter. Use 'STS' instead."
echo " Note: The version parameter overrides the channel parameter when any version other than 'latest' is used."
echo " -v,--version Use specific VERSION, Defaults to \`$version\`."
echo " -Version"
@@ -1626,7 +1863,7 @@ do
echo " -q,--quality Download the latest build of specified quality in the channel."
echo " -Quality"
echo " The possible values are: daily, signed, validated, preview, GA."
- echo " Works only in combination with channel. Not applicable for current and LTS channels and will be ignored if those channels are used."
+ echo " Works only in combination with channel. Not applicable for STS and LTS channels and will be ignored if those channels are used."
echo " For SDK use channel in A.B.Cxx format. Using quality for SDK together with channel in A.B format is not supported."
echo " Supported since 5.0 release."
echo " Note: The version parameter overrides the channel parameter when any version other than 'latest' is used, and therefore overrides the quality."
@@ -1637,7 +1874,7 @@ do
echo " -InstallDir"
echo " --architecture Architecture of dotnet binaries to be installed, Defaults to \`$architecture\`."
echo " --arch,-Architecture,-Arch"
- echo " Possible values: x64, arm, arm64 and s390x"
+ echo " Possible values: x64, arm, arm64, s390x, ppc64le and loongarch64"
echo " --os Specifies operating system to be used when selecting the installer."
echo " Overrides the OS determination approach used by the script. Supported values: osx, linux, linux-musl, freebsd, rhel.6."
echo " In case any other value is provided, the platform will be determined by the script based on machine configuration."
@@ -1662,6 +1899,8 @@ do
echo " --no-cdn,-NoCdn Disable downloading from the Azure CDN, and use the uncached feed directly."
echo " --jsonfile Determines the SDK version from a user specified global.json file."
echo " Note: global.json must have a value for 'SDK:Version'"
+ echo " --keep-zip,-KeepZip If set, downloaded file is kept."
+ echo " --zip-path, -ZipPath If set, downloaded file is stored at the specified path."
echo " -?,--?,-h,--help,-Help Shows this help message"
echo ""
echo "Install Location:"
@@ -1680,10 +1919,10 @@ do
shift
done
-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.\n"
+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.\n"
if [ "$internal" = true ] && [ -z "$(echo $feed_credential)" ]; then
message="Provide credentials via --feed-credential parameter."
@@ -1716,5 +1955,5 @@ else
fi
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, select your operating system and check the \"Dependencies\" section."
+say "To check the list of dependencies, go to https://learn.microsoft.com/dotnet/core/install, select your operating system and check the \"Dependencies\" section."
say "Installation finished successfully."
diff --git a/src/authutil.ts b/src/authutil.ts
index 9599979..f635e1b 100644
--- a/src/authutil.ts
+++ b/src/authutil.ts
@@ -1,155 +1,155 @@
-import * as fs from 'fs';
-import * as path from 'path';
-import * as core from '@actions/core';
-import * as github from '@actions/github';
-import * as xmlbuilder from 'xmlbuilder';
-import * as xmlParser from 'fast-xml-parser';
-import {ProcessEnvOptions} from 'child_process';
-
-export function configAuthentication(
- feedUrl: string,
- existingFileLocation: string = '',
- processRoot: string = process.cwd()
-) {
- const existingNuGetConfig: string = path.resolve(
- processRoot,
- existingFileLocation === ''
- ? getExistingNugetConfig(processRoot)
- : existingFileLocation
- );
-
- const tempNuGetConfig: string = path.resolve(
- processRoot,
- '../',
- 'nuget.config'
- );
-
- writeFeedToFile(feedUrl, existingNuGetConfig, tempNuGetConfig);
-}
-
-function isValidKey(key: string): boolean {
- return /^[\w\-\.]+$/i.test(key);
-}
-
-function getExistingNugetConfig(processRoot: string) {
- const defaultConfigName = 'nuget.config';
- const configFileNames = fs
- .readdirSync(processRoot)
- .filter(filename => filename.toLowerCase() === defaultConfigName);
- if (configFileNames.length) {
- return configFileNames[0];
- }
- return defaultConfigName;
-}
-
-function writeFeedToFile(
- feedUrl: string,
- existingFileLocation: string,
- tempFileLocation: string
-) {
- console.log(
- `dotnet-auth: Finding any source references in ${existingFileLocation}, writing a new temporary configuration file with credentials to ${tempFileLocation}`
- );
- let xml: xmlbuilder.XMLElement;
- let sourceKeys: string[] = [];
- let owner: string = core.getInput('owner');
- let sourceUrl: string = feedUrl;
- if (!owner) {
- owner = github.context.repo.owner;
- }
-
- if (!process.env.NUGET_AUTH_TOKEN || process.env.NUGET_AUTH_TOKEN == '') {
- throw new Error(
- 'The NUGET_AUTH_TOKEN environment variable was not provided. In this step, add the following: \r\nenv:\r\n NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}'
- );
- }
-
- if (fs.existsSync(existingFileLocation)) {
- // get key from existing NuGet.config so NuGet/dotnet can match credentials
- const curContents: string = fs.readFileSync(existingFileLocation, 'utf8');
- var json = xmlParser.parse(curContents, {ignoreAttributes: false});
-
- if (typeof json.configuration == 'undefined') {
- throw new Error(`The provided NuGet.config seems invalid.`);
- }
- if (typeof json.configuration.packageSources != 'undefined') {
- if (typeof json.configuration.packageSources.add != 'undefined') {
- // file has at least one
- if (typeof json.configuration.packageSources.add[0] == 'undefined') {
- // file has only one
- if (
- json.configuration.packageSources.add['@_value']
- .toLowerCase()
- .includes(feedUrl.toLowerCase())
- ) {
- let key = json.configuration.packageSources.add['@_key'];
- sourceKeys.push(key);
- core.debug(`Found a URL with key ${key}`);
- }
- } else {
- // file has 2+
- for (
- let i = 0;
- i < json.configuration.packageSources.add.length;
- i++
- ) {
- const source = json.configuration.packageSources.add[i];
- const value = source['@_value'];
- core.debug(`source '${value}'`);
- if (value.toLowerCase().includes(feedUrl.toLowerCase())) {
- let key = source['@_key'];
- sourceKeys.push(key);
- core.debug(`Found a URL with key ${key}`);
- }
- }
- }
- }
- }
- }
-
- xml = xmlbuilder
- .create('configuration')
- .ele('config')
- .ele('add', {key: 'defaultPushSource', value: sourceUrl})
- .up()
- .up();
-
- if (sourceKeys.length == 0) {
- let keystring = 'Source';
- xml = xml
- .ele('packageSources')
- .ele('add', {key: keystring, value: sourceUrl})
- .up()
- .up();
- sourceKeys.push(keystring);
- }
- xml = xml.ele('packageSourceCredentials');
-
- sourceKeys.forEach(key => {
- if (!isValidKey(key)) {
- throw new Error(
- "Source name can contain letters, numbers, and '-', '_', '.' symbols only. Please, fix source name in NuGet.config and try again."
- );
- }
-
- xml = xml
- .ele(key)
- .ele('add', {key: 'Username', value: owner})
- .up()
- .ele('add', {
- key: 'ClearTextPassword',
- value: process.env.NUGET_AUTH_TOKEN
- })
- .up()
- .up();
- });
-
- // If NuGet fixes itself such that on Linux it can look for environment variables in the config file (it doesn't seem to work today),
- // use this for the value above
- // process.platform == 'win32'
- // ? '%NUGET_AUTH_TOKEN%'
- // : '$NUGET_AUTH_TOKEN'
-
- var output = xml.end({pretty: true});
- fs.writeFileSync(tempFileLocation, output);
-}
+import * as fs from 'fs';
+import * as path from 'path';
+import * as core from '@actions/core';
+import * as github from '@actions/github';
+import * as xmlbuilder from 'xmlbuilder';
+import * as xmlParser from 'fast-xml-parser';
+import {ProcessEnvOptions} from 'child_process';
+
+export function configAuthentication(
+ feedUrl: string,
+ existingFileLocation: string = '',
+ processRoot: string = process.cwd()
+) {
+ const existingNuGetConfig: string = path.resolve(
+ processRoot,
+ existingFileLocation === ''
+ ? getExistingNugetConfig(processRoot)
+ : existingFileLocation
+ );
+
+ const tempNuGetConfig: string = path.resolve(
+ processRoot,
+ '../',
+ 'nuget.config'
+ );
+
+ writeFeedToFile(feedUrl, existingNuGetConfig, tempNuGetConfig);
+}
+
+function isValidKey(key: string): boolean {
+ return /^[\w\-\.]+$/i.test(key);
+}
+
+function getExistingNugetConfig(processRoot: string) {
+ const defaultConfigName = 'nuget.config';
+ const configFileNames = fs
+ .readdirSync(processRoot)
+ .filter(filename => filename.toLowerCase() === defaultConfigName);
+ if (configFileNames.length) {
+ return configFileNames[0];
+ }
+ return defaultConfigName;
+}
+
+function writeFeedToFile(
+ feedUrl: string,
+ existingFileLocation: string,
+ tempFileLocation: string
+) {
+ console.log(
+ `dotnet-auth: Finding any source references in ${existingFileLocation}, writing a new temporary configuration file with credentials to ${tempFileLocation}`
+ );
+ let xml: xmlbuilder.XMLElement;
+ let sourceKeys: string[] = [];
+ let owner: string = core.getInput('owner');
+ let sourceUrl: string = feedUrl;
+ if (!owner) {
+ owner = github.context.repo.owner;
+ }
+
+ if (!process.env.NUGET_AUTH_TOKEN || process.env.NUGET_AUTH_TOKEN == '') {
+ throw new Error(
+ 'The NUGET_AUTH_TOKEN environment variable was not provided. In this step, add the following: \r\nenv:\r\n NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}'
+ );
+ }
+
+ if (fs.existsSync(existingFileLocation)) {
+ // get key from existing NuGet.config so NuGet/dotnet can match credentials
+ const curContents: string = fs.readFileSync(existingFileLocation, 'utf8');
+ var json = xmlParser.parse(curContents, {ignoreAttributes: false});
+
+ if (typeof json.configuration == 'undefined') {
+ throw new Error(`The provided NuGet.config seems invalid.`);
+ }
+ if (typeof json.configuration.packageSources != 'undefined') {
+ if (typeof json.configuration.packageSources.add != 'undefined') {
+ // file has at least one
+ if (typeof json.configuration.packageSources.add[0] == 'undefined') {
+ // file has only one
+ if (
+ json.configuration.packageSources.add['@_value']
+ .toLowerCase()
+ .includes(feedUrl.toLowerCase())
+ ) {
+ let key = json.configuration.packageSources.add['@_key'];
+ sourceKeys.push(key);
+ core.debug(`Found a URL with key ${key}`);
+ }
+ } else {
+ // file has 2+
+ for (
+ let i = 0;
+ i < json.configuration.packageSources.add.length;
+ i++
+ ) {
+ const source = json.configuration.packageSources.add[i];
+ const value = source['@_value'];
+ core.debug(`source '${value}'`);
+ if (value.toLowerCase().includes(feedUrl.toLowerCase())) {
+ let key = source['@_key'];
+ sourceKeys.push(key);
+ core.debug(`Found a URL with key ${key}`);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ xml = xmlbuilder
+ .create('configuration')
+ .ele('config')
+ .ele('add', {key: 'defaultPushSource', value: sourceUrl})
+ .up()
+ .up();
+
+ if (sourceKeys.length == 0) {
+ let keystring = 'Source';
+ xml = xml
+ .ele('packageSources')
+ .ele('add', {key: keystring, value: sourceUrl})
+ .up()
+ .up();
+ sourceKeys.push(keystring);
+ }
+ xml = xml.ele('packageSourceCredentials');
+
+ sourceKeys.forEach(key => {
+ if (!isValidKey(key)) {
+ throw new Error(
+ "Source name can contain letters, numbers, and '-', '_', '.' symbols only. Please, fix source name in NuGet.config and try again."
+ );
+ }
+
+ xml = xml
+ .ele(key)
+ .ele('add', {key: 'Username', value: owner})
+ .up()
+ .ele('add', {
+ key: 'ClearTextPassword',
+ value: process.env.NUGET_AUTH_TOKEN
+ })
+ .up()
+ .up();
+ });
+
+ // If NuGet fixes itself such that on Linux it can look for environment variables in the config file (it doesn't seem to work today),
+ // use this for the value above
+ // process.platform == 'win32'
+ // ? '%NUGET_AUTH_TOKEN%'
+ // : '$NUGET_AUTH_TOKEN'
+
+ var output = xml.end({pretty: true});
+ fs.writeFileSync(tempFileLocation, output);
+}
diff --git a/src/installer.ts b/src/installer.ts
index d5911d9..21f9889 100644
--- a/src/installer.ts
+++ b/src/installer.ts
@@ -1,304 +1,305 @@
-// Load tempDirectory before it gets wiped by tool-cache
-import * as core from '@actions/core';
-import * as exec from '@actions/exec';
-import * as io from '@actions/io';
-import hc = require('@actions/http-client');
-import {chmodSync} from 'fs';
-import * as path from 'path';
-import {ExecOptions} from '@actions/exec/lib/interfaces';
-import * as semver from 'semver';
-
-const IS_WINDOWS = process.platform === 'win32';
-
-/**
- * Represents the inputted version information
- */
-export class DotNetVersionInfo {
- public inputVersion: string;
- private fullversion: string;
- private isExactVersionSet: boolean = false;
-
- constructor(version: string) {
- this.inputVersion = version;
-
- // Check for exact match
- if (semver.valid(semver.clean(version) || '') != null) {
- this.fullversion = semver.clean(version) as string;
- this.isExactVersionSet = true;
-
- return;
- }
-
- const parts: string[] = version.split('.');
-
- if (parts.length < 2 || parts.length > 3) this.throwInvalidVersionFormat();
-
- if (parts.length == 3 && parts[2] !== 'x' && parts[2] !== '*') {
- this.throwInvalidVersionFormat();
- }
-
- const major = this.getVersionNumberOrThrow(parts[0]);
- const minor = ['x', '*'].includes(parts[1])
- ? parts[1]
- : this.getVersionNumberOrThrow(parts[1]);
-
- this.fullversion = major + '.' + minor;
- }
-
- private getVersionNumberOrThrow(input: string): number {
- try {
- if (!input || input.trim() === '') this.throwInvalidVersionFormat();
-
- let number = Number(input);
-
- if (Number.isNaN(number) || number < 0) this.throwInvalidVersionFormat();
-
- return number;
- } catch {
- this.throwInvalidVersionFormat();
- return -1;
- }
- }
-
- private throwInvalidVersionFormat() {
- throw new Error(
- 'Invalid version format! Supported: 1.2.3, 1.2, 1.2.x, 1.2.*'
- );
- }
-
- /**
- * If true exacatly one version should be resolved
- */
- public isExactVersion(): boolean {
- return this.isExactVersionSet;
- }
-
- public version(): string {
- return this.fullversion;
- }
-}
-
-export class DotnetCoreInstaller {
- constructor(version: string, includePrerelease: boolean = false) {
- this.version = version;
- this.includePrerelease = includePrerelease;
- }
-
- public async installDotnet() {
- let output = '';
- let resultCode = 0;
-
- let calculatedVersion = await this.resolveVersion(
- new DotNetVersionInfo(this.version)
- );
-
- var envVariables: {[key: string]: string} = {};
- for (let key in process.env) {
- if (process.env[key]) {
- let value: any = process.env[key];
- envVariables[key] = value;
- }
- }
- if (IS_WINDOWS) {
- let escapedScript = path
- .join(__dirname, '..', 'externals', 'install-dotnet.ps1')
- .replace(/'/g, "''");
- let command = `& '${escapedScript}'`;
- if (calculatedVersion) {
- command += ` -Version ${calculatedVersion}`;
- }
- if (process.env['https_proxy'] != null) {
- command += ` -ProxyAddress ${process.env['https_proxy']}`;
- }
- // This is not currently an option
- if (process.env['no_proxy'] != null) {
- command += ` -ProxyBypassList ${process.env['no_proxy']}`;
- }
-
- // process.env must be explicitly passed in for DOTNET_INSTALL_DIR to be used
- const powershellPath =
- (await io.which('pwsh', false)) || (await io.which('powershell', true));
-
- var options: ExecOptions = {
- listeners: {
- stdout: (data: Buffer) => {
- output += data.toString();
- }
- },
- env: envVariables
- };
-
- resultCode = await exec.exec(
- `"${powershellPath}"`,
- [
- '-NoLogo',
- '-Sta',
- '-NoProfile',
- '-NonInteractive',
- '-ExecutionPolicy',
- 'Unrestricted',
- '-Command',
- command
- ],
- options
- );
- } else {
- let escapedScript = path
- .join(__dirname, '..', 'externals', 'install-dotnet.sh')
- .replace(/'/g, "''");
- chmodSync(escapedScript, '777');
-
- const scriptPath = await io.which(escapedScript, true);
-
- let scriptArguments: string[] = [];
- if (calculatedVersion) {
- scriptArguments.push('--version', calculatedVersion);
- }
-
- // process.env must be explicitly passed in for DOTNET_INSTALL_DIR to be used
- resultCode = await exec.exec(`"${scriptPath}"`, scriptArguments, {
- listeners: {
- stdout: (data: Buffer) => {
- output += data.toString();
- }
- },
- env: envVariables
- });
- }
-
- if (resultCode != 0) {
- throw new Error(`Failed to install dotnet ${resultCode}. ${output}`);
- }
- }
-
- static addToPath() {
- if (process.env['DOTNET_INSTALL_DIR']) {
- core.addPath(process.env['DOTNET_INSTALL_DIR']);
- core.exportVariable('DOTNET_ROOT', process.env['DOTNET_INSTALL_DIR']);
- } else {
- if (IS_WINDOWS) {
- // This is the default set in install-dotnet.ps1
- core.addPath(
- path.join(process.env['LocalAppData'] + '', 'Microsoft', 'dotnet')
- );
- core.exportVariable(
- 'DOTNET_ROOT',
- path.join(process.env['LocalAppData'] + '', 'Microsoft', 'dotnet')
- );
- } else {
- // This is the default set in install-dotnet.sh
- core.addPath(path.join(process.env['HOME'] + '', '.dotnet'));
- core.exportVariable(
- 'DOTNET_ROOT',
- path.join(process.env['HOME'] + '', '.dotnet')
- );
- }
- }
-
- console.log(process.env['PATH']);
- }
-
- // versionInfo - versionInfo of the SDK/Runtime
- async resolveVersion(versionInfo: DotNetVersionInfo): Promise {
- if (versionInfo.isExactVersion()) {
- return versionInfo.version();
- }
-
- const httpClient = new hc.HttpClient('actions/setup-dotnet', [], {
- allowRetries: true,
- maxRetries: 3
- });
-
- const releasesJsonUrl: string = await this.getReleasesJsonUrl(
- httpClient,
- versionInfo.version().split('.')
- );
-
- const releasesResponse = await httpClient.getJson(releasesJsonUrl);
- const releasesResult = releasesResponse.result || {};
- let releasesInfo: any[] = releasesResult['releases'];
- releasesInfo = releasesInfo.filter((releaseInfo: any) => {
- return (
- semver.satisfies(releaseInfo['sdk']['version'], versionInfo.version(), {
- includePrerelease: this.includePrerelease
- }) ||
- semver.satisfies(
- releaseInfo['sdk']['version-display'],
- versionInfo.version(),
- {
- includePrerelease: this.includePrerelease
- }
- )
- );
- });
-
- // Exclude versions that are newer than the latest if using not exact
- let latestSdk: string = releasesResult['latest-sdk'];
-
- releasesInfo = releasesInfo.filter((releaseInfo: any) =>
- semver.lte(releaseInfo['sdk']['version'], latestSdk, {
- includePrerelease: this.includePrerelease
- })
- );
-
- // Sort for latest version
- releasesInfo = releasesInfo.sort((a, b) =>
- semver.rcompare(a['sdk']['version'], b['sdk']['version'], {
- includePrerelease: this.includePrerelease
- })
- );
-
- if (releasesInfo.length == 0) {
- throw new Error(
- `Could not find dotnet core version. Please ensure that specified version ${versionInfo.inputVersion} is valid.`
- );
- }
-
- let release = releasesInfo[0];
- return release['sdk']['version'];
- }
-
- private async getReleasesJsonUrl(
- httpClient: hc.HttpClient,
- versionParts: string[]
- ): Promise {
- const response = await httpClient.getJson(DotNetCoreIndexUrl);
- const result = response.result || {};
- let releasesInfo: any[] = result['releases-index'];
-
- releasesInfo = releasesInfo.filter((info: any) => {
- // channel-version is the first 2 elements of the version (e.g. 2.1), filter out versions that don't match 2.1.x.
- const sdkParts: string[] = info['channel-version'].split('.');
- if (
- versionParts.length >= 2 &&
- !(versionParts[1] == 'x' || versionParts[1] == '*')
- ) {
- return versionParts[0] == sdkParts[0] && versionParts[1] == sdkParts[1];
- }
- return versionParts[0] == sdkParts[0];
- });
-
- if (releasesInfo.length === 0) {
- throw new Error(
- `Could not find info for version ${versionParts.join(
- '.'
- )} at ${DotNetCoreIndexUrl}`
- );
- }
-
- const releaseInfo = releasesInfo[0];
- if (releaseInfo['support-phase'] === 'eol') {
- core.warning(
- `${releaseInfo['product']} ${releaseInfo['channel-version']} is no longer supported and will not receive security updates in the future. Please refer to https://aka.ms/dotnet-core-support for more information about the .NET support policy.`
- );
- }
-
- return releaseInfo['releases.json'];
- }
-
- private version: string;
- private includePrerelease: boolean;
-}
-
-const DotNetCoreIndexUrl: string =
- 'https://dotnetcli.blob.core.windows.net/dotnet/release-metadata/releases-index.json';
+// Load tempDirectory before it gets wiped by tool-cache
+import * as core from '@actions/core';
+import * as exec from '@actions/exec';
+import * as io from '@actions/io';
+import hc = require('@actions/http-client');
+import {chmodSync} from 'fs';
+import * as path from 'path';
+import {ExecOptions} from '@actions/exec/lib/interfaces';
+import * as semver from 'semver';
+
+const IS_WINDOWS = process.platform === 'win32';
+
+/**
+ * Represents the inputted version information
+ */
+export class DotNetVersionInfo {
+ public inputVersion: string;
+ private fullversion: string;
+ private isExactVersionSet: boolean = false;
+
+ constructor(version: string) {
+ this.inputVersion = version;
+
+ // Check for exact match
+ if (semver.valid(semver.clean(version) || '') != null) {
+ this.fullversion = semver.clean(version) as string;
+ this.isExactVersionSet = true;
+
+ return;
+ }
+
+ const parts: string[] = version.split('.');
+
+ if (parts.length < 2 || parts.length > 3) this.throwInvalidVersionFormat();
+
+ if (parts.length == 3 && parts[2] !== 'x' && parts[2] !== '*') {
+ this.throwInvalidVersionFormat();
+ }
+
+ const major = this.getVersionNumberOrThrow(parts[0]);
+ const minor = ['x', '*'].includes(parts[1])
+ ? parts[1]
+ : this.getVersionNumberOrThrow(parts[1]);
+
+ this.fullversion = major + '.' + minor;
+ }
+
+ private getVersionNumberOrThrow(input: string): number {
+ try {
+ if (!input || input.trim() === '') this.throwInvalidVersionFormat();
+
+ let number = Number(input);
+
+ if (Number.isNaN(number) || number < 0) this.throwInvalidVersionFormat();
+
+ return number;
+ } catch {
+ this.throwInvalidVersionFormat();
+ return -1;
+ }
+ }
+
+ private throwInvalidVersionFormat() {
+ throw new Error(
+ 'Invalid version format! Supported: 1.2.3, 1.2, 1.2.x, 1.2.*'
+ );
+ }
+
+ /**
+ * If true exacatly one version should be resolved
+ */
+ public isExactVersion(): boolean {
+ return this.isExactVersionSet;
+ }
+
+ public version(): string {
+ return this.fullversion;
+ }
+}
+
+export class DotnetCoreInstaller {
+ constructor(version: string, includePrerelease: boolean = false) {
+ this.version = version;
+ this.includePrerelease = includePrerelease;
+ }
+
+ public async installDotnet() {
+ let output = '';
+ let resultCode = 0;
+
+ let calculatedVersion = await this.resolveVersion(
+ new DotNetVersionInfo(this.version)
+ );
+
+ var envVariables: {[key: string]: string} = {};
+ for (let key in process.env) {
+ if (process.env[key]) {
+ let value: any = process.env[key];
+ envVariables[key] = value;
+ }
+ }
+ if (IS_WINDOWS) {
+ let escapedScript = path
+ .join(__dirname, '..', 'externals', 'install-dotnet.ps1')
+ .replace(/'/g, "''");
+ let command = `& '${escapedScript}'`;
+ if (calculatedVersion) {
+ command += ` -Version ${calculatedVersion}`;
+ }
+ if (process.env['https_proxy'] != null) {
+ command += ` -ProxyAddress ${process.env['https_proxy']}`;
+ }
+ // This is not currently an option
+ if (process.env['no_proxy'] != null) {
+ command += ` -ProxyBypassList ${process.env['no_proxy']}`;
+ }
+
+ // process.env must be explicitly passed in for DOTNET_INSTALL_DIR to be used
+ const powershellPath =
+ (await io.which('pwsh', false)) || (await io.which('powershell', true));
+
+ var options: ExecOptions = {
+ listeners: {
+ stdout: (data: Buffer) => {
+ output += data.toString();
+ }
+ },
+ env: envVariables
+ };
+
+ resultCode = await exec.exec(
+ `"${powershellPath}"`,
+ [
+ '-NoLogo',
+ '-Sta',
+ '-NoProfile',
+ '-NonInteractive',
+ '-ExecutionPolicy',
+ 'Unrestricted',
+ '-Command',
+ command
+ ],
+ options
+ );
+ } else {
+ let escapedScript = path
+ .join(__dirname, '..', 'externals', 'install-dotnet.sh')
+ .replace(/'/g, "''");
+ chmodSync(escapedScript, '777');
+
+ const scriptPath = await io.which(escapedScript, true);
+
+ let scriptArguments: string[] = [];
+ if (calculatedVersion) {
+ scriptArguments.push('--version', calculatedVersion);
+ }
+
+ // process.env must be explicitly passed in for DOTNET_INSTALL_DIR to be used
+ resultCode = await exec.exec(`"${scriptPath}"`, scriptArguments, {
+ listeners: {
+ stdout: (data: Buffer) => {
+ output += data.toString();
+ }
+ },
+ env: envVariables
+ });
+ }
+
+ if (resultCode != 0) {
+ throw new Error(`Failed to install dotnet ${resultCode}. ${output}`);
+ }
+ }
+
+ static addToPath() {
+ if (process.env['DOTNET_INSTALL_DIR']) {
+ core.addPath(process.env['DOTNET_INSTALL_DIR']);
+ core.exportVariable('DOTNET_ROOT', process.env['DOTNET_INSTALL_DIR']);
+ } else {
+ if (IS_WINDOWS) {
+ // This is the default set in install-dotnet.ps1
+ core.addPath(
+ path.join(process.env['LocalAppData'] + '', 'Microsoft', 'dotnet')
+ );
+ core.exportVariable(
+ 'DOTNET_ROOT',
+ path.join(process.env['LocalAppData'] + '', 'Microsoft', 'dotnet')
+ );
+ } else {
+ // This is the default set in install-dotnet.sh
+ core.addPath(path.join(process.env['HOME'] + '', '.dotnet'));
+ core.exportVariable(
+ 'DOTNET_ROOT',
+ path.join(process.env['HOME'] + '', '.dotnet')
+ );
+ }
+ }
+
+ console.log(process.env['PATH']);
+ }
+
+ // versionInfo - versionInfo of the SDK/Runtime
+ async resolveVersion(versionInfo: DotNetVersionInfo): Promise {
+ if (versionInfo.isExactVersion()) {
+ return versionInfo.version();
+ }
+
+ const httpClient = new hc.HttpClient('actions/setup-dotnet', [], {
+ allowRetries: true,
+ maxRetries: 3
+ });
+
+ const releasesJsonUrl: string = await this.getReleasesJsonUrl(
+ httpClient,
+ versionInfo.version().split('.')
+ );
+
+ const releasesResponse = await httpClient.getJson(releasesJsonUrl);
+ const releasesResult = releasesResponse.result || {};
+ let releasesInfo: any[] = releasesResult['releases'];
+ releasesInfo = releasesInfo.filter((releaseInfo: any) => {
+ return (
+ semver.satisfies(releaseInfo['sdk']['version'], versionInfo.version(), {
+ includePrerelease: this.includePrerelease
+ }) ||
+ semver.satisfies(
+ releaseInfo['sdk']['version-display'],
+ versionInfo.version(),
+ {
+ includePrerelease: this.includePrerelease
+ }
+ )
+ );
+ });
+
+ // Exclude versions that are newer than the latest if using not exact
+ let latestSdk: string = releasesResult['latest-sdk'];
+
+ releasesInfo = releasesInfo.filter((releaseInfo: any) =>
+ semver.lte(releaseInfo['sdk']['version'], latestSdk, {
+ includePrerelease: this.includePrerelease
+ })
+ );
+
+ // Sort for latest version
+ releasesInfo = releasesInfo.sort((a, b) =>
+ semver.rcompare(a['sdk']['version'], b['sdk']['version'], {
+ includePrerelease: this.includePrerelease
+ })
+ );
+
+ if (releasesInfo.length == 0) {
+ throw new Error(
+ `Could not find dotnet core version. Please ensure that specified version ${versionInfo.inputVersion} is valid.`
+ );
+ }
+
+ let release = releasesInfo[0];
+ return release['sdk']['version'];
+ }
+
+ private async getReleasesJsonUrl(
+ httpClient: hc.HttpClient,
+ versionParts: string[]
+ ): Promise {
+ const response = await httpClient.getJson(DotNetCoreIndexUrl);
+
+ const result = response.result || {};
+ let releasesInfo: any[] = result['releases-index'];
+
+ releasesInfo = releasesInfo.filter((info: any) => {
+ // channel-version is the first 2 elements of the version (e.g. 2.1), filter out versions that don't match 2.1.x.
+ const sdkParts: string[] = info['channel-version'].split('.');
+ if (
+ versionParts.length >= 2 &&
+ !(versionParts[1] == 'x' || versionParts[1] == '*')
+ ) {
+ return versionParts[0] == sdkParts[0] && versionParts[1] == sdkParts[1];
+ }
+ return versionParts[0] == sdkParts[0];
+ });
+
+ if (releasesInfo.length === 0) {
+ throw new Error(
+ `Could not find info for version ${versionParts.join(
+ '.'
+ )} at ${DotNetCoreIndexUrl}`
+ );
+ }
+
+ const releaseInfo = releasesInfo[0];
+ if (releaseInfo['support-phase'] === 'eol') {
+ core.warning(
+ `${releaseInfo['product']} ${releaseInfo['channel-version']} is no longer supported and will not receive security updates in the future. Please refer to https://aka.ms/dotnet-core-support for more information about the .NET support policy.`
+ );
+ }
+
+ return releaseInfo['releases.json'];
+ }
+
+ private version: string;
+ private includePrerelease: boolean;
+}
+
+const DotNetCoreIndexUrl: string =
+ 'https://builds.dotnet.microsoft.com/dotnet/release-metadata/releases-index.json';
diff --git a/src/setup-dotnet.ts b/src/setup-dotnet.ts
index 2ae0459..82c6529 100644
--- a/src/setup-dotnet.ts
+++ b/src/setup-dotnet.ts
@@ -38,9 +38,8 @@ export async function run() {
}
if (versions.length) {
- const includePrerelease: boolean = core.getBooleanInput(
- 'include-prerelease'
- );
+ const includePrerelease: boolean =
+ core.getBooleanInput('include-prerelease');
let dotnetInstaller!: installer.DotnetCoreInstaller;
for (const version of new Set(versions)) {
dotnetInstaller = new installer.DotnetCoreInstaller(