Finding Scoop's Hidden Completion Scripts with PowerShell
I like Scoop. It's a clean, no-admin-required package manager for Windows, and it stays out of your way. One thing I ran into, though: some apps ship a tab-completion script alongside the binary, and there's currently no built-in way to know it's there.
The itch
Some Scoop packages bundle a completion script alongside the binary. Once installed, that script just sits on disk. There's no scoop completions subcommand and nothing in scoop info that points to it — reasonable, since Scoop's job is installing packages, not indexing every file inside them. But it does mean that unless you already know to go looking, you won't stumble onto it.
Where they actually live
Scoop installs every app under:
$env:USERPROFILE\scoop\apps\<app-name>\<version>\
with a current folder symlinked to whichever version is active. Completion scripts that ship with a package end up somewhere inside that per-app tree, and by convention they're named with a leading underscore — _appname.ps1 — the same convention PowerShell's own completion ecosystem uses.
Once I knew the naming pattern and roughly where to look, the rest was a filesystem search problem, not a Scoop problem.
Checking this against prior art
Before writing this up, I looked around for existing solutions. The "scoop completion" PowerShell modules that already exist solve a different problem: they add tab-completion for the scoop command itself (scoop ins<Tab> cycling to install), not for discovering completion scripts bundled with the apps Scoop installs.
There's also a 2022 GitHub Discussion on the Scoop repo where someone asks this same question — how to set up completions for an app they were packaging for Scoop. The maintainers' answer, paraphrased: Scoop's job is extracting the archive and adding the bin path, and it doesn't do anything with Windows completions beyond that, so there's no centralized place completion files land — users source the files themselves. A linked issue floated adding first-class completion support to Scoop core (a ~\scoop\completions folder with per-shell subfolders, populated at install time), but that's a design change on Scoop's side, and it doesn't look like it shipped.
That lines up with what I found: a real gap, reasonable given Scoop's scope, and one I haven't seen a user-side tool address yet.
The code
Get-ScoopCompletions walks the apps directory looking for that pattern:
function Get-ScoopCompletions {
[CmdletBinding()]
param(
[Parameter()]
[string]$ScoopHome = "$env:USERPROFILE\scoop"
)
if (-not (Test-Path $ScoopHome)) {
Write-Warning "Scoop home not found: $ScoopHome"
return
}
$appsRoot = Join-Path $ScoopHome "apps"
Get-ChildItem -Path $appsRoot -Filter "_*.ps1" -Recurse -Depth 5 -ea silentlycontinue
}
A few deliberate choices here:
-
-Filterover-Include—-Filteris handled by the provider itself rather than PowerShell's pipeline, so it's noticeably faster on a directory tree with hundreds of app folders. -
-Depth 5— bounds the recursion instead of walking arbitrarily deep. Scoop's structure is predictable enough that this is generous headroom without being unbounded. -
-ea silentlycontinue— apps directories can have permission quirks or half-installed packages; I'd rather skip those than have the whole scan die on one bad folder.
One thing I'm still going back and forth on: Scoop's current symlink means the same completion script is technically reachable through two paths — the versioned folder and the current alias. Right now Get-ScoopCompletions returns both. I could filter with Where-Object { $null -eq $_.Directory.LinkType } to only keep the "real" versioned path and drop the symlinked duplicate, but I haven't decided if that's worth the extra complexity yet.
Enable-ScoopCompletions then just dot-sources whatever comes back:
function Enable-ScoopCompletions {
[CmdletBinding()]
param(
[Parameter()]
[string]$ScoopHome = "$env:USERPROFILE\scoop"
)
$scripts = Get-ScoopCompletions -ScoopHome $ScoopHome
if (-not $scripts) {
Write-Verbose "No Scoop completion scripts found."
return
}
foreach ($script in $scripts) {
. $script.FullName
}
Write-Verbose "Scoop completions loaded."
}
Drop Enable-ScoopCompletions in your $PROFILE, and every completion script that came bundled with your Scoop apps gets wired up automatically — no more hunting through app folders by hand after installing something new.
Who actually ships these
A handful of everyday CLI tools are exactly why this was worth automating. bat, fd, lsd, and rg (ripgrep) all install a complete directory alongside the binary, with a _*.ps1 script sitting right inside it — precisely the pattern Get-ScoopCompletions is looking for.
Not every tool works this way, though. Some don't ship a static completion script at all — instead they expose a flag that generates one on demand (something like tool.exe --generate-completion powershell or tool.exe completions pwsh, naming varies by tool). For those, you'd capture the output and dot-source it, e.g.:
tool.exe generate-completion powershell | Out-String | Invoke-Expression
That's a different problem than Get-ScoopCompletions solves — it's discovery for scripts that already exist on disk, not generation on the fly — but it's worth knowing both patterns exist if you're chasing down completions for a tool that isn't in the bat/fd/lsd/rg camp.
Why filesystem discovery over parsing manifests
Scoop tracks installed apps via JSON manifests, so an alternative approach would be reading each app's manifest and checking for a declared completion script field. I went with filesystem discovery instead, for a simple reason: not every completion script is guaranteed to be declared consistently in the manifest, but the _*.ps1 naming convention on disk is. Searching the filesystem finds what's actually there rather than trusting what's supposed to be there.
Try it
Import-Module ScoopCompletions
Enable-ScoopCompletions
That's it — the completions Scoop already gave you, actually usable.
I have included this module as a part of my powershell configuration repository here.
Top comments (0)