From cd657ec6e5ce60e613a29161e01249d16d5e3c4f Mon Sep 17 00:00:00 2001 From: Garrett Rowell Date: Tue, 17 Sep 2024 14:49:24 -0600 Subject: [PATCH] Implement new chocolatey install script. Update Documentation. --- REFERENCE.md | 47 +- lib/puppet/provider/package/chocolatey.rb | 2 +- manifests/init.pp | 38 +- manifests/install.pp | 56 +-- templates/InstallChocolatey.ps1.epp | 153 ------ templates/install.ps1.epp | 573 ++++++++++++++++++++++ 6 files changed, 668 insertions(+), 201 deletions(-) delete mode 100644 templates/InstallChocolatey.ps1.epp create mode 100644 templates/install.ps1.epp diff --git a/REFERENCE.md b/REFERENCE.md index 1b6f446c..5c5be8cd 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -99,6 +99,10 @@ The following parameters are available in the `chocolatey` class: * [`log_output`](#-chocolatey--log_output) * [`chocolatey_version`](#-chocolatey--chocolatey_version) * [`install_proxy`](#-chocolatey--install_proxy) +* [`install_ignore_proxy`](#-chocolatey--install_ignore_proxy) +* [`install_proxy_user`](#-chocolatey--install_proxy_user) +* [`install_proxy_password`](#-chocolatey--install_proxy_password) +* [`install_tempdir`](#-chocolatey--install_tempdir) ##### `choco_install_location` @@ -127,11 +131,9 @@ Default value: `false` Data type: `String[1]` Specifies the source file for 7za.exe. -Supports all sources supported by Puppet's file resource. You should use -a 32bit binary for compatibility. -Defaults to 'https://chocolatey.org/7za.exe'. +Defaults to 'https://community.chocolatey.org/7za.exe'. -Default value: `'https://chocolatey.org/7za.exe'` +Default value: `'https://community.chocolatey.org/7za.exe'` ##### `choco_install_timeout_seconds` @@ -150,9 +152,9 @@ Data type: `Stdlib::Filesource` A url that will return `chocolatey.nupkg`. This must be a url, but not necessarily an OData feed. Any old url location will work. Defaults to -`'https://chocolatey.org/api/v2/package/chocolatey/'`. +`'https://community.chocolatey.org/api/v2/package/chocolatey/'`. -Default value: `'https://chocolatey.org/api/v2/package/chocolatey/'` +Default value: `'https://community.chocolatey.org/api/v2/package/chocolatey/'` ##### `enable_autouninstaller` @@ -194,6 +196,39 @@ Proxy server to use to use for installation of chocolatey itself or Default value: `undef` +##### `install_ignore_proxy` + +Data type: `Optional[Boolean]` + +If set to 'true' the installation script will ignore +the configured 'install_proxy' + +Default value: `undef` + +##### `install_proxy_user` + +Data type: `Optional[String[1]]` + +The username to use when using a proxy that requires authentication. + +Default value: `undef` + +##### `install_proxy_password` + +Data type: `Optional[Sensitive]` + +The password to use when using a proxy that requires authentication. + +Default value: `undef` + +##### `install_tempdir` + +Data type: `Optional[Stdlib::Windowspath]` + +The temporary directory Chocolatey extracts to when installing. + +Default value: `undef` + ## Resource types ### `chocolateyconfig` diff --git a/lib/puppet/provider/package/chocolatey.rb b/lib/puppet/provider/package/chocolatey.rb index 4c2c841b..268cfe77 100644 --- a/lib/puppet/provider/package/chocolatey.rb +++ b/lib/puppet/provider/package/chocolatey.rb @@ -252,7 +252,7 @@ def self.listcmd args << '-lo' args << '-r' if compiled_choco? - ["\"command(:chocolatey)\"", *args] + ["\"#{command(:chocolatey)}\"", *args] end def self.instances diff --git a/manifests/init.pp b/manifests/init.pp index 2a98b9b4..792096a8 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -45,9 +45,7 @@ # Defaults to `false`. # # @param [String] seven_zip_download_url Specifies the source file for 7za.exe. -# Supports all sources supported by Puppet's file resource. You should use -# a 32bit binary for compatibility. -# Defaults to 'https://chocolatey.org/7za.exe'. +# Defaults to 'https://community.chocolatey.org/7za.exe'. # # @param [Integer] choco_install_timeout_seconds How long in seconds should # be allowed for the install of Chocolatey (including .NET Framework 4 if @@ -56,7 +54,7 @@ # @param [String] chocolatey_download_url A url that will return # `chocolatey.nupkg`. This must be a url, but not necessarily an OData feed. # Any old url location will work. Defaults to -# `'https://chocolatey.org/api/v2/package/chocolatey/'`. +# `'https://community.chocolatey.org/api/v2/package/chocolatey/'`. # # @param [Boolean] enable_autouninstaller [Deprecated] - Should auto # uninstaller be turned on? Auto uninstaller is what allows Chocolatey to @@ -73,16 +71,30 @@ # # @param install_proxy Proxy server to use to use for installation of chocolatey itself or # `undef` to not use a proxy +# +# @param [Boolean] install_ignore_proxy If set to 'true' the installation script will ignore +# the configured 'install_proxy' +# +# @param install_proxy_user The username to use when using a proxy that requires authentication. +# +# @param install_proxy_password The password to use when using a proxy that requires authentication. +# +# @param install_tempdir The temporary directory Chocolatey extracts to when installing. +# class chocolatey ( - Stdlib::Windowspath $choco_install_location = $facts['choco_install_path'], - Boolean $use_7zip = false, - String[1] $seven_zip_download_url = 'https://chocolatey.org/7za.exe', - Integer $choco_install_timeout_seconds = 1500, - Stdlib::Filesource $chocolatey_download_url = 'https://chocolatey.org/api/v2/package/chocolatey/', - Boolean $enable_autouninstaller = true, - Boolean $log_output = false, - String[1] $chocolatey_version = $facts['chocolateyversion'], - Optional[String[1]] $install_proxy = undef, + Stdlib::Windowspath $choco_install_location = $facts['choco_install_path'], + Boolean $use_7zip = false, + String[1] $seven_zip_download_url = 'https://community.chocolatey.org/7za.exe', + Integer $choco_install_timeout_seconds = 1500, + Stdlib::Filesource $chocolatey_download_url = 'https://community.chocolatey.org/api/v2/package/chocolatey/', + Boolean $enable_autouninstaller = true, + Boolean $log_output = false, + String[1] $chocolatey_version = $facts['chocolateyversion'], + Optional[String[1]] $install_proxy = undef, + Optional[Boolean] $install_ignore_proxy = undef, + Optional[String[1]] $install_proxy_user = undef, + Optional[Sensitive] $install_proxy_password = undef, + Optional[Stdlib::Windowspath] $install_tempdir = undef, ) { class { 'chocolatey::install': } -> class { 'chocolatey::config': } diff --git a/manifests/install.pp b/manifests/install.pp index 00737c22..938e2fa1 100644 --- a/manifests/install.pp +++ b/manifests/install.pp @@ -4,14 +4,7 @@ class chocolatey::install { assert_private() - $install_proxy = $chocolatey::install_proxy - $_install_proxy = $install_proxy ? { - undef => '$false', - default => "'${install_proxy}'", - } - $_download_url = $chocolatey::chocolatey_download_url - $seven_zip_download_url = $chocolatey::seven_zip_download_url - $seven_zip_exe = "${facts['choco_temp_dir']}\\7za.exe" + $_download_url = $chocolatey::chocolatey_download_url # lint:ignore:only_variable_string if "${_download_url}" =~ /^http(s)?:\/\/.*api\/v2\/package.*\/$/ and "${chocolatey::chocolatey_version}" =~ /\d+\./ { @@ -22,19 +15,6 @@ } # lint:endignore - if $chocolatey::use_7zip { - $unzip_type = '7zip' - file { $seven_zip_exe: - ensure => file, - source => $seven_zip_download_url, - replace => false, - mode => '0755', - before => Exec['install_chocolatey_official'], - } - } else { - $unzip_type = 'windows' - } - registry_value { 'ChocolateyInstall environment value': ensure => present, path => 'HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment\ChocolateyInstall', @@ -42,20 +22,40 @@ data => $chocolatey::choco_install_location, } + # New install script exclusively uses environment variables in powershell. + # Filter out undef values and build an array of ['key=value'] to be passed in to the execs environment attribute. + # The 'chocolateyVersion' can also be passed to the install script. Instead only pass an explicit 'chocolateyDownloadUrl' that we + # already build correctly if $chocolatey::chocolatey_version is set. This allows for a custom $chocolatey:chocolatey_download_url + # to be used with no modification to the script. If the version is unset, the default behavior remains the same which is to attempt + # to install the latest available version. $install_parameters = { - 'download_url' => $download_url, - 'unzip_type' => $unzip_type, - '_install_proxy' => $_install_proxy, - 'seven_zip_exe' => $seven_zip_exe, - } + 'ChocolateyInstall' => $chocolatey::choco_install_location, + 'chocolateyDownloadUrl' => $download_url, + 'chocolateyProxyLocation' => $chocolatey::install_proxy, + 'chocolateyProxyUser' => $chocolatey::install_proxy_user, + 'chocolateyProxyPassword' => $chocolatey::install_proxy_password.unwrap, + 'chocolateyIgnoreProxy' => $chocolatey::install_ignore_proxy, + # install script defaults to using 7zip, this module defaults to using windows compression. + # this is ignored by install script if $PSVersionTable.PSVersion >= 5 + 'chocolateyUseWindowsCompression' => $chocolatey::use_7zip ? { + true => false, + false => true, + }, + 'TEMP' => $chocolatey::install_tempdir, + }.filter |$k, $v| { $v != undef }.map |$k, $v| { join([$k, $v], '=') } + # install.ps1 obtained from: https://community.chocolatey.org/install.ps1 + # Lightly modified to allow a custom $seven_zip_download_url. This has the benefit of using the same proxy + # if configured to use a proxy. For this module this is also a change in behavior as previously this was managed + # as a separate File resource. + # Since it was modified the [SIG] block was also removed exec { 'install_chocolatey_official': - command => epp('chocolatey/InstallChocolatey.ps1.epp', $install_parameters), + command => epp('chocolatey/install.ps1.epp'), creates => "${chocolatey::choco_install_location}\\bin\\choco.exe", provider => powershell, timeout => $chocolatey::choco_install_timeout_seconds, logoutput => $chocolatey::log_output, - environment => ["ChocolateyInstall=${chocolatey::choco_install_location}"], + environment => $install_parameters, require => Registry_value['ChocolateyInstall environment value'], } } diff --git a/templates/InstallChocolatey.ps1.epp b/templates/InstallChocolatey.ps1.epp deleted file mode 100644 index ab81f2d2..00000000 --- a/templates/InstallChocolatey.ps1.epp +++ /dev/null @@ -1,153 +0,0 @@ -# ============================================================================== -# Copyright 2011 - Present RealDimensions Software, LLC -# -# 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. -# ============================================================================== - -$ErrorActionPreference = 'Stop' - -# For some reason try/catch wrapping only ensures -# that none of this script runs at all -# https://tickets.puppetlabs.com/browse/MODULES-2634 -#try { - -# variables -$url = '<%= $download_url %>' -$unzipMethod = '<%= $unzip_type %>' -$install_proxy = <%= $_install_proxy %> -$7zaExe = '<%= $seven_zip_exe %>' -if ($env:TEMP -eq $null) { - $env:TEMP = Join-Path $env:SystemDrive 'temp' -} -$chocTempDir = Join-Path $env:TEMP "chocolatey" -$tempDir = Join-Path $chocTempDir "chocInstall" -if (![System.IO.Directory]::Exists($tempDir)) {[System.IO.Directory]::CreateDirectory($tempDir)} -$file = Join-Path $tempDir "chocolatey.zip" -$chocErrorLog = Join-Path $tempDir "chocError.log" - -# PowerShell v2/3 caches the output stream. Then it throws errors due -# to the FileStream not being what is expected. Fixes "The OS handle's -# position is not what FileStream expected. Do not use a handle -# simultaneously in one FileStream and in Win32 code or another -# FileStream." - -# This only works with the ConsoleHost (PowerShell InternalHost) -function Fix-PowerShellOutputRedirectionBug { - try{ - # http://www.leeholmes.com/blog/2008/07/30/workaround-the-os-handles-position-is-not-what-filestream-expected/ plus comments - $bindingFlags = [Reflection.BindingFlags] "Instance,NonPublic,GetField" - $objectRef = $host.GetType().GetField("externalHostRef", $bindingFlags).GetValue($host) - $bindingFlags = [Reflection.BindingFlags] "Instance,NonPublic,GetProperty" - $consoleHost = $objectRef.GetType().GetProperty("Value", $bindingFlags).GetValue($objectRef, @()) - [void] $consoleHost.GetType().GetProperty("IsStandardOutputRedirected", $bindingFlags).GetValue($consoleHost, @()) - $bindingFlags = [Reflection.BindingFlags] "Instance,NonPublic,GetField" - $field = $consoleHost.GetType().GetField("standardOutputWriter", $bindingFlags) - $field.SetValue($consoleHost, [Console]::Out) - [void] $consoleHost.GetType().GetProperty("IsStandardErrorRedirected", $bindingFlags).GetValue($consoleHost, @()) - $field2 = $consoleHost.GetType().GetField("standardErrorWriter", $bindingFlags) - $field2.SetValue($consoleHost, [Console]::Error) - } catch { - Write-Output "Unable to apply redirection fix. Error: $_" - } -} - -Fix-PowerShellOutputRedirectionBug - -# This should help when certain organizations have issues installing Chocolatey -# 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 will not - # 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-Output "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 and/or (2) specify internal Chocolatey package location (see https://forge.puppet.com/puppetlabs/chocolatey#manage-chocolatey-installation)." -} - -function Download-File { -param ( - [string]$url, - [string]$file - ) - Write-Output "Downloading $url to $file" - $downloader = new-object System.Net.WebClient - - if ($install_proxy) { - [System.Net.WebRequest]::DefaultWebProxy = New-Object System.Net.WebProxy($install_proxy,$true) - } - - $downloader.Proxy.Credentials=[System.Net.CredentialCache]::DefaultNetworkCredentials; - $downloader.DownloadFile($url, $file) -} - -# download the package -Download-File $url $file - -if ($unzipMethod -eq '7zip') { - # unzip the package - Write-Output "Extracting $file to $tempDir..." - Start-Process "$7zaExe" -ArgumentList "x -o`"$tempDir`" -y `"$file`"" -Wait -NoNewWindow - Remove-Item -Path "$7zaExe" -Force -} else { - if ($PSVersionTable.PSVersion.Major -lt 5) { - $shellApplication = new-object -com shell.application - $zipPackage = $shellApplication.NameSpace($file) - $destinationFolder = $shellApplication.NameSpace($tempDir) - $destinationFolder.CopyHere($zipPackage.Items(),0x10) - } else { - Expand-Archive -Path "$file" -DestinationPath "$tempDir" -Force | Out-Null - } -} - -# call chocolatey install -Write-Output "Installing chocolatey on this machine" -$toolsFolder = Join-Path $tempDir "tools" -$chocInstallPS1 = Join-Path $toolsFolder "chocolateyInstall.ps1" - -if ($PSVersionTable.PSVersion.Major -gt 2) { - & $chocInstallPS1 -} else { - $output = Invoke-Expression $chocInstallPS1 - $output - Write-Output "Any errors that occurred during install or upgrade are logged here: $chocoErrorLog" - $error | out-file $chocErrorLog -} - -Write-Output 'Ensuring chocolatey commands are on the path' -$chocInstallVariableName = "ChocolateyInstall" -$chocoPath = [Environment]::GetEnvironmentVariable($chocInstallVariableName, [System.EnvironmentVariableTarget]::User) -if ($chocoPath -eq $null -or $chocoPath -eq '') { - $chocoPath = 'C:\ProgramData\Chocolatey' -} - -$chocoBinPath = Join-Path $chocoPath 'bin' - -if ($($env:Path).ToLower().Contains($($chocoBinPath).ToLower()) -eq $false) { - $env:Path = [Environment]::GetEnvironmentVariable('Path',[System.EnvironmentVariableTarget]::Machine); -} - -Write-Output '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)) { [System.IO.Directory]::CreateDirectory($chocoPkgDir); } -Copy-Item "$file" "$nupkg" -Force -ErrorAction SilentlyContinue - -#} -#catch -#{ -# Write-Host "$($_.Exception.Message)" -# exit 1 -#} diff --git a/templates/install.ps1.epp b/templates/install.ps1.epp new file mode 100644 index 00000000..157eb056 --- /dev/null +++ b/templates/install.ps1.epp @@ -0,0 +1,573 @@ +<# + .SYNOPSIS + Downloads and installs Chocolatey on the local machine. + + .DESCRIPTION + Retrieves the Chocolatey nupkg for the latest or a specified version, and + downloads and installs the application to the local machine. + + .NOTES + ===================================================================== + Copyright 2017 - 2020 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. + ===================================================================== + + Environment Variables, specified as $env:NAME in PowerShell.exe and %NAME% in cmd.exe. + For explicit proxy, please set $env:chocolateyProxyLocation and optionally $env:chocolateyProxyUser and $env:chocolateyProxyPassword + For an explicit version of Chocolatey, please set $env:chocolateyVersion = 'versionnumber' + To target a different url for chocolatey.nupkg, please set $env:chocolateyDownloadUrl = 'full url to nupkg file' + NOTE: $env:chocolateyDownloadUrl does not work with $env:chocolateyVersion. + To use built-in compression instead of 7zip (requires additional download), please set $env:chocolateyUseWindowsCompression = 'true' + To bypass the use of any proxy, please set $env:chocolateyIgnoreProxy = 'true' + + .LINK + For organizational deployments of Chocolatey, please see https://docs.chocolatey.org/en-us/guides/organizations/organizational-deployment-guide + +#> +[CmdletBinding(DefaultParameterSetName = 'Default')] +param( + # The URL to download Chocolatey from. This defaults to the value of + # $env:chocolateyDownloadUrl, if it is set, and otherwise falls back to the + # official Chocolatey community repository to download the Chocolatey package. + # Can be used for offline installation by providing a path to a Chocolatey.nupkg. + [Parameter(Mandatory = $false)] + [string] + $ChocolateyDownloadUrl = $env:chocolateyDownloadUrl, + + # Specifies a target version of Chocolatey to install. By default, the latest + # stable version is installed. This will use the value in + # $env:chocolateyVersion by default, if that environment variable is present. + # This parameter is ignored if -ChocolateyDownloadUrl is set. + [Parameter(Mandatory = $false)] + [string] + $ChocolateyVersion = $env:chocolateyVersion, + + # If set, uses built-in Windows decompression tools instead of 7zip when + # unpacking the downloaded nupkg. This will be set by default if + # $env:chocolateyUseWindowsCompression is set to a value other than 'false' or '0'. + # + # This parameter will be ignored in PS 5+ in favour of using the + # Expand-Archive built in PowerShell cmdlet directly. + [Parameter(Mandatory = $false)] + [switch] + $UseNativeUnzip = $( + $envVar = "$env:chocolateyUseWindowsCompression".Trim() + $value = $null + if ([bool]::TryParse($envVar, [ref] $value)) { + $value + } elseif ([int]::TryParse($envVar, [ref] $value)) { + [bool]$value + } else { + [bool]$envVar + } + ), + + # If set, ignores any configured proxy. This will override any proxy + # environment variables or parameters. This will be set by default if + # $env:chocolateyIgnoreProxy is set to a value other than 'false' or '0'. + [Parameter(Mandatory = $false)] + [switch] + $IgnoreProxy = $( + $envVar = "$env:chocolateyIgnoreProxy".Trim() + $value = $null + if ([bool]::TryParse($envVar, [ref] $value)) { + $value + } + elseif ([int]::TryParse($envVar, [ref] $value)) { + [bool]$value + } + else { + [bool]$envVar + } + ), + + # Specifies the proxy URL to use during the download. This will default to + # the value of $env:chocolateyProxyLocation, if any is set. + [Parameter(ParameterSetName = 'Proxy', Mandatory = $false)] + [string] + $ProxyUrl = $env:chocolateyProxyLocation, + + # Specifies the credential to use for an authenticated proxy. By default, a + # proxy credential will be constructed from the $env:chocolateyProxyUser and + # $env:chocolateyProxyPassword environment variables, if both are set. + [Parameter(ParameterSetName = 'Proxy', Mandatory = $false)] + [System.Management.Automation.PSCredential] + $ProxyCredential +) + +#region Functions + +function Get-Downloader { + <# + .SYNOPSIS + Gets a System.Net.WebClient that respects relevant proxies to be used for + downloading data. + + .DESCRIPTION + Retrieves a WebClient object that is pre-configured according to specified + environment variables for any proxy and authentication for the proxy. + Proxy information may be omitted if the target URL is considered to be + bypassed by the proxy (originates from the local network.) + + .PARAMETER Url + Target URL that the WebClient will be querying. This URL is not queried by + the function, it is only a reference to determine if a proxy is needed. + + .EXAMPLE + Get-Downloader -Url $fileUrl + + Verifies whether any proxy configuration is needed, and/or whether $fileUrl + is a URL that would need to bypass the proxy, and then outputs the + already-configured WebClient object. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory = $false)] + [string] + $Url, + + [Parameter(Mandatory = $false)] + [string] + $ProxyUrl, + + [Parameter(Mandatory = $false)] + [System.Management.Automation.PSCredential] + $ProxyCredential + ) + + $downloader = New-Object System.Net.WebClient + + $defaultCreds = [System.Net.CredentialCache]::DefaultCredentials + if ($defaultCreds) { + $downloader.Credentials = $defaultCreds + } + + if ($ProxyUrl) { + # Use explicitly set proxy. + Write-Host "Using explicit proxy server '$ProxyUrl'." + $proxy = New-Object System.Net.WebProxy -ArgumentList $ProxyUrl, <# bypassOnLocal: #> $true + + $proxy.Credentials = if ($ProxyCredential) { + $ProxyCredential.GetNetworkCredential() + } elseif ($defaultCreds) { + $defaultCreds + } else { + Write-Warning "Default credentials were null, and no explicitly set proxy credentials were found. Attempting backup method." + (Get-Credential).GetNetworkCredential() + } + + if (-not $proxy.IsBypassed($Url)) { + $downloader.Proxy = $proxy + } + } else { + Write-Host "Not using proxy." + } + + $downloader +} + +function Request-String { + <# + .SYNOPSIS + Downloads content from a remote server as a string. + + .DESCRIPTION + Downloads target string content from a URL and outputs the resulting string. + Any existing proxy that may be in use will be utilised. + + .PARAMETER Url + URL to download string data from. + + .PARAMETER ProxyConfiguration + A hashtable containing proxy parameters (ProxyUrl and ProxyCredential) + + .EXAMPLE + Request-String https://community.chocolatey.org/install.ps1 + + Retrieves the contents of the string data at the targeted URL and outputs + it to the pipeline. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string] + $Url, + + [Parameter(Mandatory = $false)] + [hashtable] + $ProxyConfiguration + ) + + (Get-Downloader $url @ProxyConfiguration).DownloadString($url) +} + +function Request-File { + <# + .SYNOPSIS + Downloads a file from a given URL. + + .DESCRIPTION + Downloads a target file from a URL to the specified local path. + Any existing proxy that may be in use will be utilised. + + .PARAMETER Url + URL of the file to download from the remote host. + + .PARAMETER File + Local path for the file to be downloaded to. + + .PARAMETER ProxyConfiguration + A hashtable containing proxy parameters (ProxyUrl and ProxyCredential) + + .EXAMPLE + Request-File -Url https://community.chocolatey.org/install.ps1 -File $targetFile + + Downloads the install.ps1 script to the path specified in $targetFile. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory = $false)] + [string] + $Url, + + [Parameter(Mandatory = $false)] + [string] + $File, + + [Parameter(Mandatory = $false)] + [hashtable] + $ProxyConfiguration + ) + + Write-Host "Downloading $url to $file" + (Get-Downloader $url @ProxyConfiguration).DownloadFile($url, $file) +} + +function Set-PSConsoleWriter { + <# + .SYNOPSIS + Workaround for a bug in output stream handling PS v2 or v3. + + .DESCRIPTION + PowerShell v2/3 caches the output stream. Then it throws errors due to the + FileStream not being what is expected. Fixes "The OS handle's position is + not what FileStream expected. Do not use a handle simultaneously in one + FileStream and in Win32 code or another FileStream." error. + + .EXAMPLE + Set-PSConsoleWriter + + .NOTES + General notes + #> + + [CmdletBinding()] + param() + if ($PSVersionTable.PSVersion.Major -gt 3) { + return + } + + try { + # http://www.leeholmes.com/blog/2008/07/30/workaround-the-os-handles-position-is-not-what-filestream-expected/ plus comments + $bindingFlags = [Reflection.BindingFlags] "Instance,NonPublic,GetField" + $objectRef = $host.GetType().GetField("externalHostRef", $bindingFlags).GetValue($host) + + $bindingFlags = [Reflection.BindingFlags] "Instance,NonPublic,GetProperty" + $consoleHost = $objectRef.GetType().GetProperty("Value", $bindingFlags).GetValue($objectRef, @()) + [void] $consoleHost.GetType().GetProperty("IsStandardOutputRedirected", $bindingFlags).GetValue($consoleHost, @()) + + $bindingFlags = [Reflection.BindingFlags] "Instance,NonPublic,GetField" + $field = $consoleHost.GetType().GetField("standardOutputWriter", $bindingFlags) + $field.SetValue($consoleHost, [Console]::Out) + + [void] $consoleHost.GetType().GetProperty("IsStandardErrorRedirected", $bindingFlags).GetValue($consoleHost, @()) + $field2 = $consoleHost.GetType().GetField("standardErrorWriter", $bindingFlags) + $field2.SetValue($consoleHost, [Console]::Error) + } catch { + Write-Warning "Unable to apply redirection fix." + } +} + +function Test-ChocolateyInstalled { + [CmdletBinding()] + param() + + $checkPath = if ($env:ChocolateyInstall) { $env:ChocolateyInstall } else { "$env:PROGRAMDATA\chocolatey" } + + if ($Command = Get-Command choco -CommandType Application -ErrorAction Ignore) { + # choco is on the PATH, assume it's installed + Write-Warning "'choco' was found at '$($Command.Path)'." + $true + } + elseif (-not (Test-Path $checkPath)) { + # Install folder doesn't exist + $false + } + else { + # Install folder exists + if (Get-ChildItem -Path $checkPath) { + Write-Warning "Files from a previous installation of Chocolatey were found at '$($CheckPath)'." + } + + # Return true here to prevent overwriting an existing installation + $true + } +} + +function Install-7zip { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string] + $Path, + + [Parameter(Mandatory = $false)] + [hashtable] + $ProxyConfiguration + ) + if (-not (Test-Path ($Path))) { + Write-Host "Downloading 7-Zip commandline tool prior to extraction." + Request-File -Url <%= $chocolatey::seven_zip_download_url %> -File $Path -ProxyConfiguration $ProxyConfiguration + } + else { + Write-Host "7zip already present, skipping installation." + } +} + +#endregion Functions + +#region Pre-check + +# Ensure we have all our streams setup correctly, needed for older PSVersions. +Set-PSConsoleWriter + +if (Test-ChocolateyInstalled) { + $message = @( + "An existing Chocolatey installation was detected. Installation will not continue. This script will not overwrite existing installations." + "If there is no Chocolatey installation at '$env:ChocolateyInstall', delete the folder and attempt the installation again." + "" + "Please use `choco upgrade chocolatey` to handle upgrades of Chocolatey itself." + "If the existing installation is not functional or a prior installation did not complete, follow these steps:" + " - Backup the files at the path listed above so you can restore your previous installation if needed." + " - Remove the existing installation manually." + " - Rerun this installation script." + " - Reinstall any packages previously installed, if needed (refer to the `lib` folder in the backup)." + "" + "Once installation is completed, the backup folder is no longer needed and can be deleted." + ) -join [Environment]::NewLine + + Write-Warning $message + + return +} + +#endregion Pre-check + +#region growell +Get-ChildItem env: +#endregion growell + +#region Setup + +$proxyConfig = if ($IgnoreProxy -or -not $ProxyUrl) { + @{} +} else { + $config = @{ + ProxyUrl = $ProxyUrl + } + + if ($ProxyCredential) { + $config['ProxyCredential'] = $ProxyCredential + } elseif ($env:chocolateyProxyUser -and $env:chocolateyProxyPassword) { + $securePass = ConvertTo-SecureString $env:chocolateyProxyPassword -AsPlainText -Force + $config['ProxyCredential'] = [System.Management.Automation.PSCredential]::new($env:chocolateyProxyUser, $securePass) + } + + $config +} + +# 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) as that is the minimum required by Chocolatey.org. + # Use integers because the enumeration value for TLS 1.2 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). + Write-Host "Forcing web requests to allow TLS v1.2 (Required for requests to Chocolatey.org)" + [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072 +} +catch { + $errorMessage = @( + 'Unable to set PowerShell to use TLS 1.2. This is required for contacting Chocolatey as of 03 FEB 2020.' + 'https://blog.chocolatey.org/2020/01/remove-support-for-old-tls-versions/.' + '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) Call [System.Net.ServicePointManager]::SecurityProtocol = 3072; in PowerShell prior to attempting installation,' + '(3) specify internal Chocolatey package location (set $env:chocolateyDownloadUrl prior to install or host the package internally),' + '(4) use the Download + PowerShell method of install.' + 'See https://docs.chocolatey.org/en-us/choco/setup for all install options.' + ) -join [Environment]::NewLine + Write-Warning $errorMessage +} + +if ($ChocolateyDownloadUrl) { + if ($ChocolateyVersion) { + Write-Warning "Ignoring -ChocolateyVersion parameter ($ChocolateyVersion) because -ChocolateyDownloadUrl is set." + } + + Write-Host "Downloading Chocolatey from: $ChocolateyDownloadUrl" +} elseif ($ChocolateyVersion) { + Write-Host "Downloading specific version of Chocolatey: $ChocolateyVersion" + $ChocolateyDownloadUrl = "https://community.chocolatey.org/api/v2/package/chocolatey/$ChocolateyVersion" +} else { + Write-Host "Getting latest version of the Chocolatey package for download." + $queryString = [uri]::EscapeUriString("((Id eq 'chocolatey') and (not IsPrerelease)) and IsLatestVersion") + $queryUrl = 'https://community.chocolatey.org/api/v2/Packages()?$filter={0}' -f $queryString + + [xml]$result = Request-String -Url $queryUrl -ProxyConfiguration $proxyConfig + $ChocolateyDownloadUrl = $result.feed.entry.content.src +} + +if (-not $env:TEMP) { + $env:TEMP = Join-Path $env:SystemDrive -ChildPath 'temp' +} + +$chocoTempDir = Join-Path $env:TEMP -ChildPath "chocolatey" +$tempDir = Join-Path $chocoTempDir -ChildPath "chocoInstall" + +if (-not (Test-Path $tempDir -PathType Container)) { + $null = New-Item -Path $tempDir -ItemType Directory +} + +#endregion Setup + +#region Download & Extract Chocolatey + +$file = Join-Path $tempDir "chocolatey.zip" + +# If we are passed a valid local path, we do not need to download it. +if (Test-Path $ChocolateyDownloadUrl) { + Write-Host "Using Chocolatey from $ChocolateyDownloadUrl." + Copy-Item -Path $ChocolateyDownloadUrl -Destination $file +} else { + Write-Host "Getting Chocolatey from $ChocolateyDownloadUrl." + Request-File -Url $ChocolateyDownloadUrl -File $file -ProxyConfiguration $proxyConfig +} + +Write-Host "Extracting $file to $tempDir" +if ($PSVersionTable.PSVersion.Major -lt 5) { + # Determine unzipping method + # 7zip is the most compatible pre-PSv5.1 so use it unless asked to use builtin + if ($UseNativeUnzip) { + Write-Host 'Using built-in compression to unzip' + + try { + $shellApplication = New-Object -ComObject Shell.Application + $zipPackage = $shellApplication.NameSpace($file) + $destinationFolder = $shellApplication.NameSpace($tempDir) + $destinationFolder.CopyHere($zipPackage.Items(), 0x10) + } catch { + Write-Warning "Unable to unzip package using built-in compression. Set `$env:chocolateyUseWindowsCompression = ''` or omit -UseNativeUnzip and retry to use 7zip to unzip." + throw $_ + } + } else { + $7zaExe = Join-Path $tempDir -ChildPath '7za.exe' + Install-7zip -Path $7zaExe -ProxyConfiguration $proxyConfig + + $params = 'x -o"{0}" -bd -y "{1}"' -f $tempDir, $file + + # use more robust Process as compared to Start-Process -Wait (which doesn't + # wait for the process to finish in PowerShell v3) + $process = New-Object System.Diagnostics.Process + + try { + $process.StartInfo = New-Object System.Diagnostics.ProcessStartInfo -ArgumentList $7zaExe, $params + $process.StartInfo.RedirectStandardOutput = $true + $process.StartInfo.UseShellExecute = $false + $process.StartInfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden + + $null = $process.Start() + $process.BeginOutputReadLine() + $process.WaitForExit() + + $exitCode = $process.ExitCode + } + finally { + $process.Dispose() + } + + $errorMessage = "Unable to unzip package using 7zip. Perhaps try setting `$env:chocolateyUseWindowsCompression = 'true' and call install again. Error:" + if ($exitCode -ne 0) { + $errorDetails = switch ($exitCode) { + 1 { "Some files could not be extracted" } + 2 { "7-Zip encountered a fatal error while extracting the files" } + 7 { "7-Zip command line error" } + 8 { "7-Zip out of memory" } + 255 { "Extraction cancelled by the user" } + default { "7-Zip signalled an unknown error (code $exitCode)" } + } + + throw ($errorMessage, $errorDetails -join [Environment]::NewLine) + } + } +} else { + Microsoft.PowerShell.Archive\Expand-Archive -Path $file -DestinationPath $tempDir -Force +} + +#endregion Download & Extract Chocolatey + +#region Install Chocolatey + +Write-Host "Installing Chocolatey on the local machine" +$toolsFolder = Join-Path $tempDir -ChildPath "tools" +$chocoInstallPS1 = Join-Path $toolsFolder -ChildPath "chocolateyInstall.ps1" + +& $chocoInstallPS1 + +Write-Host 'Ensuring Chocolatey commands are on the path' +$chocoInstallVariableName = "ChocolateyInstall" +$chocoPath = [Environment]::GetEnvironmentVariable($chocoInstallVariableName) + +if (-not $chocoPath) { + $chocoPath = "$env:ALLUSERSPROFILE\Chocolatey" +} + +if (-not (Test-Path ($chocoPath))) { + $chocoPath = "$env:PROGRAMDATA\chocolatey" +} + +$chocoExePath = Join-Path $chocoPath -ChildPath 'bin' + +# Update current process PATH environment variable if it needs updating. +if ($env:Path -notlike "*$chocoExePath*") { + $env:Path = [Environment]::GetEnvironmentVariable('Path', [System.EnvironmentVariableTarget]::Machine); +} + +Write-Host 'Ensuring chocolatey.nupkg is in the lib folder' +$chocoPkgDir = Join-Path $chocoPath -ChildPath 'lib\chocolatey' +$nupkg = Join-Path $chocoPkgDir -ChildPath 'chocolatey.nupkg' + +if (-not (Test-Path $chocoPkgDir -PathType Container)) { + $null = New-Item -ItemType Directory -Path $chocoPkgDir +} + +Copy-Item -Path $file -Destination $nupkg -Force -ErrorAction SilentlyContinue + +#endregion Install Chocolatey