src/public/Remote/New-AitherPSSession.ps1

#Requires -Version 7.0

<#
.SYNOPSIS
    Create a PowerShell remoting session to a remote computer

.DESCRIPTION
    Creates a PowerShell remoting session (PSSession) to a remote computer using WinRM
    (Windows Remote Management) or SSH transport. Sessions can be reused for multiple
    commands, making them more efficient than one-off remote commands.

    This cmdlet supports both Windows (WinRM) and Linux/macOS (SSH) targets, making it
    ideal for cross-platform automation scenarios.

.PARAMETER ComputerName
    Target computer name or IP address. This parameter is REQUIRED.

    Examples:
    - "server01.example.com"
    - "192.168.1.100"
    - "server01.example.com,server02.example.com" (multiple computers)

.PARAMETER Credential
    PSCredential object for authentication. This parameter is REQUIRED unless you have
    existing credential access configured.

    Use Get-Credential to create a credential object, or use CredentialName to reference
    a stored credential.

.PARAMETER CredentialName
    Name of a stored credential (if using credential management system).
    Alternative to providing Credential directly.

.PARAMETER UseSSH
    Use SSH transport instead of WinRM. Required for Linux/macOS targets or when SSH
    is preferred over WinRM. When enabled, PowerShell remoting uses SSH as the transport.

.PARAMETER Port
    Port number for the connection. Defaults to:
    - 5985 for WinRM (HTTP)
    - 5986 for WinRM HTTPS (when UseSSL is specified)
    - 22 for SSH

.PARAMETER UseSSL
    Use SSL/TLS for WinRM connection (HTTPS). More secure than plain HTTP WinRM.
    When enabled, uses port 5986 by default.

.PARAMETER SessionName
    Optional name for the session. Useful for identifying sessions when managing multiple connections.

.PARAMETER ConfigurationName
    PowerShell session configuration name. Defaults to "Microsoft.PowerShell" for Windows
    or "PowerShell" for SSH.

.PARAMETER ThrottleLimit
    Maximum number of concurrent operations. Default is 32.

.INPUTS
    System.String
    You can pipe computer names to New-AitherPSSession.

.OUTPUTS
    System.Management.Automation.Runspaces.PSSession
    Returns a PSSession object that can be used with Invoke-Command.

.EXAMPLE
    $session = New-AitherPSSession -ComputerName "server01" -Credential (Get-Credential)

    Creates a WinRM session to server01 using prompted credentials.

.EXAMPLE
    $session = New-AitherPSSession -ComputerName "linux-server" -Credential (Get-Credential) -UseSSH

    Creates an SSH-based session to a Linux server.

.EXAMPLE
    $session = New-AitherPSSession -ComputerName "server01" -CredentialName "DomainAdmin" -UseSSL

    Creates a secure HTTPS WinRM session using a stored credential.

.EXAMPLE
    "server1", "server2", "server3" | New-AitherPSSession -CredentialName "Admin"

    Creates sessions to multiple servers by piping computer names.

.EXAMPLE
    $session = New-AitherPSSession -ComputerName "server01" -Credential (Get-Credential) -SessionName "Production"

    Creates a named session for easier identification.

.NOTES
    Requirements:
    - Windows targets: WinRM must be enabled and configured
    - Linux/macOS targets: SSH server must be running, PowerShell must be installed
    - Appropriate network connectivity and firewall rules

    Security:
    - Always use SSL/TLS (UseSSL) for production WinRM connections
    - Verify host keys when using SSH
    - Use least-privilege credentials
    - Close sessions when done using Remove-AitherPSSession

.LINK
    Get-AitherPSSession
    Remove-AitherPSSession
    Save-AitherPSSession
    Connect-AitherPSSession
    Invoke-AitherRemoteCommand
#>

function New-AitherPSSession {
    [OutputType([System.Management.Automation.Runspaces.PSSession])]
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName, HelpMessage = "Target computer names or IP addresses.")]
        [ValidateNotNullOrEmpty()]
        [string[]]$ComputerName,

        [Parameter(HelpMessage = "Credential to use for the connection.")]
        [System.Management.Automation.PSCredential]$Credential,

        [Parameter(HelpMessage = "Name of a stored credential to use.")]
        [string]$CredentialName,

        [Parameter(HelpMessage = "Use SSH for connection (Linux/macOS targets).")]
        [switch]$UseSSH,

        [Parameter(HelpMessage = "Custom port for the connection.")]
        [int]$Port,

        [Parameter(HelpMessage = "Use SSL/HTTPS for WinRM connection.")]
        [switch]$UseSSL,

        [Parameter(HelpMessage = "Friendly name for the session.")]
        [string]$SessionName,

        [Parameter(HelpMessage = "PowerShell session configuration name.")]
        [string]$ConfigurationName,

        [Parameter(HelpMessage = "Maximum number of concurrent operations.")]
        [int]$ThrottleLimit = 32,

        [Parameter(HelpMessage = "Show command output in console.")]
        [switch]$ShowOutput
    )

    begin {
        # Manage logging targets for this execution
        $originalLogTargets = $script:AitherLogTargets
        if ($ShowOutput) {
            if ($script:AitherLogTargets -notcontains 'Console') {
                $script:AitherLogTargets += 'Console'
            }
        }
        else {
            # Ensure Console is NOT in targets if ShowOutput is not specified
            $script:AitherLogTargets = $script:AitherLogTargets | Where-Object { $_ -ne 'Console' }
        }

        # Get credential if CredentialName provided
        if ($CredentialName -and -not $Credential) {
            try {
                $Credential = Get-AitherCredential -Name $CredentialName
            }
            catch {
                Write-AitherLog -Level Warning -Message "Could not retrieve credential '$CredentialName'. You may need to provide Credential directly." -Source 'New-AitherPSSession' -Exception $_
            }
        }
    }

    process {
        try {
            foreach ($computer in $ComputerName) {
                try {
                    $sessionParams = @{
                        ComputerName  = $computer
                        ThrottleLimit = $ThrottleLimit
                    }
                    if ($Credential) {
                        $sessionParams.Credential = $Credential
                    }
                    if ($UseSSH) {
                        $sessionParams.SSHTransport = $true
                        if ($Port) {
                            $sessionParams.Port = $Port
                        }
                        elseif (-not $Port) {
                            $sessionParams.Port = 22
                        }
                    }
                    else {
                        # WinRM
                        if ($UseSSL) {
                            $sessionParams.UseSSL = $true
                            if ($Port) {
                                $sessionParams.Port = $Port
                            }
                            else {
                                $sessionParams.Port = 5986
                            }
                        }
                        elseif ($Port) {
                            $sessionParams.Port = $Port
                        }
                        else {
                            $sessionParams.Port = 5985
                        }
                    }
                    if ($SessionName) {
                        $sessionParams.Name = $SessionName
                    }
                    if ($ConfigurationName) {
                        $sessionParams.ConfigurationName = $ConfigurationName
                    }
                    if ($PSCmdlet.ShouldProcess($computer, "Create PSSession")) {
                        Write-AitherLog -Level Information -Message "Creating PSSession to $computer" -Source $PSCmdlet.MyInvocation.MyCommand.Name -Data @{
                            UseSSH = $UseSSH
                            UseSSL = $UseSSL
                            Port   = $sessionParams.Port
                        }

                        $session = New-PSSession @sessionParams -ErrorAction Stop

                        Write-AitherLog -Level Information -Message "PSSession created successfully: $($session.Id)" -Source $PSCmdlet.MyInvocation.MyCommand.Name -Data @{
                            SessionId    = $session.Id
                            ComputerName = $session.ComputerName
                            State        = $session.State
                        }

                        Write-Output $session
                    }
                }
                catch {
                    # Use centralized error handling
                    $errorScript = Join-Path $PSScriptRoot '..' 'Private' 'Write-AitherError.ps1'
                    if (Test-Path $errorScript) {
                        . $errorScript -ErrorRecord $_ -CmdletName $PSCmdlet.MyInvocation.MyCommand.Name -Operation "Creating PSSession to $computer" -Parameters $PSBoundParameters -ErrorAction Continue
                    }
                    else {
                        # Fallback error handling
                        $errorObject = [PSCustomObject]@{
                            PSTypeName   = 'AitherZero.Error'
                            Success      = $false
                            ErrorId      = [System.Guid]::NewGuid().ToString()
                            Cmdlet       = $PSCmdlet.MyInvocation.MyCommand.Name
                            Operation    = "Creating PSSession to $computer"
                            Error        = $_.Exception.Message
                            ComputerName = $computer
                            Timestamp    = Get-Date
                        }
                        Write-Output $errorObject

                        Write-AitherLog -Level Error -Message "Failed to create PSSession to $computer : $($_.Exception.Message)" -Source $PSCmdlet.MyInvocation.MyCommand.Name -Exception $_
                    }

                    # Continue with next computer instead of throwing
                    Write-AitherLog -Level Error -Message "Failed to create session to $computer : $_" -Source 'New-AitherPSSession' -Exception $_
                }
            }
        }
        finally {
            # Restore original log targets
            $script:AitherLogTargets = $originalLogTargets
        }
    }

}