lib/SessionManager.ps1




<#
    This SessionManager Script handles creating and receiving output from a Session-Based Runspace Environment
    Output received from actions that are being run in this script are sent to TMConsole via WebSocket a websocket

    Messages sent to the WebSocket must be:
        - An JSON string
        - The Json Object must have the following properties:
            - TMTaskId: <int> This is the task.taskNumber (not the taskId)
            - Type:
                One of [
                    'Started' - Expressly creates a 'TaskCache' object which stores the output of a given Task's Action
                        ** Note - The receiving handler does not expressly require a 'Started' token, if any other types are received first
                                  And one doesn't already exist, one is created to store the other Type-d data.

                    'Info' - A 'Write-Host' message from the 'info' stream
                    'Progress' - An 'Activity Progress' message from the 'Progress' stream
                    'Failed' - An Error bearing token with a 'Message' property which is displayed in the console
                    'Completed' - A token with a simply indicating that the task is complete and all progress should reflect this

                ]

    Among others. There is "Info", "Started", "Failed", "Progress" and "Completed"
    Each of the JSON representations are of the associated PowerShell Primative Classes that are emitted by the the script using:

    - Started:
        Psuedo message, not actually created by PowerShell, it's written to the StdOut as early as possible (when this script gets the ID for the first time

    - Info: Write-Host or any pipeline output (Get-Date, for example)

    - Progress: Write-Progress
        ActivityID (Id)
        ParentActivityId
        Activity
        CurrentOpperation
        Status
        PercentComplete
        SecondsRemaining
        RecordType (1 = running, 0=Completed)

    - Errors Writtn as "Failed"
        - Errors Thrown from Infrastructure problems (Session issues, network, etc)
        - TM Action Scripts are run by the user are in a try catch and reported this way

    - Completed:
        Occurs when the Action Script provided by TM is completed, with no errors

    --- TODO
    - Verbose
        Write-Verbose output should be displayed in TMD as well (with a UI switch)

 #>



Function Start-TMCSessionManager {
    <#
    .SYNOPSIS
        Executes a new TMConsole PowerShell Server instance to handle

    .NOTES
        Name: Start-TMCSessionManager
        Author: TransitionManager
        Version: 1.0
        DateCreated: 2021-08-21


    .EXAMPLE
        Start-TMCSessionManager -Hostname '127.0.0.1' -Port 8620

    .LINK
        https://support.transitionmanager.net
    #>

    param(
        [Parameter(mandatory = $false)]
        [bool]$OutputVerbose = $false, ## PROD SETTING
        # [bool]$OutputVerbose = $true, ## DEV SETTING

        [Parameter()]$HostPID = -1,

        [Parameter()][Bool]$AllowInsecureSSL = $False,

        ## Use if debugging. This bypasses running the ActionRequest in an RS Job, and runs it locally instead.
        [Parameter()][Bool]$AllowDirectExecution = $False,    ## PROD SETTING
        # [Parameter()][Bool]$AllowDirectExecution = $True, ## DEV SETTING

        [Parameter()]
        [String]$WebSocketServer,

        [Parameter()]
        [Int32]$WebSocketPort,

        [Parameter(Mandatory = $false)]
        [Version]$TMCVersion = '0.0.0'

    )

    Begin {

        ## Enable Verbose Output if OutputVerbose was true
        if ($OutputVerbose) {
            $Global:VerbosePreference = 'Continue'
            $VerbosePreference = 'Continue'
        }

        ## Create the powershell WebSocketServer connection settings
        $PowerShellServerSettings = @{
            Hostname = $WebSocketServer.ToLower()
            Port     = $WebSocketPort
        }

        if ($OutputVerbose) {
            Write-Verbose -Message "Starting TMConsole Server Session {$($PowershellServerSettings.Hostname):$($PowershellServerSettings.Port)}"
            Write-Verbose -Message ($MyInvocation.BoundParameters | ConvertTo-Json)
        }

        ##
        ## Perform Configuration Validation/Setup
        ##

        ## Standardize access to the important Paths
        if ($OutputVerbose) {
            $Global:VerbosePreference = 'Continue'
            Write-Host 'VERBOSE: Checking TMD PsSession Configuration'
        }

        ## Skip creating the Remote Session if it's a local task
        # if (-not $AllowDirectExecution) {
        # # Test the PowerShell Configuration Settings
        # Test-TMDPSSessionConfiguration
        # }

        ##
        ## Begin Running the Session Manager
        ##

        ## Allow the TMD Watcher script to continue and move past any transient errors
        $ErrorActionPreference = 'Continue' ## PRODUCTION = 'Continue'. Setting this to 'Stop' is useful in debugging this script

        ## Enable Verbose (using bool, not switch)
        if ($OutputVerbose) {

            Write-Host "VERBOSE: PSSessionManager is running in Verbose Mode."
            $VerbosePreference = 'Continue'

            ## If Host PID is present, notify that SessionManager is bound to that pid
            if ($HostPID -ne -1) {
                Write-Host "VERBOSE: Bound to TMD PID: $HostPID"
            }
        }
        else {
            $VerbosePreference = 'SilentlyContinue'
        }

        ## Create empty cache object
        $Global:LocalSessionCache = @{
            SessionManagerState = 'Connecting'
            LastJobEndTime      = Get-Date
        }

        ## Skip creating the Remote Session if it's a local task
        if (-not $AllowDirectExecution) {
            ### REAL VALUE, this change is to confirm something
            $TmdUserSession = New-TMDPSSession

            #Check that we have a TMD Session for the user
            if (-not $TmdUserSession) {
                throw "Can't get a PSSession"
            }
        }
    }

    Process {

        ## Write a Verbose Message
        if ($OutputVerbose) { Write-Host 'VERBOSE: Starting Remote Session WebSocketClient' }

        ## Error Action
        $ErrorActionPreference = 'Continue'

        ## Skip creating the Remote Session if it's a local task
        if (-not $AllowDirectExecution) {

            ## Connect to the Session when it's available
            Connect-PSSessionWhenReady $TmdUserSession | Out-Null

            ## Check on the Session Status
            if (-Not $TmdUserSession) {
                Write-Error 'PowerShell Session has errored!'
                $Global:LocalSessionCache.SessionManagerState = 'NotRunning'
            }
        }

        ## Define the settings for the web socket client
        $StartWebSocketClientSplat = @{
            WebSocketServer      = $($WebSocketServer.ToLower())
            WebSocketPort        = $WebSocketPort
            Verbose              = $Verbose
            OutputVerbose        = $OutputVerbose
            AllowDirectExecution = $AllowDirectExecution
            AllowInsecureSSL     = $AllowInsecureSSL
            TMCVersion           = $TMCVersion
        }

        ## Allow Debugging
        if ($AllowDirectExecution) {

            ## Start the WebSocketClient directly
            Start-TMConsoleWebSocketClient @StartWebSocketClientSplat
        }
        else {

            ## Create a Script Block to run in the remote session
            $RemoteSessionScriptBlock = [scriptblock] {
                param($StartWebSocketClientSplat)
                Start-TMConsoleWebSocketClient @StartWebSocketClientSplat
            }

            ## Then Run the Remote Scriptblock in the TMDUserSession
            Write-Verbose "Starting WebSocketClient Connection in the in the TMDUserSession PSSession"

            ## Invoke the Session Manager in the remote session
            $RemotePSSessionManagerJob = Invoke-Command -Session $TmdUserSession -ScriptBlock $RemoteSessionScriptBlock -ArgumentList $StartWebSocketClientSplat -AsJob

            Write-Verbose "WebSocketClient has been started in the TMDUserSession PSSession, Monitoring status until it's no longer running"

            ## Keep this script session alive while the TMDUserSession is still open
            while ($RemotePSSessionManagerJob.JobStateInfo.State -eq 'Running') {
                Start-Sleep -Seconds 5
            }
            Write-Verbose "WebSocketClient has ended in the TMCPSSession"
        }
    }
    End {
        Write-Verbose "TMConsole.SessionManager is ending"

        ## Skip creating the Remote Session if it's a local task
        if (-not $AllowDirectExecution) {
            Write-Verbose "Cleaning up PSSessions"

            ## Disconnect and remove from the session
            if ($IsWindows) {
                Disconnect-PSSession -Session $TmdUserSession | Out-Null
                Remove-PSSession -Session $TmdUserSession | Out-Null
            }
        }
        Write-Verbose "TMConsole.SessionManager has ended"
    }
}