Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes372downloads
install-powershell.ps1500 linesDownload Raw Back to tools
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