MegaBites-AI/Windows-powershell
0372
1# Copyright (c) Microsoft Corporation.2# Licensed under the MIT License.3<#4.Synopsis5 Install PowerShell on Windows, Linux or macOS.6.DESCRIPTION7 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 Destination10 The destination path to install PowerShell to.11.Parameter Daily12 Install PowerShell from the daily build.13 Note that the 'PackageManagement' module is required to install a daily package.14.Parameter DoNotOverwrite15 Do not overwrite the destination folder if it already exists.16.Parameter AddToPath17 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.EXAMPLE21 Install the daily build22 .\install-powershell.ps1 -Daily23.EXAMPLE24 Invoke this script directly from GitHub25 Invoke-Expression "& { $(Invoke-RestMethod 'https://aka.ms/install-powershell.ps1') } -daily"26#>27[CmdletBinding(DefaultParameterSetName = "Daily")]28param(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] $Preview55)56 57Set-StrictMode -Version 3.058$ErrorActionPreference = "Stop"59 60$IsLinuxEnv = (Get-Variable -Name "IsLinux" -ErrorAction Ignore) -and $IsLinux61$IsMacOSEnv = (Get-Variable -Name "IsMacOS" -ErrorAction Ignore) -and $IsMacOS62$IsWinEnv = !$IsLinuxEnv -and !$IsMacOSEnv63 64if (-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 78if (-not $UseMSI) {79 Write-Verbose "Destination: $Destination" -Verbose80} 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 available95# and the DotNet API when it is not96function Expand-ArchiveInternal {97 [CmdletBinding()]98 param(99 $Path,100 $DestinationPath101 )102 103 if((Get-Command -Name Expand-Archive -ErrorAction Ignore))104 {105 Expand-Archive -Path $Path -DestinationPath $DestinationPath106 }107 else108 {109 Add-Type -AssemblyName System.IO.Compression.FileSystem110 $resolvedPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)111 $resolvedDestinationPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($DestinationPath)112 [System.IO.Compression.ZipFile]::ExtractToDirectory($resolvedPath,$resolvedDestinationPath)113 }114}115 116Function 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" -Verbose122 if (Test-Path -Path "$Destination.old") {123 Remove-Item "$Destination.old" -Recurse -Force124 }125 if ($IsWinEnv -and ($Destination -eq $PSHOME)) {126 # handle the case where the updated folder is currently in use127 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 use136 Move-Item "$Destination" "$Destination.old"137 }138 }139}140 141<#142.SYNOPSIS143 Validation for Add-PathTToSettingsToSettings.144.DESCRIPTION145 Validates that the parameter being validated:146 - is not null147 - is a folder and exists148 - and that it does not exist in settings where settings is:149 = the process PATH for Linux/OSX150 - the registry PATHs for Windows151#>152function Test-PathNotInSettings($Path) {153 if ([string]::IsNullOrWhiteSpace($Path)) {154 throw 'Argument is null'155 }156 157 # Remove ending DirectorySeparatorChar for comparison purposes158 $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 variables165 [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 purposes174 $InstalledPaths = $InstalledPaths | ForEach-Object { $_.TrimEnd([System.IO.Path]::DirectorySeparatorChar) }175 176 # if $InstalledPaths is in setting return false177 if ($InstalledPaths -icontains $Path) {178 throw 'Already in PATH environment variable'179 }180 181 return $true182}183 184<#185.Synopsis186 Adds a Path to settings (Supports Windows Only)187.DESCRIPTION188 Adds the target path to the target registry.189.Parameter Path190 The path to add to the registry. It is validated with Test-PathNotInSettings which ensures that:191 -The path exists192 -Is a directory193 -Is not in the registry (HKCU or HKLM)194.Parameter Target195 The target hive to install the Path to.196 Must be either User or Machine197 Defaults to User198#>199Function 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 return215 }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 value231 [string] $CurrentUnexpandedValue = $Key.GetValue('PATH', '', [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)232 233 # Keep current PathValueKind if possible/appropriate234 try {235 [Microsoft.Win32.RegistryValueKind] $PathValueKind = $Key.GetValueKind('PATH')236 } catch {237 [Microsoft.Win32.RegistryValueKind] $PathValueKind = [Microsoft.Win32.RegistryValueKind]::ExpandString238 }239 240 # Evaluate new path241 $NewPathValue = [string]::Concat($CurrentUnexpandedValue.TrimEnd([System.IO.Path]::PathSeparator), [System.IO.Path]::PathSeparator, $Path)242 243 # Upgrade PathValueKind to [Microsoft.Win32.RegistryValueKind]::ExpandString if appropriate244 if ($NewPathValue.Contains('%')) { $PathValueKind = [Microsoft.Win32.RegistryValueKind]::ExpandString }245 246 $Key.SetValue("PATH", $NewPathValue, $PathValueKind)247}248 249if (-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 SilentlyContinue262try {263 # Setting Tls to 12 to prevent the Invoke-WebRequest : The request was264 # aborted: Could not create SSL/TLS secure channel. error.265 $originalValue = [Net.ServicePointManager]::SecurityProtocol266 [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12267 268 if ($Daily) {269 $metadata = Invoke-RestMethod 'https://aka.ms/pwsh-buildinfo-daily'270 $release = $metadata.ReleaseTag -replace '^v'271 272 # Get version from currently installed PowerShell Daily if available.273 $pwshPath = if ($IsWinEnv) {Join-Path $Destination "pwsh.exe"} else {Join-Path $Destination "pwsh"}274 $currentlyInstalledVersion = if(Test-Path $pwshPath) {275 ((& $pwshPath -version) -split " ")[1]276 }277 278 if($currentlyInstalledVersion -eq $release) {279 Write-Verbose "Latest PowerShell Daily already installed." -Verbose280 return281 }282 283 if ($IsWinEnv) {284 if ($UseMSI) {285 $packageName = "PowerShell-${release}-win-${architecture}.msi"286 } else {287 $packageName = "PowerShell-${release}-win-${architecture}.zip"288 }289 } elseif ($IsLinuxEnv) {290 $packageName = "powershell-${release}-linux-${architecture}.tar.gz"291 } elseif ($IsMacOSEnv) {292 $packageName = "powershell-${release}-osx-${architecture}.tar.gz"293 }294 295 if ($architecture -ne "x64") {296 throw "The OS architecture is '$architecture'. However, we currently only support daily package for x64."297 }298 299 $downloadURL = "https://powershellinfraartifacts-gkhedzdeaghdezhr.z01.azurefd.net/install/$($metadata.ReleaseTag)/$packageName"300 Write-Verbose "About to download package from '$downloadURL'" -Verbose301 302 $packagePath = Join-Path -Path $tempDir -ChildPath $packageName303 if (!$PSVersionTable.ContainsKey('PSEdition') -or $PSVersionTable.PSEdition -eq "Desktop") {304 # On Windows PowerShell, progress can make the download significantly slower305 $oldProgressPreference = $ProgressPreference306 $ProgressPreference = "SilentlyContinue"307 }308 309 try {310 Invoke-WebRequest -Uri $downloadURL -OutFile $packagePath311 } finally {312 if (!$PSVersionTable.ContainsKey('PSEdition') -or $PSVersionTable.PSEdition -eq "Desktop") {313 $ProgressPreference = $oldProgressPreference314 }315 }316 317 $contentPath = Join-Path -Path $tempDir -ChildPath "new"318 319 $null = New-Item -ItemType Directory -Path $contentPath -ErrorAction SilentlyContinue320 if ($IsWinEnv) {321 if ($UseMSI -and $Quiet) {322 Write-Verbose "Performing quiet install"323 $ArgumentList=@("/i", $packagePath, "/quiet")324 if($MSIArguments) {325 $ArgumentList+=$MSIArguments326 }327 $process = Start-Process msiexec -ArgumentList $ArgumentList -Wait -PassThru328 if ($process.exitcode -ne 0) {329 throw "Quiet install failed, please rerun install without -Quiet switch or ensure you have administrator rights"330 }331 } elseif ($UseMSI) {332 if($MSIArguments) {333 Start-Process $packagePath -ArgumentList $MSIArguments -Wait334 } else {335 Start-Process $packagePath -Wait336 }337 } else {338 Expand-ArchiveInternal -Path $packagePath -DestinationPath $contentPath339 }340 } else {341 tar zxf $packagePath -C $contentPath342 }343 } else {344 $metadata = Invoke-RestMethod https://raw.githubusercontent.com/PowerShell/PowerShell/master/tools/metadata.json345 if ($Preview) {346 $release = $metadata.PreviewReleaseTag -replace '^v'347 } else {348 $release = $metadata.ReleaseTag -replace '^v'349 }350 351 if ($IsWinEnv) {352 if ($UseMSI) {353 $packageName = "PowerShell-${release}-win-${architecture}.msi"354 } else {355 $packageName = "PowerShell-${release}-win-${architecture}.zip"356 }357 } elseif ($IsLinuxEnv) {358 $packageName = "powershell-${release}-linux-${architecture}.tar.gz"359 } elseif ($IsMacOSEnv) {360 $packageName = "powershell-${release}-osx-${architecture}.tar.gz"361 }362 363 $downloadURL = "https://github.com/PowerShell/PowerShell/releases/download/v${release}/${packageName}"364 Write-Verbose "About to download package from '$downloadURL'" -Verbose365 366 $packagePath = Join-Path -Path $tempDir -ChildPath $packageName367 if (!$PSVersionTable.ContainsKey('PSEdition') -or $PSVersionTable.PSEdition -eq "Desktop") {368 # On Windows PowerShell, progress can make the download significantly slower369 $oldProgressPreference = $ProgressPreference370 $ProgressPreference = "SilentlyContinue"371 }372 373 try {374 Invoke-WebRequest -Uri $downloadURL -OutFile $packagePath375 } finally {376 if (!$PSVersionTable.ContainsKey('PSEdition') -or $PSVersionTable.PSEdition -eq "Desktop") {377 $ProgressPreference = $oldProgressPreference378 }379 }380 381 $contentPath = Join-Path -Path $tempDir -ChildPath "new"382 383 $null = New-Item -ItemType Directory -Path $contentPath -ErrorAction SilentlyContinue384 if ($IsWinEnv) {385 if ($UseMSI -and $Quiet) {386 Write-Verbose "Performing quiet install"387 $ArgumentList=@("/i", $packagePath, "/quiet")388 if($MSIArguments) {389 $ArgumentList+=$MSIArguments390 }391 $process = Start-Process msiexec -ArgumentList $ArgumentList -Wait -PassThru392 if ($process.exitcode -ne 0) {393 throw "Quiet install failed, please rerun install without -Quiet switch or ensure you have administrator rights"394 }395 } elseif ($UseMSI) {396 if($MSIArguments) {397 Start-Process $packagePath -ArgumentList $MSIArguments -Wait398 } else {399 Start-Process $packagePath -Wait400 }401 } else {402 Expand-ArchiveInternal -Path $packagePath -DestinationPath $contentPath403 }404 } else {405 tar zxf $packagePath -C $contentPath406 }407 }408 409 if (-not $UseMSI) {410 Remove-Destination $Destination411 if (Test-Path $Destination) {412 Write-Verbose "Copying files" -Verbose413 # only copy files as folders will already exist at $Destination414 Get-ChildItem -Recurse -Path "$contentPath" -File | ForEach-Object {415 $DestinationFilePath = Join-Path $Destination $_.fullname.replace($contentPath, "")416 Copy-Item $_.fullname -Destination $DestinationFilePath417 }418 } else {419 $null = New-Item -Path (Split-Path -Path $Destination -Parent) -ItemType Directory -ErrorAction SilentlyContinue420 Move-Item -Path $contentPath -Destination $Destination421 }422 }423 424 ## Change the mode of 'pwsh' to 'rwxr-xr-x' to allow execution425 if (-not $IsWinEnv) { chmod 755 $Destination/pwsh }426 427 if ($AddToPath -and -not $UseMSI) {428 if ($IsWinEnv) {429 if ((-not ($Destination.StartsWith($ENV:USERPROFILE))) -and430 (-not ($Destination.StartsWith($ENV:APPDATA))) -and431 (-not ($Destination.StartsWith($env:LOCALAPPDATA)))) {432 $TargetRegistry = [System.EnvironmentVariableTarget]::Machine433 try {434 Add-PathTToSettings -Path $Destination -Target $TargetRegistry435 } catch {436 Write-Warning -Message "Unable to save the new path in the machine wide registry: $_"437 $TargetRegistry = [System.EnvironmentVariableTarget]::User438 }439 } else {440 $TargetRegistry = [System.EnvironmentVariableTarget]::User441 }442 443 # If failed to install to machine wide path or path was not appropriate for machine wide path444 if ($TargetRegistry -eq [System.EnvironmentVariableTarget]::User) {445 try {446 Add-PathTToSettings -Path $Destination -Target $TargetRegistry447 } catch {448 Write-Warning -Message "Unable to save the new path in the registry for the current user : $_"449 }450 }451 } else {452 $targetPath = Join-Path -Path $Destination -ChildPath "pwsh"453 if ($IsLinuxEnv) { $symlink = "/usr/bin/pwsh" } elseif ($IsMacOSEnv) { $symlink = "/usr/local/bin/pwsh" }454 $needNewSymlink = $true455 456 if (Test-Path -Path $symlink) {457 $linkItem = Get-Item -Path $symlink458 if ($linkItem.LinkType -ne "SymbolicLink") {459 Write-Warning "'$symlink' already exists but it's not a symbolic link. Abort adding to PATH."460 $needNewSymlink = $false461 } elseif ($linkItem.Target -contains $targetPath) {462 ## The link already points to the target463 Write-Verbose "'$symlink' already points to '$targetPath'" -Verbose464 $needNewSymlink = $false465 }466 }467 468 if ($needNewSymlink) {469 $uid = id -u470 if ($uid -ne "0") { $SUDO = "sudo" } else { $SUDO = "" }471 472 Write-Verbose "Make symbolic link '$symlink' point to '$targetPath'..." -Verbose473 & $SUDO ln -fs $targetPath $symlink474 475 if ($LASTEXITCODE -ne 0) {476 Write-Error "Could not add to PATH: failed to make '$symlink' point to '$targetPath'."477 }478 }479 }480 481 ## Add to the current process 'Path' if the process is not 'pwsh'482 $runningProcessName = (Get-Process -Id $PID).ProcessName483 if ($runningProcessName -ne 'pwsh') {484 $env:Path = $Destination + [System.IO.Path]::PathSeparator + $env:Path485 }486 }487 488 if (-not $UseMSI) {489 Write-Host "PowerShell has been installed at $Destination" -ForegroundColor Green490 if ($Destination -eq $PSHOME) {491 Write-Host "Please restart pwsh" -ForegroundColor Magenta492 }493 }494} finally {495 # Restore original value496 [Net.ServicePointManager]::SecurityProtocol = $originalValue497 498 Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue499}500 