Custom remote connections #304
Conversation
|
I like that idea. |
|
This is spot on with what we discussed several months ago, providing an abstracted interface to allow implementation of different transport protocols Ultimately I would like to see remoting into its own subsystem, and provide an interface to replace PSRP XML messages with some other protocol (gRPC, JSON-RPC, whatever) that just require you to implement functions like connect, send, receive, error, etc., so that PowerShell can be remoted to more easily from other languages (typescript, python, etc.) without having to implement and parse all of PSRP's heavyweight XML messages, however this is a good first step. |
The overall scope seems nice but I do have some concerns how this custom connection will work with WSMan based connections. It's definitely the outlier in terms of how it works compared to the standard OutOfProc transports but to support a 3rd party OMI replacement it would have to be covered.
| ### Creating a connection | ||
|
|
||
| The PowerShell module implementing the new remote connection should supply all necessary cmdlets for creating and managing the connection. | ||
| In particular it should have a cmdlet that returns a `PSSession` object. |
Yes. I feel many of the implementation details are trivial and don't need to be mentioned in this proposal.
| For example, both WSMan and SSH have services that do this. | ||
| 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. | ||
|
|
||
| PowerShell currently supports a 'server' running mode, where remoting protocol messages are channeled through the process stdIn/stdOut data streams. |
Should the named pipe server also be mentioned, i.e. not the -s option but the PSHost. named pipe that pwsh creates for stuff like Enter-PSHostProcess/-CustomPipeName?
I don't see any need to include other server modes in this proposal. To keep things simple, I just included proc/stdIO mode which I feel most implementers would use. Another endpoint option is to write a PowerShell WinRM plugin host since, since that is actually the only other way to create a PowerShell remoting endpoint instance. But it is very complex and doesn't provide much value in my opinion.
Just my two cents: the way the OpenSSH client and server integration is currently done (subprocess + standard input/output streams) fits my needs perfectly except for the fact that there is no simple way to override the executable to launch (and its parameters). If this is exposed in some generic way I'd be happy, but I'm also happy if it's just exposed in the SSH remoting transport. I like it because it requires no modification to PowerShell whatsoever and no special classes, registration, etc.
While that would be nice, it's not much more of a step removed to enable a class implementation and just ask the user to install a module with your custom transport. It's already been sadly discussed to death that modifying that existing provider isn't going to fly, however this solution allows it to be reimplemented to do what you want at least.
| ```powershell | ||
| # Create PowerShell endpoint session (with default shell configuration) | ||
| # Protocol handles messages through the child process stdIn/stdOut process pipes | ||
| $proc = Start-Process -FilePath pwsh.exe -ArgumentList '-servermode -nologo -noprofile' -PassThru |
This is a bit of a misnomer, you cannot on Windows (AFAIK) hook into a processes stdio after it is created. You need to specify the pipes at creation to a handle of your choice. Start-Process can do this to an extent but only to a file rather than pipes that you control.
This doesn't rule out the named pipe communication I mentioned above though, just the stdio communication mentioned here.
This is just to illustrate what command switches are used to run PowerShell in server mode. It is not intended to be an implementation. If we move forward, I intend to provide a full example of implementing a custom connection. But this is just a proposal document.
| // Override and implement this method that creates the custom client session transport with | ||
| // bound live connection data streams. | ||
| // This method is called from within the internal PowerShell remoting implementation. | ||
| public override BaseClientSessionTransportManager CreateClientSessionTransportManager( |
I don't know how far in scope the RFC should be but it might be good to document what each of these options are for.
I don't think it is needed in this proposal, but if we move forward all of this will be documented.
| // Start connection protocol. | ||
| // This is called by internal PowerShell remoting implementation. | ||
| public override void CreateAsync() |
This is fine for OutOfProc based transports where the communication happens over 2 pipes or a single bidirectional pipe but WSMan is a bit of an outlier. It has a few special messages like the Create, Receive, Send, Command, Signal, Connect, etc WSMan messages. Is the expectation of that transport to decode the OutOfProc fragment that PowerShell will write to the stream like <Signal ../> and convert it to the relevant WSMan one? Are things like DisconnectAsync and ConnectAsync expected to be in scope for the transport manager?
Yes, this proposal for custom connections is based on a fairly simple implementation of the PSRP, and so it relies on built-in implementations for command/signal events, and has no support for connect/disconnect or redirection.
I should probably add a section that mentions some of this, but I wanted to keep this proposal fairly short and general.
But you are right, in that this may be too simple for a reasonable WinRM client implementation. Such an implementation would have to essentially demux session, command, signal messages.
I didn't want to expose command transport abstractions or mediator functions as that greatly increases complexity and makes it harder to understand and implement. My aim is to make this as simple as possible while still being useable. But unfortunately the WinRM transport implementation is very complex.
That's what I feared but it definitely understandable. I've tried to reconcile the client end of a transport between WSMan and OutOfProc based clients and settled on something like this (written in Python) https://github.com/jborean93/pypsrp/blob/c6667a44a34a3d64c89e80ccae646f25c537b25a/psrp/connection_info.py#L138-L311. Still that has some differences with how the transport manager in C# is set out but it's just some food for thought.
While it might be difficult to integrate a 3rd party WSMan client using the proposed model (in/out streams) it may still be doable. I can potentially see the custom connection reading the streams itself and converting it to the WSMan envelopes required. From a brief investigation it can easily send the WSMan create body based on the first Data packet and craft the WSMan Command/Signal/Delete bodies based on the Command, Signal, Close packets respectively.
The downside is the lack of support for disconnected operations. While that's not working on Linux today it would have been nice for it to be implemented at some point in the future. Even just opening up that possibility would be nice but maybe it's enough for an MVP and disconnected operations could be exposed at a future point if desired.
Connection disconnect/connect can still be added at some point. But it is pretty complex (what do you do with streaming data?) and we would probably want a pretty good reason to do so.
what do you do with streaming data?
I would say it's the same with WSMan, there is no more streaming between the client and server until something reconnects and there are server side policies to determine what to do with the accumulated data during that disconnected phase. It would require the server end to support this as well which as least with WSMan is already there. Other transports obviously would require extra work (if at all possible).
|
One other thing that would be great to see is supporting custom transports directly with the builtin PSSessison cmdlets like Just spitballing the idea but I could see it implemented by adding a parameter set that accepts
For example the RFC has the following example PS /Users/Paul> $sessions = New-MyCustomConnection -Computer $computerList -Credential $creds -UseMFA
PS /Users/Paul> $results = Invoke-Command -Session $sessions -File ./Scripts/WeeklyManagementTasks.ps1
PS /Users/Paul> Invoke-AnalyzeResults $results
PS /Users/Paul> $sessions | Remove-PSSessionIf the PS /Users/Paul> $connections = 1..4 | ForEach-Object { [MyCustomConnectionInfo]$_ }
PS /Users/Paul> $results = Invoke-Command -Connection $connections -File ./Scripts/WeeklyManagementTasks.ps1
PS /Users/Paul> Invoke-AnalyzeResults $results |
|
I like the idea of the RunspaceConnectionInfo parameter set. Not as intuitive as a specialized cmdlet that has parameters for the specific connection/authentication, but better than having a bunch of open remote connections, which is definitely resource heavy on the client. Another idea is to add support to PSSession object that allows dynamically managed session lifetime. So Invoke-Command can (via -ThrottleLimit parameter) can open the session and then close it after command completion. |
|
Yea I see them as complimentary scenarios each with their own advantages and disadvantages. Having both allows you to have a custom cmdlet that either returns a |
Me too. Original idea was to split monolith PowerShell Engine on subsystems so that we could remove a code from distribution if it is unneeded. Current proposal add more complicity and more confuse end users than add a value. As end user I'd prefer a transparent remoting. Why would end users think about underlying remoting transport and learn new cmdlets for every new remoting? We have an issue to add ComputerName parameter as common parameter to all cmdlets to implement remoting support to all cmdlets. In the case we could do |
That's mostly why I proposed the $connInfo = New-WSManConnectionInfo -ConnectionUri '...' -OtherParameters
# or
$connInfo = [WSManConnectionInfo]@{ConnectionUri = '...'; ...}
Invoke-Command -Connection $connInfo, $otherConn, $etc { echo 'hi' }Like with
The trouble with it accepting a string is how would it determine whether to use WSMan, SSH, insert custom transport, based on just the name itself. You would have to have some way to encapsulate the provider as well as allow options the caller wishes to set for that provider into a string. You could maybe accept a hashtable but discoverability somewhat becomes harder in this scenario compared to a cmdlet that can give this "connection info". Therein lies what I think the problem with accepting a simple string, it works great where things are just simple but as soon as you start to stray from this path it's a lot more complicated. |
No description provided.
The text was updated successfully, but these errors were encountered: