Scripting Network / TCP Connections in PowerShell

Awhile back, I introduced a script that allows you interact with remote TCP ports (such as Telnet.) While useful, it worked only interactively. It would be even more useful if you were able to script a network or TCP connection.

Let me introduce Send-TcpRequest.ps1 v2, which allows exactly that:

First, a simple scripted HTTP session:

$http = @"
GET / HTTP/1.1
Host:search.msn.com
`n`n
"@

$http | Send-TcpRequest search.msn.com 80

Second, a scripted POP3 session (Parse-TextObject comes from here: http://www.leeholmes.com/blog/parsetextObjectAWKWithAVengeance.aspx):

if(-not (test-path Variable:\mailCredential))
{
   $mailCredential = Get-Credential
}
$address = $mailCredential.UserName
$password = $mailCredential.GetNetworkCredential().Password
$pop3Commands = "USER $address","PASS $password","STAT","QUIT"
$output = $pop3Commands | Send-TcpRequest mail.leeholmes.com 110
$inbox = $output.Split("`n")[3]
$status = $inbox | Parse-TextObject -PropertyName "Response","Waiting","BytesTotal","Extra"
"{0} messages waiting, totaling {1} bytes." -f $status.Waiting,$status.BytesTotal

 

Now, here is Send-TcpRequest.ps1

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
##############################################################################
## Send-TcpRequest.ps1
##
## From Windows PowerShell Cookbook (O’Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
## Send a TCP request to a remote computer, and return the response.
## If you do not supply input to this script (via either the pipeline, or the
## -InputObject parameter,) the script operates in interactive mode.
##
## Example:
##
## $http = @"
## GET / HTTP/1.1
## Host:search.msn.com
## `n`n
## "@
##
## $http | Send-TcpRequest search.msn.com 80
##############################################################################
param(
        [string] $remoteHost = "localhost",
        [int] $port = 80,
        [switch] $UseSSL,
        [string] $inputObject,
        [int] $commandDelay = 100
     )

[string] $output = ""

## Store the input into an array that we can scan over. If there was no input,
## then we will be in interactive mode.
$currentInput = $inputObject
if(-not $currentInput)
{
    $SCRIPT:currentInput = @($input)
}
$scriptedMode = [bool] $currentInput

function Main
{
    ## Open the socket, and connect to the computer on the specified port
    if(-not $scriptedMode)
    {
        write-host "Connecting to $remoteHost on port $port"
    }

    trap { Write-Error "Could not connect to remote computer: $_"; exit }
    $socket = new-object System.Net.Sockets.TcpClient($remoteHost, $port)

    if(-not $scriptedMode)
    {
        write-host "Connected. Press ^D followed by [ENTER] to exit.`n"
    }

    $stream = $socket.GetStream()
   
    if($UseSSL)
    {
        $sslStream = New-Object System.Net.Security.SslStream $stream,$false
        $sslStream.AuthenticateAsClient($remoteHost)
        $stream = $sslStream
    }

    $writer = new-object System.IO.StreamWriter $stream

    while($true)
    {
        ## Receive the output that has buffered so far
        $SCRIPT:output += GetOutput

        ## If we’re in scripted mode, send the commands,
        ## receive the output, and exit.
        if($scriptedMode)
        {
            foreach($line in $currentInput)
            {
                $writer.WriteLine($line)
                $writer.Flush()
                Start-Sleep -m $commandDelay
                $SCRIPT:output += GetOutput
            }

            break
        }
        ## If we’re in interactive mode, write the buffered
        ## output, and respond to input.
        else
        {
            if($output) 
            {
                foreach($line in $output.Split("`n"))
                {
                    write-host $line
                }
                $SCRIPT:output = ""
            }

            ## Read the user’s command, quitting if they hit ^D
            $command = read-host
            if($command -eq ([char] 4)) { break; }

            ## Otherwise, Write their command to the remote host
            $writer.WriteLine($command)
            $writer.Flush()
        }
    }

    ## Close the streams
    $writer.Close()
    $stream.Close()

    ## If we’re in scripted mode, return the output
    if($scriptedMode)
    {
        $output
    }
}

## Read output from a remote host
function GetOutput
{
    ## Create a buffer to receive the response
    $buffer = new-object System.Byte[] 1024
    $encoding = new-object System.Text.AsciiEncoding

    $outputBuffer = ""
    $foundMore = $false

    ## Read all the data available from the stream, writing it to the
    ## output buffer when done.
    do
    {
        ## Allow data to buffer for a bit
        start-sleep -m 1000

        ## Read what data is available
        $foundmore = $false
        $stream.ReadTimeout = 1000

        do
        {
            try
            {
                $read = $stream.Read($buffer, 0, 1024)

                if($read -gt 0)
                {
                    $foundmore = $true
                    $outputBuffer += ($encoding.GetString($buffer, 0, $read))
                }
            } catch { $foundMore = $false; $read = 0 }
        } while($read -gt 0)
    } while($foundmore)

    $outputBuffer
}

. Main

[Edit: Thanks to Marco for pointing out a few issues that come up when you try to retrieve massive amounts of data (such as a newsgroup listing). I've updated the script to fix those.]
[Edit2: Updated to call it Send-TcpRequest, and support SSL]

20 Responses to “Scripting Network / TCP Connections in PowerShell”

  1. U Baatar writes:

    Tried to run the script with the $http example, but I get the following error:

    Unexpected token ‘catch’ in expression or statement.
    At C:\Users\fherrero\Documents\Send-TcpRequest.ps1:152 char:20
    + } catch <<<< { $foundMore = $false; $read = 0 }

    Thanks,
    U

  2. Lee Holmes writes:

    Hi U,

    This requires PowerShell Version 2.

  3. Darrin Henshaw writes:

    Hey Lee,

    Nice script, do you mind if I take and modify it a bit? I’m interested because with this I should be able to connect to the Manager API of our Asterisk telephony system. By specifying the port 5038 and the IP of my Asterisk server I can connect, which gives me goosebumps for what I can do. This is something I’ve been wanting to try for some time and finally got around to looking at it. Thanks.

    Cheers,

    Darrin

  4. Lee Holmes writes:

    Darrin: Be my guest — good luck with the Asterisk server!

  5. Bruno Gomes writes:

    Lee, correct me if I’m wrong, but I thought there was an "agreement" that the .ps1 extension indicated the script would run on Powershell V1 and that the .ps2 extension would be used for Powershell V2.

  6. Lee Holmes writes:

    Hi Bruno;

    The ps1 to ps2 extension change is reserved for when we need to introduce significant breaking changes: http://blogs.msdn.com/powershell/archive/2007/11/02/ctp-versioning.aspx.

    Lee

  7. Bruno Gomes writes:

    Thank you for clearing that up… But in this case, shouldn’t the script have a "#REQUIRES -Version 2" line?

  8. Joe writes:

    Hello,

    thank you for great script I am using it for scripted telnet communication with ProCurve switches and it runs great. The output is little bit corrupted though, I have tried to change the output AscciEncoding to other format, but no success – Do you have and idea what it might be?

    Thanks!

  9. Nick writes:

    Nice script, is there a way to set the Telnet type ?

    such as: TelnetTermType VT220 ?

  10. Jason writes:

    This is a little off topic, but I’m new to PS and don’t understand a fundamental-

    $http | Send-TcpRequest search.msn.com 80

    How do you tell PS what Send-TcpRequest means? Do I save your code as a .ps1? How do I import it into PS?

    Thanks,

    Jason

    PS I love v2 of your book. Just downloaded it today.

  11. Arjan writes:

    Just a short inquiry about this great script!
    I’m having a problem using an input file for my telnet commands.

    When I run the script in interactive mode, all works well, but when I put my commands in a text file and use -InputObject “c:\telnet.txt”, this doesn’t seem to work.
    I’m probably using -InputObject incorrectly, so I’m hoping you can give sort me out on this one :)

    Many thanks!
    Arjan.

  12. Niels writes:

    @Arjan , currently you give -InputObject a string “c:\telnet.txt”, powershell doesn’t know the string you give is not the string it needs to process .. you should provide the content of the file.
    -InputObject (Get-Content “c:\telnet.txt”)

  13. Arjan writes:

    @Niels, thanks for your reply. It seems that the content of the text file is now read correctly by Telnet. The only problem I have now is that the content of the file consists of several lines of code, which are now read as one long string, instead of seperate lines.
    Would you (or anyone else) by any chance know how I can get the command to read one line at a time?
    Thanks, Arjan.

  14. Ty writes:

    Is it possible to use this in conjunction with a ‘listener’ script that will spit out the stuff that I send? It is working well with some printers that we are installing, but I would like to do further testing offline and it seems like the vendor will not be able to provide any help.

  15. Ty writes:

    As info / answer to my above question, I found something on sourceforge that is working well:
    http://sockettest.sourceforge.net/

  16. Marcel writes:

    Hello, the code is nice and working, i’m quiet new in powershell as well:

    But how is it possible to make out of this $outbuffer variable a line by itself for each file?
    I really don’t get that in any way, first i thougt it would be an array.

  17. Marcel writes:

    Finally i would like to have each filename so i can download every file i want to

  18. Andrew writes:

    Thank you VERY much! I have been looking for a painless way to interact with a socket in powershell. And have now found it. :)

  19. rob writes:

    if you insist on writing “awhile”, then for consistency you need to re-write your first sentence to be:

    > Awhile back, I introduced ascript that allows [...]. It would be even more useful if you were able to script anetwork or TCP connection.

    Isn’t that alot better? I’m going to have apiece of toast now.

  20. Lee Holmes writes:

    Whew, Rob, thanks for clearing that up!

Leave a Reply