public/Install-ChocolateySoftware.ps1

# =====================================================================
# Copyright 2017 Chocolatey Software, Inc, and the
# original authors/contributors from ChocolateyGallery
# Copyright 2011 - 2017 RealDimensions Software, LLC, and the
# original authors/contributors from ChocolateyGallery
# at https://github.com/chocolatey/chocolatey.org
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# =====================================================================


<#
.SYNOPSIS
Install the Chocolatey Software from a URL to download the binary from.

.DESCRIPTION
Install Chocolatey Software either from a fixed URL where the chocolatey nupkg is stored,
or from the url of a NuGet feed containing the Chocolatey Package.
A version can be specified to lookup the Package feed for a specific version, and install it.
A proxy URL and credential can be specified to fetch the Chocolatey package, or the proxy configuration
can be ignored.

.PARAMETER ChocolateyPackageUrl
Exact URL of the chocolatey package. This can be an HTTP server, a network or local path.
This must be the .nupkg package as downloadable from here: https://chocolatey.org/packages/chocolatey

.PARAMETER PackageFeedUrl
Url of the NuGet Feed API to use for looking up the latest version of Chocolatey (available on that feed).
This is also used when searching for a specific version, doing a lookup via an Odata filter.

.PARAMETER Version
Version to install if you want to be specific, this is the way to Install a pre-release version, as when not specified,
the latest non-prerelease version is looked up from the feed defined in PackageFeedUrl.

.PARAMETER ChocoTempDir
The temporary folder to extract the Chocolatey Binaries during install. This does not set the Chocolatey Cache dir.

.PARAMETER ProxyLocation
Proxy url to use when downloading the Chocolatey Package for installation.

.PARAMETER ProxyCredential
Credential to authenticate to the proxy, if not specified but the ProxyLocation is set, an attempt
to use the Cached credential will be made.

.PARAMETER IgnoreProxy
Ensure the proxy is bypassed when downloading the Chocolatey Package from the URL.

.PARAMETER InstallationDirectory
Set the Installation Directory for Chocolatey, by creating the Environment Variable. This will persist after the installation.

.EXAMPLE
# Install latest chocolatey software from the Community repository (non pre-release version)
Install-ChocolateySoftware

.EXAMPLE
# Install latest chocolatey software from a custom internal feed
Install-ChocolateySoftware -PackageFeedUrl https://proget.mycorp.local/nuget/Choco

.NOTES
Please raise issues at https://github.com/gaelcolas/Chocolatey/issues
#>

function Install-ChocolateySoftware {
    [CmdletBinding(
        DefaultParameterSetName = 'FromFeedUrl'
    )]
    Param(
        [Parameter(
            ParameterSetName = 'FromPackageUrl'
        )]
        [uri]
        $ChocolateyPackageUrl,

        [Parameter(
            ParameterSetName = 'FromFeedUrl'
        )]
        [uri]
        $PackageFeedUrl = 'https://chocolatey.org/api/v2',

        [string]
        $Version,

        [string]
        $ChocoTempDir,

        [uri]
        $ProxyLocation,

        [pscredential]
        $ProxyCredential,

        [switch]
        $IgnoreProxy,

        [string]
        $InstallationDirectory
    )

    if($PSVersionTable.PSVersion.Major -lt 4) {
        Repair-PowerShellOutputRedirectionBug
    }

    # Attempt to set highest encryption available for SecurityProtocol.
    # PowerShell will not set this by default (until maybe .NET 4.6.x). This
    # will typically produce a message for PowerShell v2 (just an info
    # message though)
    try {
        # Set TLS 1.2 (3072), then TLS 1.1 (768), then TLS 1.0 (192), finally SSL 3.0 (48)
        # Use integers because the enumeration values for TLS 1.2 and TLS 1.1 won't
        # exist in .NET 4.0, even though they are addressable if .NET 4.5+ is
        # installed (.NET 4.5 is an in-place upgrade).
        [System.Net.ServicePointManager]::SecurityProtocol = 3072 -bor 768 -bor 192 -bor 48
    }
    catch {
        Write-Warning 'Unable to set PowerShell to use TLS 1.2 and TLS 1.1 due to old .NET Framework installed. If you see underlying connection closed or trust errors, you may need to do one or more of the following: (1) upgrade to .NET Framework 4.5+ and PowerShell v3, (2) specify internal Chocolatey package location (set $env:chocolateyDownloadUrl prior to install or host the package internally), (3) use the Download + PowerShell method of install. See https://chocolatey.org/install for all install options.'
    }

    switch ($PSCmdlet.ParameterSetName) {
        'FromFeedUrl' {
            if ($PackageFeedUrl -and ![string]::IsNullOrEmpty($Version)){
                Write-Verbose "Downloading specific version of Chocolatey: $Version"
                $url = "$PackageFeedUrl/package/chocolatey/$Version"
            }
            else {
                if(![string]::IsNullOrEmpty($PackageFeedUrl)) {
                    $url = $PackageFeedUrl
                }
                else {
                    $url = 'https://chocolatey.org/api/v2'
                }
                Write-Verbose "Getting latest version of the Chocolatey package for download."
                $url = "$url/Packages()?`$filter=((Id%20eq%20%27chocolatey%27)%20and%20(not%20IsPrerelease))%20and%20IsLatestVersion"
                Write-Debug "Retrieving Binary URL from Package Metadata: $url"

                $GetRemoteStringParams = @{
                    url = $url
                }
                $GetRemoteStringParamsName = (get-command Get-RemoteString).parameters.keys
                $KeysForRemoteString = $PSBoundParameters.keys | Where-Object { $_ -in $GetRemoteStringParamsName}
                foreach ($key in $KeysForRemoteString ) { 
                    Write-Debug "`tWith $key :: $($PSBoundParameters[$key])"
                    $null = $GetRemoteStringParams.Add($key ,$PSBoundParameters[$key]) 
                }
                [xml]$result = Get-RemoteString @GetRemoteStringParams
                Write-Debug "New URL for nupkg: $url"
                $url = $result.feed.entry.content.src
            }
        }
        'FromPackageUrl' { 
            #ignores version
            Write-Verbose "Downloading Chocolatey from : $ChocolateyPackageUrl"
            $url = $ChocolateyPackageUrl
        }
    }

    if ($null -eq $env:TEMP) {
        $env:TEMP = Join-Path $Env:SYSTEMDRIVE 'temp'
    }

    $tempDir = [io.path]::Combine($Env:TEMP,'chocolatey','chocInstall')
    if (![System.IO.Directory]::Exists($tempDir)) {
        $null = New-Item -path $tempDir -ItemType Directory
    }
    $file = Join-Path $tempDir "chocolatey.zip"

    # Download the Chocolatey package
    Write-Verbose "Getting Chocolatey from $url."
    $GetRemoteFileParams = @{
        url = $url
        file = $file
    }
    $GetRemoteFileParamsName = (get-command Get-RemoteFile).parameters.keys
    $KeysForRemoteFile = $PSBoundParameters.keys | Where-Object { $_ -in $GetRemoteFileParamsName}
    foreach ($key in $KeysForRemoteFile ) { 
        Write-Debug "`tWith $key :: $($PSBoundParameters[$key])"
        $null = $GetRemoteFileParams.Add($key ,$PSBoundParameters[$key]) 
    }
    $null = Get-RemoteFile @GetRemoteFileParams

    # unzip the package
    Write-Verbose "Extracting $file to $tempDir..."

    if ($PSVersionTable.PSVersion.Major -ge 5) {
        Expand-Archive -Path "$file" -DestinationPath "$tempDir" -Force
    }
    else {
        try {
            $shellApplication = new-object -com shell.application
            $zipPackage = $shellApplication.NameSpace($file)
            $destinationFolder = $shellApplication.NameSpace($tempDir)
            $destinationFolder.CopyHere($zipPackage.Items(),0x10)
        }
        catch {
            throw "Unable to unzip package using built-in compression. Error: `n $_"
        }
    }

    # Call chocolatey install
    Write-Verbose "Installing chocolatey on this machine"
    $TempTools = [io.path]::combine($tempDir,'tools')
    # To be able to mock
    $chocInstallPS1 = Join-Path $TempTools 'chocolateyInstall.ps1' 

    if($InstallationDirectory) {
        [Environment]::SetEnvironmentVariable('ChocolateyInstall', $InstallationDirectory, 'Machine')
        [Environment]::SetEnvironmentVariable('ChocolateyInstall', $InstallationDirectory, 'Process')
    }
    & $chocInstallPS1 | Write-Debug

    Write-Verbose 'Ensuring chocolatey commands are on the path'
    $chocoPath = [Environment]::GetEnvironmentVariable('ChocolateyInstall')
    if ($chocoPath -eq $null -or $chocoPath -eq '') {
        $chocoPath = "$env:ALLUSERSPROFILE\Chocolatey"
    }

    if (!(Test-Path ($chocoPath))) {
        $chocoPath = "$env:SYSTEMDRIVE\ProgramData\Chocolatey"
    }

    $chocoExePath = Join-Path $chocoPath 'bin'

    if ($($env:Path).ToLower().Contains($($chocoExePath).ToLower()) -eq $false) {
        $env:Path = [Environment]::GetEnvironmentVariable('Path',[System.EnvironmentVariableTarget]::Machine)
    }

    Write-Verbose 'Ensuring chocolatey.nupkg is in the lib folder'
    $chocoPkgDir = Join-Path $chocoPath 'lib\chocolatey'
    $nupkg = Join-Path $chocoPkgDir 'chocolatey.nupkg'

    if (![System.IO.Directory]::Exists($chocoPkgDir)) { 
        $null = [System.IO.Directory]::CreateDirectory($chocoPkgDir)
    }
    Copy-Item "$file" "$nupkg" -Force -ErrorAction SilentlyContinue

    if ($ChocoVersion = & "$chocoPath\choco.exe" -v) {
        Write-Verbose "Installed Chocolatey Version: $ChocoVersion"
    }
}