diff --git a/dotnetsdk.cmd b/dotnetsdk.cmd new file mode 100644 index 0000000000..f920d71aec --- /dev/null +++ b/dotnetsdk.cmd @@ -0,0 +1,8 @@ +@Echo off + +PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0dotnetsdk.ps1' %*" + +IF EXIST "%USERPROFILE%\.dotnet\temp-set-envvars.cmd" ( + CALL "%USERPROFILE%\.dotnet\temp-set-envvars.cmd" + DEL "%USERPROFILE%\.dotnet\temp-set-envvars.cmd" +) diff --git a/dotnetsdk.ps1 b/dotnetsdk.ps1 new file mode 100644 index 0000000000..7fedf7845b --- /dev/null +++ b/dotnetsdk.ps1 @@ -0,0 +1,774 @@ +param( + [parameter(Position=0)] + [string] $Command, + [string] $Proxy, + [switch] $Verbosity = $false, + [alias("p")][switch] $Persistent = $false, + [alias("f")][switch] $Force = $false, + [alias("r")][string] $Runtime, + + [alias("arch")][string] $Architecture, + [switch] $X86 = $false, + [alias("amd64")][switch] $X64 = $false, + + [alias("w")][switch] $Wait = $false, + [alias("a")] + [string] $Alias = $null, + [switch] $NoNative = $false, + [parameter(Position=1, ValueFromRemainingArguments=$true)] + [string[]]$Args=@(), + [switch] $Quiet, + [string] $OutputVariable, + [switch] $AssumeElevated +) + +# "Constants" (in as much as PowerShell will allow) +$RuntimePackageName = "dotnet" +$RuntimeFriendlyName = ".NET Runtime" +$RuntimeProgramFilesName = "Microsoft .NET Cross-Platform Runtime" +$RuntimeFolderName = ".dotnet" +$DefaultFeed = "https://www.myget.org/F/aspnetvnext/api/v2" +$CrossGenCommand = "k-crossgen" + +$selectedArch=$null; +$defaultArch="x86" +$selectedRuntime=$null +$defaultRuntime="clr" + +# Get or calculate userDotNetPath +$userDotNetPath = $env:DOTNET_USER_PATH +if(!$userDotNetPath) { $userDotNetPath = $env:USERPROFILE + "\$RuntimeFolderName" } +$userDotNetRuntimesPath = $userDotNetPath + "\runtimes" + +# Get the feed from the environment variable or set it to the default value +$feed = $env:DOTNET_FEED +if (!$feed) +{ + $feed = $DefaultFeed; +} +$feed = $feed.TrimEnd("/") + +# In some environments, like Azure Websites, the Write-* cmdlets don't work +$useHostOutputMethods = $true + +function String-IsEmptyOrWhitespace([string]$str) { + return [string]::IsNullOrEmpty($str) -or $str.Trim().length -eq 0 +} + +$scriptPath = $myInvocation.MyCommand.Definition + +function DotNetSdk-Help { +@" +.NET SDK Manager - Build 10304 + +USAGE: dotnetsdk [options] + +dotnetsdk upgrade [-X86|-X64] [-r|-Runtime CLR|CoreCLR] [-g|-Global] [-f|-Force] [-Proxy
] [-NoNative] + install latest .NET Runtime from feed + set 'default' alias to installed version + add KRE bin to user PATH environment variable + -g|-Global install to machine-wide location + -f|-Force upgrade even if latest is already installed + -Proxy
use given address as proxy when accessing remote server (e.g. https://username:password@proxyserver:8080/). Alternatively set proxy using http_proxy environment variable. + -NoNative Do not generate native images (Effective only for CoreCLR flavors) + +dotnetsdk install |||latest [-X86|-X64] [-r|-Runtime CLR|CoreCLR] [-a|-Alias ] [-f|-Force] [-Proxy
] [-NoNative] + | install requested .NET Runtime from feed + install requested .NET Runtime from package on local filesystem + latest install latest .NET Runtime from feed + add .NET Runtime bin to path of current command line + -p|-Persistent add .NET Runtime bin to PATH environment variables persistently + -a|-Alias set alias for requested .NET Runtime on install + -f|-Force install even if specified version is already installed + -Proxy
use given address as proxy when accessing remote server (e.g. https://username:password@proxyserver:8080/). Alternatively set proxy using http_proxy environment variable. + -NoNative Do not generate native images (Effective only for CoreCLR flavors) + +dotnetsdk use |||none [-X86|-X64] [-r|-Runtime CLR|CoreCLR] [-p|-Persistent] + || add .NET Runtime bin to path of current command line + none remove .NET Runtime bin from path of current command line + -p|-Persistent add .NET Runtime bin to PATH environment variable across all processes run by the current user + +dotnetsdk list + list .NET Runtime versions installed + +dotnetsdk alias + list .NET Runtime aliases which have been defined + +dotnetsdk alias + display value of the specified alias + +dotnetsdk alias || [-X86|-X64] [-r|-Runtime CLR|CoreCLR] + the name of the alias to set + || the .NET Runtime version to set the alias to. Alternatively use the version of the specified alias + +dotnetsdk unalias + remove the specified alias + +"@ -replace "`n","`r`n" | Console-Write +} + +function DotNetSdk-Global-Setup { + # Sets up the 'dotnetsdk' tool and adds the user-local dotnet install directory to the DOTNET_HOME variable + # Note: We no longer do global install via this tool. The MSI handles global install of runtimes AND will set + # the machine level DOTNET_HOME value. + + # In this configuration, the user-level path will OVERRIDE the global path because it is placed first. + + $dotnetsdkBinPath = "$userDotNetPath\bin" + + If (Needs-Elevation) + { + $arguments = "-ExecutionPolicy unrestricted & '$scriptPath' setup -wait" + Start-Process "$psHome\powershell.exe" -Verb runAs -ArgumentList $arguments -Wait + Console-Write "Adding $dotnetsdkBinPath to process PATH" + Set-Path (Change-Path $env:Path $dotnetsdkBinPath ($dotnetsdkBinPath)) + Console-Write "Adding %USERPROFILE%\$RuntimeFolderName to process DOTNET_HOME" + $envDotNetHome = $env:DOTNET_HOME + $envDotNetHome = Change-Path $envDotNetHome "%USERPROFILE%\$RuntimeFolderName" ("%USERPROFILE%\$RuntimeFolderName") + $env:DOTNET_HOME = $envDotNetHome + Console-Write "Setup complete" + break + } + + $scriptFolder = [System.IO.Path]::GetDirectoryName($scriptPath) + + Console-Write "Copying file $dotnetsdkBinPath\dotnetsdk.ps1" + md $dotnetsdkBinPath -Force | Out-Null + copy "$scriptFolder\dotnetsdk.ps1" "$dotnetsdkBinPath\dotnetsdk.ps1" + + Console-Write "Copying file $dotnetsdkBinPath\dotnetsdk.cmd" + copy "$scriptFolder\dotnetsdk.cmd" "$dotnetsdkBinPath\dotnetsdk.cmd" + + Console-Write "Adding $dotnetsdkBinPath to process PATH" + Set-Path (Change-Path $env:Path $dotnetsdkBinPath ($dotnetsdkBinPath)) + + Console-Write "Adding $dotnetsdkBinPath to user PATH" + $userPath = [Environment]::GetEnvironmentVariable("Path", [System.EnvironmentVariableTarget]::User) + $userPath = Change-Path $userPath $dotnetsdkBinPath ($dotnetsdkBinPath) + [Environment]::SetEnvironmentVariable("Path", $userPath, [System.EnvironmentVariableTarget]::User) + + Console-Write "Adding %USERPROFILE%\$RuntimeFolderName to process DOTNET_HOME" + $envDotNetHome = $env:DOTNET_HOME + $envDotNetHome = Change-Path $envDotNetHome "%USERPROFILE%\$RuntimeFolderName" ("%USERPROFILE%\$RuntimeFolderName") + $env:DOTNET_HOME = $envDotNetHome + + Console-Write "Adding %USERPROFILE%\$RuntimeFolderName to machine DOTNET_HOME" + $machineDotNetHome = [Environment]::GetEnvironmentVariable("DOTNET_HOME", [System.EnvironmentVariableTarget]::Machine) + $machineDotNetHome = Change-Path $machineDotNetHome "%USERPROFILE%\$RuntimeFolderName" ("%USERPROFILE%\$RuntimeFolderName") + [Environment]::SetEnvironmentVariable("DOTNET_HOME", $machineDotNetHome, [System.EnvironmentVariableTarget]::Machine) +} + +function DotNetSdk-Upgrade { +param( + [boolean] $isGlobal +) + $Persistent = $true + $Alias="default" + DotNetSdk-Install "latest" $isGlobal +} + +function Add-Proxy-If-Specified { +param( + [System.Net.WebClient] $wc +) + if (!$Proxy) { + $Proxy = $env:http_proxy + } + if ($Proxy) { + $wp = New-Object System.Net.WebProxy($Proxy) + $pb = New-Object UriBuilder($Proxy) + if (!$pb.UserName) { + $wp.Credentials = [System.Net.CredentialCache]::DefaultCredentials + } else { + $wp.Credentials = New-Object System.Net.NetworkCredential($pb.UserName, $pb.Password) + } + $wc.Proxy = $wp + } +} + +function DotNetSdk-Find-Latest { +param( + [string] $platform, + [string] $architecture +) + Console-Write "Determining latest version" + + $url = "$feed/GetUpdates()?packageIds=%27$RuntimePackageName-$platform-win-$architecture%27&versions=%270.0%27&includePrerelease=true&includeAllVersions=false" + + $wc = New-Object System.Net.WebClient + Add-Proxy-If-Specified($wc) + Write-Verbose "Downloading $url ..." + [xml]$xml = $wc.DownloadString($url) + + $version = Select-Xml "//d:Version" -Namespace @{d='http://schemas.microsoft.com/ado/2007/08/dataservices'} $xml + + if (String-IsEmptyOrWhitespace($version)) { + throw "There are no runtimes for platform '$platform', architecture '$architecture' in the feed '$feed'" + } + + return $version +} + +function Do-DotNetSdk-Download { +param( + [string] $runtimeFullName, + [string] $runtimesFolder +) + $parts = $runtimeFullName.Split(".", 2) + + $url = "$feed/package/" + $parts[0] + "/" + $parts[1] + $runtimeFolder = Join-Path $runtimesFolder $runtimeFullName + $runtimeFile = Join-Path $runtimeFolder "$runtimeFullName.nupkg" + + If (Test-Path $runtimeFolder) { + if($Force) + { + rm $runtimeFolder -Recurse -Force + } else { + Console-Write "$runtimeFullName already installed." + return; + } + } + + Console-Write "Downloading $runtimeFullName from $feed" + + #Downloading to temp location + $runtimeTempDownload = Join-Path $runtimesFolder "temp" + $tempDownloadFile = Join-Path $runtimeTempDownload "$runtimeFullName.nupkg" + + if(Test-Path $runtimeTempDownload) { + del "$runtimeTempDownload\*" -recurse + } else { + md $runtimeTempDownload -Force | Out-Null + } + + $wc = New-Object System.Net.WebClient + Add-Proxy-If-Specified($wc) + Write-Verbose "Downloading $url ..." + $wc.DownloadFile($url, $tempDownloadFile) + + Do-DotNetSdk-Unpack $tempDownloadFile $runtimeTempDownload + + md $runtimeFolder -Force | Out-Null + Console-Write "Installing to $runtimeFolder" + mv "$runtimeTempDownload\*" $runtimeFolder + Remove-Item "$runtimeTempDownload" -Force | Out-Null +} + +function Do-DotNetSdk-Unpack { +param( + [string] $runtimeFile, + [string] $runtimeFolder +) + Console-Write "Unpacking to $runtimeFolder" + + $compressionLib = [System.Reflection.Assembly]::LoadWithPartialName('System.IO.Compression.FileSystem') + + if($compressionLib -eq $null) { + try { + # Shell will not recognize nupkg as a zip and throw, so rename it to zip + $runtimeZip = [System.IO.Path]::ChangeExtension($runtimeFile, "zip") + Rename-Item $runtimeFile $runtimeZip + # Use the shell to uncompress the nupkg + $shell_app=new-object -com shell.application + $zip_file = $shell_app.namespace($runtimeZip) + $destination = $shell_app.namespace($runtimeFolder) + $destination.Copyhere($zip_file.items(), 0x14) #0x4 = don't show UI, 0x10 = overwrite files + } + finally { + # make it a nupkg again + Rename-Item $runtimeZip $runtimeFile + } + } else { + [System.IO.Compression.ZipFile]::ExtractToDirectory($runtimeFile, $runtimeFolder) + } + + If (Test-Path ($runtimeFolder + "\[Content_Types].xml")) { + Remove-Item ($runtimeFolder + "\[Content_Types].xml") + } + If (Test-Path ($runtimeFolder + "\_rels\")) { + Remove-Item ($runtimeFolder + "\_rels\") -Force -Recurse + } + If (Test-Path ($runtimeFolder + "\package\")) { + Remove-Item ($runtimeFolder + "\package\") -Force -Recurse + } +} + +function DotNetSdk-Install { +param( + [string] $versionOrAlias, + [boolean] $isGlobal +) + if ($versionOrAlias -eq "latest") { + $versionOrAlias = DotNetSdk-Find-Latest (Requested-Platform $defaultRuntime) (Requested-Architecture $defaultArch) + } + + if ($versionOrAlias.EndsWith(".nupkg")) { + $runtimeFullName = [System.IO.Path]::GetFileNameWithoutExtension($versionOrAlias) + } else { + $runtimeFullName = Requested-VersionOrAlias $versionOrAlias + } + + $packageFolder = $userDotNetRuntimesPath + + if ($versionOrAlias.EndsWith(".nupkg")) { + Set-Variable -Name "selectedArch" -Value (Package-Arch $runtimeFullName) -Scope Script + Set-Variable -Name "selectedRuntime" -Value (Package-Platform $runtimeFullName) -Scope Script + + $runtimeFolder = "$packageFolder\$runtimeFullName" + $folderExists = Test-Path $runtimeFolder + + if ($folderExists -and $Force) { + del $runtimeFolder -Recurse -Force + $folderExists = $false; + } + + if ($folderExists) { + Console-Write "Target folder '$runtimeFolder' already exists" + } else { + $tempUnpackFolder = Join-Path $packageFolder "temp" + $tempDownloadFile = Join-Path $tempUnpackFolder "$runtimeFullName.nupkg" + + if(Test-Path $tempUnpackFolder) { + del "$tempUnpackFolder\*" -recurse + } else { + md $tempUnpackFolder -Force | Out-Null + } + copy $versionOrAlias $tempDownloadFile + + Do-DotNetSdk-Unpack $tempDownloadFile $tempUnpackFolder + md $runtimeFolder -Force | Out-Null + Console-Write "Installing to $runtimeFolder" + mv "$tempUnpackFolder\*" $runtimeFolder + Remove-Item "$tempUnpackFolder" -Force | Out-Null + } + + $packageVersion = Package-Version $runtimeFullName + + DotNetSdk-Use $packageVersion + if (!$(String-IsEmptyOrWhitespace($Alias))) { + DotNetSdk-Alias-Set $Alias $packageVersion + } + } + else + { + Do-DotNetSdk-Download $runtimeFullName $packageFolder + DotNetSdk-Use $versionOrAlias + if (!$(String-IsEmptyOrWhitespace($Alias))) { + DotNetSdk-Alias-Set "$Alias" $versionOrAlias + } + } + + if ($runtimeFullName.Contains("CoreCLR")) { + if ($NoNative) { + Console-Write "Native image generation is skipped" + } + else { + Console-Write "Compiling native images for $runtimeFullName to improve startup performance..." + Start-Process $CrossGenCommand -Wait + Console-Write "Finished native image compilation." + } + } +} + +function DotNetSdk-List { + $dotnetHome = $env:DOTNET_HOME + if (!$dotnetHome) { + $dotnetHome = "$userDotNetPath" + } + + md ($userDotNetPath + "\alias\") -Force | Out-Null + $aliases = Get-ChildItem ($userDotNetPath + "\alias\") | Select @{label='Alias';expression={$_.BaseName}}, @{label='Name';expression={Get-Content $_.FullName }} + + $items = @() + foreach($portion in $dotnetHome.Split(';')) { + $path = [System.Environment]::ExpandEnvironmentVariables($portion) + if (Test-Path("$path\runtimes")) { + $items += Get-ChildItem ("$path\runtimes\dotnet-*") | List-Parts $aliases + } + } + + $items | Sort-Object Version, Runtime, Architecture, Alias | Format-Table -AutoSize -Property @{name="Active";expression={$_.Active};alignment="center"}, "Version", "Runtime", "Architecture", "Location", "Alias" +} + +filter List-Parts { + param($aliases) + + $hasBin = Test-Path($_.FullName+"\bin") + if (!$hasBin) { + return + } + $active = $false + foreach($portion in $env:Path.Split(';')) { + # Append \ to the end because otherwise you might see + # multiple active versions if the folders have the same + # name prefix (like 1.0-beta and 1.0) + if ($portion.StartsWith($_.FullName + "\")) { + $active = $true + } + } + + $fullAlias="" + $delim="" + + foreach($alias in $aliases){ + if($_.Name.Split('\', 2) -contains $alias.Name){ + $fullAlias += $delim + $alias.Alias + $delim = ", " + } + } + + $parts1 = $_.Name.Split('.', 2) + $parts2 = $parts1[0].Split('-', 4) + return New-Object PSObject -Property @{ + Active = if ($active) { "*" } else { "" } + Version = $parts1[1] + Runtime = $parts2[1] + OperatingSystem = $parts2[2] + Architecture = $parts2[3] + Location = $_.Parent.FullName + Alias = $fullAlias + } +} + +function DotNetSdk-Use { +param( + [string] $versionOrAlias +) + Validate-Full-Package-Name-Arguments-Combination $versionOrAlias + + if ($versionOrAlias -eq "none") { + Console-Write "Removing .NET Runtime from process PATH" + Set-Path (Change-Path $env:Path "" ($userDotNetRuntimesPath)) + + if ($Persistent) { + Console-Write "Removing .NET Runtime from user PATH" + $userPath = [Environment]::GetEnvironmentVariable("Path", [System.EnvironmentVariableTarget]::User) + $userPath = Change-Path $userPath "" ($userDotNetRuntimesPath) + [Environment]::SetEnvironmentVariable("Path", $userPath, [System.EnvironmentVariableTarget]::User) + } + return; + } + + $runtimeFullName = Requested-VersionOrAlias $versionOrAlias + + $runtimeBin = Locate-DotNetBinFromFullName $runtimeFullName + if ($runtimeBin -eq $null) { + throw "Cannot find $runtimeFullName, do you need to run 'dotnetsdk install $versionOrAlias'?" + } + + Console-Write "Adding $runtimeBin to process PATH" + Set-Path (Change-Path $env:Path $runtimeBin ($userDotNetRuntimesPath)) + + if ($Persistent) { + Console-Write "Adding $runtimeBin to user PATH" + $userPath = [Environment]::GetEnvironmentVariable("Path", [System.EnvironmentVariableTarget]::User) + $userPath = Change-Path $userPath $runtimeBin ($userDotNetRuntimesPath) + [Environment]::SetEnvironmentVariable("Path", $userPath, [System.EnvironmentVariableTarget]::User) + } +} + +function DotNetSdk-Alias-List { + md ($userDotNetPath + "\alias\") -Force | Out-Null + + Get-ChildItem ($userDotNetPath + "\alias\") | Select @{label='Alias';expression={$_.BaseName}}, @{label='Name';expression={Get-Content $_.FullName }} | Format-Table -AutoSize +} + +function DotNetSdk-Alias-Get { +param( + [string] $name +) + md ($userDotNetPath + "\alias\") -Force | Out-Null + $aliasFilePath=$userDotNetPath + "\alias\" + $name + ".txt" + if (!(Test-Path $aliasFilePath)) { + Console-Write "Alias '$name' does not exist" + $script:exitCode = 1 # Return non-zero exit code for scripting + } else { + $aliasValue = (Get-Content ($userDotNetPath + "\alias\" + $name + ".txt")) + Console-Write "Alias '$name' is set to $aliasValue" + } +} + +function DotNetSdk-Alias-Set { +param( + [string] $name, + [string] $value +) + $runtimeFullName = Requested-VersionOrAlias $value + $aliasFilePath = $userDotNetPath + "\alias\" + $name + ".txt" + $action = if (Test-Path $aliasFilePath) { "Updating" } else { "Setting" } + Console-Write "$action alias '$name' to '$runtimeFullName'" + md ($userDotNetPath + "\alias\") -Force | Out-Null + $runtimeFullName | Out-File ($aliasFilePath) ascii +} + +function DotNetSdk-Unalias { +param( + [string] $name +) + $aliasPath=$userDotNetPath + "\alias\" + $name + ".txt" + if (Test-Path -literalPath "$aliasPath") { + Console-Write "Removing alias $name" + Remove-Item -literalPath $aliasPath + } else { + Console-Write "Cannot remove alias, '$name' is not a valid alias name" + $script:exitCode = 1 # Return non-zero exit code for scripting + } +} + +function Locate-DotNetBinFromFullName() { +param( + [string] $runtimeFullName +) + $dotnetHome = $env:DOTNET_HOME + if (!$dotnetHome) { + $dotnetHome = "$globalDotNetPath;$userDotNetPath" + } + foreach($portion in $dotnetHome.Split(';')) { + $path = [System.Environment]::ExpandEnvironmentVariables($portion) + $runtimeBin = "$path\runtimes\$runtimeFullName\bin" + if (Test-Path "$runtimeBin") { + return $runtimeBin + } + } + return $null +} + +function Package-Version() { +param( + [string] $runtimeFullName +) + return $runtimeFullName -replace '[^.]*.(.*)', '$1' +} + +function Package-Platform() { +param( + [string] $runtimeFullName +) + return $runtimeFullName -replace 'dotnet-([^-]*).*', '$1' +} + +function Package-Arch() { +param( + [string] $runtimeFullName +) + return $runtimeFullName -replace 'dotnet-[^-]*-[^-]*-([^.]*).*', '$1' +} + + +function Requested-VersionOrAlias() { +param( + [string] $versionOrAlias +) + Validate-Full-Package-Name-Arguments-Combination $versionOrAlias + + $runtimeBin = Locate-DotNetBinFromFullName $versionOrAlias + + # If the name specified is an existing package, just use it as is + if ($runtimeBin -ne $null) { + return $versionOrAlias + } + + If (Test-Path ($userDotNetPath + "\alias\" + $versionOrAlias + ".txt")) { + $aliasValue = Get-Content ($userDotNetPath + "\alias\" + $versionOrAlias + ".txt") + # Split dotnet-coreclr-win-x86.1.0.0-beta3-10922 into version and name sections + $parts = $aliasValue.Split('.', 2) + $pkgVersion = $parts[1] + # dotnet-coreclr-win-x86 + $parts = $parts[0].Split('-', 4) + $pkgPlatform = Requested-Platform $parts[1] + $pkgArchitecture = Requested-Architecture $parts[3] + } else { + $pkgVersion = $versionOrAlias + $pkgPlatform = Requested-Platform $defaultRuntime + $pkgArchitecture = Requested-Architecture $defaultArch + } + return $RuntimePackageName + "-" + $pkgPlatform + "-win-" + $pkgArchitecture + "." + $pkgVersion +} + +function Requested-Platform() { +param( + [string] $default +) + if (!(String-IsEmptyOrWhitespace($selectedRuntime))) {return $selectedRuntime} + return $default +} + +function Requested-Architecture() { +param( + [string] $default +) + if (!(String-IsEmptyOrWhitespace($selectedArch))) {return $selectedArch} + return $default +} + +function Change-Path() { +param( + [string] $existingPaths, + [string] $prependPath, + [string[]] $removePaths +) + $newPath = $prependPath + foreach($portion in $existingPaths.Split(';')) { + $skip = $portion -eq "" + foreach($removePath in $removePaths) { + if ($removePath -and ($portion.StartsWith($removePath))) { + $skip = $true + } + } + if (!$skip) { + $newPath = $newPath + ";" + $portion + } + } + return $newPath +} + +function Set-Path() { +param( + [string] $newPath +) + md $userDotNetPath -Force | Out-Null + $env:Path = $newPath +@" +SET "PATH=$newPath" +"@ | Out-File ($userDotNetPath + "\temp-set-envvars.cmd") ascii +} + +function Needs-Elevation() { + if($AssumeElevated) { + return $false + } + + $user = [Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent() + $elevated = $user.IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") + return -NOT $elevated +} + +function Requested-Switches() { + $arguments = "" + if ($X86) {$arguments = "$arguments -x86"} + if ($X64) {$arguments = "$arguments -x64"} + if ($selectedRuntime) {$arguments = "$arguments -runtime $selectedRuntime"} + if ($Persistent) {$arguments = "$arguments -persistent"} + if ($Force) {$arguments = "$arguments -force"} + if (!$(String-IsEmptyOrWhitespace($Alias))) {$arguments = "$arguments -alias '$Alias'"} + return $arguments +} + +function Validate-And-Santitize-Switches() +{ + if ($X86 -and $X64) {throw "You cannot select both x86 and x64 architectures"} + + if ($Runtime) { + $validRuntimes = "CoreCLR", "CLR" + $match = $validRuntimes | ? { $_ -like $Runtime } | Select -First 1 + if (!$match) {throw "'$runtime' is not a valid runtime"} + Set-Variable -Name "selectedRuntime" -Value $match.ToLowerInvariant() -Scope Script + } + + if($Architecture) { + $validArchitectures = "x64", "x86" + $match = $validArchitectures | ? { $_ -like $Architecture } | Select -First 1 + if(!$match) {throw "'$architecture' is not a valid architecture"} + Set-Variable -Name "selectedArch" -Value $match.ToLowerInvariant() -Scope Script + } + else { + if ($X64) { + Set-Variable -Name "selectedArch" -Value "x64" -Scope Script + } elseif ($X86) { + Set-Variable -Name "selectedArch" -Value "x86" -Scope Script + } + } + +} + +$script:capturedOut = @() +function Console-Write() { +param( + [Parameter(ValueFromPipeline=$true)] + [string] $message +) + if($OutputVariable) { + # Update the capture output + $script:capturedOut += @($message) + } + + if(!$Quiet) { + if ($useHostOutputMethods) { + try { + Write-Host $message + } + catch { + $script:useHostOutputMethods = $false + Console-Write $message + } + } + else { + [Console]::WriteLine($message) + } + } +} + +function Console-Write-Error() { +param( + [Parameter(ValueFromPipeline=$true)] + [string] $message +) + if ($useHostOutputMethods) { + try { + Write-Error $message + } + catch { + $script:useHostOutputMethods = $false + Console-Write-Error $message + } + } + else { + [Console]::Error.WriteLine($message) + } +} + +function Validate-Full-Package-Name-Arguments-Combination() { +param( + [string] $versionOrAlias +) + if ($versionOrAlias -like "dotnet-*" -and + ($selectedArch -or $selectedRuntime)) { + throw "Runtime or architecture cannot be specified when using the full package name." + } +} + +$script:exitCode = 0 +try { + Validate-And-Santitize-Switches + switch -wildcard ($Command + " " + $Args.Count) { + "setup 0" {DotNetSdk-Global-Setup} + "upgrade 0" {DotNetSdk-Upgrade $false} + "install 1" {DotNetSdk-Install $Args[0] $false} + "list 0" {DotNetSdk-List} + "use 1" {DotNetSdk-Use $Args[0]} + "alias 0" {DotNetSdk-Alias-List} + "alias 1" {DotNetSdk-Alias-Get $Args[0]} + "alias 2" {DotNetSdk-Alias-Set $Args[0] $Args[1]} + "unalias 1" {DotNetSdk-Unalias $Args[0]} + "help 0" {DotNetSdk-Help} + " 0" {DotNetSdk-Help} + default {throw "Unknown command"}; + } +} +catch { + Console-Write-Error $_ + Console-Write "Type 'dotnetsdk help' for help on how to use dotnetsdk." + $script:exitCode = -1 +} +if ($Wait) { + Console-Write "Press any key to continue ..." + $x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown,AllowCtrlC") +} + +# If the user specified an output variable, push the value up to the parent scope +if($OutputVariable) { + Set-Variable $OutputVariable $script:capturedOut -Scope 1 +} + +exit $script:exitCode diff --git a/dotnetsdk.sh b/dotnetsdk.sh new file mode 100644 index 0000000000..8509bf2ca1 --- /dev/null +++ b/dotnetsdk.sh @@ -0,0 +1,416 @@ +# dotnetsdk.sh +# Source this file from your .bash-profile or script to use + +_dotnetsdk_has() { + type "$1" > /dev/null 2>&1 + return $? +} + +if _dotnetsdk_has "unsetopt"; then + unsetopt nomatch 2>/dev/null +fi + +if [ -z "$DOTNET_USER_HOME" ]; then + eval DOTNET_USER_HOME=~/.dotnet +fi + +DOTNET_USER_PACKAGES="$DOTNET_USER_HOME/runtimes" +if [ -z "$DOTNET_FEED" ]; then + DOTNET_FEED="https://www.myget.org/F/aspnetvnext/api/v2" +fi + +_dotnetsdk_find_latest() { + local platform="mono" + + if ! _dotnetsdk_has "curl"; then + echo 'dotnetsdk needs curl to proceed.' >&2; + return 1 + fi + + local url="$DOTNET_FEED/GetUpdates()?packageIds=%27dotnet-$platform%27&versions=%270.0%27&includePrerelease=true&includeAllVersions=false" + xml="$(curl $url 2>/dev/null)" + echo $xml | grep \<[a-zA-Z]:Version\>* >> /dev/null || return 1 + version="$(echo $xml | sed 's/.*<[a-zA-Z]:Version>\([^<]*\).*/\1/')" + echo $version +} + +_dotnetsdk_strip_path() { + echo "$1" | sed -e "s#$DOTNET_USER_PACKAGES/[^/]*$2[^:]*:##g" -e "s#:$DOTNET_USER_PACKAGES/[^/]*$2[^:]*##g" -e "s#$DOTNET_USER_PACKAGES/[^/]*$2[^:]*##g" +} + +_dotnetsdk_prepend_path() { + if [ -z "$1" ]; then + echo "$2" + else + echo "$2:$1" + fi +} + +_dotnetsdk_package_version() { + local runtimeFullName="$1" + echo "$runtimeFullName" | sed "s/[^.]*.\(.*\)/\1/" +} + +_dotnetsdk_package_name() { + local runtimeFullName="$1" + echo "$runtimeFullName" | sed "s/\([^.]*\).*/\1/" +} + +_dotnetsdk_package_runtime() { + local runtimeFullName="$1" + echo "$runtimeFullName" | sed "s/KRE-\([^.-]*\).*/\1/" +} + +_dotnetsdk_download() { + local runtimeFullName="$1" + local runtimeFolder="$2" + + local pkgName=$(_dotnetsdk_package_name "$runtimeFullName") + local pkgVersion=$(_dotnetsdk_package_version "$runtimeFullName") + local url="$DOTNET_FEED/package/$pkgName/$pkgVersion" + local runtimeFile="$runtimeFolder/$runtimeFullName.nupkg" + + if [ -e "$runtimeFolder" ]; then + echo "$runtimeFullName already installed." + return 0 + fi + + echo "Downloading $runtimeFullName from $DOTNET_FEED" + + if ! _dotnetsdk_has "curl"; then + echo "dotnetsdk needs curl to proceed." >&2; + return 1 + fi + + mkdir -p "$runtimeFolder" > /dev/null 2>&1 + + local httpResult=$(curl -L -D - "$url" -o "$runtimeFile" 2>/dev/null | grep "^HTTP/1.1" | head -n 1 | sed "s/HTTP.1.1 \([0-9]*\).*/\1/") + + [[ $httpResult == "404" ]] && echo "$runtimeFullName was not found in repository $DOTNET_FEED" && return 1 + [[ $httpResult != "302" && $httpResult != "200" ]] && echo "HTTP Error $httpResult fetching $runtimeFullName from $DOTNET_FEED" && return 1 + + _dotnetsdk_unpack $runtimeFile $runtimeFolder + return $? +} + +_dotnetsdk_unpack() { + local runtimeFile="$1" + local runtimeFolder="$2" + + echo "Installing to $runtimeFolder" + + if ! _dotnetsdk_has "unzip"; then + echo "dotnetsdk needs unzip to proceed." >&2; + return 1 + fi + + unzip $runtimeFile -d $runtimeFolder > /dev/null 2>&1 + + [ -e "$runtimeFolder/[Content_Types].xml" ] && rm "$runtimeFolder/[Content_Types].xml" + + [ -e "$runtimeFolder/_rels/" ] && rm -rf "$runtimeFolder/_rels/" + + [ -e "$runtimeFolder/package/" ] && rm -rf "$runtimeFolder/_package/" + + #Set shell commands as executable + find "$runtimeFolder/bin/" -type f \ + -exec sh -c "head -c 11 {} | grep '/bin/bash' > /dev/null" \; -print | xargs chmod 775 +} + +_dotnetsdk_requested_version_or_alias() { + local versionOrAlias="$1" + local runtimeBin=$(_dotnetsdk_locate_runtime_bin_from_full_name "$versionOrAlias") + + # If the name specified is an existing package, just use it as is + if [ -n "$runtimeBin" ]; then + echo "$versionOrAlias" + else + if [ -e "$DOTNET_USER_HOME/alias/$versionOrAlias.alias" ]; then + local runtimeFullName=$(cat "$DOTNET_USER_HOME/alias/$versionOrAlias.alias") + local pkgName=$(echo $runtimeFullName | sed "s/\([^.]*\).*/\1/") + local pkgVersion=$(echo $runtimeFullName | sed "s/[^.]*.\(.*\)/\1/") + local pkgPlatform=$(echo "$pkgName" | sed "s/dotnet-\([^.-]*\).*/\1/") + else + local pkgVersion=$versionOrAlias + local pkgPlatform="mono" + fi + + echo "dotnet-$pkgPlatform.$pkgVersion" + fi +} + +# This will be more relevant if we support global installs +_dotnetsdk_locate_runtime_bin_from_full_name() { + local runtimeFullName=$1 + [ -e "$DOTNET_USER_PACKAGES/$runtimeFullName/bin" ] && echo "$DOTNET_USER_PACKAGES/$runtimeFullName/bin" && return +} + +dotnetsdk() +{ + if [ $# -lt 1 ]; then + dotnetsdk help + return + fi + + case $1 in + "help" ) + echo "" + echo ".NET SDK Manager - Build 10304" + echo "" + echo "USAGE: dotnetsdk [options]" + echo "" + echo "dotnetsdk upgrade" + echo "install latest .NET Runtime from feed" + echo "add .NET Runtime bin to path of current command line" + echo "set installed version as default" + echo "" + echo "dotnetsdk install |||latest [-a|-alias ] [-p -persistent]" + echo "| install requested .NET Runtime from feed" + echo " install requested .NET Runtime from local package on filesystem" + echo "latest install latest version of .NET Runtime from feed" + echo "-a|-alias set alias for requested .NET Runtime on install" + echo "-p -persistent set installed version as default" + echo "add .NET Runtime bin to path of current command line" + echo "" + echo "dotnetsdk use |||none [-p -persistent]" + echo "|| add .NET Runtime bin to path of current command line " + echo "none remove .NET Runtime bin from path of current command line" + echo "-p -persistent set selected version as default" + echo "" + echo "dotnetsdk list" + echo "list .NET Runtime versions installed " + echo "" + echo "dotnetsdk alias" + echo "list .NET Runtime aliases which have been defined" + echo "" + echo "dotnetsdk alias " + echo "display value of the specified alias" + echo "" + echo "dotnetsdk alias ||" + echo " the name of the alias to set" + echo "|| the .NET Runtime version to set the alias to. Alternatively use the version of the specified alias" + echo "" + echo "dotnetsdk unalias " + echo "remove the specified alias" + echo "" + ;; + + "upgrade" ) + [ $# -ne 1 ] && dotnetsdk help && return + dotnetsdk install latest -p + ;; + + "install" ) + [ $# -lt 2 ] && dotnetsdk help && return + shift + local persistent= + local versionOrAlias= + local alias= + while [ $# -ne 0 ] + do + if [[ $1 == "-p" || $1 == "-persistent" ]]; then + local persistent="-p" + elif [[ $1 == "-a" || $1 == "-alias" ]]; then + local alias=$2 + shift + elif [[ -n $1 ]]; then + [[ -n $versionOrAlias ]] && echo "Invalid option $1" && dotnetsdk help && return 1 + local versionOrAlias=$1 + fi + shift + done + if [[ "$versionOrAlias" == "latest" ]]; then + echo "Determining latest version" + versionOrAlias=$(_dotnetsdk_find_latest) + [[ $? == 1 ]] && echo "Error: Could not find latest version from feed $DOTNET_FEED" && return 1 + echo "Latest version is $versionOrAlias" + fi + if [[ "$versionOrAlias" == *.nupkg ]]; then + local runtimeFullName=$(basename $versionOrAlias | sed "s/\(.*\)\.nupkg/\1/") + local runtimeVersion=$(_dotnetsdk_package_version "$runtimeFullName") + local runtimeFolder="$DOTNET_USER_PACKAGES/$runtimeFullName" + local runtimeFile="$runtimeFolder/$runtimeFullName.nupkg" + + if [ -e "$runtimeFolder" ]; then + echo "$runtimeFullName already installed" + else + mkdir "$runtimeFolder" > /dev/null 2>&1 + cp -a "$versionOrAlias" "$runtimeFile" + _dotnetsdk_unpack "$runtimeFile" "$runtimeFolder" + [[ $? == 1 ]] && return 1 + fi + dotnetsdk use "$runtimeVersion" "$persistent" + [[ -n $alias ]] && dotnetsdk alias "$alias" "$runtimeVersion" + else + local runtimeFullName="$(_dotnetsdk_requested_version_or_alias $versionOrAlias)" + local runtimeFolder="$DOTNET_USER_PACKAGES/$runtimeFullName" + _dotnetsdk_download "$runtimeFullName" "$runtimeFolder" + [[ $? == 1 ]] && return 1 + dotnetsdk use "$versionOrAlias" "$persistent" + [[ -n $alias ]] && dotnetsdk alias "$alias" "$versionOrAlias" + fi + ;; + + "use" ) + [ $# -gt 3 ] && dotnetsdk help && return + [ $# -lt 2 ] && dotnetsdk help && return + + shift + local persistent= + while [ $# -ne 0 ] + do + if [[ $1 == "-p" || $1 == "-persistent" ]]; then + local persistent="true" + elif [[ -n $1 ]]; then + local versionOrAlias=$1 + fi + shift + done + + if [[ $versionOrAlias == "none" ]]; then + echo "Removing .NET Runtime from process PATH" + # Strip other version from PATH + PATH=$(_dotnetsdk_strip_path "$PATH" "/bin") + + if [[ -n $persistent && -e "$DOTNET_USER_HOME/alias/default.alias" ]]; then + echo "Setting default .NET Runtime to none" + rm "$DOTNET_USER_HOME/alias/default.alias" + fi + return 0 + fi + + local runtimeFullName=$(_dotnetsdk_requested_version_or_alias "$versionOrAlias") + local runtimeBin=$(_dotnetsdk_locate_runtime_bin_from_full_name "$runtimeFullName") + + if [[ -z $runtimeBin ]]; then + echo "Cannot find $runtimeFullName, do you need to run 'dotnetsdk install $versionOrAlias'?" + return 1 + fi + + echo "Adding" $runtimeBin "to process PATH" + + PATH=$(_dotnetsdk_strip_path "$PATH" "/bin") + PATH=$(_dotnetsdk_prepend_path "$PATH" "$runtimeBin") + + if [[ -n $persistent ]]; then + local runtimeVersion=$(_dotnetsdk_package_version "$runtimeFullName") + dotnetsdk alias default "$runtimeVersion" + fi + ;; + + "alias" ) + [[ $# -gt 3 ]] && dotnetsdk help && return + + [[ ! -e "$DOTNET_USER_HOME/alias/" ]] && mkdir "$DOTNET_USER_HOME/alias/" > /dev/null + + if [[ $# == 1 ]]; then + echo "" + local format="%-20s %s\n" + printf "$format" "Alias" "Name" + printf "$format" "-----" "----" + if [ -d "$DOTNET_USER_HOME/alias" ]; then + for _dotnetsdk_file in $(find "$DOTNET_USER_HOME/alias" -name *.alias); do + local alias="$(basename $_dotnetsdk_file | sed 's/\.alias//')" + local name="$(cat $_dotnetsdk_file)" + printf "$format" "$alias" "$name" + done + fi + echo "" + return + fi + + local name="$2" + + if [[ $# == 2 ]]; then + [[ ! -e "$DOTNET_USER_HOME/alias/$name.alias" ]] && echo "There is no alias called '$name'" && return + cat "$DOTNET_USER_HOME/alias/$name.alias" + echo "" + return + fi + + local runtimeFullName=$(_dotnetsdk_requested_version_or_alias "$3") + + [[ ! -d "$DOTNET_USER_PACKAGES/$runtimeFullName" ]] && echo "$runtimeFullName is not an installed .NET Runtime version" && return 1 + + local action="Setting" + [[ -e "$DOTNET_USER_HOME/alias/$name.alias" ]] && action="Updating" + echo "$action alias '$name' to '$runtimeFullName'" + echo "$runtimeFullName" > "$DOTNET_USER_HOME/alias/$name.alias" + ;; + + "unalias" ) + [[ $# -ne 2 ]] && dotnetsdk help && return + + local name=$2 + local aliasPath="$DOTNET_USER_HOME/alias/$name.alias" + [[ ! -e "$aliasPath" ]] && echo "Cannot remove alias, '$name' is not a valid alias name" && return 1 + echo "Removing alias $name" + rm "$aliasPath" >> /dev/null 2>&1 + ;; + + "list" ) + [[ $# -gt 2 ]] && dotnetsdk help && return + + [[ ! -d $DOTNET_USER_PACKAGES ]] && echo ".NET Runtime is not installed." && return 1 + + local searchGlob="dotnet-*" + if [ $# == 2 ]; then + local versionOrAlias=$2 + local searchGlob=$(_dotnetsdk_requested_version_or_alias "$versionOrAlias") + fi + echo "" + + # Separate empty array declaration from initialization + # to avoid potential ZSH error: local:217: maximum nested function level reached + local arr + arr=() + + # Z shell array-index starts at one. + local i=1 + local format="%-20s %s\n" + if [ -d "$DOTNET_USER_HOME/alias" ]; then + for _dotnetsdk_file in $(find "$DOTNET_USER_HOME/alias" -name *.alias); do + arr[$i]="$(basename $_dotnetsdk_file | sed 's/\.alias//')/$(cat $_dotnetsdk_file)" + let i+=1 + done + fi + + local formatString="%-6s %-20s %-7s %-20s %s\n" + printf "$formatString" "Active" "Version" "Runtime" "Location" "Alias" + printf "$formatString" "------" "-------" "-------" "--------" "-----" + + local formattedHome=`(echo $DOTNET_USER_PACKAGES | sed s=$HOME=~=g)` + for f in $(find $DOTNET_USER_PACKAGES -name "$searchGlob" \( -type d -or -type l \) -prune -exec basename {} \;); do + local active="" + [[ $PATH == *"$DOTNET_USER_PACKAGES/$f/bin"* ]] && local active=" *" + local pkgName=$(_dotnetsdk_package_runtime "$f") + local pkgVersion=$(_dotnetsdk_package_version "$f") + + local alias="" + local delim="" + for i in "${arr[@]}"; do + temp="dotnet-$pkgName.$pkgVersion" + temp2="dotnet-$pkgName-x86.$pkgVersion" + if [[ ${i#*/} == $temp || ${i#*/} == $temp2 ]]; then + alias+="$delim${i%/*}" + delim=", " + fi + done + + printf "$formatString" "$active" "$pkgVersion" "$pkgName" "$formattedHome" "$alias" + [[ $# == 2 ]] && echo "" && return 0 + done + + echo "" + [[ $# == 2 ]] && echo "$versionOrAlias not found" && return 1 + ;; + + *) + echo "Unknown command $1" + return 1 + esac + + return 0 +} + +dotnetsdk list default >/dev/null && dotnetsdk use default >/dev/null || true