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, [Parameter()][String]$CertificateThumbprint, ## Use if debugging. This bypasses running the ActionRequest in an RS Job, and runs it locally instead. [Parameter()][Bool]$AllowDirectExecution = $False, [Parameter()][String]$WebSocketServer, [Parameter()][Int32]$WebSocketPort ) 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 } Write-Verbose -Message "Starting TMConsole WebSocketClient 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 = @{ Hostname = $($PowerShellServerSettings.Hostname.toString()) Port = $($PowerShellServerSettings.Port.toString()) Verbose = $Verbose OutputVerbose = $OutputVerbose AllowDirectExecution = $AllowDirectExecution AllowInsecureSSL = $AllowInsecureSSL } ## 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 1 } Write-Verbose "WebSocketClient has ended in the TMDUserSession PSSession" } } 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" } } |