Azure DevOps API: Get all files (source and target) of Pull Request - api

I searched quite a bit, found several threads here (e.g. this, that, and more), but I could not find the answer to the following task:
Use the Azure DevOps API to retrieve the content changes (basically the file before and after) of all the files of a specific PR.
I can find a PR, can loop through changes, iterations, commits (in various combinations), but I have not been able to download both the first and the last version of each or the files (and there should be a way as I can view the before and after in a PR in DevOps).
Any hints where/how I can retrieve both versions of a file of a certain commit/change/iteration?
Many thanks in advance!
Cheers,
Udo

Thanks for all the hints. Looks like I managed to find a wat to pull it. Please feel free to correct my approach.
Here's the complete PowerShell file:
[CmdletBinding()]
param (
[int]
$pullRequestId,
[string]
$repoName
)
# --- your own values ---
$pat = 'your-personal access token for Azure DevOps'
$urlOrganization = 'your Azure DevOps organization'
$urlProject = 'your Azure DevOps project'
$basePath = "$($env:TEMP)/pullRequest/" # a location to store all the data
# --- your own values ---
if (!$repoName)
{
$userInput = Read-Host "Please enter the repository name"
if (!$userInput)
{
Write-Error "No repository name given."
exit
}
$repoName = $userInput
}
if (!$pullRequestId)
{
$userInput = Read-Host "Please enter the PullRequest ID for $($repoName)"
if (!$userInput)
{
Write-Error "No PullRequest ID given."
exit
}
$pullRequestId = $userInput
}
$prPath = "$($basePath)$($pullRequestId)"
$sourcePath = "$($basePath)$($pullRequestId)/before"
$targetPath = "$($basePath)$($pullRequestId)/after"
# --- helper methods ---
function CleanLocation([string]$toBeCreated)
{
RemoveLocation $toBeCreated
CreateLocation $toBeCreated
if (!(Test-Path $toBeCreated))
{
Write-Error "Path '$toBeCreated' could not be created"
}
}
function CreateLocation([string]$toBeCreated)
{
if (!(Test-Path $toBeCreated)) { New-Item -ErrorAction Ignore -ItemType directory -Path $toBeCreated > $null }
}
function RemoveLocation([string]$toBeDeleted)
{
if (Test-Path $toBeDeleted) { Remove-Item -Path $toBeDeleted -Recurse -Force }
}
function DeleteFile([string]$toBeDeleted)
{
if (Test-Path $toBeDeleted) { Remove-Item -Path $toBeDeleted -Force }
}
# --- helper methods ---
# --- Azure DevOps helper methods ---
function GetFromDevOps($url)
{
$patToken = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($pat)"))
$repoHeader = #{ "Authorization" = "Basic $patToken" }
$response = $(Invoke-WebRequest $url -Headers $repoHeader)
if ($response.StatusCode -ne 200)
{
Write-Error "FAILED: $($response.StatusDescription)"
}
return $response
}
function JsonFromDevOps($url)
{
$response = GetFromDevOps $url
return ConvertFrom-Json -InputObject $response.Content
}
function DownloadFromDevOps($url, $filename)
{
$patToken = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes(":$($pat)"))
$repoHeader = #{ "Authorization" = "Basic $patToken" }
$ProgressPreference = 'SilentlyContinue'
Invoke-WebRequest $url -Headers $repoHeader -OutFile $filename
$ProgressPreference = 'Continue'
}
function DownloadAndSaveFile($itemUrl, $outputPath, $path, $overwrite)
{
$outFile = "$outputPath$($path)"
$fileExists = Test-Path $outFile
if (!$fileExists -or $overwrite)
{
$outPath = [System.IO.Path]::GetDirectoryName($outFile)
CreateLocation $outPath
if ($fileExists)
{
Write-Host " overwriting to $outputPath"
}
else
{
Write-Host " downloading to $outputPath"
}
DownloadFromDevOps $itemUrl $outFile
}
}
function DownloadFiles($changes, $beforePath, $beforeCommitId, $afterPath, $afterCommitId)
{
foreach ($change in $changes)
{
$item = $change.item
if ($item.isFolder)
{
continue;
}
$path = $item.path
$originalPath = $change.originalPath
if (!$path)
{
$path = $change.originalPath
}
$displayPath = $path ?? $originalPath
$changeType = $change.changeType
Write-Host "[$($changeType)] $($displayPath)"
if (($changeType -eq "edit, rename"))
{
$itemUrl = "$($urlRepository)/items?path=$($originalPath)&versionDescriptor.version=$($beforeCommitId)&versionDescriptor.versionOptions=0&versionDescriptor.versionType=2&download=true"
DownloadAndSaveFile $itemUrl $beforePath $originalPath
}
# just get the source/before version
if ($changeType -eq "delete")
{
$itemUrl = "$($urlRepository)/items?path=$($originalPath)&versionDescriptor.version=$($beforeCommitId)&versionDescriptor.versionOptions=0&versionDescriptor.versionType=2&download=true"
DownloadAndSaveFile $itemUrl $beforePath $originalPath
DeleteFile "$($afterPath)$($originalPath)"
}
if ($changeType -eq "edit")
{
$itemUrl = "$($urlRepository)/items?path=$($path)&versionDescriptor.version=$($beforeCommitId)&versionDescriptor.versionOptions=0&versionDescriptor.versionType=2&download=true"
DownloadAndSaveFile $itemUrl $beforePath $path
}
if (($changeType -eq "add") -or ($changeType -eq "edit") -or ($changeType -eq "edit, rename"))
{
$itemUrl = "$($urlRepository)/items?path=$($path)&versionDescriptor.version=$($afterCommitId)&versionDescriptor.versionOptions=0&versionDescriptor.versionType=2&download=true"
DownloadAndSaveFile $itemUrl $afterPath $path $true
}
$validChangeTypes = #('add', 'delete', 'edit', 'edit, rename')
if (!($validChangeTypes.Contains($changeType)))
{
Write-Warning "Unknown change type $($changeType)"
}
}
}
# --- Azure DevOps helper methods ---
$urlBase = "https://dev.azure.com/$($urlOrganization)/$($urlProject)/_apis"
$urlRepository = "$($urlBase)/git/repositories/$($repoName)"
$urlPullRequests = "$($urlRepository)/pullRequests"
$urlPullRequest = "$($urlPullRequests)/$($pullRequestId)"
$urlIterations = "$($urlPullRequest)/iterations"
CleanLocation $prPath
$iterations = JsonFromDevOps $urlIterations
foreach ($iteration in $iterations.value)
{
# the modified file
$srcId = $iteration.sourceRefCommit.commitId
# the original file
$comId = $iteration.commonRefCommit.commitId
$comId = $iteration.targetRefCommit.commitId
$urlIterationChanges = "$($urlIterations)/$($iteration.id)/changes"
$iterationChanges = JsonFromDevOps $urlIterationChanges
DownloadFiles $iterationChanges.changeEntries $sourcePath $comId $targetPath $srcId
}

Related

Launch comand, doctrine:query:sql with command ( Symfony 6 )

I want to create one file Command for add SQL file with data on my BDD with Symfony command.
When i use:
php bin/console doctrine:query:sql "$(< ./sql/zones.sql)", this command work.
But when i try on my file command :
public function execute(InputInterface $input, OutputInterface $output)
{
$command = $this->getApplication()->find('doctrine:query:sql');
$files = ['$(< ./sql/zones.sql)'];
foreach ($files as $file){
$arguments = [
'sql' => $file
];
$greetInput = new ArrayInput($arguments);
$command->run($greetInput, $output);
}
}
they return me SQLSTATE[42000] so i think he don't found the file.
I try to change the path of $file
'../../sql/zones.sql',
'./sql/zones.sql',
'"$(< ./sql/zones.sql)"',
'"$(< ../../sql/zones.sql)"'
i fixed this with :
public function execute(InputInterface $input, OutputInterface $output) :int
{
$command = $this->getApplication()->find('doctrine:query:sql');
$files = ['./sql/zones.sql', './sql/status.sql', './sql/zone_prioritaire.sql', './sql/villes.sql', './sql/rememberMe.sql'];
foreach ($files as $file){
if (file_exists($file)){
$arguments = [
'command' => 'doctrine:query:sql',
'sql' => trim(file_get_contents($file))
];
$greetInput = new ArrayInput($arguments);
$command->run($greetInput, $output);
} else {
throw new Exception("File '{$file}'not found.");
}
}
return Command::SUCCESS;
}

Azure Runbook not deleting Files in Fileshare

I am trying to to use Azure Powershell Runbooks to delete files out of the Azure Fileshare. There are no errors returned, but the file is not deleted. The Automation Account has a Run As account setup that is not expired or anything and the script works if I run it from my local machine. Looking for some advise on this.
$ctx = New-AzureStorageContext -StorageAccountName "" -StorageAccountKey ""
$shareName = ""
$directoryPath = ".cloudconsole"
$DirIndex = 0
$day = 1
$startdate = (Get-Date).AddDays(-180)
$endDate = (Get-date).AddDays(-32)
$dirsToList = New-Object System.Collections.Generic.List[System.Object]
$shareroot = Get-AzureStorageFile -ShareName $shareName -Path $directoryPath -context $ctx
$dirsToList += $shareroot
While ($dirsToList.Count -gt $DirIndex)
{
$dir = $dirsToList[$DirIndex]
$DirIndex ++
$fileListItems = $dir | Get-AzureStorageFile
$dirsListOut = $fileListItems | where {$_.GetType().Name -eq "AzureStorageFileDirectory"}
$dirsToList += $dirsListOut
$files = $fileListItems | where {$_.GetType().Name -eq "AzureStorageFile"}
foreach($file in $files)
{
$task = $file.CloudFile.FetchAttributesAsync()
$task.Wait()
if ($file.CloudFile.Properties.LastModified -ge $startdate -and $file.CloudFile.Properties.LastModified -ge $endDate )
{
if ($file.CloudFile.Properties.LastModified.day -ne '01' )
{
$file | Remove-AzureStorageFile
}
}
if ($file.CloudFile.Properties.LastModified -lt $startdate)
{
$file | Remove-AzureStorageFile
}
}
}
BoxJumper: As kavyasaraboju-MT referred, below piece of script should help you.
$ctx = New-AzStorageContext -StorageAccountName $accountName -StorageAccountKey $key
$shareName = <shareName>
$startdate = (Get-Date).AddDays(-180)
$endDate = (Get-date).AddDays(-32)
$DirIndex = 0
$dirsToList = New-Object System.Collections.Generic.List[System.Object]
# Get share root Dir
$shareroot = Get-AzStorageFile -ShareName $shareName -Path . -context $ctx
$dirsToList += $shareroot
# List files recursively and remove file older than 14 days
While ($dirsToList.Count -gt $DirIndex)
{
$dir = $dirsToList[$DirIndex]
$DirIndex ++
$fileListItems = $dir | Get-AzStorageFile
$dirsListOut = $fileListItems | where {$_.GetType().Name -eq "AzureStorageFileDirectory"}
$dirsToList += $dirsListOut
$files = $fileListItems | where {$_.GetType().Name -eq "AzureStorageFile"}
foreach($file in $files)
{
# Fetch Attributes of each file and output
$task = $file.CloudFile.FetchAttributesAsync()
$task.Wait()
# remove file if it's modified between after last 180 days and before last 30 Days
if ($file.CloudFile.Properties.LastModified -lt (Get-Date).AddDays(-32) -and $file.CloudFile.Properties.LastModified -ge (Get-Date).AddDays(-180))
{
## print the file LMT
# $file | Select #{ Name = "Uri"; Expression = { $_.CloudFile.SnapshotQualifiedUri} }, #{ Name = "LastModified"; Expression = { $_.CloudFile.Properties.LastModified } }
# remove file
$file | Remove-AzStorageFile
}
}
#Debug log
# Write-Host $DirIndex $dirsToList.Length $dir.CloudFileDirectory.SnapshotQualifiedUri.ToString()
}

Efficient Script to get all Extended File Properties

I'm pretty new working with Powershell and i have some working code but I'm not sure how to get it into an efficient routine to return all of the extended file properties of some video files i have.
I have:
# The basic setup for the next steps
$path = 'C:\test\videotocheck.mp4'
$shell = New-Object -COMObject Shell.Application
$folder = Split-Path $path
$file = Split-Path $path -Leaf
$shellfolder = $shell.Namespace($folder)
$shellfile = $shellfolder.ParseName($file)
# This command gets a list of all the extended attributes available for this file
0..500 | Foreach-Object { '{0} = {1}' -f $_, $shellfolder.GetDetailsOf($null, $_) }
# These commands get the individual attributes picked out of the list above
$shellfolder.GetDetailsOf($shellfile, 314)
$shellfolder.GetDetailsOf($shellfile, 316)
All i want to do is provide a filename and have it give me a list back of all of the attributes and their values (if they have one.)
Example:
I intend to use this in a SQL stored procedure. I can work with different types of output if that's easier.
I'm mostly interested in dimensions
Any guidance would be appreciated.
To get all of this extended metadate you could use the function below.
You can give it the path to a single file, or the path to a folder where the files are.
function Get-MetaData {
[CmdletBinding()]
[OutputType([Psobject[]])]
Param (
# Path can be the path to a folder or the full path and filename of a single file
[Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
[string]$Path,
# Pattern is unused if Path is pointing to a single file
[Alias('Filter')]
[string]$Pattern = '*.*',
[Alias('Indices')]
[int[]]$Properties = 1..500,
# Recurse is unused if Path is pointing to a single file
[switch]$Recurse,
[switch]$IncludeEmptyProperties
)
$item = Get-Item -Path $Path -ErrorAction SilentlyContinue
if (!$item) { Write-Error "$Path could not be found."; return }
if (!$item.PSIsContainer) {
# it's a file
$files = #($item)
$Path = $item.DirectoryName
}
else {
# it's a folder
$files = Get-ChildItem -Path $Path -Filter $Pattern -File -Recurse:$Recurse
}
$shell = New-Object -ComObject "Shell.Application"
$objDir = $shell.NameSpace($Path)
foreach($file in $files) {
$objFile = $objDir.ParseName($file.Name)
$mediaFile = $objDir.Items()
foreach($index in $Properties) {
$name = $objDir.GetDetailsOf($mediaFile, $index)
if (![string]::IsNullOrWhiteSpace($name)) {
$value = $objDir.GetDetailsOf($objFile, $index)
if (![string]::IsNullOrWhiteSpace($value) -or $IncludeEmptyProperties) {
[PsCustomObject]#{
Path = $file.FullName
Index = $index
Property = $name
Value = $value
}
}
}
}
}
# clean-up Com objects
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($objFile)
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($objDir)
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($shell)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
}
You can of course play around with the different parameters the function can take like -Pattern '*.mp4' to only list properties for mp4 files or add switch -IncludeEmptyProperties to also list properties that exist for that file type, but have no value for the specified file.
With parameter Properties you can give the function an array of int32 values of the property indices to return. If you leave that open, the function tries to get all properties from index 1 to index 500 (if available).
Most of the interesting property indices can be found at:
audio/video files: 0,1,2,3,4,5,9,11,12,13,14,15,16,17,18,19,20,21,22,26,27,28,36,164,165,194,213,220,223,237,243
font files:
0,1,2,3,4,5,20,21,25,33,34,164,165,166,196,310
image files:
0,1,2,3,4,5,9,11,31,164,165,174,175,176,177,178,194,196
Use it like this:
$result = Get-MetaData -Path '<pathToTheFile_OR_pathToTheFolder>'
# output to GridView
$result | Out-GridView
# output to CSV file
$result | Export-Csv -Path '<pathToTheOutput.csv>' -NoTypeInformation
Here's a simplified version similar to Theo's. It will accept file/folder paths or objects via pipeline or as parameter.
Function Get-FileMetaData {
[cmdletbinding()]
Param
(
[parameter(valuefrompipeline,ValueFromPipelineByPropertyName,Position=1,Mandatory)]
$InputObject
)
begin
{
$shell = New-Object -ComObject Shell.Application
}
process
{
foreach($object in $InputObject)
{
if($object -is [string])
{
try
{
$object = Get-Item $object -ErrorAction Stop
}
catch
{
Write-Warning "Error while processing $object : $($_.exception.message)"
break
}
}
try
{
Test-Path $object -ErrorAction Stop
}
catch
{
Write-Warning "Error while processing $($object.fullname) : $($_.exception.message)"
break
}
switch ($object)
{
{$_ -is [System.IO.DirectoryInfo]}{
write-host Processing folder $object.FullName -ForegroundColor Cyan
$currentfolder = $shell.namespace($object.FullName)
$items = $currentfolder.items()
}
{$_ -is [System.IO.FileInfo]}{
write-host Processing file $object.FullName -ForegroundColor Cyan
$parent = Split-Path $object
$currentfolder = $shell.namespace($parent)
$items = $currentfolder.ParseName((Split-Path $object -Leaf))
}
}
try
{
foreach($item in $items)
{
0..512 | ForEach-Object -Begin {$ht = [ordered]#{}}{
if($value = $currentfolder.GetDetailsOf($item,$_))
{
if($propname = $currentfolder.GetDetailsOf($null,$_))
{
$ht.Add($propname,$value)
}
}
} -End {[PSCustomObject]$ht}
}
}
catch
{
Write-Warning "Error while processing $($item.fullname) : $($_.exception.message)"
}
}
}
end
{
$shell = $null
}
}

Unable to get past the 400 Error

Code to create a new user:
function New-DropBoxUser {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true, ValueFromPipeline=$true, Position=0)]
[string]$FullName
)
# Split name into username #
$FirstName, $LastName = $FullName.split(' ')
$UserName = ($FirstName[0]+$LastName).toLower()
$email = "$UserName#mycomp.com"
$Body = #{
"new_members" = #(#{
"member_email" = $email;
"member_given_name" = $FirstName;
"member_surname" = $LastName;
"send_welcome_email" = "true";
"role" = #{
".tag" = "member_only"
}
})
}
Write-Host ''
Write-Host 'Creating DropBox User...' -ForegroundColor 'Yellow' -BackgroundColor 'Black'
$AuthToken = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
$token = "Bearer $AuthToken"
$response = Invoke-RestMethod `
-Method Post `
-Uri "https://api.dropboxapi.com/2/team/members/add" `
-Headers #{ Authorization = $token } `
-ContentType "application/json; charset=utf-8" `
-Body (ConvertTo-Json $Body)
}
Output:
Invoke-RestMethod : The remote server returned an error: (400) Bad Request.
Anyone, please tell me the fix. I tried the Endpoint "team/members/list" to fetch user list using the same $token and it works.
I am totally new to Dropbox API so my code is borrowed from Google, while being good with PowerShell I tried to patch up using many different codes but none worked.
When all else fails, RTFM, whose parameters say:
{
"new_members": [
{
"member_email": "tom.s#company.com",
"member_given_name": "Tom",
"member_surname": "Silverstone",
"member_external_id": "company_id:342432",
"send_welcome_email": true,
"role": "member_only"
}
],
"force_async": false
}
Try removing .tag from the role definition and replace "true" with $true
$Body = #{
"new_members" = #(#{
"member_email" = $email
"member_given_name" = $FirstName
"member_surname" = $LastName
"send_welcome_email" = $true
"role" = "member_only"
})
}

Nano Server 2016 - how to install .net core 2.0 sdk?

I'm trying to deploy a core 2.0 app on Nano Server 2016, but when I try to install the 2.0 sdk via dotnet-install.ps1, I get this error:
dotnet-install: Downloading link: https://dotnetcli.azureedge.net/dotnet/Sdk/2.0.0/dotnet-sdk-2.0.0-win-x64.zip
dotnet-install: Cannot download: https://dotnetcli.azureedge.net/dotnet/Sdk/2.0.0/dotnet-sdk-2.0.0-win-x64.zip
dotnet-install: Downloading legacy link: https://dotnetcli.azureedge.net/dotnet/Sdk/2.0.0/dotnet-dev-win-x64.2.0.0.zip
Exception calling "IsBypassed" with "1" argument(s): "Operation is not supported on this platform."
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : PlatformNotSupportedException
Is there an alternative way to do this?
I found an alternate script that works properly:
#
# 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
- Branch name
examples: release/2.0.0; Master
.PARAMETER Version
Default: latest
Represents a build version on specific channel. Possible values:
- latest - most latest build on specific channel
- coherent - most latest coherent build on specific channel
coherent applies only to SDK downloads
- 3-part version in a format A.B.C - represents specific version of build
examples: 2.0.0-preview2-006120; 1.1.0
.PARAMETER InstallDir
Default: %LocalAppData%\Microsoft\dotnet
Path to where to install dotnet. Note that binaries will be placed directly in a given directory.
.PARAMETER Architecture
Default: <auto> - this value represents currently running OS architecture
Architecture of dotnet binaries to be installed.
Possible values are: <auto>, x64 and x86
.PARAMETER SharedRuntime
Default: false
Installs just the shared runtime bits, not the entire SDK
.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
This parameter typically is not changed by the user.
It allows to change URL for the Azure feed used by this installer.
.PARAMETER UncachedFeed
This parameter typically is not changed by the user.
It allows to change URL for the Uncached feed used by this installer.
.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.
#>
[cmdletbinding()]
param(
[string]$Channel="LTS",
[string]$Version="Latest",
[string]$InstallDir="<auto>",
[string]$Architecture="<auto>",
[switch]$SharedRuntime,
[switch]$DryRun,
[switch]$NoPath,
[string]$AzureFeed="https://dotnetcli.azureedge.net/dotnet",
[string]$UncachedFeed="https://dotnetcli.blob.core.windows.net/dotnet",
[string]$ProxyAddress,
[switch]$ProxyUseDefaultCredentials
)
Set-StrictMode -Version Latest
$ErrorActionPreference="Stop"
$ProgressPreference="SilentlyContinue"
$BinFolderRelativePath=""
# example path with regex: shared/1.0.0-beta-12345/somepath
$VersionRegEx="/\d+\.\d+[^/]+/"
$OverrideNonVersionedFiles=$true
function Say($str) {
Write-Output "dotnet-install: $str"
}
function Say-Verbose($str) {
Write-Verbose "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, [int]$MaxAttempts = 3, [int]$SecondsBetweenAttempts = 1) {
$Attempts = 0
while ($true) {
try {
return $ScriptBlock.Invoke()
}
catch {
$Attempts++
if ($Attempts -lt $MaxAttempts) {
Start-Sleep $SecondsBetweenAttempts
}
else {
throw
}
}
}
}
function Get-Machine-Architecture() {
Say-Invocation $MyInvocation
# possible values: AMD64, IA64, x86
return $ENV:PROCESSOR_ARCHITECTURE
}
# TODO: Architecture and CLIArchitecture should be unified
function Get-CLIArchitecture-From-Architecture([string]$Architecture) {
Say-Invocation $MyInvocation
switch ($Architecture.ToLower()) {
{ $_ -eq "<auto>" } { return Get-CLIArchitecture-From-Architecture $(Get-Machine-Architecture) }
{ ($_ -eq "amd64") -or ($_ -eq "x64") } { return "x64" }
{ $_ -eq "x86" } { return "x86" }
default { throw "Architecture not supported. If you think this is a bug, please report it at https://github.com/dotnet/cli/issues" }
}
}
function Get-Version-Info-From-Version-Text([string]$VersionText) {
Say-Invocation $MyInvocation
$Data = #($VersionText.Split([char[]]#(), [StringSplitOptions]::RemoveEmptyEntries));
$VersionInfo = #{}
$VersionInfo.CommitHash = $Data[0].Trim()
$VersionInfo.Version = $Data[1].Trim()
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)
{
Invoke-With-Retry(
{
$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($ProxyAddress){
$HttpClientHandler = New-Object System.Net.Http.HttpClientHandler
$HttpClientHandler.Proxy = New-Object System.Net.WebProxy -Property #{Address=$ProxyAddress;UseDefaultCredentials=$ProxyUseDefaultCredentials}
$HttpClient = New-Object System.Net.Http.HttpClient -ArgumentList $HttpClientHandler
}
else {
$HttpClient = New-Object System.Net.Http.HttpClient
}
# Default timeout for HttpClient is 100s. For a 50 MB download this assumes 500 KB/s average, any less will time out
# 10 minutes allows it to work over much slower connections.
$HttpClient.Timeout = New-TimeSpan -Minutes 10
$Response = $HttpClient.GetAsync($Uri).Result
if (($Response -eq $null) -or (-not ($Response.IsSuccessStatusCode)))
{
$ErrorMsg = "Failed to download $Uri."
if ($Response -ne $null)
{
$ErrorMsg += " $Response"
}
throw $ErrorMsg
}
return $Response
}
finally {
if ($HttpClient -ne $null) {
$HttpClient.Dispose()
}
}
})
}
function Get-Latest-Version-Info([string]$AzureFeed, [string]$Channel, [bool]$Coherent) {
Say-Invocation $MyInvocation
$VersionFileUrl = $null
if ($SharedRuntime) {
$VersionFileUrl = "$UncachedFeed/Runtime/$Channel/latest.version"
}
else {
if ($Coherent) {
$VersionFileUrl = "$UncachedFeed/Sdk/$Channel/latest.coherent.version"
}
else {
$VersionFileUrl = "$UncachedFeed/Sdk/$Channel/latest.version"
}
}
$Response = GetHTTPResponse -Uri $VersionFileUrl
$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-Info-From-Version-Text $VersionText
return $VersionInfo
}
function Get-Specific-Version-From-Version([string]$AzureFeed, [string]$Channel, [string]$Version) {
Say-Invocation $MyInvocation
switch ($Version.ToLower()) {
{ $_ -eq "latest" } {
$LatestVersionInfo = Get-Latest-Version-Info -AzureFeed $AzureFeed -Channel $Channel -Coherent $False
return $LatestVersionInfo.Version
}
{ $_ -eq "coherent" } {
$LatestVersionInfo = Get-Latest-Version-Info -AzureFeed $AzureFeed -Channel $Channel -Coherent $True
return $LatestVersionInfo.Version
}
default { return $Version }
}
}
function Get-Download-Link([string]$AzureFeed, [string]$Channel, [string]$SpecificVersion, [string]$CLIArchitecture) {
Say-Invocation $MyInvocation
if ($SharedRuntime) {
$PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/dotnet-runtime-$SpecificVersion-win-$CLIArchitecture.zip"
}
else {
$PayloadURL = "$AzureFeed/Sdk/$SpecificVersion/dotnet-sdk-$SpecificVersion-win-$CLIArchitecture.zip"
}
Say-Verbose "Constructed primary payload URL: $PayloadURL"
return $PayloadURL
}
function Get-LegacyDownload-Link([string]$AzureFeed, [string]$Channel, [string]$SpecificVersion, [string]$CLIArchitecture) {
Say-Invocation $MyInvocation
if ($SharedRuntime) {
$PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/dotnet-win-$CLIArchitecture.$SpecificVersion.zip"
}
else {
$PayloadURL = "$AzureFeed/Sdk/$SpecificVersion/dotnet-dev-win-$CLIArchitecture.$SpecificVersion.zip"
}
Say-Verbose "Constructed legacy payload URL: $PayloadURL"
return $PayloadURL
}
function Get-User-Share-Path() {
Say-Invocation $MyInvocation
$InstallRoot = $env:DOTNET_INSTALL_DIR
if (!$InstallRoot) {
$InstallRoot = "$env:LocalAppData\Microsoft\dotnet"
}
return $InstallRoot
}
function Resolve-Installation-Path([string]$InstallDir) {
Say-Invocation $MyInvocation
if ($InstallDir -eq "<auto>") {
return Get-User-Share-Path
}
return $InstallDir
}
function Get-Version-Info-From-Version-File([string]$InstallRoot, [string]$RelativePathToVersionFile) {
Say-Invocation $MyInvocation
$VersionFile = Join-Path -Path $InstallRoot -ChildPath $RelativePathToVersionFile
Say-Verbose "Local version file: $VersionFile"
if (Test-Path $VersionFile) {
$VersionText = cat $VersionFile
Say-Verbose "Local version file text: $VersionText"
return Get-Version-Info-From-Version-Text $VersionText
}
Say-Verbose "Local version file not found."
return $null
}
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: Path to a package: $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) {
$match = [regex]::match($path, $VersionRegEx)
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 ($dir -ne $null) {
$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 (($PathWithVersion -eq $null) -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)
}
}
}
}
finally {
if ($Zip -ne $null) {
$Zip.Dispose()
}
}
}
function DownloadFile([Uri]$Uri, [string]$OutPath) {
$Stream = $null
try {
$Response = GetHTTPResponse -Uri $Uri
$Stream = $Response.Content.ReadAsStreamAsync().Result
$File = [System.IO.File]::Create($OutPath)
$Stream.CopyTo($File)
$File.Close()
}
finally {
if ($Stream -ne $null) {
$Stream.Dispose()
}
}
}
function Prepend-Sdk-InstallRoot-To-Path([string]$InstallRoot, [string]$BinFolderRelativePath) {
$BinPath = Get-Absolute-Path $(Join-Path -Path $InstallRoot -ChildPath $BinFolderRelativePath)
if (-Not $NoPath) {
Say "Adding to current process PATH: `"$BinPath`". Note: This change will not be visible if PowerShell was run as a child process."
$env:path = "$BinPath;" + $env:path
}
else {
Say "Binaries of dotnet can be found in $BinPath"
}
}
$CLIArchitecture = Get-CLIArchitecture-From-Architecture $Architecture
$SpecificVersion = Get-Specific-Version-From-Version -AzureFeed $AzureFeed -Channel $Channel -Version $Version
$DownloadLink = Get-Download-Link -AzureFeed $AzureFeed -Channel $Channel -SpecificVersion $SpecificVersion -CLIArchitecture $CLIArchitecture
$LegacyDownloadLink = Get-LegacyDownload-Link -AzureFeed $AzureFeed -Channel $Channel -SpecificVersion $SpecificVersion -CLIArchitecture $CLIArchitecture
if ($DryRun) {
Say "Payload URLs:"
Say "Primary - $DownloadLink"
Say "Legacy - $LegacyDownloadLink"
Say "Repeatable invocation: .\$($MyInvocation.MyCommand) -Version $SpecificVersion -Channel $Channel -Architecture $CLIArchitecture -InstallDir $InstallDir"
exit 0
}
$InstallRoot = Resolve-Installation-Path $InstallDir
Say-Verbose "InstallRoot: $InstallRoot"
$IsSdkInstalled = Is-Dotnet-Package-Installed -InstallRoot $InstallRoot -RelativePathToPackage "sdk" -SpecificVersion $SpecificVersion
Say-Verbose ".NET SDK installed? $IsSdkInstalled"
if ($IsSdkInstalled) {
Say ".NET SDK version $SpecificVersion is already installed."
Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot -BinFolderRelativePath $BinFolderRelativePath
exit 0
}
New-Item -ItemType Directory -Force -Path $InstallRoot | Out-Null
$installDrive = $((Get-Item $InstallRoot).PSDrive.Name);
Write-Output "${installDrive}:";
$free = Get-CimInstance -Class win32_logicaldisk | where Deviceid -eq "${installDrive}:"
if ($free.Freespace / 1MB -le 100 ) {
Say "There is not enough disk space on drive ${installDrive}:"
exit 0
}
$ZipPath = [System.IO.Path]::GetTempFileName()
Say-Verbose "Zip path: $ZipPath"
Say "Downloading link: $DownloadLink"
try {
DownloadFile -Uri $DownloadLink -OutPath $ZipPath
}
catch {
Say "Cannot download: $DownloadLink"
$DownloadLink = $LegacyDownloadLink
$ZipPath = [System.IO.Path]::GetTempFileName()
Say-Verbose "Legacy zip path: $ZipPath"
Say "Downloading legacy link: $DownloadLink"
DownloadFile -Uri $DownloadLink -OutPath $ZipPath
}
Say "Extracting zip from $DownloadLink"
Extract-Dotnet-Package -ZipPath $ZipPath -OutPath $InstallRoot
Remove-Item $ZipPath
Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot -BinFolderRelativePath $BinFolderRelativePath
Say "Installation finished"
exit 0
Save as dotnet-install.ps1.
Run .\dotnet-install.ps1 -Version 2.0.0