Welcome PowerShell User! This recipe is just one of the hundreds of useful resources contained in the PowerShell Cookbook.
If you own the book already, login here to get free, online, searchable access to the entire book's content.
If not, the Windows PowerShell Cookbook is available at Amazon, or any of your other favourite book retailers. If you want to see what the PowerShell Cookbook has to offer, enjoy this free 90 page e-book sample: "The Windows PowerShell Interactive Shell".
Although it’s common to work at an abstract level with websites and web services, an entirely separate style of internet-enabled scripting comes from interacting with the remote computer at a much lower level. This lower level (called the TCP level, for Transmission Control Protocol) forms the communication foundation of most internet protocols—such as Telnet, SMTP (sending mail), POP3 (receiving mail), and HTTP (retrieving web content).
The .NET Framework provides classes that let you interact with many of the internet protocols directly: the System.Net.Mail.SmtpClient
class for SMTP, the
System.Net.WebClient
class for HTTP, and a few others. When the .NET Framework doesn’t support an internet protocol that you need, though, you can often script the application protocol directly if you know the details of how it works.
Example 12-10 shows how to receive information about mail waiting in a remote POP3 mailbox, using the Send-TcpRequest
script given in Example 12-11.
## Get the user credential
if
(
-not
(
Test-Path
Variable
:
\
mailCredential
))
{
$mailCredential
=
Get-Credential
}
$address
=
$mailCredential
.
UserName
$password
=
$mailCredential
.
GetNetworkCredential
().
Password
## Connect to the remote computer, send the commands, and receive the output
$pop3Commands
=
"USER $address"
,
"PASS $password"
,
"STAT"
,
"QUIT"
$output
=
$pop3Commands
|
Send-TcpRequest
.
myserver
.
com
110
$inbox
=
$output
.
Split
(
"
`n
"
)[
3
]
## Parse the output for the number of messages waiting and total bytes
$status
=
$inbox
|
ConvertFrom-String
-PropertyName
"Response"
,
"Waiting"
,
"BytesTotal"
,
"Extra"
"{0} messages waiting, totaling {1} bytes."
-f
$status
.
Waiting
,
$status
.
BytesTotal
In Example 12-10, you connect to port 110 of the remote mail server. You then issue commands to request the status of the mailbox in a form that the mail server understands. The format of this network conversation is specified and required by the standard POP3 protocol. Example 12-10 uses the ConvertFrom-String
command, which is provided in Recipe 5.15.
Example 12-11 supports the core functionality of Example 12-10. It lets you easily work with plain-text TCP protocols.
##############################################################################
##
## Send-TcpRequest
##
## From PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
##############################################################################
<#
.SYNOPSIS
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
PS > $http = @"
GET / HTTP/1.1
Host:bing.com
`n`n
"@
$http | Send-TcpRequest bing.com 80
#>
[
CmdletBinding
()]
param
(
## The computer to connect to
[
Parameter
()]
[string]
$ComputerName
=
"localhost"
,
## A switch to determine if you just want to test the connection
[
Parameter
()]
[switch]
$Test
,
## The port to use
[
Parameter
()]
[int]
$Port
=
80
,
## A switch to determine if the connection should be made using SSL
[
Parameter
()]
[switch]
$UseSSL
,
## The input string to send to the remote host
[
Parameter
(
ValueFromPipeline
)]
[string]
$InputObject
,
## The delay, in milliseconds, to wait between commands
[
Parameter
()]
[int]
$Delay
=
100
)
Set-StrictMode
-Version
3
[string]
$SCRIPT: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
)
{
$currentInput
=
@(
$input
)
}
$scriptedMode
=
(
[bool]
$currentInput
)
-or
$test
function
Main
{
## Open the socket, and connect to the computer on the specified port
if
(
-not
$scriptedMode
)
{
write-host
"Connecting to $computerName on port $port"
}
try
{
$tcpClient
=
New-Object
Net
.
Sockets
.
TcpClient
(
$computerName
,
$port
)
}
catch
{
if
(
$test
)
{
$false
}
else
{
Write-Error
"Could not connect to remote computer: $_"
}
return
}
## If we're just testing the connection, we've made the connection
## successfully, so just return $true
if
(
$test
)
{
$true
;
return
}
## If this is interactive mode, supply the prompt
if
(
-not
$scriptedMode
)
{
write-host
"Connected. Press ^D followed by [ENTER] to exit.
`n
"
}
$stream
=
$tcpClient
.
GetStream
()
## If we wanted to use SSL, set up that portion of the connection
if
(
$UseSSL
)
{
try
{
$sslStream
=
New-Object
System
.
Net
.
Security
.
SslStream
$stream
,
$false
$sslStream
.
AuthenticateAsClient
(
$ComputerName
)
$stream
=
$sslStream
}
catch
[System.IO.IOException]
{
## Try again with explicit SSL (TLS)
$tcpClient
=
new-object
System
.
Net
.
Sockets
.
TcpClient
(
$ComputerName
,
$port
)
$stream
=
$tcpClient
.
GetStream
()
$writer
=
new-object
System
.
IO
.
StreamWriter
$stream
$writer
.
WriteLine
(
"EHLO"
)
$writer
.
Flush
()
$writer
.
WriteLine
(
"STARTTLS"
)
$writer
.
Flush
()
$null
=
GetOutput
$sslStream
=
New-Object
System
.
Net
.
Security
.
SslStream
$stream
,
$false
$sslStream
.
AuthenticateAsClient
(
$ComputerName
)
$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
$Delay
$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
For more information about running scripts, see Recipe 1.2.
Recipe 1.2, “Run Programs, Scripts, and Existing Tools”
Recipe 5.15, “Convert Text Streams to Objects”