Skip to content

Commit 1931544

Browse files
authored
Merge pull request #304 from PaulHigin/custom_remote_connection
Custom remote connections
2 parents 08376df + 7488314 commit 1931544

File tree

1 file changed

+281
-0
lines changed

1 file changed

+281
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
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

Comments
 (0)