Is it possible to fake windows console api?

2019-04-20 04:22发布

I've written a ssh server in c# and I thought it'd be neat to hook up powershell as a shell. I've tried 2 methods to get this to work properly but both are far from perfect. Here's what I've tried:

  1. Launch powershell.exe and redirect it's std(in/out). This doesn't work well since powershell.exe detects it is redirected, changes it's behaviour. What's more, it expects input data on the stdid, not commands. So it uses the console api to read commands.
  2. Host powershell in a "wrapper" application. This has the advantage of being able to provide a "console" implementation to powershell (via the PSHostRawUserInterface). This works better, but you can still invoke commands (mostly real console applications) like "... | more", that expect to be able to use the console api, and subsequently try to read from the console of the wrapper process.

So what I'd like to do is have a set of functions replace the regular console input/output functions that console applications use, so I can handle them. But that seems rather drastic to the point of being a bad design idea (imo).

Right now I am on the idea of manipulating the console by sending the relevant keys with native/Pinvoke functions like WriteConsoleInput. I gather that it might be possible to fake the console that way. But I don't see how I would then "read" what happens on the console.

Also keep in mind, it's a service, so preferably it shouldn't spawn an actual console window, although perhaps in windows session 0 that wouldn't show up and not matter.

2条回答
在下西门庆
2楼-- · 2019-04-20 04:54

I installed PowerShellInside as suggested by JPBlanc, but didn't use it for very long. The one connection thing is just too limiting, and I don't like being limited (especially if that limitation is profit based but thats a whole other discussion i shouldn't get into). And despite being a solution to the original problem, it feels unsatisfactory because it doesn't solve the programming problem I ran into.

However, I did eventually manage to solve said problem, indeed by using the windows api calls in a wrapper process. Because there are quite a few pitfalls, I decided to anwser my own question and give others looking at the same problem some pointers. The basic structure is as follows:

  • Start the wrapper process with redirected stdin/-out (and stderr if you want). (In my case stdin and out will be streams of xterm control sequences and data, because that is the ssh way)
  • Using GetStdHandle() retrive the redirected input and output handles. Next SetStdHandle()'s to the CreateFile() of "CONIN$" and "CONOUT$", such that child processes inherits the the console and does not have the redirections of the wrapper process. (Note that a security descriptor allowing inheriting is needed for createfile)
  • Setup the console mode, size, title, Ctrl-C handler's, etc. Note: be sure to set a font if you want unicode support, I used Lucida Console (.FontFamily = 54, .FaceName = "Lucida Console"). Without this, reading the characters from your console output will return codepaged versions, which are horrible to work with in managed code.
  • Reading output can be done with the SetWinEventHook(), be sure to use out-of-context notification, because I'm pretty sure that having your managed application suddenly run in another process context/address space is a Bad Idea™ (I'm so sure that I didn't even try). The event will fire for every console window, not just your own. So filter all calls to the callback by window handle. Retrive the window handle of the current console application with GetConsoleWindow(). Also don't forget to unhook the callback when the application is done.
  • Note, upto this point be sure not to use (or do anything that causes the load of) the System.Console class, or things more than likely will go wrong. Usage after this point will behave as if the sub process had written to the output.
  • Spawn the needed sub process (Note, you must use .UseShellExecute = false or it will not inherit the console)
  • You can start providing input to the console using WriteConsoleInput()
  • At this point (or on a separate thread) you have to run a windows message loop or you will not recieve console event notification callbacks. You can simply use the parameterless Application.Run() to do this. To break the message loop, you must at some point post an exit message to your message loop. I did this with Application.Exit() in the subprocess's .Exited event. (Note use .EnableRaisingEvents for this to work)
  • Calls will now be made to your win event callback when something on your console changes. Pay attention to the scroll event, this might work somewhat unexpected. Also make no assumptions about synchronous delivery. If the sub process writes 3 lines, by the time you are processing the first event, the remaining 3 lines might already have been written. To be fair, windows does a nice job of composing events such that you don't get swamped with single character changes and can keep up with the changes.
  • Be sure to mark all PInvoke definitions with CharSet=CharSet.Unicode if they contain a character anywhere in the input or output. PInvoke.net missed quite a few of these.

The net result of all of this: a wrapper application for the windows console api. The wrapper can read/write the redirected stdin and stdout to communicate with the world. Ofcourse if you want to get fancy you could use any stream here (named pipe, tcp/ip, etc..). I implemented a few xterm control sequences and managed to get a fully working terminal wrapper that should be capable of wrapping any windows console process, translate the xterm input to input on the target application's console input and process the application's output to xterm control sequences. I even got the mouse to work. Starting powershell.exe as sub process now solves the original problem of running powershell in a ssh session. Cmd.exe also works. If anyone is interrested I'll see about posting the full code somewhere.

查看更多
男人必须洒脱
3楼-- · 2019-04-20 05:11

You've got PSSession for this purpose and the Enter-PSSession CmdLet. What will your SSH with Powershell do that PSSession is not doing ?

But if you want to do that here is a solution whithout writting anything : Using PowerShell through SSH


Edited 02/11/2011

PowerShell inside provide another way to do it whithout writting anything (free for personal usage).

Host03 sample, can perhaps provide basic code to do what you wat to do.

查看更多
登录 后发表回答