ChoVersion.psm1
#Region '.\Header\Constants.ps1' 0 if ($PSScriptRoot -match 'Header$') { Write-Warning "This module isn't really meant to be imported from source. YMMV" $PSScriptRoot = $PSScriptRoot | Split-Path } $Shim = Get-FileHash "$PSScriptRoot\lib\shim.exe" -ErrorAction SilentlyContinue -ErrorVariable MissingShim if ($MissingShim) { Write-Error "The 'shim.exe' is missing from '$PSScriptRoot\lib' and ChoVersion will not work. Please reinstall ChoVersion." } if ($Shim.Hash -ne "AA685053F4A5C0E7145F2A27514C8A56CEAE25B0824062326F04037937CAA558") { Write-Error "The 'shim.exe' file has been changed in '$PSScriptRoot\lib' and ChoVersion will not work. Please reinstall ChoVersion." } #EndRegion '.\Header\Constants.ps1' 12 #Region '.\Public\Add-ToolPath.ps1' 0 filter Add-ToolPath { <# .SYNOPSIS Prepend a path to the PATH environment variable, with support for Github Actions and Azure Pipelines .EXAMPLE Add-ToolPath -Path 'C:\Program Files\Git\bin' Adds the Git bin directory to the PATH environment variable for the current process. When run in a Github Actions environment, will add the Path to the ENV:GITHUB_PATH files When run in an Azure Pipelines environment, will output a `##vso[task.prependpath]` command #> [CmdletBinding()] param( # The path to prepend to the PATH environment variable [Parameter(Mandatory, ValueFromPipelineByPropertyName, Position = 0)] [ValidateScript({ Test-Path $_ })] [string]$Path, # If set, and on Windows, update the user's PATH [Alias("Permanent")] [switch]$SetForUserExperimental ) # If they pass the path to a file, we'll look at the folder if (Test-Path $Path -PathType Leaf) { $Path = Split-Path $Path } $ChocoLib = "$Env:ChocolateyInstall\lib\" if ($Path.ToLowerInvariant().StartsWith($ChocoLib.ToLowerInvariant())) { $RemovablePaths = $Path -replace "[\d\.]+$" } ## ShimGen is a whole different option # if ($ExecutablePath.ToLowerInvariant().StartsWith()) { # try { # if ((Get-FileHash $ExecutablePath) -eq $Shim.Hash) { # Set-Content ([IO.Path]::ChangeExtension($ExecutablePath,".shim")) "path = $($ChoPackage.Path)" # } # Copy-Item $Shim $ExecutablePath -Force # } catch [UnauthorizedAccessException] { # Write-Warning "Elevating to overwrite the chocolatey shim at: $ExecutablePath" # $pwsh = Get-Command PowerShell, pwsh -ErrorAction SilentlyContinue | Select-Object -First 1 # Start-Process $pwsh.Name -Verb RunAs -ArgumentList "-Command", "Copy-Item", $Shim, $ExecutablePath # } # } else { # Write-Warning "The default '$Name' command was not a chocolatey copy: $ExecutablePath" # } # On Windows, we can prepend the path in the user environment to make it sticky if ($SetForUserExperimental -and (-not (Test-Path Variable:IsWindows) -or $IsWindows)) { [string[]]$EnvPath = [System.Environment]::GetEnvironmentVariable("PATH", "User").Split([IO.Path]::PathSeparator).Where{ $_ -ine $Path } # we might want to remove any previous copies of this from the path if ($Path.ToLowerInvariant().StartsWith($ChocoLib.ToLowerInvariant())) { $ChocoLib = $Path.Substring(0, $ChocoLib.Length) $Name, $Folder = $Path.Substring($ChocoLib.Length) -split "(?=[\\/])", 2 $Pattern = $Name -replace "\.[\d\.]+$", "\.[\d\.]+" $RemovablePaths = [regex]::Escape($ChocoLib) + $Pattern + [regex]::Escape($Folder) $EnvPath = $EnvPath -notmatch $RemovablePaths } [string]$EnvPath = @(@($Path) + $EnvPath) -join [IO.Path]::PathSeparator Write-Verbose "Set User PATH: $ENVPATH" [System.Environment]::SetEnvironmentVariable("PATH", $EnvPath, "User") } ## Prepend the path in the current session Write-Verbose "Prepending '$Path' to PATH" $ENV:PATH = $Path + [IO.Path]::PathSeparator + $Env:PATH if (Test-Path ENV:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI) { ## Prepend the path in Azure DevOps: Write-Host "##vso[task.prependpath]$Path" } if (Test-Path Env:GITHUB_PATH) { ## Prepend the path in Github Actions: Add-Content $Env:GITHUB_PATH $Path -Encoding UTF8 } } #EndRegion '.\Public\Add-ToolPath.ps1' 85 #Region '.\Public\Get-ChoVersion.ps1' 0 filter Get-ChoVersion { [CmdletBinding()] param( # The name of the chocolatey package [Parameter(ValueFromPipelineByPropertyName, Mandatory, Position = 0)] [string]$Package, # The version of the chocolatey package [Parameter(ValueFromPipelineByPropertyName)] [string]$Version, # The base name of the executable (without the .exe), like "terraform" [Parameter(ValueFromPipelineByPropertyName)] [string]$Executable = "*", # If set, lists all versions available. # Otherwise, Get-ChoVersion only returns the specified or newest version [switch]$ListAvailable ) if (!$Executable) { $Executable = "*" } $Pattern = $Package -replace "[-\.]", "|" $ChildParam = @{ Filter = "$Executable.exe" Recurse = $true OutVariable = "App" } $ChocoApplications = if ($Version) { choco list --by-id-only $Package --version $Version --localonly -allversions --limitoutput --includeprograms | ConvertFrom-Csv -Delimiter "|" -Header Package, Version } else { choco list --by-id-only $Package --localonly -allversions --limitoutput --includeprograms | ConvertFrom-Csv -Delimiter "|" -Header Package, Version } if (!$ChocoApplications) { Write-Error "Choco package '$Package'$(if($Version){ " version $Version" }) not found!" return } if ($ChocoApplications.Count -gt 1) { # On Windows PowerShell, there's no built-in [semver] type accelerator if (-not ('semver' -as [type])) { Import-Module PackageManagement -Scope Global $xlr8r = [psobject].assembly.gettype("System.Management.Automation.TypeAccelerators") $xlr8r::Add('semver', [Microsoft.PackageManagement.Provider.Utility.SemanticVersion]) } $ChocoApplications = $ChocoApplications | Sort-Object { [semver]$_.Version } -Descending if (!$ListAvailable) { $ChocoApplications = $ChocoApplications[0] } } @($ChocoApplications).ForEach{ Add-Member -InputObject $_ -Passthru -NotePropertyName Path -NotePropertyValue $( if (Test-Path "$Env:ChocolateyInstall\lib\$($_.Package).$($_.Version)") { $ChildParam["Path"] = "$Env:ChocolateyInstall\lib\$($_.Package).$($_.Version)" } elseif (Test-Path $Env:ChocolateyInstall\lib\$($_.Package)) { $ChildParam["Path"] = "$Env:ChocolateyInstall\lib\$($_.Package)" } Get-ChildItem @ChildParam | Convert-Path | Where-Object { # we can try matching based on the package name $Executable -ne "*" -or $_ -match $Pattern } | Select-Object -Unique -First 1 ) } } #EndRegion '.\Public\Get-ChoVersion.ps1' 72 #Region '.\Public\Set-ChoVersion.ps1' 0 filter Set-ChoVersion { <# .SYNOPSIS Set which version of a chocolatey tool package should be used. .DESCRIPTION Set-ChoVersion changes your PATH environment variable to include the Chocolatey lib folder for the specified version of the package. If it's not already installed, it will be installed. .EXAMPLE Set-ChoVersion terraform Ensures that terraform is available on the PATH without worrying about the version .EXAMPLE Set-ChoVersion gitversion.portable 5.8.1 Ensures that gitversion version 5.8.1 is available on the PATH Set-Choversion will assume that the executable is named "gitversion" because it knows about ".portable" and ".install" package conventions .Example Set-ChoVersion @{ Package = "terraform"; Version = "1.0.1" }, @{ Package = "bicep"; Version = "0.4.6" } Switches to terraform 1.0.1 and bicep 0.4.6 .Example Set-Content ChoVersion.psd1 @" @{ ChocolateyPackages = @( @{ Package = "gitversion.portable" Executable = "gitversion" Version = "5.8.1" } ) } "@ Set-ChoVersion Shows how a psd1 file can be used in a project repository to support installing tool dependencies and using specific versions of tools. #> [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = "Chocolatey", ConfirmImpact = "Medium")] param( # The name of the chocolatey package [Parameter(ParameterSetName = "One", Mandatory, ValueFromPipelineByPropertyName, Position = 0)] [string]$Package, # The version of the chocolatey package [Parameter(ParameterSetName = "One", ValueFromPipelineByPropertyName, Position = 1)] [string]$Version, # The base name of the executable (without the .exe), like "terraform" [Parameter(ParameterSetName = "One", ValueFromPipelineByPropertyName)] [string]$Executable = ($Package -replace "\.portable$|\.install$"), # An array of hashtables specifying multiple applications to set or install # This parameter is intended to support configuration via a ChoVersion.psd1 file like: # @{ChocolateyPackages = @(@{ Package = "terraform"; Version = "1.0.0" })} [Parameter(ValueFromPipelineByPropertyName, ParameterSetName = "Chocolatey", Position = 0)] [hashtable[]]$ChocolateyPackages, # If set, makes the change permanent for the current user by modifying their PATH at user scope [Alias("Permanent")] [switch]$SetForUserExperimental ) # Set executable from Package if it's piped in empty if (!$Executable -and $Package) { $Executable = $Package -replace ".+\.portable$|.+\.install$" } # There's got to be an easier way to show *all* parameter values (including defaults) if ($DebugPreference -eq "Continue") { $Parameters = @($PSCmdlet.MyInvocation.MyCommand.Parameters.Keys) Write-Debug (@( $MyInvocation.MyCommand.Name $((Get-Variable -Name $Parameters -Scope Local -ErrorAction SilentlyContinue). Where({$_.Value}). ForEach({ "-" + $_.Name, $_.Value}) ) "$([char]27)[90m# ParameterSet:" $PSCmdlet.ParameterSetName "$([char]27)[0m" ) -join " ") } if ($PSCmdlet.ParameterSetName -eq 'Chocolatey') { if (-not $ChocolateyPackages) { # Only import default parameters if we actually need them Import-ParameterConfiguration } if ($ChocolateyPackages) { $Packages = $ChocolateyPackages.ForEach{ [PSCustomObject]$_ } Write-Verbose "Installing multiple ChocolateyPackages: $($Packages | Format-Table -Auto | Out-String)" $Packages | Set-ChoVersion -SetForUserExperimental:$SetForUserExperimental return } else { Write-Warning "No ChocolateyPackages specified" return } } $null = $PSBoundParameters.Remove("SetForUserExperimental") $null = $PSBoundParameters.Remove("Confirm") Write-Verbose "Setting choco package '$Package'$(if($Version){ " version $Version" })$(if($Executable){ " for $Executable" })" if (!(($ChoPackage = Get-ChoVersion @PSBoundParameters -ErrorAction SilentlyContinue))) { if ($Version) { # TODO: I want to support version ranges which would require listing all the versions: # choco list -a -r -e $Package | # ConvertFrom-Csv -Delimiter '|' -Header name, version | # Select-Object Name, @{ N = 'Version'; E = { [NuGet.Versioning.NuGetVersion]$_.version }} | # Where-Object { # ($Version.Float -and $Version.Float.Satisfies($_.Version.ToString())) -or # (!$Version.Float -and $Version.Satisfies($_.Version.ToString())) # } choco install -y $Package --version $Version --sxs } else { choco install -y $Package --sxs } $ChoPackage = Get-ChoVersion @PSBoundParameters -ErrorAction Stop } $ExecutablePath = Get-Command $Executable -ErrorAction SilentlyContinue | Convert-Path if ($ExecutablePath -eq $ChoPackage.Path) { # nothing to do, it's already the default return } if ($PSCmdlet.ShouldProcess("Use $($ChoPackage.Package) v$($ChoPackage.Version)", "Prepend PATH for '$($ChoPackage.Path)'")) { $ChoPackage | Add-ToolPath -SetForUserExperimental:$SetForUserExperimental } } #EndRegion '.\Public\Set-ChoVersion.ps1' 128 |