A little while ago I started writing a telnet client in Microsoft's PowerShell. After it sat unfinished for a time I finally got around to improving it with multiple threads and cleaning it up. It now works well so I decided to post about it.
Why would I write a telnet client in PowerShell? Just for fun mainly. It has been a good project to learn some more about PowerShell and may be of use for automating the configuration of Cisco routers and switches or running scripts on other servers.
The most interesting thing I learned as I worked through the project was about how to get PowerShell to support multiple threads. Using the low level .NET Framework System.Net.Sockets.Socket class added to the complexity.
To start off with I created the telnet client using a "while" loop that ran continuously and caused the script to use up 20% of the CPU while doing nothing. I couldn't fix this with a sleep timer because it made the client unresponsive. The problem was I needed to respond asynchronously to reception of data through the TCP connection, and respond to user input at the console. Very easy to do with C#, but in PowerShell?
To implement the reception and transmission of data at the same time asynchronously I started by trying to use the Asynchronous Programming Model used in the .NET Framework. This is a little tricky because the tread pool used in PowerShell is different to the thread pool used in .NET. I did find a way of using the async callback methods with PowerShell from a blog post by Oisin Greham. I still had issues trying to get this to work though.
I gave up on using the async methods of the Socket class and started looking for alternatives. It would have been nice to use the Register-ObjectEvent cmdlet and other event cmdlets but the Socket class does not have any publicly visible events to consume.
I briefly looked at the PowerShell Jobs cmdlets, but they didn't work well for this application because they use the remoting subsystem which serializes objects when they are passed between jobs. This means passing an object by reference is not possible and I need a reference to the connected Socket. That's when I came across the concept of creating a new PowerShell object using [PowerShell]::Create().
When [PowerShell]::Create() is called from a PowerShell script or console, a new instance of PowerShell with an empty pipeline is returned for you to make dance and sing any way you like. The beauty of this new PowerShell object is you can pass objects by reference meaning I could pass the connected Socket.
So now I have two threads to use in my PowerShell telnet client. The main PowerShell process creates a child PowerShell process and initiates it with a script to receive data from the socket. After initiating the child a "while" loop is used with a blocking call to the $Host.UI.RawUI.ReadKey() method to wait for user input.
Rather than explain the code in any more detail, I will let the code do the talking. If you want to use this code use the Gist link: https://gist.github.com/grantcarthew/6985142