Skip to content

Instantly share code, notes, and snippets.

@grantcarthew
Last active June 1, 2022 13:48
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save grantcarthew/6985142 to your computer and use it in GitHub Desktop.
Save grantcarthew/6985142 to your computer and use it in GitHub Desktop.
A full graceful telnet client using PowerShell and the .NET Framework. http://uglygizmo.blogspot.com.au/
<#
.SYNOPSIS
A full graceful telnet client using PowerShell and the .NET Framework.
.DESCRIPTION
This script was made with a view of using it to have full control over the text
stream for automating Cisco router and switch configurations.
.PARAMETER TelnetHost
The address of the server or router hosting the telnet service.
.PARAMETER Port
The TCP port number of the Telnet service running on the Telnet host.
#>
param
(
[parameter(Mandatory)]
[String]
$TelnetHost,
[Int]
$Port = 23
)
# Initialize variables
[System.Text.ASCIIEncoding]$ASCIIEncoding = [System.Text.Encoding]::ASCII
[System.Net.Sockets.Socket]$Socket = $null
# Checking host address and port.
if ($TelnetHost -match ":")
{
$hostBytes = $TelnetHost.Split(':')
$TelnetHost = $hostBytes[0]
$Port = $hostBytes[1]
}
if ([String]::IsNullOrEmpty($TelnetHost))
{
Write-Host -Object "Error: Invalid host address (null or empty)." -ForegroundColor Red
return
}
# Setup and connect the TCP Socket.
$Socket = New-Object -TypeName System.Net.Sockets.Socket(
[System.Net.Sockets.AddressFamily]::InterNetwork,
[System.Net.Sockets.SocketType]::Stream,
[System.Net.Sockets.ProtocolType]::Tcp)
$Socket.NoDelay = $true
try
{
$Socket.Connect($TelnetHost, $Port)
}
catch
{
"Unable to connect to host {0}:{1}" -f $TelnetHost,$Port
return
}
# This state object is used to pass the connected Socket and the
# PowerShell parent Host reference to the child PowerShell object.
$State = [PSCustomObject]@{"Socket"=$Socket;"Host"=$Host;"Exception"=$null}
# This script block is used as the receive code for the Socket
# from within the child PowerShell object.
$Script = {
param($state)
# This encoding object is used to decode the return string.
[System.Text.ASCIIEncoding]$ASCIIEncoding = [System.Text.Encoding]::ASCII
# TELNET commands
[Byte]$GA = 249 # Go Ahead
[Byte]$WILL = 251 # Desire to begin
[Byte]$WONT = 252 # Refusal to perform
[Byte]$DO = 253 # Request that the other party perform
[Byte]$DONT = 254 # Demand that the other party stop performing
[Byte]$IAC = 255 # Interpret as Command
# TELNET options
[Byte]$ECHO = 1 # Used to check the echo mode
[Byte]$SUPP = 3 # Suppress go ahead
# Used to hold the number of bytes returned from the network stream.
[Int]$bytes = 0
# Buffer to hold the returned Bytes.
[Byte[]]$buffer = New-Object -TypeName Byte[]($state.Socket.ReceiveBufferSize)
# This is the main receive loop.
while ($state.Socket.Connected)
{
try
{
# The following statement will block the thread until data is received.
$bytes = $state.Socket.Receive($buffer)
}
catch
{
# This exception reference is used to pass the error back to the
# parent PowerShell process.
$state.Exception = $Error[0]
break
}
if ($bytes -gt 0)
{
$index = 0
$responseLen = 0
# The index is used to move through the buffer to analyze the received data
# looking for Telnet commands and options.
while ($index -lt $bytes)
{
if ($buffer[$index] -eq $IAC)
{
try
{
switch ($buffer[$index + 1])
{
# If two IACs are together they represent one data byte 255
$IAC
{
$buffer[$responseLen++] = $buffer[$index]
$index += 2
break
}
# Ignore the Go-Ahead command
$GA
{
$index += 2
break
}
# Respond WONT to all DOs and DONTs
{($_ -eq $DO) -or ($_ -eq $DONT)}
{
$buffer[$index + 1] = $WONT
$state.Socket.Send($buffer, $index, 3,
[System.Net.Sockets.SocketFlags]::None) | Out-Null
$index += 3
break
}
# Respond DONT to all WONTs
$WONT
{
$buffer[$index + 1] = $DONT
$state.Socket.Send($buffer, $index, 3,
[System.Net.Sockets.SocketFlags]::None) | Out-Null
$index += 3
break
}
# Respond DO to WILL ECHO and WILL SUPPRESS GO-AHEAD
# Respond DONT to all other WILLs
$WILL
{
[Byte]$action = $DONT
if ($buffer[$index + 2] -eq $ECHO)
{
$action = $DO
}
elseif ($buffer[$index + 2] -eq $SUPP)
{
$action = $DO
}
$buffer[$index + 1] = $action
$state.Socket.Send($buffer, $index, 3,
[System.Net.Sockets.SocketFlags]::None) | Out-Null
$index += 3;
break;
}
}
}
catch
{
# If there aren't enough bytes to form a command, terminate the loop.
$index = $bytes
}
}
else
{
if ($buffer[$index] -ne 0)
{
$buffer[$responseLen++] = $buffer[$index]
}
$index++
}
}
# Displays the response with no command codes on the parent PowerShell object.
$returnString = $ASCIIEncoding.GetString($buffer, 0, $responseLen)
$state.Host.UI.Write($returnString)
}
}
} # End of the child PowerShell script definition.
# Create a child PowerShell object to run the background Socket receive method.
$PS = [PowerShell]::Create()
$PS.AddScript($Script).AddArgument($State) | Out-Null
[System.IAsyncResult]$AsyncJobResult = $null
try
{
# The receive job is started asynchronously.
$AsyncJobResult = $PS.BeginInvoke()
while ($Socket.Connected)
{
# Wait for keys to be pressed in the parent PowerShell console window.
# This is a blocking call so the telnet session may get disconnected
# while waiting for a key to be pressed.
$Key = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyUp").Character
# Check the socket and see if it is still active.
$PollCheck1 = $Socket.Poll(5000,[System.Net.Sockets.SelectMode]::SelectRead)
$PollCheck2 = ($Socket.Available -eq 0)
if (($PollCheck1 -and $PollCheck2) -or ($State.Exception -ne $null)) { break }
# Socket seems good, send the data.
$Data = $ASCIIEncoding.GetBytes($key)
$Socket.Send($Data) | Out-Null
}
}
finally
{
# Cleanup the socket and child PowerShell process.
if ($Socket -ne $null)
{
$Socket.Close()
$Socket.Dispose()
$Socket = $null
}
if ($PS -ne $null -and $AsyncJobResult -ne $null)
{
$PS.EndInvoke($AsyncJobResult)
$PS.Dispose()
}
}
@essentialexch
Copy link

So, I took this as a base and added "Echo" and "Cooked" parameters, as well as changing the input loop so that it doesn't block waiting on input (which allows the output loop to detect when a socket has been closed properly). I also added a fair amount of write-verbose debugging. I'm new to this github thingie, so I just downloaded the zip file. I tried attaching my updated script as a TXT file, but github didn't seem to like it. How do I get the changes to you?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment