Private/Resolve-InstanceName.ps1

# SPDX-License-Identifier: AGPL-3.0-or-later
# Copyright (C) 2026 Tyler Vigario

<#
  Registered instance names, and the resolution of a -Name argument against them.

  Registration IS the scheduled task: it is what Start-, Stop-, Restart- and
  Update-GreenroomInstance start, stop, re-run or re-register, and a state directory with no
  task is a remnant none of them can act on. So tasks are the source, read in ONE place --
  these commands had drifted into separate copies of the same enumeration.

  ROOT FOLDER ONLY. Register-GreenroomTask registers without -TaskPath, which is the root,
  and Get-ScheduledTask without -TaskPath searches EVERY folder: a same-named task elsewhere
  -- left behind by a manual import, say -- would otherwise list the instance twice, which
  both breaks the "exactly one" rule and acts on it twice under a wildcard.

  -ErrorAction IGNORE, not SilentlyContinue, on every lookup. On a real host a lookup that
  finds nothing raises a CIM "not found" error, and SilentlyContinue only hides it: the
  record still lands in $Error and in the caller's -ErrorVariable, AHEAD of the error the
  command actually reports. MEASURED: `Stop-GreenroomSession nope -ErrorVariable e` gave
  $e[0] from Get-ScheduledTask ('CmdletizationQuery_NotFound'). For a probe, not found is
  the answer, not a failure, so it should leave nothing behind.
#>

function Get-RegisteredInstanceName {
    [CmdletBinding()]
    [OutputType([string])]
    param()

    @(Get-ScheduledTask -TaskPath '\' -TaskName 'greenroom-*' -ErrorAction Ignore |
      ForEach-Object { $_.TaskName -replace '^greenroom-', '' } | Sort-Object -Unique)
}

<#
  Turn a -Name argument into the instance names a session command acts on.

    no name exactly one registered instance, or an error listing them
    wildcards every registered instance that matches, sorted; a warning if none do
    otherwise that instance, if its task exists

  Instance names cannot contain wildcard characters -- ValidatePattern allows only letters,
  digits, dot, dash and underscore -- so a pattern can never be mistaken for a name.

  ERRORS AND WARNINGS ARE WRITTEN THROUGH THE CALLER'S $PSCmdlet. Written from here, they
  were attributed to this private function: `Stop-GreenroomSession nope` reported
  "Resolve-InstanceName : no scheduled task", with an error id naming a function the operator
  cannot see or call. Through the caller they name the command that was typed, and the
  caller's -ErrorAction and -WarningAction decide what happens to them.
#>

function Resolve-InstanceName {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [string]$Name,
        [Parameter(Mandatory)][System.Management.Automation.PSCmdlet]$Cmdlet
    )

    if ($Name -and -not [System.Management.Automation.WildcardPattern]::ContainsWildcardCharacters($Name)) {
        if (Get-ScheduledTask -TaskPath '\' -TaskName "greenroom-$Name" -ErrorAction Ignore) { return $Name }
        $Cmdlet.WriteError([System.Management.Automation.ErrorRecord]::new(
            [System.Management.Automation.ItemNotFoundException]::new(
                "no scheduled task 'greenroom-$Name' -- is '$Name' installed?"),
            'InstanceNotRegistered', [System.Management.Automation.ErrorCategory]::ObjectNotFound, $Name))
        return
    }

    # @() IS LOAD-BEARING. A function returning a one-element array has it unrolled to a
    # bare string on the way out, and indexing a string returns a CHARACTER: with a single
    # instance named 'probe', $registered[0] was 'p', and the command went on to start a
    # task called 'greenroom-p'. Caught by the no-name test, not by reading it.
    $registered = @(Get-RegisteredInstanceName)

    if (-not $Name) {
        if ($registered.Count -eq 1) { return $registered[0] }
        $Cmdlet.WriteError([System.Management.Automation.ErrorRecord]::new(
            [System.ArgumentException]::new(
                "an instance name is required. Registered: $(if ($registered) { $registered -join ', ' } else { 'none' })"),
            'InstanceNameRequired', [System.Management.Automation.ErrorCategory]::InvalidArgument, $null))
        return
    }

    $matched = @($registered | Where-Object { $_ -like $Name })
    if ($matched.Count -eq 0) { $Cmdlet.WriteWarning("no registered instance matches '$Name'") }
    $matched
}