"Fossies" - the Fresh Open Source Software Archive

Member "PowerShell-7.2.6/tools/install-powershell.ps1" (11 Aug 2022, 20864 Bytes) of package /linux/misc/PowerShell-7.2.6.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Microsoft PowerShell source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    1 # Copyright (c) Microsoft Corporation.
    2 # Licensed under the MIT License.
    3 <#
    4 .Synopsis
    5     Install PowerShell on Windows, Linux or macOS.
    6 .DESCRIPTION
    7     By default, the latest PowerShell release package will be installed.
    8     If '-Daily' is specified, then the latest PowerShell daily package will be installed.
    9 .Parameter Destination
   10     The destination path to install PowerShell to.
   11 .Parameter Daily
   12     Install PowerShell from the daily build.
   13     Note that the 'PackageManagement' module is required to install a daily package.
   14 .Parameter DoNotOverwrite
   15     Do not overwrite the destination folder if it already exists.
   16 .Parameter AddToPath
   17     On Windows, add the absolute destination path to the 'User' scope environment variable 'Path';
   18     On Linux, make the symlink '/usr/bin/pwsh' points to "$Destination/pwsh";
   19     On MacOS, make the symlink '/usr/local/bin/pwsh' points to "$Destination/pwsh".
   20 .EXAMPLE
   21     Install the daily build
   22     .\install-powershell.ps1 -Daily
   23 .EXAMPLE
   24     Invoke this script directly from GitHub
   25     Invoke-Expression "& { $(Invoke-RestMethod 'https://aka.ms/install-powershell.ps1') } -daily"
   26 #>
   27 [CmdletBinding(DefaultParameterSetName = "Daily")]
   28 param(
   29     [Parameter(ParameterSetName = "Daily")]
   30     [string] $Destination,
   31 
   32     [Parameter(ParameterSetName = "Daily")]
   33     [switch] $Daily,
   34 
   35     [Parameter(ParameterSetName = "Daily")]
   36     [switch] $DoNotOverwrite,
   37 
   38     [Parameter(ParameterSetName = "Daily")]
   39     [switch] $AddToPath,
   40 
   41     [Parameter(ParameterSetName = "MSI")]
   42     [switch] $UseMSI,
   43 
   44     [Parameter(ParameterSetName = "MSI")]
   45     [switch] $Quiet,
   46 
   47     [Parameter(ParameterSetName = "MSI")]
   48     [switch] $AddExplorerContextMenu,
   49 
   50     [Parameter(ParameterSetName = "MSI")]
   51     [switch] $EnablePSRemoting,
   52 
   53     [Parameter()]
   54     [switch] $Preview
   55 )
   56 
   57 Set-StrictMode -Version 3.0
   58 $ErrorActionPreference = "Stop"
   59 
   60 $IsLinuxEnv = (Get-Variable -Name "IsLinux" -ErrorAction Ignore) -and $IsLinux
   61 $IsMacOSEnv = (Get-Variable -Name "IsMacOS" -ErrorAction Ignore) -and $IsMacOS
   62 $IsWinEnv = !$IsLinuxEnv -and !$IsMacOSEnv
   63 
   64 if (-not $Destination) {
   65     if ($IsWinEnv) {
   66         $Destination = "$env:LOCALAPPDATA\Microsoft\powershell"
   67     } else {
   68         $Destination = "~/.powershell"
   69     }
   70 
   71     if ($Daily) {
   72         $Destination = "${Destination}-daily"
   73     }
   74 }
   75 
   76 $Destination = $PSCmdlet.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Destination)
   77 
   78 if (-not $UseMSI) {
   79     Write-Verbose "Destination: $Destination" -Verbose
   80 } else {
   81     if (-not $IsWinEnv) {
   82         throw "-UseMSI is only supported on Windows"
   83     } else {
   84         $MSIArguments = @()
   85         if($AddExplorerContextMenu) {
   86             $MSIArguments += "ADD_EXPLORER_CONTEXT_MENU_OPENPOWERSHELL=1"
   87         }
   88         if($EnablePSRemoting) {
   89             $MSIArguments += "ENABLE_PSREMOTING=1"
   90         }
   91     }
   92 }
   93 
   94 # Expand an archive using Expand-archive when available
   95 # and the DotNet API when it is not
   96 function Expand-ArchiveInternal {
   97     [CmdletBinding()]
   98     param(
   99         $Path,
  100         $DestinationPath
  101     )
  102 
  103     if((Get-Command -Name Expand-Archive -ErrorAction Ignore))
  104     {
  105         Expand-Archive -Path $Path -DestinationPath $DestinationPath
  106     }
  107     else
  108     {
  109         Add-Type -AssemblyName System.IO.Compression.FileSystem
  110         $resolvedPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)
  111         $resolvedDestinationPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($DestinationPath)
  112         [System.IO.Compression.ZipFile]::ExtractToDirectory($resolvedPath,$resolvedDestinationPath)
  113     }
  114 }
  115 
  116 Function Remove-Destination([string] $Destination) {
  117     if (Test-Path -Path $Destination) {
  118         if ($DoNotOverwrite) {
  119             throw "Destination folder '$Destination' already exist. Use a different path or omit '-DoNotOverwrite' to overwrite."
  120         }
  121         Write-Verbose "Removing old installation: $Destination" -Verbose
  122         if (Test-Path -Path "$Destination.old") {
  123             Remove-Item "$Destination.old" -Recurse -Force
  124         }
  125         if ($IsWinEnv -and ($Destination -eq $PSHOME)) {
  126             # handle the case where the updated folder is currently in use
  127             Get-ChildItem -Recurse -File -Path $PSHOME | ForEach-Object {
  128                 if ($_.extension -eq "old") {
  129                     Remove-Item $_
  130                 } else {
  131                     Move-Item $_.fullname "$($_.fullname).old"
  132                 }
  133             }
  134         } else {
  135             # Unix systems don't keep open file handles so you can just move files/folders even if in use
  136             Move-Item "$Destination" "$Destination.old"
  137         }
  138     }
  139 }
  140 
  141 <#
  142 .SYNOPSIS
  143     Validation for Add-PathTToSettingsToSettings.
  144 .DESCRIPTION
  145     Validates that the parameter being validated:
  146     - is not null
  147     - is a folder and exists
  148     - and that it does not exist in settings where settings is:
  149         = the process PATH for Linux/OSX
  150         - the registry PATHs for Windows
  151 #>
  152 function Test-PathNotInSettings($Path) {
  153     if ([string]::IsNullOrWhiteSpace($Path)) {
  154         throw 'Argument is null'
  155     }
  156 
  157     # Remove ending DirectorySeparatorChar for comparison purposes
  158     $Path = [System.Environment]::ExpandEnvironmentVariables($Path.TrimEnd([System.IO.Path]::DirectorySeparatorChar));
  159 
  160     if (-not [System.IO.Directory]::Exists($Path)) {
  161         throw "Path does not exist: $Path"
  162     }
  163 
  164     # [System.Environment]::GetEnvironmentVariable automatically expands all variables
  165     [System.Array] $InstalledPaths = @()
  166     if ([System.Environment]::OSVersion.Platform -eq "Win32NT") {
  167         $InstalledPaths += @(([System.Environment]::GetEnvironmentVariable('PATH', [System.EnvironmentVariableTarget]::User)) -split ([System.IO.Path]::PathSeparator))
  168         $InstalledPaths += @(([System.Environment]::GetEnvironmentVariable('PATH', [System.EnvironmentVariableTarget]::Machine)) -split ([System.IO.Path]::PathSeparator))
  169     } else {
  170         $InstalledPaths += @(([System.Environment]::GetEnvironmentVariable('PATH'), [System.EnvironmentVariableTarget]::Process) -split ([System.IO.Path]::PathSeparator))
  171     }
  172 
  173     # Remove ending DirectorySeparatorChar in all items of array for comparison purposes
  174     $InstalledPaths = $InstalledPaths | ForEach-Object { $_.TrimEnd([System.IO.Path]::DirectorySeparatorChar) }
  175 
  176     # if $InstalledPaths is in setting return false
  177     if ($InstalledPaths -icontains $Path) {
  178         throw 'Already in PATH environment variable'
  179     }
  180 
  181     return $true
  182 }
  183 
  184 <#
  185 .Synopsis
  186     Adds a Path to settings (Supports Windows Only)
  187 .DESCRIPTION
  188     Adds the target path to the target registry.
  189 .Parameter Path
  190     The path to add to the registry. It is validated with Test-PathNotInSettings which ensures that:
  191     -The path exists
  192     -Is a directory
  193     -Is not in the registry (HKCU or HKLM)
  194 .Parameter Target
  195     The target hive to install the Path to.
  196     Must be either User or Machine
  197     Defaults to User
  198 #>
  199 Function Add-PathTToSettings {
  200     [CmdletBinding()]
  201     param(
  202         [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
  203         [ValidateNotNullOrEmpty()]
  204         [ValidateScript({Test-PathNotInSettings $_})]
  205         [string] $Path,
  206 
  207         [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
  208         [ValidateNotNullOrEmpty()]
  209         [ValidateSet([System.EnvironmentVariableTarget]::User, [System.EnvironmentVariableTarget]::Machine)]
  210         [System.EnvironmentVariableTarget] $Target = ([System.EnvironmentVariableTarget]::User)
  211     )
  212 
  213     if (-not $IsWinEnv) {
  214         return
  215     }
  216 
  217     if ($Target -eq [System.EnvironmentVariableTarget]::User) {
  218         [string] $Environment = 'Environment'
  219         [Microsoft.Win32.RegistryKey] $Key = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey($Environment, [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree)
  220     } else {
  221         [string] $Environment = 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment'
  222         [Microsoft.Win32.RegistryKey] $Key = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey($Environment, [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree)
  223     }
  224 
  225     # $key is null here if it the user was unable to get ReadWriteSubTree access.
  226     if ($null -eq $Key) {
  227         throw (New-Object -TypeName 'System.Security.SecurityException' -ArgumentList "Unable to access the target registry")
  228     }
  229 
  230     # Get current unexpanded value
  231     [string] $CurrentUnexpandedValue = $Key.GetValue('PATH', '', [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
  232 
  233     # Keep current PathValueKind if possible/appropriate
  234     try {
  235         [Microsoft.Win32.RegistryValueKind] $PathValueKind = $Key.GetValueKind('PATH')
  236     } catch {
  237         [Microsoft.Win32.RegistryValueKind] $PathValueKind = [Microsoft.Win32.RegistryValueKind]::ExpandString
  238     }
  239 
  240     # Evaluate new path
  241     $NewPathValue = [string]::Concat($CurrentUnexpandedValue.TrimEnd([System.IO.Path]::PathSeparator), [System.IO.Path]::PathSeparator, $Path)
  242 
  243     # Upgrade PathValueKind to [Microsoft.Win32.RegistryValueKind]::ExpandString if appropriate
  244     if ($NewPathValue.Contains('%')) { $PathValueKind = [Microsoft.Win32.RegistryValueKind]::ExpandString }
  245 
  246     $Key.SetValue("PATH", $NewPathValue, $PathValueKind)
  247 }
  248 
  249 if (-not $IsWinEnv) {
  250     $architecture = "x64"
  251 } elseif ($(Get-ComputerInfo -Property OsArchitecture).OsArchitecture -eq "ARM 64-bit Processor") {
  252     $architecture = "arm64"
  253 } else {
  254     switch ($env:PROCESSOR_ARCHITECTURE) {
  255         "AMD64" { $architecture = "x64" }
  256         "x86" { $architecture = "x86" }
  257         default { throw "PowerShell package for OS architecture '$_' is not supported." }
  258     }
  259 }
  260 $tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ([System.IO.Path]::GetRandomFileName())
  261 $null = New-Item -ItemType Directory -Path $tempDir -Force -ErrorAction SilentlyContinue
  262 try {
  263     # Setting Tls to 12 to prevent the Invoke-WebRequest : The request was
  264     # aborted: Could not create SSL/TLS secure channel. error.
  265     $originalValue = [Net.ServicePointManager]::SecurityProtocol
  266     [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
  267 
  268     if ($Daily) {
  269         $metadata = Invoke-RestMethod 'https://aka.ms/pwsh-buildinfo-daily'
  270         $release = $metadata.ReleaseTag -replace '^v'
  271         $blobName = $metadata.BlobName
  272 
  273         # Get version from currently installed PowerShell Daily if available.
  274         $pwshPath = if ($IsWinEnv) {Join-Path $Destination "pwsh.exe"} else {Join-Path $Destination "pwsh"}
  275         $currentlyInstalledVersion = if(Test-Path $pwshPath) {
  276             ((& $pwshPath -version) -split " ")[1]
  277         }
  278 
  279         if($currentlyInstalledVersion -eq $release) {
  280             Write-Verbose "Latest PowerShell Daily already installed." -Verbose
  281             return
  282         }
  283 
  284         if ($IsWinEnv) {
  285             if ($UseMSI) {
  286                 $packageName = "PowerShell-${release}-win-${architecture}.msi"
  287             } else {
  288                 $packageName = "PowerShell-${release}-win-${architecture}.zip"
  289             }
  290         } elseif ($IsLinuxEnv) {
  291             $packageName = "powershell-${release}-linux-${architecture}.tar.gz"
  292         } elseif ($IsMacOSEnv) {
  293             $packageName = "powershell-${release}-osx-${architecture}.tar.gz"
  294         }
  295 
  296         if ($architecture -ne "x64") {
  297             throw "The OS architecture is '$architecture'. However, we currently only support daily package for x64."
  298         }
  299 
  300 
  301         $downloadURL = "https://pscoretestdata.blob.core.windows.net/${blobName}/${packageName}"
  302         Write-Verbose "About to download package from '$downloadURL'" -Verbose
  303 
  304         $packagePath = Join-Path -Path $tempDir -ChildPath $packageName
  305         if (!$PSVersionTable.ContainsKey('PSEdition') -or $PSVersionTable.PSEdition -eq "Desktop") {
  306             # On Windows PowerShell, progress can make the download significantly slower
  307             $oldProgressPreference = $ProgressPreference
  308             $ProgressPreference = "SilentlyContinue"
  309         }
  310 
  311         try {
  312             Invoke-WebRequest -Uri $downloadURL -OutFile $packagePath
  313         } finally {
  314             if (!$PSVersionTable.ContainsKey('PSEdition') -or $PSVersionTable.PSEdition -eq "Desktop") {
  315                 $ProgressPreference = $oldProgressPreference
  316             }
  317         }
  318 
  319         $contentPath = Join-Path -Path $tempDir -ChildPath "new"
  320 
  321         $null = New-Item -ItemType Directory -Path $contentPath -ErrorAction SilentlyContinue
  322         if ($IsWinEnv) {
  323             if ($UseMSI -and $Quiet) {
  324                 Write-Verbose "Performing quiet install"
  325                 $ArgumentList=@("/i", $packagePath, "/quiet")
  326                 if($MSIArguments) {
  327                     $ArgumentList+=$MSIArguments
  328                 }
  329                 $process = Start-Process msiexec -ArgumentList $ArgumentList -Wait -PassThru
  330                 if ($process.exitcode -ne 0) {
  331                     throw "Quiet install failed, please rerun install without -Quiet switch or ensure you have administrator rights"
  332                 }
  333             } elseif ($UseMSI) {
  334                 if($MSIArguments) {
  335                     Start-Process $packagePath -ArgumentList $MSIArguments -Wait
  336                 } else {
  337                     Start-Process $packagePath -Wait
  338                 }
  339             } else {
  340                 Expand-ArchiveInternal -Path $packagePath -DestinationPath $contentPath
  341             }
  342         } else {
  343             tar zxf $packagePath -C $contentPath
  344         }
  345     } else {
  346         $metadata = Invoke-RestMethod https://raw.githubusercontent.com/PowerShell/PowerShell/master/tools/metadata.json
  347         if ($Preview) {
  348             $release = $metadata.PreviewReleaseTag -replace '^v'
  349         } else {
  350             $release = $metadata.ReleaseTag -replace '^v'
  351         }
  352 
  353         if ($IsWinEnv) {
  354             if ($UseMSI) {
  355                 if ($architecture -eq "arm64") {
  356                     $packageName = "PowerShell-${release}-win-${architecture}.msix"
  357                 } else {
  358                     $packageName = "PowerShell-${release}-win-${architecture}.msi"
  359                 }
  360             } else {
  361                 $packageName = "PowerShell-${release}-win-${architecture}.zip"
  362             }
  363         } elseif ($IsLinuxEnv) {
  364             $packageName = "powershell-${release}-linux-${architecture}.tar.gz"
  365         } elseif ($IsMacOSEnv) {
  366             $packageName = "powershell-${release}-osx-${architecture}.tar.gz"
  367         }
  368 
  369         $downloadURL = "https://github.com/PowerShell/PowerShell/releases/download/v${release}/${packageName}"
  370         Write-Verbose "About to download package from '$downloadURL'" -Verbose
  371 
  372         $packagePath = Join-Path -Path $tempDir -ChildPath $packageName
  373         if (!$PSVersionTable.ContainsKey('PSEdition') -or $PSVersionTable.PSEdition -eq "Desktop") {
  374             # On Windows PowerShell, progress can make the download significantly slower
  375             $oldProgressPreference = $ProgressPreference
  376             $ProgressPreference = "SilentlyContinue"
  377         }
  378 
  379         try {
  380             Invoke-WebRequest -Uri $downloadURL -OutFile $packagePath
  381         } finally {
  382             if (!$PSVersionTable.ContainsKey('PSEdition') -or $PSVersionTable.PSEdition -eq "Desktop") {
  383                 $ProgressPreference = $oldProgressPreference
  384             }
  385         }
  386 
  387         $contentPath = Join-Path -Path $tempDir -ChildPath "new"
  388 
  389         $null = New-Item -ItemType Directory -Path $contentPath -ErrorAction SilentlyContinue
  390         if ($IsWinEnv) {
  391             if ($UseMSI -and $architecture -eq "arm64") {
  392                 Add-AppxPackage -Path $packagePath
  393             } elseif ($UseMSI -and $Quiet) {
  394                 Write-Verbose "Performing quiet install"
  395                 $ArgumentList=@("/i", $packagePath, "/quiet")
  396                 if($MSIArguments) {
  397                     $ArgumentList+=$MSIArguments
  398                 }
  399                 $process = Start-Process msiexec -ArgumentList $ArgumentList -Wait -PassThru
  400                 if ($process.exitcode -ne 0) {
  401                     throw "Quiet install failed, please rerun install without -Quiet switch or ensure you have administrator rights"
  402                 }
  403             } elseif ($UseMSI) {
  404                 if($MSIArguments) {
  405                     Start-Process $packagePath -ArgumentList $MSIArguments -Wait
  406                 } else {
  407                     Start-Process $packagePath -Wait
  408                 }
  409             } else {
  410                 Expand-ArchiveInternal -Path $packagePath -DestinationPath $contentPath
  411             }
  412         } else {
  413             tar zxf $packagePath -C $contentPath
  414         }
  415     }
  416 
  417     if (-not $UseMSI) {
  418         Remove-Destination $Destination
  419         if (Test-Path $Destination) {
  420             Write-Verbose "Copying files" -Verbose
  421             # only copy files as folders will already exist at $Destination
  422             Get-ChildItem -Recurse -Path "$contentPath" -File | ForEach-Object {
  423                 $DestinationFilePath = Join-Path $Destination $_.fullname.replace($contentPath, "")
  424                 Copy-Item $_.fullname -Destination $DestinationFilePath
  425             }
  426         } else {
  427             $null = New-Item -Path (Split-Path -Path $Destination -Parent) -ItemType Directory -ErrorAction SilentlyContinue
  428             Move-Item -Path $contentPath -Destination $Destination
  429         }
  430     }
  431 
  432     ## Change the mode of 'pwsh' to 'rwxr-xr-x' to allow execution
  433     if (-not $IsWinEnv) { chmod 755 $Destination/pwsh }
  434 
  435     if ($AddToPath -and -not $UseMSI) {
  436         if ($IsWinEnv) {
  437             if ((-not ($Destination.StartsWith($ENV:USERPROFILE))) -and
  438                 (-not ($Destination.StartsWith($ENV:APPDATA))) -and
  439                 (-not ($Destination.StartsWith($env:LOCALAPPDATA)))) {
  440                 $TargetRegistry = [System.EnvironmentVariableTarget]::Machine
  441                 try {
  442                     Add-PathTToSettings -Path $Destination -Target $TargetRegistry
  443                 } catch {
  444                     Write-Warning -Message "Unable to save the new path in the machine wide registry: $_"
  445                     $TargetRegistry = [System.EnvironmentVariableTarget]::User
  446                 }
  447             } else {
  448                 $TargetRegistry = [System.EnvironmentVariableTarget]::User
  449             }
  450 
  451             # If failed to install to machine wide path or path was not appropriate for machine wide path
  452             if ($TargetRegistry -eq [System.EnvironmentVariableTarget]::User) {
  453                 try {
  454                     Add-PathTToSettings -Path $Destination -Target $TargetRegistry
  455                 } catch {
  456                     Write-Warning -Message "Unable to save the new path in the registry for the current user : $_"
  457                 }
  458             }
  459         } else {
  460             $targetPath = Join-Path -Path $Destination -ChildPath "pwsh"
  461             if ($IsLinuxEnv) { $symlink = "/usr/bin/pwsh" } elseif ($IsMacOSEnv) { $symlink = "/usr/local/bin/pwsh" }
  462             $needNewSymlink = $true
  463 
  464             if (Test-Path -Path $symlink) {
  465                 $linkItem = Get-Item -Path $symlink
  466                 if ($linkItem.LinkType -ne "SymbolicLink") {
  467                     Write-Warning "'$symlink' already exists but it's not a symbolic link. Abort adding to PATH."
  468                     $needNewSymlink = $false
  469                 } elseif ($linkItem.Target -contains $targetPath) {
  470                     ## The link already points to the target
  471                     Write-Verbose "'$symlink' already points to '$targetPath'" -Verbose
  472                     $needNewSymlink = $false
  473                 }
  474             }
  475 
  476             if ($needNewSymlink) {
  477                 $uid = id -u
  478                 if ($uid -ne "0") { $SUDO = "sudo" } else { $SUDO = "" }
  479 
  480                 Write-Verbose "Make symbolic link '$symlink' point to '$targetPath'..." -Verbose
  481                 & $SUDO ln -fs $targetPath $symlink
  482 
  483                 if ($LASTEXITCODE -ne 0) {
  484                     Write-Error "Could not add to PATH: failed to make '$symlink' point to '$targetPath'."
  485                 }
  486             }
  487         }
  488 
  489         ## Add to the current process 'Path' if the process is not 'pwsh'
  490         $runningProcessName = (Get-Process -Id $PID).ProcessName
  491         if ($runningProcessName -ne 'pwsh') {
  492             $env:Path = $Destination + [System.IO.Path]::PathSeparator + $env:Path
  493         }
  494     }
  495 
  496     if (-not $UseMSI) {
  497         Write-Host "PowerShell has been installed at $Destination" -ForegroundColor Green
  498         if ($Destination -eq $PSHOME) {
  499             Write-Host "Please restart pwsh" -ForegroundColor Magenta
  500         }
  501     }
  502 } finally {
  503     # Restore original value
  504     [Net.ServicePointManager]::SecurityProtocol = $originalValue
  505 
  506     Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
  507 }