Public/Get-SshdConfiguration.ps1

#!/usr/bin/env pwsh
Set-StrictMode -Version Latest

function Get-SshdConfiguration {
    <#
    .SYNOPSIS
        Gets the effective SSH daemon configuration as objects.

    .DESCRIPTION
        Runs sshd -T and returns one object per line of the dump. sshd -T resolves
        Include files, Match blocks, and defaults into the settings the daemon
        actually enforces, so the result reflects what runs rather than what any one
        file says. Keywords are lowercase, as sshd prints them. A keyword that can
        repeat, such as hostkey, returns one object per value.

        sshd -T reads the host private keys, so it usually needs root or an elevated
        session. Throws with sshd's error output when sshd fails, for example when the
        host keys can't be read or the configuration has a syntax error. When sshd
        reports a missing privilege separation directory, such as /run/sshd, the error
        explains how to create it.

    .PARAMETER ConnectionSpec
        Evaluates Match blocks for a described connection, in the sshd -C format, for
        example 'user=root,host=10.0.0.5,addr=10.0.0.5'. Without it, the result covers
        the global configuration only.

    .INPUTS
        None. You can't pipe objects to this function.

    .OUTPUTS
        System.Management.Automation.PSCustomObject. One object per setting with
        Keyword and Value.

    .EXAMPLE
        Get-SshdConfiguration | Where-Object -Property Keyword -EQ 'permitrootlogin'

        Shows whether root can log in.

    .EXAMPLE
        Get-SshdConfiguration -ConnectionSpec 'user=admin,host=10.0.0.5,addr=10.0.0.5' |
            Where-Object -Property Keyword -In 'permitrootlogin', 'passwordauthentication', 'x11forwarding'

        Shows the authentication settings that apply to one connection, after Match
        blocks.

    .EXAMPLE
        Get-SshdConfiguration | Where-Object -Property Keyword -EQ 'hostkey' | Select-Object -ExpandProperty Value

        Lists every host key file.
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(HelpMessage = 'Connection to evaluate Match blocks for, such as user=root,host=10.0.0.5,addr=10.0.0.5.')]
        [ValidateNotNullOrEmpty()]
        [string]$ConnectionSpec
    )

    # Debian keeps sshd in /usr/sbin, which is not on a regular user's PATH.
    $sshd = @(Get-Command -Name sshd, /usr/sbin/sshd -CommandType Application -ErrorAction SilentlyContinue)
    if ($sshd.Count -eq 0) {
        throw 'Get-SshdConfiguration requires sshd, the OpenSSH server, which was not found.'
    }

    $sshdArguments = @(
        '-T'
        if ($PSBoundParameters.ContainsKey('ConnectionSpec')) {
            '-C', $ConnectionSpec
        }
    )

    $dump = (& $sshd[0].Source @sshdArguments 2>&1)
    if ($LASTEXITCODE -ne 0) {
        $sshdError = $dump -join ' '

        # The ssh service creates this directory when it starts, so it is missing where sshd has never run, as in WSL.
        if ($sshdError -match 'Missing privilege separation directory: (\S+)') {
            $privilegeDirectory = $Matches[1]
            $guidance = @"
sshd -T requires $privilegeDirectory, which the ssh service creates when it starts.
Start the service, then retry:

    sudo systemctl start ssh # Debian, Ubuntu
    sudo systemctl start sshd # RHEL, Fedora, Arch

Or create the directory without starting the service:

    sudo mkdir -p $privilegeDirectory
"@

            throw $guidance
        }

        throw "sshd -T failed (exit $LASTEXITCODE): $sshdError"
    }

    foreach ($line in $dump) {
        $keyword, $value = "$line".Split(' ', 2)
        [PSCustomObject]@{
            Keyword = $keyword
            Value   = [string]$value
        }
    }
}