|
| 1 | +--- |
| 2 | +RFC: RFC0063 |
| 3 | +Author: Paul Higinbotham |
| 4 | +Status: Draft |
| 5 | +Area: Microsoft.PowerShell.Core |
| 6 | +Comments Due: 10/22/2021 |
| 7 | +Plan to implement: Yes |
| 8 | +--- |
| 9 | + |
| 10 | +# Custom Remote Connections |
| 11 | + |
| 12 | +PowerShell currently supports six types of remote connections. |
| 13 | + |
| 14 | +- WinRM remote connections |
| 15 | +- SSH remote connections |
| 16 | +- Named pipe local connections |
| 17 | +- Local process connections |
| 18 | +- Hyper-V local VM connections (Windows only) |
| 19 | +- Hyper-V local Container connections (Windows only) |
| 20 | + |
| 21 | +But these connections are implemented internally in the PowerShell engine, and adding new connection types requires modifying the PowerShell engine. |
| 22 | +This RFC is a proposal to add support for optional custom remote connections, so that new remoting connection types can be created and used without having to change PowerShell engine code. |
| 23 | + |
| 24 | +Custom endpoints and listening services are out of scope for this proposal. |
| 25 | +PowerShell remoting does not provide endpoint hosting or connection listeners, and relies on external solutions such as what WinRM and SSH provide. |
| 26 | + |
| 27 | +## Motivation |
| 28 | + |
| 29 | +As a third party contributor or PowerShell team member, I can create a new remoting connection type as a PowerShell module. |
| 30 | +Users can download the module and use it with PowerShell remoting cmdlets. |
| 31 | + |
| 32 | +### Example 1 |
| 33 | + |
| 34 | +PowerShell core used to provide an experimental remoting connection from non-Windows machines to Windows machine WinRM endpoints, through an OMI bridge. |
| 35 | +But it is no longer supported and OMI changes break this connection on many non-Windows platforms. |
| 36 | + |
| 37 | +As a user and community member, I want to create a module that will support and maintain this connection independent of the PowerShell team and without having to submit changes to PowerShell engine code. |
| 38 | + |
| 39 | +```powershell |
| 40 | +PS /Users/Paul> Install-PSResource -Name PSWSManConnection -Repository PSGallery |
| 41 | +PS /Users/Paul> $session = New-PSWSManPSSession -Computer WindowsMachine1 -Credential Domain\UserName |
| 42 | +
|
| 43 | +PowerShell credential request |
| 44 | +Enter your credentials. |
| 45 | +Password for user Domain\UserName: ************** |
| 46 | +
|
| 47 | +PS /Users/Paul> Enter-PSSession $session |
| 48 | +[WindowsMachine1]: PS C:\> Users\UserName> |
| 49 | +``` |
| 50 | + |
| 51 | +This example installs the `PSWSManConnection` module from the PowerShell Gallery onto a macOS machine. |
| 52 | +Next the user runs the module provided `New-PSWSManPSSession` cmdlet to create a `PSSession` session instance using the new connection. |
| 53 | +The `PSSession` object is then used with PowerShell's `Enter-PSSession` cmdlet to interact directly with the remote session from the command line. |
| 54 | + |
| 55 | +### Example 2 |
| 56 | + |
| 57 | +I have created a remote shell solution that uses custom multi-factor authentication, and is installed on all of the machines I am responsible for managing. |
| 58 | +I want to use PowerShell remoting over this custom shell connection. |
| 59 | + |
| 60 | +I can do this by creating a PowerShell module, `MyCustomConnection`, that uses my custom remote shell. |
| 61 | + |
| 62 | +```powershell |
| 63 | +PS /Users/Paul> $sessions = New-MyCustomConnection -Computer $computerList -Credential $creds -UseMFA |
| 64 | +PS /Users/Paul> $results = Invoke-Command -Session $sessions -File ./Scripts/WeeklyManagementTasks.ps1 |
| 65 | +PS /Users/Paul> Invoke-AnalyzeResults $results |
| 66 | +PS /Users/Paul> $sessions | Remove-PSSession |
| 67 | +``` |
| 68 | + |
| 69 | +This example creates a set of remote sessions from a list of computers to be managed, using a connection and authentication from a custom module. |
| 70 | +Once the sessions are created, existing PowerShell core remoting cmdlets are used to invoke script on the sessions and manage them. |
| 71 | +The PowerShell `Invoke-Command` is used to run a weekly management script on all sessions in parallel, and then analyze the returned results. |
| 72 | +Lastly, the sessions to the remote machines are all closed using the PowerShell `Remove-PSSession` cmdlet. |
| 73 | + |
| 74 | +## Design |
| 75 | + |
| 76 | +The design proposal is to allow third parties to create their own custom remoting connections by deriving from and providing custom implementations of the `RunspaceConnectionInfo` and `ClientSessionTransportManagerBase` abstract classes. |
| 77 | +These implementations would be part of a PowerShell binary module, which also includes a cmdlet that returns a live connection instance as a `PSSession` object. |
| 78 | +The `PSSession` object can then be passed to existing PowerShell remoting cmdlets to interact with the session. |
| 79 | +The implementation binary can also include other relevant cmdlets and expose public APIs for creating and managing the custom connection. |
| 80 | + |
| 81 | +### RunspaceConnectionInfo |
| 82 | + |
| 83 | +The `System.Management.Automation.Runspaces.RunspaceConnectionInfo` abstract class is already publicly accessible. |
| 84 | +Derivations of this class are used to define specific connection types, including new properties needed for the connection or authentication. |
| 85 | +A derivation must also provide an implementation for the `CreateClientSessionTransportManager` method, which returns an instance of the client session transport manager for the new connection. |
| 86 | + |
| 87 | +### ClientSessionTransport |
| 88 | + |
| 89 | +The `ClientSessionTransportManagerBase` abstract class is not currently publicly accessible, and will need to be made public. |
| 90 | +Derivations of this class are used to bind live connection data streams to the PowerShell remoting layer. |
| 91 | + |
| 92 | +Once a connection is established, the data streams are bound to internal PowerShell remoting components through a `ClientSessionTransportManager`, which is derived from the public `ClientSessionTransportManagerBase` abstract class. |
| 93 | + |
| 94 | +The derived class will override a few virtual functions, the most important of which is the `CreateAsync` method. |
| 95 | +This method runs immediately before protocol messaging begins, and needs to perform two vital functions: |
| 96 | + |
| 97 | +- Create a text writer wrapper object, that is used to write protocol messages to the target input data stream. |
| 98 | + |
| 99 | +- Start a listening thread for protocol messages from the target output data stream. |
| 100 | + |
| 101 | +### Data Streams |
| 102 | + |
| 103 | +All connection data streams used in the transport must be derived from .NET TextReader or TextWriter classes. |
| 104 | +Connections typically have two data streams, one for writing messages to the target, and one for reading messages from the target. |
| 105 | +However, some connections have a third error stream that require a special reader thread. |
| 106 | +An example would be a Process connection that uses stdOut for reading from the target, stdIn for writing to the client, and stdError for reading connection error messages. |
| 107 | +The stdError messages are not part of the remoting protocol and need to be handled separately. |
| 108 | + |
| 109 | +### Creating a connection |
| 110 | + |
| 111 | +The PowerShell module implementing the new remote connection should supply all necessary cmdlets for creating and managing the connection. |
| 112 | +In particular it should have a cmdlet that returns a `PSSession` object. |
| 113 | + |
| 114 | +```powershell |
| 115 | +PS C:\> Import-Module -Name MyCustomConnection |
| 116 | +PS C:\> New-MyCustomSession -ComputerName remoteVM1 -Cred $myCredentials |
| 117 | +
|
| 118 | +Id Name Transport ComputerName ComputerType State ConfigurationName Availability |
| 119 | + -- ---- --------- ------------ ------------ ----- ----------------- ------------ |
| 120 | + 1 Runspace1 MyCustom remoteVM1 RemoteMachine Opened DefaultShell Available |
| 121 | +``` |
| 122 | + |
| 123 | +However, if the new connection info class is made public accessible, the PowerShell remoting API can also be used to create a connection instance. |
| 124 | + |
| 125 | +```powershell |
| 126 | +PS C:\> Import-Module -Name MyCustomConnection |
| 127 | +PS C:\> $connectionInfo = [MyCustomConnection.CustomConnectionInfo]::new($computerName, $creds) |
| 128 | +PS C:\> $runspace = [runspacefactory]::CreateRunspace($connectionInfo) |
| 129 | +PS C:\> $runspace.Open() |
| 130 | +PS C:\> $runspace |
| 131 | +
|
| 132 | + Id Name ComputerName Type State Availability |
| 133 | + -- ---- ------------ ---- ----- ------------ |
| 134 | + 2 Runspace2 remote-1 Remote Opened Available |
| 135 | +``` |
| 136 | + |
| 137 | +### Endpoints |
| 138 | + |
| 139 | +A remote connection must include a PowerShell session on the target machine, that handles remoting protocol messages from the client. |
| 140 | +This is called the remoting endpoint. |
| 141 | +An endpoint typically involves a service that listens for client connection requests, and securely creates the PowerShell session under the correct account and with correct permissions. |
| 142 | +For example, both WSMan and SSH have services that do this. |
| 143 | +Endpoints such as these are outside the scope of this feature proposal, and it is assumed that any custom connection scheme will include some sort of endpoint support. |
| 144 | + |
| 145 | +PowerShell currently supports a 'server' running mode, where remoting protocol messages are channeled through the process stdIn/stdOut data streams. |
| 146 | +Any custom endpoint solution will leverage this to establish a PowerShell session and handle remoting protocol messages. |
| 147 | +Currently, this is the only way to run PowerShell as an endpoint server, and should be sufficient for any custom solution. |
| 148 | +Also this server mode is available on all supported PowerShell versions (v5.1 - v7.1+). |
| 149 | + |
| 150 | +#### Example - PowerShell in server mode |
| 151 | + |
| 152 | +This example illustrates how PowerShell can be run in server mode. |
| 153 | + |
| 154 | +```powershell |
| 155 | +# Create PowerShell endpoint session (with default shell configuration) |
| 156 | +# Protocol handles messages through the child process stdIn/stdOut process pipes |
| 157 | +$proc = Start-Process -FilePath pwsh.exe -ArgumentList '-servermode -nologo -noprofile' -PassThru |
| 158 | +
|
| 159 | +# Pass child process object to custom connection endpoint handler that reads/writes connection streams from stdIn/stdOut pipes |
| 160 | +[MyCustomConnection.CustomConnectionEndpoint]::SetEndPointProcess($proc) |
| 161 | +``` |
| 162 | + |
| 163 | +### Implementation Example |
| 164 | + |
| 165 | +This is a simple example of how to create a custom ConnectionInfo and Transport implementation |
| 166 | + |
| 167 | +```csharp |
| 168 | +namespace MyCustomConnection |
| 169 | +{ |
| 170 | + // Derived custom connection information class |
| 171 | + public sealed class CustomConnectionInfo : RunspaceConnectionInfo |
| 172 | + { |
| 173 | + // New multi-factor authentication switch for connection. |
| 174 | + // Otherwise use existing 'ComputerName' and 'Credential' properties from derived class. |
| 175 | + public SwitchParameter MFA { get; private set; } |
| 176 | + |
| 177 | + // Message reader and writer. |
| 178 | + internal StreamReader messageReader { get; private set; } |
| 179 | + internal StreamWriter messageWriter { get; private set; } |
| 180 | + |
| 181 | + // Override and implement this method that creates the custom client session transport with |
| 182 | + // bound live connection data streams. |
| 183 | + // This method is called from within the internal PowerShell remoting implementation. |
| 184 | + public override BaseClientSessionTransportManager CreateClientSessionTransportManager( |
| 185 | + Guid instanceId, |
| 186 | + string sessionName, |
| 187 | + PSRemotingCryptoHelper cryptoHelper) |
| 188 | + { |
| 189 | + // Establish connection with this connection information and create connection reader/writer |
| 190 | + // data streams. |
| 191 | + StartConnection(); |
| 192 | + |
| 193 | + return new CustomConnectionClientSessionTransportManager( |
| 194 | + this, |
| 195 | + instanceId, |
| 196 | + cryptoHelper); |
| 197 | + } |
| 198 | + } |
| 199 | + |
| 200 | + // Derived custom client session transport manager class. |
| 201 | + public sealed class CustomConnectionClientSessionTransportManager : ClientSessionTransportManagerBase |
| 202 | + { |
| 203 | + CustomConnectionInfo _connectionInfo; |
| 204 | + |
| 205 | + private CustomConnectionClientSessionTransportManager() |
| 206 | + { } |
| 207 | + |
| 208 | + // Constructor. |
| 209 | + internal CustomConnectionClientSessionTransportManager( |
| 210 | + CustomConnectionInfo connectionInfo, |
| 211 | + Guid runspaceId, |
| 212 | + PSRemotingCryptoHelper cryptoHelper) : base (runspaceId, cryptoHelper) |
| 213 | + { |
| 214 | + if (connectionInfo == null) { throw new PSArgumentException("connectionInfo"); } |
| 215 | + _connectionInfo = connectionInfo; |
| 216 | + } |
| 217 | + |
| 218 | + // Start connection protocol. |
| 219 | + // This is called by internal PowerShell remoting implementation. |
| 220 | + public override void CreateAsync() |
| 221 | + { |
| 222 | + // Create writer for this connection. |
| 223 | + stdInWriter = new TransportTextWriter(_connectionInfo.messageWriter); |
| 224 | + |
| 225 | + // Start reader thread for this connection. |
| 226 | + StartReaderThread(_connectionInfo.messageReader); |
| 227 | + } |
| 228 | + } |
| 229 | +} |
| 230 | +``` |
| 231 | + |
| 232 | +## Alternate Design Options |
| 233 | + |
| 234 | +### PowerShell Subsystem |
| 235 | + |
| 236 | +Another way to implement a custom connection, other than as a PowerShell module, is as a PowerShell subsystem. |
| 237 | +PowerShell subsystems are an experimental feature where external feature implementations can be registered with PowerShell. |
| 238 | +A registered subsystem can integrate with PowerShell via a public interface, but also define cmdlets that become available in the session. |
| 239 | + |
| 240 | +This proposal does not use the engine subsystem infrastructure because it was deemed unnecessary. |
| 241 | +Implementing custom connections as a PowerShell module is sufficient, and subsystems provide no additional value. |
| 242 | + |
| 243 | +## Background Information |
| 244 | + |
| 245 | +### PowerShell Remoting |
| 246 | + |
| 247 | +PowerShell remoting consists of a number of internal engine components. |
| 248 | +It provides a way to establish a PowerShell session on a remote target, which can be another physical machine, virtual machine, or local process. |
| 249 | +Commands and scripts can then be run in the remote session, with data objects being returned to the client. |
| 250 | +PowerShell manipulates object instances and so returned data are objects that are serialized when transported over the remoting connection, and then deserialized after arriving at the destination. |
| 251 | +If the object type is known, PowerShell will deserialize it back into its original type. |
| 252 | +Unknown types are deserialized into a general PowerShell `PSCustomObject` type, that is essentially a property bag. |
| 253 | + |
| 254 | +The core part of PowerShell remoting is the remoting protocol (PSRP) implementation, which handles all messages and data over the connection. |
| 255 | +But there is also the need to create the connections, along with mechanisms to transport protocol messages/data over the connection. |
| 256 | +These functions are considered to be outside PowerShell core remoting, and specific to each connection type. |
| 257 | +PowerShell provides abstractions for these functions, but leaves the implementation to each connection type. |
| 258 | + |
| 259 | +#### Example - WinRM remoting |
| 260 | + |
| 261 | +This is the first remoting connection supported in PowerShell 2.0 and it is based on Windows WinRM. |
| 262 | +WinRM is Windows secure remote shell solution. |
| 263 | +PowerShell remoting uses WinRM to establish a remote connection, and then create a PowerShell session on a remote target. |
| 264 | +PowerShell uses the WinRM plugin architecture to create PowerShell target endpoint sessions as WinRM remote shell instances. |
| 265 | +The PowerShell engine provides WinRM specific connection objects, `WSManConnectionInfo`, to create a connection through WinRM. |
| 266 | +It also contains a transport implementation for transferring protocol messages and data through the WinRM connection. |
| 267 | + |
| 268 | +#### Example - SSH remoting |
| 269 | + |
| 270 | +SSH remoting was added to PowerShell v6.0 and is similar to WinRM remoting, in that SSH is also a secure remote shell solution. |
| 271 | +SSH remoting works on all PowerShell platforms (Linux, macOS, Windows), that have SSH installed. |
| 272 | +Similar to WinRM, SSH authenticates a user and establishes the PowerShell remoting connection to the target computer. |
| 273 | +The connection is defined in an SSH specific `SSHConnectionInfo` object. |
| 274 | +There is also an SSH specific transport implementation that transfer the protocol messages and data over the SSH connection. |
| 275 | +The target endpoint PowerShell session is created by SSH as a configured subsystem, where PowerShell runs in a server mode that handles remoting protocol messages. |
| 276 | + |
| 277 | +#### Example - Local process remoting |
| 278 | + |
| 279 | +PowerShell jobs (`Start-Job`) use PowerShell remoting but the remote connection is to a local child process instead of a remote machine. |
| 280 | +Per the common remoting pattern, there is a connection object, `NewProcessConnectionInfo`, that defines the connection, and a transport implementation used to transfer protocol message/data over the connection. |
| 281 | +Establishing the connection means creating the child process where the job will run, and then cleaning up the child process after the connection/session is terminated. |
0 commit comments