# Usage: scoop shim [...] [options] [other_args] # Summary: Manipulate Scoop shims # Help: Available subcommands: add, rm, list, info, alter. # # To add a custom shim, use the 'add' subcommand: # # scoop shim add [[--|--%] ...] # # To remove shims, use the 'rm' subcommand: (CAUTION: this could remove shims added by an app manifest) # # scoop shim rm [...] # # To list all shims or matching shims, use the 'list' subcommand: # # scoop shim list [...] # # To show a shim's information, use the 'info' subcommand: # # scoop shim info # # To alternate a shim's target source, use the 'alter' subcommand: # # scoop shim alter # # Options: # -g, --global Manipulate global shim(s) # # HINT: The FIRST terminator token ('--' or PowerShell '--%'), if any, is treated as the option # terminator and will NOT be included; everything after it is passed to the shim. # So if you want to pass arguments like '-g' or '--global' to the shim, put them after a '--' or '--%'. # Examples: # POSIX-style: scoop shim add myapp 'D:\path\myapp.exe' '--' myapp_args --global # PowerShell: scoop shim add myapp D:\path\myapp.exe --% myapp_args --global # Notes: # - In PowerShell, '--' should be quoted to avoid parsing. # - '--%' disables PowerShell parsing of the remainder; pass literals as needed. param($SubCommand) . "$PSScriptRoot\..\lib\getopt.ps1" . "$PSScriptRoot\..\lib\install.ps1" # for rm_shim . "$PSScriptRoot\..\lib\system.ps1" # 'Add-Path' (indirectly) if ($SubCommand -notin @('add', 'rm', 'list', 'info', 'alter')) { if (!$SubCommand) { error ' missing' } else { error "'$SubCommand' is not one of available subcommands: add, rm, list, info, alter" } my_usage exit 1 } $opt, $other, $err = getopt $Args 'g' 'global' if ($err) { error "scoop shim: $err"; exit 1 } $global = $opt.g -or $opt.global if ($SubCommand -ne 'list' -and $other.Length -eq 0) { error " must be specified for subcommand '$SubCommand'" my_usage exit 1 } if (-not (Get-FormatData ScoopShims)) { Update-FormatData "$PSScriptRoot\..\supporting\formats\ScoopTypes.Format.ps1xml" } $localShimDir = shimdir $false $globalShimDir = shimdir $true function Get-ShimInfo($ShimPath) { $info = [Ordered]@{} $info.Name = strip_ext (fname $ShimPath) $info.Path = $ShimPath -replace 'shim$', 'exe' $info.Source = (get_app_name_from_shim $ShimPath) -replace '^$', 'External' $info.Type = if ($ShimPath.EndsWith('.ps1')) { 'ExternalScript' } else { 'Application' } $altShims = Get-Item -Path "$ShimPath.*" -Exclude '*.shim', '*.cmd', '*.ps1' if ($altShims) { $info.Alternatives = (@($info.Source) + ($altShims | ForEach-Object { $_.Extension.Remove(0, 1) } | Select-Object -Unique)) -join ' ' } $info.IsGlobal = $ShimPath.StartsWith("$globalShimDir") $info.IsHidden = !((Get-Command -Name $info.Name).Path -eq $info.Path) [PSCustomObject]$info } function Get-ShimPath($ShimName, $Global) { '.shim', '.ps1' | ForEach-Object { $shimPath = Join-Path (shimdir $Global) "$ShimName$_" if (Test-Path -LiteralPath $shimPath) { return $shimPath } } } switch ($SubCommand) { 'add' { if ($other.Length -lt 2 -or $other[1] -eq '') { error " must be specified for subcommand 'add'" my_usage exit 1 } $shimName = $other[0] $commandPath = $other[1] if ($other.Length -gt 2) { $commandArgs = $other[2..($other.Length - 1)] } if ($commandPath -notmatch '[\\/]') { $shortPath = $commandPath $commandPath = Get-ShimTarget (Get-ShimPath $shortPath $global) if (!$commandPath) { $exCommand = Get-Command $shortPath -ErrorAction SilentlyContinue if ($exCommand -and $exCommand.CommandType -eq 'Application') { $commandPath = $exCommand.Path } # TODO - add support for more command types: Alias, Cmdlet, ExternalScript, Filter, Function, Script, and Workflow } } if ($commandPath -and (Test-Path $commandPath)) { Write-Host "Adding $(if ($global) { 'global' } else { 'local' }) shim $([char]0x1b)[36m$shimName$([char]0x1b)[0m..." shim $commandPath $global $shimName $commandArgs } else { error "Command path does not exist: $([char]0x1b)[36m$($other[1])$([char]0x1b)[31m" exit 3 } } 'rm' { $failed = @() $other | ForEach-Object { if (Get-ShimPath $_ $global) { rm_shim $_ (shimdir $global) } else { $failed += $_ } } if ($failed) { $failed | ForEach-Object { error "$(if ($global) { 'Global' } else { 'Local' }) shim not found: $([char]0x1b)[36m$_$([char]0x1b)[31m" } exit 3 } } 'list' { $other = @($other) -ne '*' # Validate all given patterns before matching. $other | ForEach-Object { try { $pattern = $_ [void][Regex]::New($pattern) } catch { error "Invalid pattern: $([char]0x1b)[35m$pattern$([char]0x1b)[31m" exit 1 } } $pattern = $other -join '|' $shims = @() if (!$global) { $shims += Get-ChildItem -Path $localShimDir -Recurse -Include '*.shim', '*.ps1' | Where-Object { !$pattern -or ($_.BaseName -match $pattern) } | Select-Object -ExpandProperty FullName } if (Test-Path $globalShimDir) { $shims += Get-ChildItem -Path $globalShimDir -Recurse -Include '*.shim', '*.ps1' | Where-Object { !$pattern -or ($_.BaseName -match $pattern) } | Select-Object -ExpandProperty FullName } $shims.ForEach({ Get-ShimInfo $_ }) | Add-Member -TypeName 'ScoopShims' -PassThru } 'info' { $shimName = $other[0] $shimPath = Get-ShimPath $shimName $global if ($shimPath) { Get-ShimInfo $shimPath } else { error "$(if ($global) { 'Global' } else { 'Local' }) shim not found: $([char]0x1b)[36m$shimName$([char]0x1b)[31m" if (Get-ShimPath $shimName (!$global)) { Write-Host "But a $(if ($global) { 'local' } else { 'global' }) shim exists, run 'scoop shim info $shimName$(if (!$global) { ' --global' })' to show its info." exit 2 } exit 3 } } 'alter' { $shimName = $other[0] $shimPath = Get-ShimPath $shimName $global if ($shimPath) { $shimInfo = Get-ShimInfo $shimPath if ($null -eq $shimInfo.Alternatives) { error "No alternatives of $([char]0x1b)[36m$shimName$([char]0x1b)[31m found." exit 2 } $shimInfo.Alternatives = $shimInfo.Alternatives.Split(' ') [System.Management.Automation.Host.ChoiceDescription[]]$altApps = 1..$shimInfo.Alternatives.Length | ForEach-Object { New-Object System.Management.Automation.Host.ChoiceDescription "&$($_)`b$($shimInfo.Alternatives[$_ - 1])", "Sets '$shimName' shim from $($shimInfo.Alternatives[$_ - 1])." } $selected = $Host.UI.PromptForChoice("Alternatives of '$shimName' command", "Please choose one that provides '$shimName' as default:", $altApps, 0) if ($selected -eq 0) { Write-Host "$([char]0x1b)[36m$shimName$([char]0x1b)[0m is already from $([char]0x1b)[33m$($shimInfo.Source)$([char]0x1b)[0m, nothing changed." } else { $newApp = $shimInfo.Alternatives[$selected] Write-Host "Use $([char]0x1b)[36m$shimName$([char]0x1b)[0m from $([char]0x1b)[33m$newApp$([char]0x1b)[0m as default... " -NoNewline $pathNoExt = strip_ext $shimPath '', '.shim', '.cmd', '.ps1' | ForEach-Object { $oldShimPath = "$pathNoExt$_" $newShimPath = "$oldShimPath.$newApp" if (Test-Path -Path $oldShimPath -PathType Leaf) { Rename-Item -Path $oldShimPath -NewName "$oldShimPath.$($shimInfo.Source)" -Force if (Test-Path -Path $newShimPath -PathType Leaf) { Rename-Item -Path $newShimPath -NewName $oldShimPath -Force } } } Write-Host 'Done.' } } else { error "$(if ($global) { 'Global' } else { 'Local' }) shim not found: $([char]0x1b)[36m$shimName$([char]0x1b)[31m" if (Get-ShimPath $shimName (!$global)) { Write-Host "But a $(if ($global) { 'local' } else { 'global' }) shim exists, run 'scoop shim alter $shimName$(if (!$global) { ' --global' })' to alternate its source." exit 2 } exit 3 } } } exit 0