From cc4cc5cc1a536b582c1eb4c7f02bb67929d4f087 Mon Sep 17 00:00:00 2001 From: Rain Sallow Date: Tue, 21 Feb 2023 14:01:45 -0500 Subject: [PATCH] (#3255) Rewrite chocolateysetup.psm1 This script has barely been touched in the last decade, in part because it's difficult to follow. Gave it a much needed revision to remove unnecessary clutter, make its code patterns more consistent, and be much more sensible in how it declares and calls commands, so that we can be much more confident that things are working how we expect them to. This should help make it much easier to make changes to going forward, should we ever need to. --- .../chocolatey/tools/chocolateysetup.psm1 | 969 ++++++++++-------- 1 file changed, 567 insertions(+), 402 deletions(-) diff --git a/nuspec/chocolatey/chocolatey/tools/chocolateysetup.psm1 b/nuspec/chocolatey/chocolatey/tools/chocolateysetup.psm1 index 66c25928b5..b396290caa 100644 --- a/nuspec/chocolatey/chocolatey/tools/chocolateysetup.psm1 +++ b/nuspec/chocolatey/chocolatey/tools/chocolateysetup.psm1 @@ -1,96 +1,121 @@ -$thisScriptFolder = (Split-Path -Parent $MyInvocation.MyCommand.Definition) -$chocoInstallVariableName = "ChocolateyInstall" -$sysDrive = $env:SystemDrive -$tempDir = $env:TEMP -$defaultChocolateyPathOld = "$sysDrive\Chocolatey" - -$originalForegroundColor = $host.ui.RawUI.ForegroundColor +$script:thisScriptFolder = Split-Path -Parent $MyInvocation.MyCommand.Definition +$script:chocoInstallVariableName = "ChocolateyInstall" +$script:tempDir = $env:TEMP +$script:defaultChocolateyPathOld = "$env:SystemDrive\Chocolatey" +$script:netFx48InstallTries = 0 function Write-ChocolateyWarning { - param ( - [string]$message = '' + [CmdletBinding()] + param( + [Parameter()] + [string] + $Message + ) + + try { + Write-Host "WARNING: $Message" -ForegroundColor Yellow -ErrorAction Stop + } + catch { + Write-Output "WARNING: $Message" + } +} + +function Write-ChocolateyError { + [CmdletBinding()] + param( + [Parameter()] + [string] + $Message ) try { - Write-Host "WARNING: $message" -ForegroundColor "Yellow" -ErrorAction "Stop" + Write-Host "ERROR: $Message" -ForegroundColor Red -ErrorAction Stop } catch { - Write-Output "WARNING: $message" + Write-Output "ERROR: $Message" } } -function Write-ChocolateyError { - param ( - [string]$message = '' +function Write-ChocolateyInfo { + [CmdletBinding()] + param( + [Parameter()] + [string] + $Message ) try { - Write-Host "ERROR: $message" -ForegroundColor "Red" -ErrorAction "Stop" + Write-Host $Message -ErrorAction Stop } catch { - Write-Output "ERROR: $message" + Write-Output $Message } } -function Remove-ShimWithAuthenticodeSignature { - param ( - [string] $filePath +function Remove-SignedShim { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string] + $Path ) - if (!(Test-Path $filePath)) { + + if (-not (Test-Path $Path)) { + Write-ChocolateyDebug "No shim to remove was found at $Path" return } - $signature = Get-AuthenticodeSignature $filePath -ErrorAction SilentlyContinue + $signature = Get-AuthenticodeSignature $Path -ErrorAction SilentlyContinue - if (!$signature -or !$signature.SignerCertificate) { - Write-ChocolateyWarning "Shim found in $filePath, but was not signed. Ignoring removal..." + if (-not $signature -or -not $signature.SignerCertificate) { + Write-ChocolateyWarning "Shim found at $Path, but will not be removed as it is unsigned" return } $possibleSignatures = @( 'RealDimensions Software, LLC' 'Chocolatey Software, Inc\.' - ) + ) -join '|' - $possibleSignatures | ForEach-Object { - if ($signature.SignerCertificate.Subject -match "$_") { - Write-Output "Removing shim $filePath" - $null = Remove-Item "$filePath" + if ($signature.SignerCertificate.Subject -notmatch $possibleSignatures) { + # This means the file was found, however did not get removed as it contained a authenticode signature that + # is not ours. + Write-ChocolateyWarning "Shim found in $Path, but will not be removed as it has an unexpected signature" + return + } - if (Test-Path "$filePath.ignore") { - $null = Remove-Item "$filePath.ignore" - } + Write-ChocolateyInfo "Removing shim $Path" + $null = Remove-Item -Path $Path - if (Test-Path "$filePath.old") { - $null = Remove-Item "$filePath.old" - } + foreach ($file in "$Path.ignore", "$Path.old") { + if (Test-Path $file) { + $null = Remove-Item -Path $file } } - - # This means the file was found, however did not get removed as it contained a authenticode signature that - # is not ours. - if (Test-Path $filePath) { - Write-ChocolateyWarning "Shim found in $filePath, but did not match our signature. Ignoring removal..." - return - } } -function Remove-UnsupportedShimFiles { - param([string[]]$Paths) +function Remove-UnsupportedShims { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string[]] + $Path + ) $shims = @("cpack.exe", "cver.exe", "chocolatey.exe", "cinst.exe", "clist.exe", "cpush.exe", "cuninst.exe", "cup.exe") - $Paths | ForEach-Object { - $path = $_ - $shims | ForEach-Object { Join-Path $path $_ } | Where-Object { Test-Path $_ } | ForEach-Object { - $shimPath = $_ - Write-Debug "Removing shim from '$shimPath'." + foreach ($item in $Path) { + foreach ($exe in $shims) { + $shimPath = Join-Path -Path $item -ChildPath $exe - try { - Remove-ShimWithAuthenticodeSignature -filePath $shimPath - } - catch { - Write-ChocolateyWarning "Unable to remove '$shimPath'. Please remove the file manually." + if (Test-Path $shimPath) { + Write-Debug "Attempting to remove shim from '$shimPath'." + try { + Remove-SignedShim -Path $shimPath + } + catch { + Write-ChocolateyWarning "Unable to remove '$shimPath'. Please remove the file manually." + } } } } @@ -98,39 +123,48 @@ function Remove-UnsupportedShimFiles { function Initialize-Chocolatey { <# - .DESCRIPTION + .DESCRIPTION This will initialize the Chocolatey tool by - a) setting up the "chocolateyPath" (the location where all chocolatey nuget packages will be installed) - b) Installs chocolatey into the "chocolateyPath" - c) Installs .net 4.8 if needed - d) Adds Chocolatey to the PATH environment variable so you have access to the choco commands. - .PARAMETER ChocolateyPath - Allows you to override the default path of (C:\ProgramData\chocolatey\) by specifying a directory chocolatey will install nuget packages. - - .EXAMPLE + a) setting up the "chocolateyPath" (the location where all chocolatey nuget packages will be installed) + b) Installs chocolatey into the "chocolateyPath" + c) Installs .net 4.8 if needed + d) Adds Chocolatey to the PATH environment variable so you have access to the choco commands. + .PARAMETER Path + Allows you to override the default chocolateyPath of (C:\ProgramData\chocolatey\) by specifying a directory to install Chocolatey into. + + .EXAMPLE C:\PS> Initialize-Chocolatey Installs chocolatey into the default C:\ProgramData\Chocolatey\ directory. - .EXAMPLE - C:\PS> Initialize-Chocolatey -chocolateyPath "D:\ChocolateyInstalledNuGets\" + .EXAMPLE + C:\PS> Initialize-Chocolatey -Path "D:\ChocolateyInstalledNuGets\" Installs chocolatey into the custom directory D:\ChocolateyInstalledNuGets\ #> + [CmdletBinding()] param( - [Parameter(Mandatory = $false)][string]$chocolateyPath = '' + # The path to install/initialize Chocolatey CLI into. + [Parameter()] + [string] + $Path ) + Write-Debug "Initialize-Chocolatey" - $installModule = Join-Path $thisScriptFolder 'chocolateyInstall\helpers\chocolateyInstaller.psm1' + # Import the chocolateyInstaller module to load helper functions + $installModule = Join-Path -Path $script:thisScriptFolder -ChildPath 'chocolateyInstall\helpers\chocolateyInstaller.psm1' Import-Module $installModule -Force - Install-DotNet48IfMissing + Install-DotNet48IfMissing - if ($chocolateyPath -eq '') { + $chocolateyPath = if ($Path) { + $Path + } + else { $programData = [Environment]::GetFolderPath("CommonApplicationData") - $chocolateyPath = Join-Path "$programData" 'chocolatey' + Join-Path -Path $programData -ChildPath 'chocolatey' } # variable to allow insecure directory: @@ -139,155 +173,206 @@ function Initialize-Chocolatey { $allowInsecureRootInstall = $true } - # if we have an already environment variable path, use it. - $alreadyInitializedNugetPath = Get-ChocolateyInstallFolder - if ($alreadyInitializedNugetPath -and $alreadyInitializedNugetPath -ne $chocolateyPath -and ($allowInsecureRootInstall -or $alreadyInitializedNugetPath -ne $defaultChocolateyPathOld)) { + # if we have an already set environment variable path, use it. + $alreadyInitializedNugetPath = Get-ChocolateyInstallPath + $useExistingChocolateyPath = $alreadyInitializedNugetPath -and + $alreadyInitializedNugetPath -ne $chocolateyPath -and + ($allowInsecureRootInstall -or $alreadyInitializedNugetPath -ne $script:defaultChocolateyPathOld) + + if ($useExistingChocolateyPath) { $chocolateyPath = $alreadyInitializedNugetPath } else { - Set-ChocolateyInstallFolder $chocolateyPath + Set-EnvChocolateyInstall -Path $chocolateyPath } - Create-DirectoryIfNotExists $chocolateyPath - Ensure-Permissions $chocolateyPath - #set up variables to add - $chocolateyExePath = Join-Path $chocolateyPath 'bin' - $chocolateyLibPath = Join-Path $chocolateyPath 'lib' + Initialize-Directory -Path $chocolateyPath + Set-Permissions -Path $chocolateyPath + + $chocolateyExePath = Join-Path -Path $chocolateyPath -ChildPath 'bin' + $chocolateyLibPath = Join-Path -Path $chocolateyPath -ChildPath 'lib' - if ($tempDir -eq $null) { - $tempDir = Join-Path $chocolateyPath 'temp' - Create-DirectoryIfNotExists $tempDir + if (-not $script:tempDir) { + $script:tempDir = Join-Path -Path $chocolateyPath -ChildPath 'temp' + Initialize-Directory -Path $script:tempDir } - $yourPkgPath = [System.IO.Path]::Combine($chocolateyLibPath, "yourPackageName") - @" + $yourPkgPath = -Path $chocolateyLibPath -ChildPath "yourPackageName" + Write-ChocolateyInfo @" We are setting up the Chocolatey package repository. -The packages themselves go to `'$chocolateyLibPath`' +The packages themselves go to '$chocolateyLibPath' (i.e. $yourPkgPath). -A shim file for the command line goes to `'$chocolateyExePath`' - and points to an executable in `'$yourPkgPath`'. +A shim file for the command line goes to '$chocolateyExePath' + and points to an executable in '$yourPkgPath'. Creating Chocolatey folders if they do not already exist. -"@ | Write-Output +"@ - #create the base structure if it doesn't exist - Create-DirectoryIfNotExists $chocolateyExePath - Create-DirectoryIfNotExists $chocolateyLibPath + # create the base structure if it doesn't exist + $chocolateyExePath, $chocolateyLibPath | Initialize-Directory $possibleShimPaths = @( - Join-Path "$chocolateyPath" "redirects" - Join-Path "$thisScriptFolder" "chocolateyInstall\redirects" + Join-Path -Path $chocolateyPath -ChildPath "redirects" + Join-Path -Path $script:thisScriptFolder -ChildPath "chocolateyInstall\redirects" ) - Remove-UnsupportedShimFiles -Paths $possibleShimPaths + Remove-UnsupportedShims -Path $possibleShimPaths - Install-ChocolateyFiles $chocolateyPath - Ensure-ChocolateyLibFiles $chocolateyLibPath + Install-ChocolateyFiles -Path $chocolateyPath + Initialize-ChocolateyLibFolder -Path $chocolateyLibPath - Install-ChocolateyBinFiles $chocolateyPath $chocolateyExePath + Install-ChocolateyBinFiles -Path $chocolateyExePath -ChocolateyPath $chocolateyPath - $chocolateyExePathVariable = $chocolateyExePath.ToLower().Replace($chocolateyPath.ToLower(), "%DIR%..\").Replace("\\", "\") - Initialize-ChocolateyPath $chocolateyExePath $chocolateyExePathVariable - Process-ChocolateyBinFiles $chocolateyExePath $chocolateyExePathVariable + Add-ChocolateyToPath -Path $chocolateyExePath + Edit-ChocolateyBinFiles -Path $chocolateyExePath - $realModule = Join-Path $chocolateyPath "helpers\chocolateyInstaller.psm1" - Import-Module "$realModule" -Force + # Reload the chocolateyInstaller module from the actual choco install path + $realModule = Join-Path -Path $chocolateyPath -VChildPath "helpers\chocolateyInstaller.psm1" + Import-Module $realModule -Force - if (-not $allowInsecureRootInstall -and (Test-Path($defaultChocolateyPathOld))) { - Upgrade-OldChocolateyInstall $defaultChocolateyPathOld $chocolateyPath - Install-ChocolateyBinFiles $chocolateyPath $chocolateyExePath + if (-not $allowInsecureRootInstall -and (Test-Path $script:defaultChocolateyPathOld)) { + Move-OldChocolateyInstall -Path $script:defaultChocolateyPathOld -Destination $chocolateyPath + Install-ChocolateyBinFiles -Path $chocolateyExePath -ChocolateyPath $chocolateyPath } Add-ChocolateyProfile - Invoke-Chocolatey-Initial - if ($env:ChocolateyExitCode -eq $null -or $env:ChocolateyExitCode -eq '') { + Invoke-Chocolatey + if ([string]::IsNullOrEmpty($env:ChocolateyExitCode)) { $env:ChocolateyExitCode = 0 } - @" -Chocolatey (choco.exe) is now ready. + Write-ChocolateyInfo @" +Chocolatey CLI (choco.exe) is now ready. You can call choco from anywhere, command line or powershell by typing choco. -Run choco /? for a list of functions. +Run choco --help for a list of functions. You may need to shut down and restart powershell and/or consoles first prior to using choco. -"@ | Write-Output +"@ if (-not $allowInsecureRootInstall) { - Remove-OldChocolateyInstall $defaultChocolateyPathOld + Remove-OldChocolateyInstall -Path $script:defaultChocolateyPathOld } - Remove-UnsupportedShimFiles -Paths $chocolateyExePath + Remove-UnsupportedShims -Path $chocolateyExePath } -function Set-ChocolateyInstallFolder { +function Set-EnvChocolateyInstall { + [CmdletBinding()] param( - [string]$folder + [Parameter()] + [string] + $Path ) - Write-Debug "Set-ChocolateyInstallFolder" + + Write-Debug "Set-ChocolateyInstallPath" $environmentTarget = [System.EnvironmentVariableTarget]::User - # removing old variable - Install-ChocolateyEnvironmentVariable -variableName "$chocoInstallVariableName" -variableValue $null -variableType $environmentTarget + + # Remove old user-scope variable + Install-ChocolateyEnvironmentVariable -variableName $script:chocoInstallVariableName -variableValue $null -variableType $environmentTarget if (Test-ProcessAdminRights) { Write-Debug "Administrator installing so using Machine environment variable target instead of User." $environmentTarget = [System.EnvironmentVariableTarget]::Machine - # removing old variable - Install-ChocolateyEnvironmentVariable -variableName "$chocoInstallVariableName" -variableValue $null -variableType $environmentTarget + + # Remove old machine-scope variable + Install-ChocolateyEnvironmentVariable -variableName $script:chocoInstallVariableName -variableValue $null -variableType $environmentTarget } else { - Write-ChocolateyWarning "Setting ChocolateyInstall Environment Variable on USER and not SYSTEM variables.`n This is due to either non-administrator install OR the process you are running is not being run as an Administrator." + Write-ChocolateyWarning "Setting ChocolateyInstall Environment Variable on USER and not SYSTEM variables. This is due to either non-administrator install OR the process you are running is not being run as an Administrator." } - Write-Output "Creating $chocoInstallVariableName as an environment variable (targeting `'$environmentTarget`') `n Setting $chocoInstallVariableName to `'$folder`'" - Write-ChocolateyWarning "It's very likely you will need to close and reopen your shell `n before you can use choco." - Install-ChocolateyEnvironmentVariable -variableName "$chocoInstallVariableName" -variableValue "$folder" -variableType $environmentTarget + Write-ChocolateyInfo "Creating $script:chocoInstallVariableName as an environment variable (targeting '$environmentTarget') `n Setting $script:chocoInstallVariableName to '$Path'" + Write-ChocolateyWarning "It's very likely you will need to close and reopen your shell before you can use choco." + Install-ChocolateyEnvironmentVariable -variableName $script:chocoInstallVariableName -variableValue $Path -variableType $environmentTarget } -function Get-ChocolateyInstallFolder() { - Write-Debug "Get-ChocolateyInstallFolder" - [Environment]::GetEnvironmentVariable($chocoInstallVariableName) +function Get-ChocolateyInstallPath { + Write-Debug "Get-ChocolateyInstallPath" + [Environment]::GetEnvironmentVariable($script:chocoInstallVariableName) } -function Create-DirectoryIfNotExists($folderName) { - Write-Debug "Create-DirectoryIfNotExists" - if (![System.IO.Directory]::Exists($folderName)) { - [System.IO.Directory]::CreateDirectory($folderName) | Out-Null +function Initialize-Directory { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [string] + $Path + ) + process { + Write-Debug "Initialize-Directory" + if (-not (Test-Path $Path)) { + $null = New-Item -Path $Path -ItemType Directory + } } } function Get-LocalizedWellKnownPrincipalName { - param ( + [CmdletBinding()] + param( [Parameter(Mandatory = $true)] - [Security.Principal.WellKnownSidType] $WellKnownSidType + [Security.Principal.WellKnownSidType] + $WellKnownSidType ) + $sid = New-Object -TypeName 'System.Security.Principal.SecurityIdentifier' -ArgumentList @($WellKnownSidType, $null) $account = $sid.Translate([Security.Principal.NTAccount]) return $account.Value } -function Ensure-Permissions { +function Set-AccessRule { + [CmdletBinding()] param( - [string]$folder + [Parameter(Mandatory = $true)] + $Principal, + + [Parameter(Mandatory = $true)] + [System.Security.AccessControl.FileSystemRights] + $Rights, + + [Parameter(Mandatory = $true)] + [System.Security.AccessControl.FileSystemSecurity] + $Acl, + + [Parameter()] + [System.Security.AccessControl.InheritanceFlags] + $InheritanceFlags = [Security.AccessControl.InheritanceFlags]::ContainerInherit -bor [Security.AccessControl.InheritanceFlags]::ObjectInherit, + + [Parameter()] + [System.Security.AccessControl.PropagationFlags] + $PropagationFlags = [Security.AccessControl.PropagationFlags]::None + ) + + $rule = New-Object -TypeName 'System.Security.AccessControl.FileSystemAccessRule' -ArgumentList @($Principal, $Rights, $InheritanceFlags, $PropagationFlags, "Allow") + $Acl.SetAccessRule($rule) +} + +function Set-Permissions { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string] + $Path ) - Write-Debug "Ensure-Permissions" + + Write-Debug "Set-Permissions" $defaultInstallPath = "$env:SystemDrive\ProgramData\chocolatey" try { - $defaultInstallPath = Join-Path ([Environment]::GetFolderPath("CommonApplicationData")) 'chocolatey' + $defaultInstallPath = Join-Path -Path ([Environment]::GetFolderPath("CommonApplicationData")) -ChildPath 'chocolatey' } catch { # keep first setting } - if ($folder.ToLower() -ne $defaultInstallPath.ToLower()) { + if ($Path -ne $defaultInstallPath) { Write-ChocolateyWarning "Installation folder is not the default. Not changing permissions. Please ensure your installation is secure." return } # Everything from here on out applies to the default installation folder - if (!(Test-ProcessAdminRights)) { + if (-not (Test-ProcessAdminRights)) { throw "Installation of Chocolatey to default folder requires Administrative permissions. Please run from elevated prompt. Please see https://chocolatey.org/install for details and alternatives if needing to install as a non-administrator." } @@ -295,29 +380,24 @@ function Ensure-Permissions { $ErrorActionPreference = 'Stop' try { # get current acl - $acl = Get-Acl $folder + $acl = Get-Acl -Path $Path Write-Debug "Removing existing permissions." $acl.Access | ForEach-Object { $acl.RemoveAccessRuleAll($_) } - $inheritanceFlags = ([Security.AccessControl.InheritanceFlags]::ContainerInherit -bor [Security.AccessControl.InheritanceFlags]::ObjectInherit) - $propagationFlags = [Security.AccessControl.PropagationFlags]::None $rightsFullControl = [Security.AccessControl.FileSystemRights]::FullControl $rightsModify = [Security.AccessControl.FileSystemRights]::Modify - $rightsReadExecute = [Security.AccessControl.FileSystemRights]::ReadAndExecute - $rightsWrite = [Security.AccessControl.FileSystemRights]::Write - Write-Output "Restricting write permissions to Administrators" + Write-ChocolateyInfo "Restricting write permissions to Administrators" + $builtinAdmins = Get-LocalizedWellKnownPrincipalName -WellKnownSidType ([Security.Principal.WellKnownSidType]::BuiltinAdministratorsSid) - $adminsAccessRule = New-Object System.Security.AccessControl.FileSystemAccessRule($builtinAdmins, $rightsFullControl, $inheritanceFlags, $propagationFlags, "Allow") - $acl.SetAccessRule($adminsAccessRule) $localSystem = Get-LocalizedWellKnownPrincipalName -WellKnownSidType ([Security.Principal.WellKnownSidType]::LocalSystemSid) - $localSystemAccessRule = New-Object System.Security.AccessControl.FileSystemAccessRule($localSystem, $rightsFullControl, $inheritanceFlags, $propagationFlags, "Allow") - $acl.SetAccessRule($localSystemAccessRule) $builtinUsers = Get-LocalizedWellKnownPrincipalName -WellKnownSidType ([Security.Principal.WellKnownSidType]::BuiltinUsersSid) - $usersAccessRule = New-Object System.Security.AccessControl.FileSystemAccessRule($builtinUsers, $rightsReadExecute, $inheritanceFlags, $propagationFlags, "Allow") - $acl.SetAccessRule($usersAccessRule) + + Set-AccessRule -Principal $builtinAdmins -Rights $rightsFullControl -Acl $acl + Set-AccessRule -Principal $localSystem -Rights $rightsFullControl -Acl $acl + Set-AccessRule -Principal $builtinUsers -Rights $rightsFullControl -Acl $acl $allowCurrentUser = $env:ChocolateyInstallAllowCurrentUser -eq 'true' if ($allowCurrentUser) { @@ -325,9 +405,8 @@ function Ensure-Permissions { $currentUser = [Security.Principal.WindowsIdentity]::GetCurrent() if ($currentUser.Name -ne $localSystem) { - $userAccessRule = New-Object System.Security.AccessControl.FileSystemAccessRule($currentUser.Name, $rightsModify, $inheritanceFlags, $propagationFlags, "Allow") Write-ChocolateyWarning 'Adding Modify permission for current user due to $env:ChocolateyInstallAllowCurrentUser. This could lead to escalation of privilege attacks. Consider not allowing this.' - $acl.SetAccessRule($userAccessRule) + Set-AccessRule -Principal $currentUser.Name -Rights $rightsModify -Acl $acl } } else { @@ -342,74 +421,101 @@ function Ensure-Permissions { $acl.SetAccessRuleProtection($true, $false) # enact the changes against the actual - Set-Acl -Path $folder -AclObject $acl - - # set an explicit append permission on the logs folder - Write-Debug "Allow users to append to log files." - $logsFolder = "$folder\logs" - Create-DirectoryIfNotExists $logsFolder - $logsAcl = Get-Acl $logsFolder - $usersAppendAccessRule = New-Object System.Security.AccessControl.FileSystemAccessRule($builtinUsers, $rightsWrite, [Security.AccessControl.InheritanceFlags]::ObjectInherit, [Security.AccessControl.PropagationFlags]::InheritOnly, "Allow") - $logsAcl.SetAccessRule($usersAppendAccessRule) - $logsAcl.SetAccessRuleProtection($false, $true) - Set-Acl -Path $logsFolder -AclObject $logsAcl + Set-Acl -Path $Path -AclObject $acl + + Initialize-LogDirectory -Path "$Path\logs" } catch { - Write-ChocolateyWarning "Not able to set permissions for $folder." + Write-ChocolateyWarning "Not able to set permissions for $Path." } $ErrorActionPreference = $currentEA } -function Upgrade-OldChocolateyInstall { +function Initialize-LogDirectory { + [CmdletBinding()] param( - [string]$chocolateyPathOld = "$sysDrive\Chocolatey", - [string]$chocolateyPath = "$($env:ALLUSERSPROFILE)\chocolatey" + [Parameter(Mandatory = $true)] + [string] + $Path ) - Write-Debug "Upgrade-OldChocolateyInstall" + Write-Debug 'Initialize-LogDirectory' - if (Test-Path $chocolateyPathOld) { - Write-Output "Attempting to upgrade `'$chocolateyPathOld`' to `'$chocolateyPath`'." - Write-ChocolateyWarning "Copying the contents of `'$chocolateyPathOld`' to `'$chocolateyPath`'. `n This step may fail if you have anything in this folder running or locked." - Write-Output 'If it fails, just manually copy the rest of the items out and then delete the folder.' - Write-ChocolateyWarning "!!!! ATTN: YOU WILL NEED TO CLOSE AND REOPEN YOUR SHELL !!!!" - #-ForegroundColor Magenta -BackgroundColor Black + # set an explicit append permission on the logs folder + Write-Debug "Granting users Append permission for log files." + Initialize-Directory -Path $Path - $chocolateyExePathOld = Join-Path $chocolateyPathOld 'bin' - 'Machine', 'User' | - ForEach-Object { - $path = Get-EnvironmentVariable -Name 'PATH' -Scope $_ - $updatedPath = [System.Text.RegularExpressions.Regex]::Replace($path, [System.Text.RegularExpressions.Regex]::Escape($chocolateyExePathOld) + '(?>;)?', '', [System.Text.RegularExpressions.RegexOptions]::IgnoreCase) - if ($updatedPath -ne $path) { - Write-Output "Updating `'$_`' PATH to reflect removal of '$chocolateyPathOld'." - try { - Set-EnvironmentVariable -Name 'Path' -Value $updatedPath -Scope $_ -ErrorAction Stop - } - catch { - Write-ChocolateyWarning "Was not able to remove the old environment variable from PATH. You will need to do this manually" - } + $logsAcl = Get-Acl -Path $Path + $rightsWrite = [Security.AccessControl.FileSystemRights]::Write + + Set-AccessRule -Principal $builtinUsers -Rights $rightsWrite -InheritanceFlags ObjectInherit -PropagationFlags InheritOnly -Acl $logsAcl + $logsAcl.SetAccessRuleProtection($false, $true) + Set-Acl -Path $Path -AclObject $logsAcl +} + +function Move-OldChocolateyInstall { + [CmdletBinding()] + param( + [Parameter()] + [string] + $Path = "$env:SystemDrive\Chocolatey", + + [Parameter()] + [string] + $Destination = "$($env:ALLUSERSPROFILE)\chocolatey" + ) + + Write-Debug "Move-OldChocolateyInstall" + + if (Test-Path $Path) { + Write-ChocolateyInfo "Attempting to upgrade `'$Path`' to `'$Destination`'." + Write-ChocolateyWarning "Copying the contents of `'$Path`' to `'$Destination`'.`n This step may fail if you have anything in this folder running or locked." + Write-ChocolateyInfo 'If it fails, just manually copy the rest of the items out and then delete the folder.' + Write-ChocolateyWarning "NOTE: YOU WILL NEED TO CLOSE AND REOPEN YOUR SHELL" + + $chocolateyExePathOld = Join-Path -Path $Path -ChildPath 'bin' + + foreach ($scope in 'Machine', 'User') { + $envPath = Get-EnvironmentVariable -Name 'PATH' -Scope $scope + + # TODO: use -replace once we have at least PSv3 as a minimum + $updatedPath = [System.Text.RegularExpressions.Regex]::Replace( + $envPath, + [System.Text.RegularExpressions.Regex]::Escape($chocolateyExePathOld) + '(?>;)?', + [string]::Empty, + [System.Text.RegularExpressions.RegexOptions]::IgnoreCase + ) + + if ($updatedPath -ne $envPath) { + Write-ChocolateyInfo "Updating '$scope' PATH to reflect removal of '$Path'." + try { + Set-EnvironmentVariable -Name 'Path' -Value $updatedPath -Scope $_ -ErrorAction Stop + } + catch { + Write-ChocolateyWarning "Was not able to remove the old environment variable from PATH. You will need to do this manually" } } + } + + Copy-Item -Path "$Path\lib\*" -Destination "$Destination\lib" -Force -Recurse - Copy-Item "$chocolateyPathOld\lib\*" "$chocolateyPath\lib" -Force -Recurse + $from = "$Path\bin" + $to = "$Destination\bin" - $from = "$chocolateyPathOld\bin" - $to = "$chocolateyPath\bin" - # TODO: This exclusion list needs to be updated once shims are removed $exclude = @("choco.exe", "RefreshEnv.cmd") Get-ChildItem -Path $from -Recurse -Exclude $exclude | ForEach-Object { Write-Debug "Copying $_ `n to $to" if ($_.PSIsContainer) { - Copy-Item $_ -Destination (Join-Path $to $_.Parent.FullName.Substring($from.length)) -Force -ErrorAction SilentlyContinue + Copy-Item $_ -Destination (Join-Path -Path $to -ChildPath $_.Parent.FullName.Substring($from.length)) -Force -ErrorAction SilentlyContinue } else { - $fileToMove = (Join-Path $to $_.FullName.Substring($from.length)) + $fileToMove = (Join-Path -Path $to -ChildPath $_.FullName.Substring($from.length)) try { - Copy-Item $_ -Destination $fileToMove -Exclude $exclude -Force -ErrorAction Stop + Copy-Item -Path $_ -Destination $fileToMove -Exclude $exclude -Force -ErrorAction Stop } catch { - Write-ChocolateyWarning "Was not able to move `'$fileToMove`'. You may need to reinstall the shim" + Write-ChocolateyWarning "Was not able to move '$fileToMove'. You may need to reinstall the shim" } } } @@ -417,188 +523,227 @@ function Upgrade-OldChocolateyInstall { } function Remove-OldChocolateyInstall { + [CmdletBinding()] param( - [string]$chocolateyPathOld = "$sysDrive\Chocolatey" + [Parameter()] + [string] + $Path = "$env:SystemDrive\Chocolatey" ) + Write-Debug "Remove-OldChocolateyInstall" - if (Test-Path $chocolateyPathOld) { - Write-ChocolateyWarning "This action will result in Log Errors, you can safely ignore those. `n You may need to finish removing '$chocolateyPathOld' manually." + if (Test-Path $Path) { + Write-ChocolateyWarning "This action will result in Log Errors, you can safely ignore those.`n You may need to finish removing '$Path' manually." try { - Get-ChildItem -Path "$chocolateyPathOld" | ForEach-Object { + Get-ChildItem -Path $Path | ForEach-Object { if (Test-Path $_.FullName) { Write-Debug "Removing $_ unless matches .log" - Remove-Item $_.FullName -Exclude *.log -Recurse -Force -ErrorAction SilentlyContinue + Remove-Item -Path $_.FullName -Exclude *.log -Recurse -Force -ErrorAction SilentlyContinue } } - Write-Output "Attempting to remove `'$chocolateyPathOld`'. This may fail if something in the folder is being used or locked." - Remove-Item "$($chocolateyPathOld)" -Force -Recurse -ErrorAction Stop + Write-ChocolateyInfo "Attempting to remove '$Path'. This may fail if something in the folder is being used or locked." + Remove-Item -Path $Path -Force -Recurse -ErrorAction Stop } catch { - Write-ChocolateyWarning "Was not able to remove `'$chocolateyPathOld`'. You will need to manually remove it." + Write-ChocolateyWarning "Was not able to remove '$Path'. You will need to manually remove it." } } } function Install-ChocolateyFiles { + [CmdletBinding()] param( - [string]$chocolateyPath + [Parameter(Mandatory = $true)] + [string] + $Path ) + Write-Debug "Install-ChocolateyFiles" Write-Debug "Removing install files in chocolateyInstall, helpers, redirects, and tools" - "$chocolateyPath\chocolateyInstall", "$chocolateyPath\helpers", "$chocolateyPath\redirects", "$chocolateyPath\tools" | ForEach-Object { - #Write-Debug "Checking path $_" - if (Test-Path $_) { - Get-ChildItem -Path "$_" | ForEach-Object { - #Write-Debug "Checking child path $_ ($($_.FullName))" - if (Test-Path $_.FullName) { - Write-Debug "Removing $_ unless matches .log" - Remove-Item $_.FullName -Exclude *.log -Recurse -Force -ErrorAction SilentlyContinue - } - } + "$Path\chocolateyInstall", "$Path\helpers", "$Path\redirects", "$Path\tools" | + Where-Object { Test-Path $_ } | + Get-ChildItem | + ForEach-Object { + Write-Debug "Removing '$_' and children except those matching *.log" + Remove-Item -Path $_.FullName -Exclude *.log -Recurse -Force -ErrorAction SilentlyContinue } - } - Write-Debug "Attempting to move choco.exe to choco.exe.old so we can place the new version here." # rename the currently running process / it will be locked if it exists - $chocoExe = Join-Path $chocolateyPath 'choco.exe' - if (Test-Path ($chocoExe)) { + Write-Debug "Attempting to move choco.exe to choco.exe.old" + $chocoExe = Join-Path $Path 'choco.exe' + if (Test-Path $chocoExe) { Write-Debug "Renaming '$chocoExe' to '$chocoExe.old'" try { - Remove-Item "$chocoExe.old" -Force -ErrorAction SilentlyContinue - Move-Item $chocoExe "$chocoExe.old" -Force -ErrorAction SilentlyContinue + Remove-Item -Path "$chocoExe.old" -Force -ErrorAction SilentlyContinue + Move-Item -Path $chocoExe "$chocoExe.old" -Force -ErrorAction Stop } catch { - Write-ChocolateyWarning "Was not able to rename `'$chocoExe`' to `'$chocoExe.old`'." + Write-ChocolateyWarning "Was not able to rename '$chocoExe' to '$chocoExe.old'." } } # remove pdb file if it is found - $chocoPdb = Join-Path $chocolateyPath 'choco.pdb' - if (Test-Path ($chocoPdb)) { - Remove-Item "$chocoPdb" -Force -ErrorAction SilentlyContinue + $chocoPdb = Join-Path -Path $Path -ChildPath 'choco.pdb' + if (Test-Path $chocoPdb) { + Remove-Item -Path $chocoPdb -Force -ErrorAction SilentlyContinue } Write-Debug "Unpacking files required for Chocolatey." - $chocoInstallFolder = Join-Path $thisScriptFolder "chocolateyInstall" - $chocoExe = Join-Path $chocoInstallFolder 'choco.exe' - $chocoExeDest = Join-Path $chocolateyPath 'choco.exe' - Copy-Item $chocoExe $chocoExeDest -Force + $chocoInstallFolder = Join-Path -Path $script:thisScriptFolder -ChildPath "chocolateyInstall" + $chocoExe = Join-Path -Path $chocoInstallFolder -ChildPath 'choco.exe' + $chocoExeDest = Join-Path -Path $Path -ChildPath 'choco.exe' - Write-Debug "Copying the contents of `'$chocoInstallFolder`' to `'$chocolateyPath`'." - Copy-Item $chocoInstallFolder\* $chocolateyPath -Recurse -Force + Copy-Item -Path $chocoExe -Destination $chocoExeDest -Force + + Write-Debug "Copying the contents of '$chocoInstallFolder' to '$Path'." + Copy-Item -Path "$chocoInstallFolder\*" -Destination $Path -Recurse -Force } -function Ensure-ChocolateyLibFiles { +function Initialize-ChocolateyLibFolder { + [CmdletBinding()] param( - [string]$chocolateyLibPath + [Parameter(Mandatory = $true)] + [string] + $Path ) - Write-Debug "Ensure-ChocolateyLibFiles" - $chocoPkgDirectory = Join-Path $chocolateyLibPath 'chocolatey' - Create-DirectoryIfNotExists $chocoPkgDirectory + Write-Debug "Initialize-ChocolateyLibFolder" + $chocoPkgDirectory = Join-Path -Path $Path -ChildPath 'chocolatey' + + Initialize-Directory -Path $chocoPkgDirectory - if (!(Test-Path("$chocoPkgDirectory\chocolatey.nupkg"))) { - Write-Output "chocolatey.nupkg file not installed in lib.`n Attempting to locate it from bootstrapper." - $chocoZipFile = Join-Path $tempDir "chocolatey\chocoInstall\chocolatey.zip" + if (-not (Test-Path "$chocoPkgDirectory\chocolatey.nupkg")) { + Write-ChocolateyInfo "chocolatey.nupkg file not installed in lib.`n Attempting to locate it from bootstrapper." + $chocoZipFile = Join-Path -Path $script:tempDir -ChildPath "chocolatey\chocoInstall\chocolatey.zip" Write-Debug "First the zip file at '$chocoZipFile'." - Write-Debug "Then from a neighboring chocolatey.*nupkg file '$thisScriptFolder/../../'." + Write-Debug "Then from a neighboring chocolatey.*nupkg file '$script:thisScriptFolder/../../'." - if (Test-Path("$chocoZipFile")) { + if (Test-Path "$chocoZipFile") { Write-Debug "Copying '$chocoZipFile' to '$chocoPkgDirectory\chocolatey.nupkg'." - Copy-Item "$chocoZipFile" "$chocoPkgDirectory\chocolatey.nupkg" -Force -ErrorAction SilentlyContinue + Copy-Item -Path $chocoZipFile -Destination "$chocoPkgDirectory\chocolatey.nupkg" -Force -ErrorAction SilentlyContinue } - if (!(Test-Path("$chocoPkgDirectory\chocolatey.nupkg"))) { - $chocoPkg = Get-ChildItem "$thisScriptFolder/../../" | - Where-Object { $_.name -match "^chocolatey.*nupkg" } | - Sort-Object name -Descending | - Select-Object -First 1 - if ($chocoPkg -ne '') { - $chocoPkg = $chocoPkg.FullName - } - "$chocoZipFile", "$chocoPkg" | ForEach-Object { - if ($_ -ne $null -and $_ -ne '') { - if (Test-Path $_) { - Write-Debug "Copying '$_' to '$chocoPkgDirectory\chocolatey.nupkg'." - Copy-Item $_ "$chocoPkgDirectory\chocolatey.nupkg" -Force -ErrorAction SilentlyContinue - } + if (-not (Test-Path "$chocoPkgDirectory\chocolatey.nupkg")) { + $chocoPkg = Get-ChildItem -Path "$script:thisScriptFolder/../../" | + Where-Object { $_.Name -match "^chocolatey.*nupkg" } | + Sort-Object -Property Name -Descending | + Select-Object -First 1 -ExpandProperty FullName + + $chocoZipFile, $chocoPkg | + Where-Object { $_ -and (Test-Path $_) } | + ForEach-Object { + Write-Debug "Copying '$_' to '$chocoPkgDirectory\chocolatey.nupkg'." + Copy-Item -Path $_ -Destination "$chocoPkgDirectory\chocolatey.nupkg" -Force -ErrorAction SilentlyContinue } - } } } } function Install-ChocolateyBinFiles { + [CmdletBinding()] param( - [string] $chocolateyPath, - [string] $chocolateyExePath + [Parameter(Mandatory = $true)] + [string] + $Path, + + [Parameter(Mandatory = $true)] + [string] + $ChocolateyPath ) + Write-Debug "Install-ChocolateyBinFiles" Write-Debug "Installing the bin file redirects" - $redirectsPath = Join-Path $chocolateyPath 'redirects' - if (!(Test-Path "$redirectsPath")) { + + $redirectsPath = Join-Path -Path $ChocolateyPath -ChildPath 'redirects' + + if (-not (Test-Path $redirectsPath)) { Write-ChocolateyWarning "$redirectsPath does not exist" return } - $exeFiles = Get-ChildItem "$redirectsPath" -Include @("*.exe", "*.cmd") -Recurse + $exeFiles = Get-ChildItem -Path $redirectsPath -Include @("*.exe", "*.cmd") -Recurse foreach ($exeFile in $exeFiles) { - $exeFilePath = $exeFile.FullName - $exeFileName = [System.IO.Path]::GetFileName("$exeFilePath") - $binFilePath = Join-Path $chocolateyExePath $exeFileName - $binFilePathRename = $binFilePath + '.old' - $batchFilePath = $binFilePath.Replace(".exe", ".bat") - $bashFilePath = $binFilePath.Replace(".exe", "") - if (Test-Path ($batchFilePath)) { - Remove-Item $batchFilePath -Force -ErrorAction SilentlyContinue - } - if (Test-Path ($bashFilePath)) { - Remove-Item $bashFilePath -Force -ErrorAction SilentlyContinue - } - if (Test-Path ($binFilePathRename)) { - try { - Write-Debug "Attempting to remove $binFilePathRename" - Remove-Item $binFilePathRename -Force -ErrorAction Stop - } - catch { - Write-ChocolateyWarning "Was not able to remove `'$binFilePathRename`'. This may cause errors." - } + Install-Redirect -Path $exeFile -ChocolateyPath $ChocolateyPath -ChocolateyBinPath $Path + } +} + +function Install-Redirect { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string] + $Path, + + [Parameter(Mandatory = $true)] + [string] + $ChocolateyPath, + + [Parameter(Mandatory = $true)] + [string] + $ChocolateyBinPath + ) + + $file = Get-Item $Path + $exeFilePath = $file.FullName + $exeFileName = $file.Name + + $binFilePath = Join-Path -Path $ChocolateyBinPath -ChildPath $exeFileName + $oldBinFilePath = "$binFilePath.old" + + $batchFilePath = $binFilePath.Replace(".exe", ".bat") + $bashFilePath = $binFilePath.Replace(".exe", "") + + $batchFilePath, $bashFilePath | + Where-Object { Test-Path $_ } | + Remove-Item -Recurse -Force -ErrorAction SilentlyContinue + + if (Test-Path ($oldBinFilePath)) { + try { + Write-Debug "Attempting to remove $oldBinFilePath" + Remove-Item -Path $oldBinFilePath -Force -ErrorAction Stop } - if (Test-Path ($binFilePath)) { - try { - Write-Debug "Attempting to rename $binFilePath to $binFilePathRename" - Move-Item -Path $binFilePath -Destination $binFilePathRename -Force -ErrorAction Stop - } - catch { - Write-ChocolateyWarning "Was not able to rename `'$binFilePath`' to `'$binFilePathRename`'." - } + catch { + Write-ChocolateyWarning "Unable to remove '$oldBinFilePath'. This may cause errors." } + } + if (Test-Path ($binFilePath)) { try { - Write-Debug "Attempting to copy $exeFilePath to $binFilePath" - Copy-Item -Path $exeFilePath -Destination $binFilePath -Force -ErrorAction Stop + Write-Debug "Attempting to rename $binFilePath to $oldBinFilePath" + Move-Item -Path $binFilePath -Destination $oldBinFilePath -Force -ErrorAction Stop } catch { - Write-ChocolateyWarning "Was not able to replace `'$binFilePath`' with `'$exeFilePath`'. You may need to do this manually." + Write-ChocolateyWarning "Unable to move '$binFilePath' to '$oldBinFilePath'." } + } - $commandShortcut = [System.IO.Path]::GetFileNameWithoutExtension("$exeFilePath") - Write-Debug "Added command $commandShortcut" + try { + Write-Debug "Attempting to copy $exeFilePath to $binFilePath" + Copy-Item -Path $exeFilePath -Destination $binFilePath -Force -ErrorAction Stop } + catch { + Write-ChocolateyWarning "Unable to replace '$binFilePath' with '$exeFilePath'. You may need to do this manually." + } + + $commandShortcut = [System.IO.Path]::GetFileNameWithoutExtension($exeFilePath) + Write-Debug "Added command $commandShortcut" } -function Initialize-ChocolateyPath { +function Add-ChocolateyToPath { + [CmdletBinding()] param( - [string]$chocolateyExePath = "$($env:ALLUSERSPROFILE)\chocolatey\bin", - [string]$chocolateyExePathVariable = "%$($chocoInstallVariableName)%\bin" + [Parameter()] + [string] + $Path = "$env:ALLUSERSPROFILE\chocolatey\bin" ) - Write-Debug "Initialize-ChocolateyPath" + + Write-Debug "Add-ChocolateyToPath" Write-Debug "Initializing Chocolatey Path if required" + $environmentTarget = [System.EnvironmentVariableTarget]::User if (Test-ProcessAdminRights) { Write-Debug "Administrator installing so using Machine environment variable target instead of User." @@ -608,71 +753,89 @@ function Initialize-ChocolateyPath { Write-ChocolateyWarning "Setting ChocolateyInstall Path on USER PATH and not SYSTEM Path.`n This is due to either non-administrator install OR the process you are running is not being run as an Administrator." } - Install-ChocolateyPath -pathToInstall "$chocolateyExePath" -pathType $environmentTarget + Install-ChocolateyPath -pathToInstall $Path -pathType $environmentTarget } -function Process-ChocolateyBinFiles { +function Edit-ChocolateyBinFiles { + [CmdletBinding()] param( - [string]$chocolateyExePath = "$($env:ALLUSERSPROFILE)\chocolatey\bin", - [string]$chocolateyExePathVariable = "%$($chocoInstallVariableName)%\bin" + [Parameter()] + [string] + $Path = "$($env:ALLUSERSPROFILE)\chocolatey\bin" ) + Write-Debug "Process-ChocolateyBinFiles" - $processedMarkerFile = Join-Path $chocolateyExePath '_processed.txt' - if (!(Test-Path $processedMarkerFile)) { - $files = Get-ChildItem $chocolateyExePath -Include *.bat -Recurse - if ($files -ne $null -and $files.Count -gt 0) { - Write-Debug "Processing Bin files" - foreach ($file in $files) { - Write-Output "Processing $($file.Name) to make it portable" - $fileStream = [System.IO.File]::Open("$file", 'Open', 'Read', 'ReadWrite') - $reader = New-Object System.IO.StreamReader($fileStream) + + $processedMarkerFile = Join-Path -Path $Path -ChildPath '_processed.txt' + if (Test-Path $processedMarkerFile) { + return + } + + $binFiles = Get-ChildItem $Path -Include *.bat -Recurse + if ($binFiles -and $binFiles.Count -gt 0) { + Write-Debug "Processing Bin files" + foreach ($file in $binFiles) { + Write-ChocolateyInfo "Processing $($file.Name) to make it portable" + + try { + $fileStream = [System.IO.File]::Open($file, 'Open', 'Read', 'ReadWrite') + $reader = New-Object -TypeName 'System.IO.StreamReader' -ArgumentList @($fileStream) $fileText = $reader.ReadToEnd() + } + finally { $reader.Close() $fileStream.Close() + } - $fileText = $fileText.ToLower().Replace("`"" + $chocolateyPath.ToLower(), "SET DIR=%~dp0%`n""%DIR%..\").Replace("\\", "\") + $fileText = $fileText.ToLower().Replace('"' + $chocolateyPath.ToLower(), "SET DIR=%~dp0%`n""%DIR%..\").Replace("\\", "\") - Set-Content $file -Value $fileText -Encoding Ascii - } + Set-Content -Path $file -Value $fileText -Encoding Ascii } - - Set-Content $processedMarkerFile -Value "$([System.DateTime]::Now.Date)" -Encoding Ascii } + + Set-Content -Path $processedMarkerFile -Value "$([System.DateTime]::Now.Date)" -Encoding Ascii } # Adapted from http://www.west-wind.com/Weblog/posts/197245.aspx -function Get-FileEncoding($Path) { - if ($PSVersionTable.PSVersion.Major -lt 6) { +function Get-FileEncoding { + [CmdletBinding()] + param( + [Parameter()] + $Path + ) + + [byte[]] $bytes = if ($PSVersionTable.PSVersion.Major -lt 6) { Write-Debug "Detected Powershell version < 6 ; Using -Encoding byte parameter" - $bytes = [byte[]](Get-Content $Path -Encoding byte -ReadCount 4 -TotalCount 4) + Get-Content $Path -Encoding byte -ReadCount 4 -TotalCount 4 } else { Write-Debug "Detected Powershell version >= 6 ; Using -AsByteStream parameter" - $bytes = [byte[]](Get-Content $Path -AsByteStream -ReadCount 4 -TotalCount 4) + Get-Content $Path -AsByteStream -ReadCount 4 -TotalCount 4 } - if (!$bytes) { + if (-not $bytes -or $bytes.Length -lt 4) { return 'utf8' } - switch -regex ('{0:x2}{1:x2}{2:x2}{3:x2}' -f $bytes[0], $bytes[1], $bytes[2], $bytes[3]) { + $hexBytes = '{0:x2}{1:x2}{2:x2}{3:x2}' -f $bytes[0], $bytes[1], $bytes[2], $bytes[3] + switch -regex ($hexBytes) { '^efbbbf' { - return 'utf8' + 'utf8' } '^2b2f76' { - return 'utf7' + 'utf7' } '^fffe' { - return 'unicode' + 'unicode' } '^feff' { - return 'bigendianunicode' + 'bigendianunicode' } '^0000feff' { - return 'utf32' + 'utf32' } default { - return 'ascii' + 'ascii' } } } @@ -681,41 +844,38 @@ function Add-ChocolateyProfile { Write-Debug "Add-ChocolateyProfile" try { $profileFile = "$profile" - if ($profileFile -eq $null -or $profileFile -eq '') { - Write-Output 'Not setting tab completion: Profile variable ($profile) resulted in an empty string.' + if (-not $profileFile) { + Write-ChocolateyInfo 'Not setting tab completion: Profile variable ($profile) resulted in an empty string.' return } - $profileDirectory = (Split-Path -Parent $profileFile) - - if ($env:ChocolateyNoProfile -ne $null -and $env:ChocolateyNoProfile -ne '') { - Write-Warning "Not setting tab completion: Environment variable "ChocolateyNoProfile" exists and is set." + if ($env:ChocolateyNoProfile) { + Write-Warning "Not setting tab completion: Environment variable "ChocolateyNoProfile" is set." return } $localSystem = Get-LocalizedWellKnownPrincipalName -WellKnownSidType ([Security.Principal.WellKnownSidType]::LocalSystemSid) - # get current user $currentUser = [Security.Principal.WindowsIdentity]::GetCurrent() + if ($currentUser.Name -eq $localSystem) { Write-Warning "Not setting tab completion: Current user is SYSTEM user." return } - if (!(Test-Path($profileDirectory))) { + $profileDirectory = Split-Path -Parent $profileFile + + if (-not (Test-Path $profileDirectory)) { Write-Debug "Creating '$profileDirectory'" - New-Item "$profileDirectory" -Type Directory -Force -ErrorAction SilentlyContinue | Out-Null + $null = New-Item $profileDirectory -Type Directory -Force -ErrorAction SilentlyContinue } - if (!(Test-Path($profileFile))) { + if (-not (Test-Path $profileFile)) { Write-Warning "Not setting tab completion: Profile file does not exist at '$profileFile'." return - - #Write-Debug "Creating '$profileFile'" - #"" | Out-File $profileFile -Encoding UTF8 } # Check authenticode, but only if file is greater than 5 bytes - $profileFileInfo = New-Object System.IO.FileInfo($profileFile) + $profileFileInfo = Get-Item $profileFile if ($profileFileInfo.Length -gt 5) { $signature = Get-AuthenticodeSignature $profile if ($signature.Status -ne 'NotSigned') { @@ -724,7 +884,7 @@ function Add-ChocolateyProfile { } } - $profileInstall = @' + $profileScript = @' # Import the Chocolatey Profile that contains the necessary code to enable # tab-completions to function for `choco`. @@ -732,8 +892,8 @@ function Add-ChocolateyProfile { # for `choco` will not function. # See https://ch0.co/tab-completion for details. $ChocolateyProfile = "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1" -if (Test-Path($ChocolateyProfile)) { - Import-Module "$ChocolateyProfile" +if (Test-Path $ChocolateyProfile) { + Import-Module $ChocolateyProfile } '@ @@ -743,96 +903,101 @@ if (Test-Path($ChocolateyProfile)) { return } - Write-Output 'Adding Chocolatey to the profile. This will provide tab completion, refreshenv, etc.' - $profileInstall | Out-File $profileFile -Append -Encoding (Get-FileEncoding $profileFile) + Write-ChocolateyInfo 'Adding Chocolatey to the profile. This will provide tab completion, refreshenv, etc.' + $profileScript | Add-Content -Path $profileFile -Encoding (Get-FileEncoding $profileFile) Write-ChocolateyWarning 'Chocolatey profile installed. Reload your profile - type . $profile' if ($PSVersionTable.PSVersion.Major -lt 3) { Write-ChocolateyWarning "Tab completion does not currently work in PowerShell v2. `n Please upgrade to a more recent version of PowerShell to take advantage of tab completion." - #Write-ChocolateyWarning "To load tab expansion, you need to install PowerTab. `n See https://powertab.codeplex.com/ for details." } } catch { Write-ChocolateyWarning "Unable to add Chocolatey to the profile. You will need to do it manually. Error was '$_'" - @' -This is how add the Chocolatey Profile manually. -Find your $profile. Then add the following lines to it: - -$ChocolateyProfile = "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1" -if (Test-Path($ChocolateyProfile)) { - Import-Module "$ChocolateyProfile" -} -'@ | Write-Output + Write-ChocolateyInfo @" +To add the Chocolatey Profile manually: +Find your `$profile file. Then add the following lines to it: +$profileScript +"@ } } -$netFx48InstallTries = 0 - function Install-DotNet48IfMissing { + [CmdletBinding()] param( - $forceFxInstall = $false + [Parameter()] + [switch] + $Force ) + # we can't take advantage of any chocolatey module functions, because they # haven't been unpacked because they require .NET Framework 4.8 - Write-Debug "Install-DotNet48IfMissing called with `$forceFxInstall=$forceFxInstall" - $NetFxArch = "Framework" - if ([IntPtr]::Size -eq 8) { - $NetFxArch = "Framework64" + Write-Debug "Install-DotNet48IfMissing called with `$Force=$Force" + + if (-not $Force) { + # Skip if .NET 4.8 is already installed + $net4Version = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full" -ErrorAction SilentlyContinue).Release + if ($net4Version -ge 528040) { + return + } } $NetFx48Url = 'https://download.visualstudio.microsoft.com/download/pr/2d6bb6b2-226a-4baa-bdec-798822606ff1/8494001c276a4b96804cde7829c04d7f/ndp48-x86-x64-allos-enu.exe' - $NetFx48Path = "$tempDir" + $NetFx48Path = $script:tempDir $NetFx48InstallerFile = 'ndp48-x86-x64-allos-enu.exe' - $NetFx48Installer = Join-Path $NetFx48Path $NetFx48InstallerFile + $NetFx48Installer = Join-Path -Path $NetFx48Path -ChildPath $NetFx48InstallerFile - if (((Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full" -ErrorAction SilentlyContinue).Release -lt 528040) -or $forceFxInstall) { - Write-Output "The registry key for .Net 4.8 was not found or this is forced" + Write-ChocolateyInfo "The registry key for .Net 4.8 was not found or this is forced" - if (Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending") { - Write-Warning "A reboot is required. `n If you encounter errors, reboot the system and attempt the operation again" - } + if (Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending") { + Write-Warning "A reboot is required. `n If you encounter errors, reboot the system and attempt the operation again" + } - $netFx48InstallTries += 1 + $script:netFx48InstallTries += 1 - if (!(Test-Path $NetFx48Installer)) { - Write-Output "Downloading `'$NetFx48Url`' to `'$NetFx48Installer`' - the installer is 100+ MBs, so this could take a while on a slow connection." - (New-Object Net.WebClient).DownloadFile("$NetFx48Url","$NetFx48Installer") - } + if (-not (Test-Path $NetFx48Installer)) { + Write-ChocolateyInfo "Downloading '$NetFx48Url' to '$NetFx48Installer' - the installer is 100+ MBs, so this could take a while on a slow connection." + (New-Object -TypeName 'System.Net.WebClient').DownloadFile($NetFx48Url, $NetFx48Installer) + } - $psi = New-Object System.Diagnostics.ProcessStartInfo - $psi.WorkingDirectory = "$NetFx48Path" - $psi.FileName = "$NetFx48InstallerFile" - # https://msdn.microsoft.com/library/ee942965(v=VS.100).aspx#command_line_options - # http://blogs.msdn.com/b/astebner/archive/2010/05/12/10011664.aspx - # For the actual setup.exe (if you want to unpack first) - /repair /x86 /x64 /ia64 /parameterfolder Client /q /norestart - $psi.Arguments = "/q /norestart" + $startInfo = New-Object -TypeName 'System.Diagnostics.ProcessStartInfo' + $startInfo.WorkingDirectory = $NetFx48Path + $startInfo.FileName = $NetFx48InstallerFile + # https://msdn.microsoft.com/library/ee942965(v=VS.100).aspx#command_line_options + # http://blogs.msdn.com/b/astebner/archive/2010/05/12/10011664.aspx + # For the actual setup.exe (if you want to unpack first) - /repair /x86 /x64 /ia64 /parameterfolder Client /q /norestart + $startInfo.Arguments = "/q /norestart" - Write-Output "Installing `'$NetFx48Installer`' - this may take awhile with no output." - $s = [System.Diagnostics.Process]::Start($psi); - $s.WaitForExit(); - if ($s.ExitCode -eq 1641 -or $s.ExitCode -eq 3010) { - throw ".NET Framework 4.8 was installed, but a reboot is required. `n Please reboot the system and try to install/upgrade Chocolatey again." - } - if ($s.ExitCode -ne 0) { - if ($netFx48InstallTries -ge 2) { - Write-ChocolateyError ".NET Framework install failed with exit code `'$($s.ExitCode)`'. `n This will cause the rest of the install to fail." - throw "Error installing .NET Framework 4.8 (exit code $($s.ExitCode)). `n Please install the .NET Framework 4.8 manually and reboot the system `n and then try to install/upgrade Chocolatey again. `n Download at `'$NetFx48Url`'" - } else { - Write-ChocolateyWarning "Try #$netFx48InstallTries of .NET framework install failed with exit code `'$($s.ExitCode)`'. Trying again." - Install-DotNet48IfMissing $true - } + Write-ChocolateyInfo "Installing '$NetFx48Installer' - this may take a while with no output." + $installProcess = [System.Diagnostics.Process]::Start($startInfo) + $installProcess.WaitForExit() + + $rebootNeededExitCodes = 1641, 3010 + if ($rebootNeededExitCodes -contains $installProcess.ExitCode) { + throw ".NET Framework 4.8 was installed, but a reboot is required. `n Please reboot the system and try to install/upgrade Chocolatey again." + } + + if ($installProcess.ExitCode -ne 0) { + if ($script:netFx48InstallTries -ge 2) { + Write-ChocolateyError ".NET Framework install failed with exit code '$($installProcess.ExitCode)'. `n This will cause the rest of the install to fail." + throw "Error installing .NET Framework 4.8 (exit code $($installProcess.ExitCode)). `n Please install the .NET Framework 4.8 manually and reboot the system `n and then try to install/upgrade Chocolatey again. `n Download at '$NetFx48Url'" } + + Write-ChocolateyWarning "Attempt #$script:netFx48InstallTries of .NET framework install failed with exit code '$($installProcess.ExitCode)'. Trying again." + Install-DotNet48IfMissing -Force } } -function Invoke-Chocolatey-Initial { - Write-Debug "Initializing Chocolatey files, etc by running Chocolatey..." +function Invoke-Chocolatey { + [CmdletBinding()] + param() + + Write-Debug "Initializing Chocolatey files, etc by running Chocolatey" try { - $chocoInstallationFolder = Get-ChocolateyInstallFolder + $chocoInstallationFolder = Get-ChocolateyInstallPath $chocoExe = Join-Path -Path $chocoInstallationFolder -ChildPath "choco.exe" - & $chocoExe -v | Out-Null + & $chocoExe --version Write-Debug "Chocolatey execution completed successfully." } catch { @@ -840,4 +1005,4 @@ function Invoke-Chocolatey-Initial { } } -Export-ModuleMember -Function Initialize-Chocolatey; +Export-ModuleMember -Function Initialize-Chocolatey