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