Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom remote connections #304

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

@PaulHigin
Copy link
Contributor

@PaulHigin PaulHigin commented Sep 20, 2021

No description provided.

@constantinhager
Copy link

@constantinhager constantinhager commented Sep 21, 2021

I like that idea.

@JustinGrote
Copy link

@JustinGrote JustinGrote commented Sep 21, 2021

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.

Copy link

@jborean93 jborean93 left a comment

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.

We would have to make PSSession constructor public. Currently it's internal stopping someone from creating an instance of this without using reflection. I'm not sure if that should be mentioned under the other 2 classes called out.

Copy link
Contributor Author

@PaulHigin PaulHigin Sep 21, 2021

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?

Copy link
Contributor Author

@PaulHigin PaulHigin Sep 21, 2021

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.

Copy link
Contributor Author

@PaulHigin PaulHigin Sep 21, 2021

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.

Copy link
Contributor Author

@PaulHigin PaulHigin Sep 21, 2021

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?

Copy link
Contributor Author

@PaulHigin PaulHigin Sep 21, 2021

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.

Copy link
Contributor Author

@PaulHigin PaulHigin Sep 22, 2021

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).

@jborean93
Copy link

@jborean93 jborean93 commented Sep 21, 2021

One other thing that would be great to see is supporting custom transports directly with the builtin PSSessison cmdlets like New-PSSession, Invoke-Command, etc. The custom module can still provide their own cmdlets as mentioned in the RFC if they wish but being able to support 3rd party transports with the proivded cmdlets follows quite nicely with the PSProvider architecture that's present with *-Item and provides a consistent way of working.

Just spitballing the idea but I could see it implemented by adding a parameter set that accepts -Connection <RunspaceConnectionInfo[]> which would allow both builtin connections and 3rd party ones. The benefit of this approach is:

  • It works with both builtin and custom connection info classes
    • I can mix and match WSMan/ssh/etc in the one Invoke-Command call
  • Creating the connection info class won't start the session
    • Makes it an inexpensive operation to build the connection list
    • Invoke-Command already has the logic for fanning out and dealing with the session creation quite nicely
  • The caller doesn't need to manage (setup/teardown) the PSSession
    • Simplified the code even more
  • Keeps consistency with the cmdlets used for remoting operations, New-PSSession, Invoke-Command, Enter-PSSession, etc
    • Seems to be similar to how the PSProvider cmdlets like Get-Item, etc works

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-PSSession

If the $computerList was quite large then building the sessions could take quite some time or use up a lot of resources to keep the PSSession active. You also need to ensure that you remove the PSSessions once you are finished with them. By having Invoke-Command support something like -Connection you are delegating the session setup, invocation, and teardown to Invoke-Command which can more efficiently fan-out and throttle the provided list for you, e.g.

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

@PaulHigin
Copy link
Contributor Author

@PaulHigin PaulHigin commented Sep 22, 2021

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.

@jborean93
Copy link

@jborean93 jborean93 commented Sep 22, 2021

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 PSSession or RunspaceConnectionInfo object that can then be used by native cmdlets like Invoke-Command.

@iSazonov
Copy link
Contributor

@iSazonov iSazonov commented Sep 22, 2021

Ultimately I would like to see remoting into its own subsystem,

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 Get-Uptime -ComputerName wincomp, linuxcomp, maccomp, here each remote computer could has different remoting transport. It would be great UX.

@jborean93
Copy link

@jborean93 jborean93 commented Sep 22, 2021

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?

That's mostly why I proposed the -Connection option to use with Invoke-Command, etc. Building the connection info could be done with a custom cmdlet the module would provide or the class itself could be built like any normal .NET class; e.g.

$connInfo = New-WSManConnectionInfo -ConnectionUri '...' -OtherParameters

# or

$connInfo = [WSManConnectionInfo]@{ConnectionUri = '...'; ...}

Invoke-Command -Connection $connInfo, $otherConn, $etc { echo 'hi' }

Like with -ComputerName, -HostName, -Session, the -Connection parameter can be used in the same places.

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 Get-Uptime -ComputerName wincomp, linuxcomp, maccomp, here each remote computer could has different remoting transport. It would be great UX.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Linked issues

Successfully merging this pull request may close these issues.

None yet

6 participants