en-US/about_CompleterActions_Migration.help.txt

TOPIC
    about_CompleterActions_Migration

SYNOPSIS
    Explains what changed in CompleterActions 3.0.0 and how to move a 2.x
    profile or script to it: the removed 1.x names, the public .NET record
    types, the PowerShell 7.4 engine floor, and the strict grammar at
    registration and import. A closing section lists what changed in
    CompleterActions 2.0.0 for a reader still moving from 1.x.

LONG DESCRIPTION

    CompleterActions 3.0.0 is the breaking release the 2.x line announced.
    Four things change for a caller:

    - The 1.x names are removed. Get-CompleterRegistration,
      Register-CompleterRegistration, and Unregister-CompleterRegistration,
      which 2.x kept as deprecated aliases, are gone, together with the
      three legacy wrappers that carried them, the -ManagedOnly and
      -DiscoveredOnly switches of the Get wrapper, and the once-per-process
      deprecation warning. The module exports eleven functions and no
      aliases.
    - The records are public .NET types. CompleterRegistration,
      ImportedCompleterRegistration, CompleterScriptFinding, CompletionMatch,
      and the CompleterState and CompleterType enums live in the
      CompleterActions namespace of a compiled assembly,
      CompleterActions.Core.dll, that the manifest loads. A type literal such
      as [CompleterActions.CompleterRegistration] works from any script.
    - The minimum engine is PowerShell 7.4. The manifest declares
      PowerShellVersion = '7.4'.
    - The strict grammar also runs at registration and import. A strict
      script that does not conform is refused by Register-Completer -Lazy
      and reported as an invalid entry by Import-CompleterSet, instead of
      registering as Pending and failing on its first tab press.

    Two more things move into the compiled assembly without changing what a
    caller sees: the import-time engine probe with the reflection into the
    engine's completer tables, and the walk of the strict grammar. The
    probe's message, every finding of Test-CompleterScript, and every
    warning, error, and verbose line of the eleven commands are the same as
    in 2.2.0, apart from the two grammar errors described below.

    Nothing else changes. Every command keeps its name, parameters, and
    parameter sets. Completer set files keep their schema: a set written by
    2.2.0 imports on 3.0.0 with the same records, and a set written by
    3.0.0 imports on 2.2.0. Completer scripts need no change.

REMOVED COMMANDS

    | 2.x | 3.0 |
    | ---------------------------------------- | ----------------------------- |
    | Get-CompleterRegistration (alias) | Get-Completer |
    | Register-CompleterRegistration (alias) | Register-Completer |
    | Unregister-CompleterRegistration (alias) | Unregister-Completer |
    | Get-CompleterRegistrationLegacy | Get-Completer |
    | Register-CompleterRegistrationLegacy | Register-Completer |
    | Unregister-CompleterRegistrationLegacy | Unregister-Completer |
    | -ManagedOnly | -State Active, Pending, |
    | | Failed, Stale |
    | -DiscoveredOnly | -State Discovered, Conflicted |
    | the deprecation warning | nothing is written |

    Calling an old name fails with PowerShell's own
    CommandNotFoundException:

        The term 'Get-CompleterRegistration' is not recognized as a name of
        a cmdlet, function, script file, or executable program.

    That is what a session sees once 3.0 is the only CompleterActions on
    PSModulePath. While a 2.x copy is still installed beside it, command
    discovery autoloads that 2.x module for the old name instead, so the
    call succeeds against 2.x and the session then holds both versions.
    Uninstall the 2.x copies you no longer pin before relying on the error:

        Get-InstalledPSResource CompleterActions |
            Where-Object Version -lt 3.0.0 |
            Uninstall-PSResource

    The module adds no message of its own, because a module cannot intercept
    a name it does not export. The three *Legacy functions existed only so
    the aliases had an exported target, and they go with them. -ManagedOnly
    and -DiscoveredOnly were accepted only by the legacy Get wrapper;
    Get-Completer never had them.

    Before:

        Import-CompleterScript -Path .\git_completer.ps1 |
            Register-CompleterRegistration
        Get-CompleterRegistration -ManagedOnly
        Get-CompleterRegistration -DiscoveredOnly
        Unregister-CompleterRegistration -CommandName git -Native

    After:

        Import-CompleterScript -Path .\git_completer.ps1 |
            Register-Completer
        Get-Completer -State Active, Pending, Failed, Stale
        Get-Completer -State Discovered, Conflicted
        Unregister-Completer -CommandName git -Native

    The new names take the parameters the aliases forwarded, so the rename
    is the whole change. A 2.x profile that already uses the new names
    needs nothing from this section.

PUBLIC TYPES

    The four records and the two enums are public .NET types in the
    CompleterActions namespace:

    - [CompleterActions.CompleterRegistration]
    - [CompleterActions.ImportedCompleterRegistration]
    - [CompleterActions.CompleterScriptFinding]
    - [CompleterActions.CompletionMatch]
    - [CompleterActions.CompleterState]: Active, Stale, Conflicted, Pending,
      Failed, Discovered
    - [CompleterActions.CompleterType]: Native, Parameter

    Property names, property order, enum names, and enum values are the
    ones 2.x declared, so Select-Object *, Format-List, and ConvertTo-Csv
    show the same columns in the same order. In 2.x the records were
    PowerShell classes private to the module. Eight things differ for a
    caller:

    1. -is and ::new() work everywhere. Once the module is imported,
       `$record -is [CompleterActions.CompleterRegistration]` is $true at
       the prompt and in any script, and
       `[CompleterActions.CompleterRegistration]::new()` builds a record. In
       2.x both resolved only inside the module. The type literal resolves
       as long as the assembly is loaded in the process, so it keeps working
       after Remove-Module.

    2. PSTypeNames loses the bare class name. A record's PSTypeNames is
       CompleterActions.CompleterRegistration, System.Object. In 2.x it was
       CompleterActions.CompleterRegistration, CompleterRegistration,
       System.Object, because the PowerShell class carried its own bare name
       and inserted the dotted one in front. The dotted name is still first,
       so the format views apply unchanged,
       `$record.PSTypeNames[0] -eq 'CompleterActions.CompleterRegistration'`
       is still $true, and a deserialized record is still
       Deserialized.CompleterActions.CompleterRegistration. Only a check for
       the bare name 'CompleterRegistration' in PSTypeNames stops matching.

    3. String properties are '' when they have no value. Every string
       property of the four types starts as '' and stores '' when $null is
       assigned, so ParameterName on a native record, ScriptPath and
       LoadError on a record without a script, and ToolTip on a completion
       match are '' rather than $null. 2.x gave the same '' on the records
       the module returned; 3.0 extends it to an instance you build with
       ::new(), whose strings were $null in 2.x. ImportModule and
       ScriptBlock stay $null until they are set.

    4. Enum values serialize as integers, as they did in 2.x. ConvertTo-Json
       writes "State": 3 for Pending and Export-Clixml writes the integer.
       Pass -EnumsAsStrings to ConvertTo-Json, or store `[string] $_.State`,
       when a script persists records. Comparisons with strings keep
       working: `$_.State -eq 'Pending'`, `Where-Object State -eq Pending`,
       `-State Pending`, and tab completion of -State values all work,
       because the parameter is typed [CompleterActions.CompleterState[]].

    5. The enums are usable from a script.
       `[enum]::GetNames([CompleterActions.CompleterState])` and
       `[CompleterActions.CompleterState]::Failed` work anywhere. In 2.x
       they needed `& (Get-Module CompleterActions) { [CompleterState] }`,
       and that form no longer resolves: the bare names are gone inside the
       module too.

    6. Hashtable conversion works.
       `[CompleterActions.CompleterScriptFinding] @{ Path = 'x'; Line = 1 }`
       builds a finding through its property setters, as the module's own
       helpers do.

    7. OutputType names are unchanged.
       `(Get-Command Get-Completer).OutputType.Name` is still
       CompleterActions.CompleterRegistration, and the other commands keep
       their 2.x output type names.

    8. Remoting and jobs are unchanged. Across a remoting or job boundary a
       record arrives deserialized, as in 2.x: its type name is
       Deserialized.CompleterActions.CompleterRegistration and State is a
       plain string such as 'Pending', so `$_.State -eq 'Pending'` still
       holds on the receiving side.

    The assembly is CompleterActions.Core.dll, in the lib folder of the
    module, and the manifest loads it through RequiredAssemblies before the
    module's script runs. It exports no cmdlet. Its AssemblyVersion is
    3.0.0.0 for every 3.x release, so a type keeps its identity across
    minor releases; the file version and informational version carry the
    module version. An assembly cannot be unloaded from a process: after
    an update, start a new pwsh session to load the new
    CompleterActions.Core.dll. The loaded version is
    `[CompleterActions.CompleterRegistration].Assembly.GetName().Version`.

    The assembly also holds the engine access and the strict grammar, in
    the CompleterActions.Internal namespace. Those types are public only
    because PowerShell cannot call internal members. They are not a
    contract and may change in any 3.x release; do not call them from a
    script.

ENGINE FLOOR

    The manifest declares PowerShellVersion = '7.4', and
    CompatiblePSEditions stays @('Core'). On PowerShell 7.0 to 7.3,
    Import-Module CompleterActions fails with PowerShell's own minimum
    version error before any module file runs. The CI matrix has started at
    7.4 since 1.3.0, so a tested engine sees no change.

    A machine that must stay on an older engine keeps CompleterActions
    2.2.0, for example with
    `Install-PSResource CompleterActions -Version 2.2.0` and
    `Import-Module CompleterActions -RequiredVersion 2.2.0`.

    The engine probe that runs at import keeps its message. On an engine
    whose internals changed, Import-Module still fails with one error that
    names the running PowerShell version and the members it could not
    resolve:

        CompleterActions cannot run on PowerShell <version>: the required
        runtime member(s) '<list>' could not be resolved. Completer
        discovery depends on PowerShell internals; check for a module update
        that supports this engine version.

    What moved is where that check lives. The compiled layer resolves each
    engine member once at import and keeps it, so a member that resolved at
    import cannot fail to resolve later in the session.

THE GRAMMAR AT REGISTRATION AND IMPORT

    In 2.x the strict grammar ran only when a script loaded, on the first
    tab press for one of its targets, because walking every script at
    import cost about 20 ms each. The compiled walk is cheap enough to run
    at import, so 3.0 also runs it wherever a strict script is already
    parsed for its targets. The first-tab check stays exactly as it was: a
    script that changes between registration and its first tab press is
    still checked in its changed form before it runs.

    Register-Completer -Lazy. Without -Trusted, the targets are read from
    the parsed script, and the grammar now runs on that parse. A script that
    does not conform fails the call with the error Import-CompleterScript
    gives, one line per finding, and nothing is registered for any of its
    targets. The check runs before -WhatIf is honoured, so -WhatIf fails the
    same way:

        Completer script 'C:\Completers\tool_completer.ps1' does not conform
        to the strict import grammar. Run Test-CompleterScript to work
        through the findings, or import with -Trusted to run the script
        as-is.
        Line 1, column 1 (CommandAst): The script uses unsupported top-level
        command 'Get-Date'. ...

    Import-CompleterSet. A strict entry that does not take the hash fast
    path is parsed for its targets, as in 2.x, and the grammar now runs on
    that parse. A script that does not conform is an invalid entry,
    reported from its first finding:

        Entry 4 ('tool_completer/tool_completer.ps1'): The script does not
        conform to the strict import grammar: line 1, column 1
        (CommandAst): The script uses unsupported top-level command
        'Get-Date'. Run Test-CompleterScript to work through the findings,
        or mark the entry Trusted to run it as-is.

    It takes the path of every invalid entry. Without -SkipInvalid the set
    fails and nothing is registered. With -SkipInvalid the entry is skipped
    with the usual "Completer set '<path>' skipped Entry <n> ..." warning
    and the other entries register. When the same entry also declares
    Targets that the script does not match, both problems are reported,
    the grammar first.

    What does not change:

    - An entry whose Hash matches its script takes the fast path. It is
      neither parsed nor walked at import, because the export that wrote
      the Hash derived its targets from the same text, and the first tab
      press runs the grammar before the script executes. A fully hashed
      set of unchanged scripts imports with exactly the work it did in
      2.2.0; only a new or changed strict script pays for the walk.
    - Trusted entries and -Trusted registrations are never walked.
    - Test-CompleterSet reports drift, not grammar, and finds what it found
      in 2.2.0. Test-CompleterScript stays the grammar gate for a
      repository.
    - Export-CompleterSet does not run the grammar.

    What a profile sees: a set that in 2.x imported a non-conforming strict
    script as a Pending record, which turned Failed on its first tab press,
    now reports that entry when the profile runs. A profile that imports
    with -SkipInvalid gets one warning for the entry at session start and
    every other entry registers as before. A profile that imports without
    -SkipInvalid fails the whole set until the script is fixed, marked
    Trusted in the set, or removed from it. A repository whose scripts all
    pass Test-CompleterScript sees no difference.

CHECKLIST FOR A PROFILE

    1. Run PowerShell 7.4 or later, or pin CompleterActions 2.2.0. Uninstall
       the 2.x copies you no longer pin; while one is installed, an old name
       autoloads it instead of failing.
    2. Replace Get-CompleterRegistration, Register-CompleterRegistration,
       and Unregister-CompleterRegistration with Get-Completer,
       Register-Completer, and Unregister-Completer, and any *Legacy
       function call with the same new name.
    3. Replace -ManagedOnly with -State Active, Pending, Failed, Stale and
       -DiscoveredOnly with -State Discovered, Conflicted, or with the
       narrower state you meant.
    4. Replace PSTypeNames checks for the bare name 'CompleterRegistration'
       with the dotted name or with
       `-is [CompleterActions.CompleterRegistration]`, and replace
       `& (Get-Module CompleterActions) { [CompleterState] }` with
       [CompleterActions.CompleterState].
    5. Run Test-CompleterScript over the completer repository and fix every
       Error finding, or mark the script Trusted in the set. A
       non-conforming strict script is now reported at import instead of
       failing at its first tab press.
    6. Keep -SkipInvalid on the Import-CompleterSet line if one bad entry
       should not stop the rest of the set from registering.
    7. Start a new pwsh session after updating, so the new
       CompleterActions.Core.dll is the one loaded.

    Completer set files and conforming completer scripts need no change.

MOVING FROM 1.x

    A 1.x profile moves through 2.0 first. 2.0.0 renamed the three commands
    with the CompleterRegistration noun, and 3.0 removed the old names:

    | 1.x | 2.0 and 3.0 |
    | -------------------------------- | ---------------------- |
    | Get-CompleterRegistration | Get-Completer |
    | Register-CompleterRegistration | Register-Completer |
    | Unregister-CompleterRegistration | Unregister-Completer |

    Three more changes from 2.0.0 apply to a 1.x profile. The 2.0 edition
    of this topic, shipped with every 2.x release, covered them in full,
    so a profile that already runs on 2.x was migrated by it:

    - Targets are explicit. -Key is gone; name a target with -CommandName
      plus -Native or -ParameterName, or pipe a record that carries
      IsNative. Keys are output-only identifiers.
    - One -State filter replaced -ManagedOnly and -DiscoveredOnly, and a
      runtime registration that no managed record describes reports State
      'Discovered' instead of 'Active'.
    - The records are typed: State and CompleterType are enums, empty
      string properties are '' instead of $null, and Get-Completer sorts
      by CompleterType, CommandName, and ParameterName before paging.

SEE ALSO

    Get-Help Get-Completer -Full
    Get-Help Register-Completer -Full
    Get-Help Unregister-Completer -Full
    about_Import_Completers
    about_Completer_Sets