Actions/Target/SSH.ps1

<#
.SYNOPSIS
Target action: evaluate resources against a remote computer over SSH (#57 §4).
 
.DESCRIPTION
Opens a PSSession over SSH (PowerShell 7+ SSH remoting). SSH has no CIM-over-SSH transport, so
this target is only usable with the DscV3 engine (which reaches its remote target through
Invoke-Command over a PSSession, not a CimSession) - fails fast, before opening a connection,
when paired with the DscV2 engine so the mismatch is reported clearly rather than as a
mysterious later failure.
 
Proved against a real sshd by Tests/PipelineRunner/DSCConfiguration/Integration/
SSHTarget.Integration.tests.ps1, which runs on the hosted Ubuntu agent: it registers a
'powershell' subsystem in sshd_config, authorises the agent account's own key, and connects the
agent to itself, so the parameter hashtable below is accepted by the real New-PSSession and the
session it returns carries the shipped DscV3 engine action to the far side. The unit suite beside
it still mocks New-PSSession and covers dispatch and the DscV2 fail-fast guard.
 
The target's sshd must have that subsystem registered - a host that accepts an ssh login but has
no 'powershell' subsystem refuses the subsystem request, and New-PSSession fails on a machine that
is plainly reachable.
 
Authentication is the runner account's own: the runner passes only ComputerName, Engine and
Credential to a Target action, so UserName/KeyFilePath above are never populated from a 'target'
block today and SSH falls back to the running account's ~/.ssh configuration. On an agent that is
the service account's profile, not the profile of whoever configured the machine, and that
account's public key has to be authorised on the target.
 
.PARAMETER Context
Hashtable with:
  ComputerName [string] - required, the remote computer name or IP (passed as -HostName).
  Engine [string] - the resolved engine name, used only for the DscV2 fail-fast check.
  UserName [string] - optional SSH username.
  KeyFilePath [string] - optional path to an SSH private key.
 
.OUTPUTS
[hashtable] @{ ComputerName; IsRemote = $true; CimSession = $null; PSSession }
#>

param(
    [hashtable]$Context = @{}
)

if ([string]::IsNullOrWhiteSpace([string]$Context.ComputerName)) {
    throw "[Actions/Target/SSH] 'ComputerName' is required in the Target context."
}

if ([string]$Context.Engine -eq 'DscV2') {
    throw "[Actions/Target/SSH] SSH remoting requires the DscV3 engine (there is no CIM-over-SSH transport for DscV2/Invoke-DscResource); the resolved engine was 'DscV2'."
}

$sessionParams = @{ HostName = [string]$Context.ComputerName; SSHTransport = $true }
if ($Context.UserName)    { $sessionParams.UserName = [string]$Context.UserName }
if ($Context.KeyFilePath) { $sessionParams.KeyFilePath = [string]$Context.KeyFilePath }

$psSession = New-PSSession @sessionParams

return @{
    ComputerName = [string]$Context.ComputerName
    IsRemote     = $true
    CimSession   = $null
    PSSession    = $psSession
}