en-US/about_Completer_Sets.help.txt
|
TOPIC about_Completer_Sets SYNOPSIS Explains completer set files: the schema, the per-entry trust tier, how Import-CompleterSet validates and registers a set, the Pending and Failed lifecycle of lazily loaded completers, and the module's PSReadLine neutrality promise. LONG DESCRIPTION A completer set is a data file that lists completer scripts and the targets each one registers. It turns a profile that walks a completer repository, imports every script, and registers the results into one line: Import-CompleterSet -Path ~\Completers\completers.psd1 Three commands work with sets: - `Export-CompleterSet` writes a set from registration records, either the managed registrations that came from scripts or records piped in from `Get-Completer` or `Import-CompleterScript`. - `Import-CompleterSet` reads a set, validates every entry up front, and registers each valid entry's targets. - `Test-CompleterSet` reports drift between a set and the scripts on disk, and returns nothing when the set is current. A set never contains code. It is a PowerShell data file (`.psd1`) that the commands read with `Import-PowerShellDataFile`, which evaluates data only and refuses expressions, so a set file cannot execute a completer script or anything else when it is read. THE SET FILE SCHEMA A set file is a hashtable with a `Version` and an `Entries` array. Each entry names one script: @{ Version = 1 Entries = @( @{ Path = 'git_completer.ps1' Trusted = $false Hash = 'SHA256:9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08' Targets = @( @{ CommandName = 'git'; Native = $true } ) } @{ Path = 'tools\Invoke-DemoTool.Completer.ps1' Trusted = $true Hash = 'SHA256:E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855' Targets = @( @{ CommandName = 'Invoke-DemoTool'; ParameterName = 'Name' } ) } ) } - `Version` Required. This module reads version 1. - `Entries` Required and non-empty. One hashtable per script. - `Path` Required. The completer script, which must exist and be a `.ps1` file. Only a fully qualified path is used as written. Any other form, a relative path and also a drive-relative one such as `C:scripts\x.ps1`, resolves against the directory that contains the set file, not the current location, so a repository can carry its set file next to its scripts. Either separator is accepted, so a set written on Windows imports on Linux and macOS. - `Trusted` Optional, default `$false`. Selects the import tier for the entry. See THE TRUST TIER PER ENTRY. - `Hash` Optional. `SHA256:` followed by 64 hexadecimal digits: the hash of the script's text with its line endings normalised to LF, so a Windows and a Linux checkout of the same script hash alike. It is a cache key for a strict entry's targets, not a signature, and it does not change the trust tier. A value in any other form is treated as absent, and a reader that predates the key, such as 2.0.0, ignores it, so the set stays version 1. See VALIDATION BEFORE REGISTRATION. - `Targets` The completer targets the script registers. Each target is a hashtable with `CommandName` and either `Native = $true` for a native command completer or `ParameterName` for a command parameter completer. `Targets` is required for trusted entries and optional for strict entries; see the next section for why. `Export-CompleterSet` always writes `Targets`, and writes `Path` relative to the set file whenever the script shares a root with it, using forward slashes so the file is portable. It writes a `Hash` on every entry; a trusted entry whose script cannot be read is written without one, with a warning. For a strict entry it writes every target the script registers, in the order and casing the script uses, and refuses, naming the missing targets and writing nothing, when the records it was given cover only some of them, as they do after `Register-Completer -Lazy -CommandName` selected a subset; such an entry would fail the target check on import. A trusted entry is written with the targets the records carry, so a subset of a trusted script's targets exports and imports as given. THE TRUST TIER PER ENTRY Each entry chooses its own import tier, and strict is the default. This is the same choice `Import-CompleterScript` offers with `-Trusted`, made once per script and recorded in the set, so a set that points at a script from elsewhere stays safe while your own scripts can keep shapes the strict grammar does not allow. `about_Import_Completers` describes the two tiers in full. Strict entries (`Trusted = $false` or omitted): - The targets are derived from the script without running it. The strict grammar requires literal `-CommandName`, `-ParameterName`, and `-Native` arguments, so the targets can be read from the parsed script; an entry whose arguments cannot be read statically is reported with the position of the offending argument. - The script must pass the strict import grammar when it loads, exactly as `Import-CompleterScript` requires. `Import-CompleterSet` does not run the grammar itself: it parses each strict script once, unless the entry's `Hash` matches, to validate the entry, registers the targets that parse derived, and walks none of them; a script that fails the grammar moves to `Failed` on its first tab press with the findings in `LoadError`. Run `Test-CompleterScript` over the repository to catch that ahead of time. - `Targets` may be omitted. When present, it must match what the script declares, or the entry is reported as a mismatch. Declaring targets is a way to notice that a script changed underneath the set. Trusted entries (`Trusted = $true`): - The grammar is skipped and the script is dot-sourced as-is when it loads, exactly as `Import-CompleterScript -Trusted` does. - `Targets` is required. A trusted script is not parsed for its targets because its registrations may be built dynamically, so the set has to say which targets the script provides. VALIDATION BEFORE REGISTRATION `Import-CompleterSet` checks every entry before it registers anything: - the entry is a hashtable with a `Path` - the file exists and is a `.ps1` script - `Trusted`, when present, is `$true` or `$false` - each target has a `CommandName` and either `Native = $true` or a `ParameterName` - trusted entries declare `Targets` - strict entries expose literal targets that can be derived without running the script - strict entries that declare `Targets` match the script - no target is listed by two entries of the set - without `-Force`, no target already carries a managed or runtime registration for a different completer; an entry that repeats a registration the session already has is reused When any entry fails, the command throws one error that lists every problem, numbered by entry, and registers nothing: Completer set 'C:\Completers\completers.psd1' has 2 invalid entries and nothing was registered. Fix the entries or use -SkipInvalid to register the valid ones. Entry 3 ('old_completer.ps1'): The file 'C:\Completers\old_completer .ps1' does not exist. Entry 7 ('mine.ps1'): Trusted entries must declare Targets, because a trusted script is not parsed for them. With `-SkipInvalid` the same problems are written as warnings and the valid entries register. `-Force` passes through to the registration and replaces existing managed or runtime registrations for the set's targets. The command supports `-WhatIf`, and validation never executes a completer script. A strict entry that declares `Targets` and carries a `Hash` that matches its script is not parsed: its declared `Targets` are registered as they are, and the parse errors, the literal-argument check, and the comparison with the script's targets are skipped, because the export that wrote the `Hash` ran them against the same text. Every other check above still runs. An absent, unrecognised, or different `Hash`, or a script that cannot be read for it, falls back to the parse, and a stale `Hash` is not a warning; `-Verbose` writes one line per entry saying how its targets were read and one summary line per set. A hand-edited entry that lists a key twice keeps the first occurrence's casing on this fast path and the last occurrence's casing on the parse path. A trusted entry's `Hash` is ignored at import. The strict grammar still runs on the first tab press, so a wrong `Hash` can at worst leave a `Failed` record, never run a script that was not checked. The set registers as one transaction against one snapshot of the session's registrations: the managed table and the runtime completer dictionaries are read once for validation and registration together, and if any runtime or managed write fails, every change the set made is rolled back, replaced registrations included, so nothing from the set stays registered. The returned objects are the `CompleterActions.CompleterRegistration` records for the set's targets, the same records `Get-Completer` returns. LAZY LOADING, PENDING, AND FAILED Registering a set does not run the scripts it lists. Each target gets a small stub in the runtime completer table and a managed record whose `State` is `Pending`. The record also carries `ScriptPath`, the script the stub will load, and `Trusted`, the tier it will load under. On the first tab press for a target, inside the ordinary completer call, the stub imports the script through the same machinery as `Import-CompleterScript`, strict or trusted per the record, finds the real script block for its own target, replaces the runtime entry with it, moves the record to `Active`, and delegates that first call to the real block. Later presses hit the real completer directly. A session that never uses a completer never pays for loading it. A script that registers several targets is loaded once: every other Pending target of the same script is swapped in from that same import. When a script registers the same target more than once, the last registration wins, exactly as it does when the script is dot-sourced. If the script fails to load on that first press, the press returns no completions, no error reaches the host, and the prompt is never interrupted. The stub stores the error message on the managed record as `LoadError`, sets `State` to `Failed`, and removes its runtime entry, so later presses on that command get PowerShell's default completion exactly as if no completer had been registered. `Get-Completer` shows the `Failed` record and its `LoadError`; `-State Pending, Failed` selects both. Only the target whose press failed moves to `Failed`; the script's other targets stay `Pending` and fail the same way on their own first press. `Reset-Completer` retries the load without registering anything again: it returns the record to `Pending`, and the next tab press loads the script. Registering the script again with `-Force`, or importing the set again with `-Force`, also retries it. The full state list is therefore: - `Pending` registered lazily and not loaded yet - `Active` the record describes the live runtime value - `Failed` the script failed to load; see `LoadError` - `Stale` a managed record whose runtime value was replaced or removed outside this module - `Conflicted` a live runtime value that replaced a managed registration outside this module - `Discovered` a live runtime value that no managed record describes THE PSREADLINE NEUTRALITY PROMISE Nothing the module does alters PSReadLine's default behaviour. Lazy loading runs entirely inside the normal argument completer call that `TabExpansion2` makes. The module never hooks PSReadLine key handlers, never replaces `TabExpansion2`, and never touches PSReadLine options or prediction. `Get-PSReadLineKeyHandler` returns the same handlers before and after `Import-CompleterSet`, and after a failed load, tab on that command behaves exactly as it would with no completer registered. WORKFLOW Build the set once from a completer repository, without registering anything in the current session: Get-ChildItem -Path ~\Completers -Recurse -Filter *_completer.ps1 | Import-CompleterScript | Export-CompleterSet -Path ~\Completers\completers.psd1 Or write it from the registrations a session already has: Export-CompleterSet -Path ~\Completers\completers.psd1 Then replace the import pipeline in the profile with one line: Import-CompleterSet -Path ~\Completers\completers.psd1 Scripts that need the trusted tier are marked once in the set file, with their targets, and stay marked. Run `Test-CompleterScript` over the repository when a script changes: importing the set does not run the grammar, so a strict script that stops conforming still imports as `Pending` and is only reported, as `Failed` with the findings in `LoadError`, on its first tab press. A strict script whose targets change is caught at import when its entry declares `Targets`. Check the set against its folder with `Test-CompleterSet`, for example in the repository's CI. It returns one finding per drift, positioned at the line and column of the set file it concerns: an entry whose script is missing, whose declared `Targets` no longer match its strict script, or whose `Hash` is missing, stale, or malformed, and any completer script that no entry lists. `Error` findings are the ones that would make `Import-CompleterSet` reject the set, and `Warning` findings mark a set that still imports but parses more than it needs to or misses a script. Each finding's `Hint` names the fix, usually regenerating the set with `Export-CompleterSet`, and a current set returns nothing. The command never registers or runs anything: Test-CompleterSet -Path ~\Completers\completers.psd1 EXAMPLE 1 Import a set and inspect what it registered: Import-CompleterSet -Path ~\Completers\completers.psd1 | Format-Table CommandName, ParameterName, CompleterType, State EXAMPLE 2 Import a set that has a broken entry, keeping the rest: Import-CompleterSet -Path ~\Completers\completers.psd1 -SkipInvalid Each invalid entry is written as a warning with its entry number and the reason. EXAMPLE 3 Check what a set would do without changing the session: Import-CompleterSet -Path ~\Completers\completers.psd1 -WhatIf Validation runs in full, so an invalid set still fails here. EXAMPLE 4 Find records that did not load and see why: Get-Completer -State Failed | Format-List CommandName, ScriptPath, LoadError EXAMPLE 5 Retry every record that failed to load, after fixing the scripts: Get-Completer -State Failed | Reset-Completer Each record returns to `Pending` and loads again on its next tab press. A record that cannot be reset, such as one whose script was deleted, is reported as an error, and the others are still reset. SEE ALSO Get-Help Import-CompleterSet -Full Get-Help Export-CompleterSet -Full Get-Help Test-CompleterSet -Full Get-Help Register-Completer -Full Get-Help Reset-Completer -Full Get-Help Get-Completer -Full Get-Help Test-CompleterScript -Full about_Import_Completers about_CompleterActions_Migration |