PowerShell Cookbook

Twitter Updates

    follow me on Twitter

    Search

    Categories

     

    On this page

    Scripting Network / TCP Connections in PowerShell

    Archive

    Blogroll

    Disclaimer
    I work for Microsoft.

    The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

    RSS 2.0 | Atom 1.0 | CDF

    Send mail to the author(s) E-mail

    Total Posts: 252
    This Year: 3
    This Month: 0
    This Week: 0
    Comments: 736

    Sign In

     Tuesday, October 27, 2009
    Wednesday, October 28, 2009 2:51:58 AM (Pacific Daylight Time, UTC-07:00) ( )

    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]

    Comments [6] | | #