Although PowerShell provides an enormous benefit even when your scripts interact only with the local system, working with data sources from the Internet opens exciting and unique opportunities. For example, you might download files or information from the Internet, interact with a web service, store your output as HTML, or even send an email that reports the results of a long-running script.
Through its cmdlets and access to the networking support in the .NET Framework, PowerShell provides ample opportunities for Internet-enabled administration.
Use the DownloadFile()
method
from the .NET Framework’s System.Net.WebClient
class to download a
file:
PS > $source = "http://www.leeholmes.com/favicon.ico" PS > $destination = "c: empfavicon.ico" PS > PS > $wc = New-Object System.Net.WebClient PS > $wc.DownloadFile($source, $destination)
The System.Net.WebClient
class from the .NET
Framework lets you easily upload and download data from remote web
servers.
The WebClient
class acts much like a web browser,
in that you can specify a user agent, a proxy (if your outgoing
connection requires one), and even credentials.
All web browsers send a user agent identifier
along with their web request. This identifier tells the website what
application is making the request—such as Internet Explorer, Firefox, or
an automated crawler from a search engine. Many websites check this user
agent identifier to determine how to display the page. Unfortunately,
many fail entirely if they can’t determine the user agent for the
incoming request. To make the System.Net.WebClient
identify itself as
Internet Explorer, use the following commands instead:
$userAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2;)" $wc = New-Object System.Net.WebClient $wc.Headers.Add("user-agent", $userAgent)
Notice that the solution uses a fully
qualified path for the destination file. This is an important step, as
otherwise the DownloadFile()
method
saves its files to the directory in which
PowerShell.exe started (the root of your user
profile directory by default).
You can use the DownloadFile()
method to download web pages
just as easily as you download files. Just supply a URL as a source
(such as http://blogs.msdn.com/powershell/rss.xml)
instead of a filename. If you ultimately intend to parse or read through
the downloaded page, the DownloadString()
method
may be more appropriate.
For more information on how to download and parse web pages, see Download a Web Page from the Internet.
Use the DownloadString()
method from the .NET
Framework’s System.Net.WebClient
class to download a web page or plain text file into a string.
PS > $source = "http://blogs.msdn.com/powershell/rss.xml" PS > PS > $wc = New-Object System.Net.WebClient PS > $content = $wc.DownloadString($source)
The most common reason to download a web page from the Internet is to extract unstructured information from it. Although web services are becoming increasingly popular, they are still far less common than web pages that display useful data. Because of this, retrieving data from services on the Internet often comes by means of screen scraping: downloading the HTML of the web page and then carefully separating out the content you want from the vast majority of the content that you do not.
The technique of screen scraping has been around much longer than the Internet! As long as computer systems have generated output designed primarily for humans, screen scraping tools have risen to make this output available to other computer programs.
Unfortunately, screen scraping is an error-prone way to extract content.
That’s not an exaggeration! As proof, Example 12-2 (shown later in this recipe) broke four or five times while the first edition of this book was being written, and then again after it was published. Such are the perils of screen scraping.
If the web page authors change the underlying HTML, your code will usually stop working correctly. If the site’s HTML is written as valid XHTML, you may be able to use PowerShell’s built-in XML support to more easily parse the content.
For more information about PowerShell’s built-in XML support, see Access Information in an XML File.
Despite its fragility, pure screen scraping is often the only alternative. Since screen scraping is just text manipulation, you have the same options you do with other text reports. For some fairly structured web pages, you can get away with a single regular expression replacement (plus cleanup), as shown in Example 12-1.
Example 12-1. Search-Twitter.ps1
############################################################################## ## ## Search-Twitter ## ## From Windows PowerShell Cookbook (O'Reilly) ## by Lee Holmes (http://www.leeholmes.com/guide) ## ############################################################################## <# .SYNOPSIS Search Twitter for recent mentions of a search term .EXAMPLE Search-Twitter PowerShell Searches Twitter for the term "PowerShell" #> param( ## The term to search for $Pattern = "PowerShell" ) Set-StrictMode -Version Latest ## Create the URL that contains the Twitter search results Add-Type -Assembly System.Web $queryUrl = 'http://integratedsearch.twitter.com/search.html?q={0}' $queryUrl = $queryUrl -f ([System.Web.HttpUtility]::UrlEncode($pattern)) ## Download the web page $wc = New-Object System.Net.WebClient $wc.Encoding = [System.Text.Encoding]::UTF8 $results = $wc.DownloadString($queryUrl) ## Extract the text of the messages, which are contained in ## segments that look like "<div class='msg'>...</div>" $matches = $results | Select-String -Pattern '(?s)<div[^>]*msg[^>]*>.*?</div>' -AllMatches foreach($match in $matches.Matches) { ## Replace anything in angle brackets with an empty string, ## leaving just plain text remaining. $tweet = $match.Value -replace '<[^>]*>', '' ## Output the text [System.Web.HttpUtility]::HtmlDecode($tweet.Trim()) + "`n" }
Text parsing on less structured web pages, while possible to accomplish with complicated regular expressions, can often be made much simpler through more straightforward text manipulation. Example 12-2 uses this second approach to fetch “Instant Answers” from Bing.
Example 12-2. Get-Answer.ps1
############################################################################## ## ## Get-Answer ## ## From Windows PowerShell Cookbook (O'Reilly) ## by Lee Holmes (http://www.leeholmes.com/guide) ## ############################################################################## <# .SYNOPSIS Uses Bing Answers to answer your question .EXAMPLE Get-Answer "(5 + e) * sqrt(x) = Pi" Calculation (5+e )*sqrt ( x)=pi : x=0.165676 .EXAMPLE Get-Answer msft stock Microsoft Corp (US:MSFT) NASDAQ 29.66 -0.35 (-1.17%) After Hours: 30.02 +0.36 (1.21%) Open: 30.09 Day's Range: 29.59 - 30.20 Volume: 55.60 M 52 Week Range: 17.27 - 31.50 P/E Ratio: 16.30 Market Cap: 260.13 B #> Set-StrictMode -Version Latest $question = $args -join " " function Main { ## Load the System.Web.HttpUtility DLL, to let us URLEncode Add-Type -Assembly System.Web ## Get the web page into a single string with newlines between ## the lines. $encoded = [System.Web.HttpUtility]::UrlEncode($question) $url = "http://www.bing.com/search?q=$encoded" $text = (new-object System.Net.WebClient).DownloadString($url) ## Find the start of the answers section $startIndex = $text.IndexOf('<div class="ans">') ## The end is either defined by an "attribution" div ## or the start of a "results" div $endIndex = $text.IndexOf('<div class="sn_att2">') if($endIndex -lt 0) { $endIndex = $text.IndexOf('<div id="results">') } ## If we found a result, then filter the result if(($startIndex -ge 0) -and ($endIndex -ge 0)) { ## Pull out the text between the start and end portions $partialText = $text.Substring($startIndex, $endIndex - $startIndex) ## Very fragile screen scraping here. Replace a bunch of ## tags that get placed on new lines with the newline ## character, and a few others with spaces. $partialText = $partialText -replace '<div[^>]*>',"`n" $partialText = $partialText -replace '<tr[^>]*>',"`n" $partialText = $partialText -replace '<li[^>]*>',"`n" $partialText = $partialText -replace '<br[^>]*>',"`n" $partialText = $partialText -replace '<span[^>]*>'," " $partialText = $partialText -replace '<td[^>]*>'," " $partialText = CleanHtml $partialText ## Now split the results on newlines, trim each line, and then ## join them back. $partialText = $partialText -split "`n" | Foreach-Object { $_.Trim() } | Where-Object { $_ } $partialText = $partialText -join "`n" [System.Web.HttpUtility]::HtmlDecode($partialText.Trim()) } else { "`nNo answer found." } } ## Clean HTML from a text chunk function CleanHtml ($htmlInput) { $tempString = [Regex]::Replace($htmlInput, "(?s)<[^>]*>", "") $tempString.Replace("  ", "") } . Main
For more information about running scripts, see Run Programs, Scripts, and Existing Tools.
When working with HTML, it is common to require advanced regular expressions that separate the content you care about from the content you don’t. A perfect example of this is extracting all the HTML links from a web page.
Links come in many forms, depending on how lenient you want to be. They may be well-formed according to the various HTML standards. They may use relative paths or they may use absolute paths. They may place double quotes around the URL or they may place single quotes around the URL. If you’re really unlucky, they may accidentally include quotes on only one side of the URL.
Example 12-3 demonstrates
some approaches for dealing with this type of advanced parsing task. Given
a web page that you’ve downloaded from the Internet, it extracts all links
from the page and returns a list of the URLs on that page. It also fixes
URLs that were originally written as relative URLs (for example, /file.zip
) to include the server from which they
originated.
Example 12-3. Get-PageUrls.ps1
############################################################################## ## ## Get-PageUrls ## ## From Windows PowerShell Cookbook (O'Reilly) ## by Lee Holmes (http://www.leeholmes.com/guide) ############################################################################## <# .SYNOPSIS Parse all of the URLs out of a given file. .EXAMPLE Get-PageUrls microsoft.html http://www.microsoft.com Gets all of the URLs from HTML stored in microsoft.html, and converts relative URLs to the domain of http://www.microsoft.com .EXAMPLE Get-PageUrls microsoft.html http://www.microsoft.com 'aspx$' Gets all of the URLs from HTML stored in microsoft.html, converts relative URLs to the domain of http://www.microsoft.com, and returns only URLs that end in 'aspx'. #> param( ## The filename to parse [Parameter(Mandatory = $true)] [string] $Path, ## The URL from which you downloaded the page. ## For example, http://www.microsoft.com [Parameter(Mandatory = $true)] [string] $BaseUrl, ## The Regular Expression pattern with which to filter ## the returned URLs [string] $Pattern = ".*" ) Set-StrictMode -Version Latest ## Load the System.Web DLL so that we can decode URLs Add-Type -Assembly System.Web ## Defines the regular expression that will parse an URL ## out of an anchor tag. $regex = "<s*as*[^>]*?hrefs*=s*[`"']*([^`"'>]+)[^>]*?>" ## Parse the file for links function Main { ## Do some minimal source URL fixups, by switching backslashes to ## forward slashes $baseUrl = $baseUrl.Replace("", "/") if($baseUrl.IndexOf("://") -lt 0) { throw "Please specify a base URL in the form of " + "http://server/path_to_file/file.html" } ## Determine the server from which the file originated. This will ## help us resolve links such as "/somefile.zip" $baseUrl = $baseUrl.Substring(0, $baseUrl.LastIndexOf("/") + 1) $baseSlash = $baseUrl.IndexOf("/", $baseUrl.IndexOf("://") + 3) if($baseSlash -ge 0) { $domain = $baseUrl.Substring(0, $baseSlash) } else { $domain = $baseUrl } ## Put all of the file content into a big string, and ## get the regular expression matches $content = [String]::Join(' ', (Get-Content $path)) $contentMatches = @(GetMatches $content $regex) foreach($contentMatch in $contentMatches) { if(-not ($contentMatch -match $pattern)) { continue } if($contentMatch -match "javascript:") { continue } $contentMatch = $contentMatch.Replace("", "/") ## Hrefs may look like: ## ./file ## file ## ../../../file ## /file ## url ## We'll keep all of the relative paths, as they will resolve. ## We only need to resolve the ones pointing to the root. if($contentMatch.IndexOf("://") -gt 0) { $url = $contentMatch } elseif($contentMatch[0] -eq "/") { $url = "$domain$contentMatch" } else { $url = "$baseUrl$contentMatch" $url = $url.Replace("/./", "/") } ## Return the URL, after first removing any HTML entities [System.Web.HttpUtility]::HtmlDecode($url) } } function GetMatches([string] $content, [string] $regex) { $returnMatches = new-object System.Collections.ArrayList ## Match the regular expression against the content, and ## add all trimmed matches to our return list $resultingMatches = [Regex]::Matches($content, $regex, "IgnoreCase") foreach($match in $resultingMatches) { $cleanedMatch = $match.Groups[1].Value.Trim() [void] $returnMatches.Add($cleanedMatch) } $returnMatches } . Main
For more information about running scripts, see Run Programs, Scripts, and Existing Tools.
Use the New-WebserviceProxy
cmdlet to work with
a web service.
PS > $url = "http://terraservice.net/TerraService.asmx" PS > $terraServer = New-WebserviceProxy $url -Namespace Cookbook PS > $place = New-Object Cookbook.Place PS > $place.City = "Redmond" PS > $place.State = "WA" PS > $place.Country = "USA" PS > $facts = $terraserver.GetPlaceFacts($place) PS > $facts.Center Lon Lat --- --- -122.110000610352 47.6699981689453
Although screen scraping (parsing the HTML of a web page) is the most common way to obtain data from the Internet, web services are becoming increasingly common. Web services provide a significant advantage over HTML parsing, as they are much less likely to break when the web designer changes minor features in a design.
The benefit to web services isn’t just their more stable interface, however. When working with web services, the .NET Framework lets you generate proxies that let you interact with the web service as easily as you would work with a regular .NET object. That is because to you, the web service user, these proxies act almost exactly the same as any other .NET object. To call a method on the web service, simply call a method on the proxy.
The New-WebserviceProxy
cmdlet simplifies all of the work required to connect to a web service,
making it just as easy as a call to the New-Object
cmdlet.
The primary differences you will notice when working with a web service proxy (as opposed to a regular .NET object) are the speed and Internet connectivity requirements. Depending on conditions, a method call on a web service proxy could easily take several seconds to complete. If your computer (or the remote computer) experiences network difficulties, the call might even return a network error message (such as a timeout) instead of the information you had hoped for.
If the web service requires authentication in
a domain, specify the -Use
Default
Credential
parameter. If it requires
explicit credentials, use the -Credential
parameter.
When you create a new web service proxy,
PowerShell creates a new .NET object on your behalf that connects to
that web service. All .NET types live within a namespace to prevent them from
conflicting with other types that have the same name, so PowerShell automatically generates the
namespace name for you. You normally won’t need to pay attention to this
namespace. However, some web services require input objects that the web
service also defines, such as the Place
object in the
solution. For these web services, use the -Namespace
parameter to place the web service (and its support objects) in a
namespace of your choice.
Support objects from one web service proxy cannot be consumed by a different web service proxy, even if they are two proxies to a web service at the same URL. If you need to work with two connections to a web service at the same URL, and your task requires creating support objects for that service, be sure to use two different namespaces for those proxies.
The New-WebserviceProxy
cmdlet was introduced in version two of PowerShell. If you need to
connect to a web service from version one of PowerShell, see Program: Connect-WebService.
For more information about running scripts, see Run Programs, Scripts, and Existing Tools.
Connect to a Web Service discusses how to
connect to a web service on the Internet. However, the
New-WebserviceProxy
cmdlet in that recipe was
introduced in version two of PowerShell. If you need to connect to a web
service from version one of PowerShell, Example 12-4 is your solution. It lets you
connect to a remote web service if you know the location of its service
description file (WSDL). It generates the web service proxy for you,
letting you interact with it as you would any other .NET object.
Example 12-4. Connect-WebService.ps1
############################################################################## ## Connect-WebService ## ## From Windows PowerShell Cookbook (O'Reilly) ## by Lee Holmes (http://www.leeholmes.com/guide) ## ## Connect to a given web service, and create a type that allows you to ## interact with that web service. In PowerShell version two, use the ## New-WebserviceProxy cmdlet. ## ## Example: ## ## $wsdl = "http://terraservice.net/TerraService.asmx?WSDL" ## $terraServer = Connect-WebService $wsdl ## $place = New-Object Place ## $place.City = "Redmond" ## $place.State = "WA" ## $place.Country = "USA" ## $facts = $terraserver.GetPlaceFacts($place) ## $facts.Center ############################################################################## param( ## The URL that contains the WSDL [string] $WsdlLocation = $(throw "Please specify a WSDL location"), ## The namespace to use to contain the web service proxy [string] $Namespace, ## Switch to identify web services that require authentication [Switch] $RequiresAuthentication ) ## Create the web service cache, if it doesn't already exist if(-not (Test-Path Variable:Lee.Holmes.WebServiceCache)) { ${GLOBAL:Lee.Holmes.WebServiceCache} = @{} } ## Check if there was an instance from a previous connection to ## this web service. If so, return that instead. $oldInstance = ${GLOBAL:Lee.Holmes.WebServiceCache}[$wsdlLocation] if($oldInstance) { $oldInstance return } ## Load the required Web Services DLL Add-Type -Assembly System.Web.Services ## Download the WSDL for the service, and create a service description from ## it. $wc = New-Object System.Net.WebClient if($requiresAuthentication) { $wc.UseDefaultCredentials = $true } $wsdlStream = $wc.OpenRead($wsdlLocation) ## Ensure that we were able to fetch the WSDL if(-not (Test-Path Variable:wsdlStream)) { return } $serviceDescription = [Web.Services.Description.ServiceDescription]::Read($wsdlStream) $wsdlStream.Close() ## Ensure that we were able to read the WSDL into a service description if(-not (Test-Path Variable:serviceDescription)) { return } ## Import the web service into a CodeDom $serviceNamespace = New-Object System.CodeDom.CodeNamespace if($namespace) { $serviceNamespace.Name = $namespace } $codeCompileUnit = New-Object System.CodeDom.CodeCompileUnit $serviceDescriptionImporter = New-Object Web.Services.Description.ServiceDescriptionImporter $serviceDescriptionImporter.AddServiceDescription( $serviceDescription, $null, $null) [void] $codeCompileUnit.Namespaces.Add($serviceNamespace) [void] $serviceDescriptionImporter.Import( $serviceNamespace, $codeCompileUnit) ## Generate the code from that CodeDom into a string $generatedCode = New-Object Text.StringBuilder $stringWriter = New-Object IO.StringWriter $generatedCode $provider = New-Object Microsoft.CSharp.CSharpCodeProvider $provider.GenerateCodeFromCompileUnit($codeCompileUnit, $stringWriter, $null) ## Compile the source code. $references = @("System.dll", "System.Web.Services.dll", "System.Xml.dll") $compilerParameters = New-Object System.CodeDom.Compiler.CompilerParameters $compilerParameters.ReferencedAssemblies.AddRange($references) $compilerParameters.GenerateInMemory = $true $compilerResults = $provider.CompileAssemblyFromSource($compilerParameters, $generatedCode) ## Write any errors if generated. if($compilerResults.Errors.Count -gt 0) { $errorLines = "" foreach($error in $compilerResults.Errors) { $errorLines += "`n`t" + $error.Line + ":`t" + $error.ErrorText } Write-Error $errorLines return } ## There were no errors. Create the webservice object and return it. else { ## Get the assembly that we just compiled $assembly = $compilerResults.CompiledAssembly ## Find the type that had the WebServiceBindingAttribute. ## There may be other "helper types" in this file, but they will ## not have this attribute $type = $assembly.GetTypes() | Where-Object { $_.GetCustomAttributes( [System.Web.Services.WebServiceBindingAttribute], $false) } if(-not $type) { Write-Error "Could not generate web service proxy." return } ## Create an instance of the type, store it in the cache, ## and return it to the user. $instance = $assembly.CreateInstance($type) ## Many services that support authentication also require it on the ## resulting objects if($requiresAuthentication) { if(@($instance.PsObject.Properties | where { $_.Name -eq "UseDefaultCredentials" }).Count -eq 1) { $instance.UseDefaultCredentials = $true } } ${GLOBAL:Lee.Holmes.WebServiceCache}[$wsdlLocation] = $instance $instance }
For more information about running scripts, see Run Programs, Scripts, and Existing Tools.
Use PowerShell’s ConvertTo-Html
cmdlet to convert
command output into a web page. For example, to create a quick HTML
summary of PowerShell’s commands:
PS > $filename = "c: emphelp.html" PS > PS > $commands = Get-Command | Where { $_.CommandType -ne "Alias" } PS > $summary = $commands | Get-Help | Select Name,Synopsis PS > $summary | ConvertTo-Html | Set-Content $filename
When you use the ConvertTo-Html
cmdlet to export command output
to a file, PowerShell generates an HTML table that represents the
command output. In the table, it creates a row for each object that you
provide. For each row, PowerShell creates columns to represent the
values of your object’s properties.
If the table format makes the output difficult
to read, ConvertTo-Html
offers the
-As
parameter that lets you set the output style to
either Table
or List
.
While the default output is useful, you can
customize the structure and style of the resulting HTML as much as you
see fit. For example, the -PreContent
and -Post
Content
parameters let you include
additional text before and after the resulting table or list. The
-Head
parameter lets you define the content of the
HEAD section of the HTML. Even if you want to generate most of the HTML
from scratch, you can still use the -Fragment
parameter to generate just the
inner table or list.
For more information about the ConvertTo-Html
cmdlet, type Get-
Help
ConvertTo-Html
.
Use the Send-MailMessage
cmdlet to send an
email.
PS > Send-MailMessage -To [email protected] ` -From [email protected] ` -Subject "Hello!" ` -Body "Hello, from another satisfied Cookbook reader!" ` -SmtpServer mail.example.com
The Send-MailMessage
cmdlet
supports everything you would expect an email-centric cmdlet to support:
attachments, plain text messages, HTML messages, priority, receipt
requests, and more. The most difficult aspect usually is remembering the
correct SMTP server to use.
The Send-MailMessage
cmdlet
helps solve this problem as well. If you don’t specify the -SmtpServer
parameter, it uses the server
specified in the $PSEmailServer
variable, if
any.
The Send-MailMessage
cmdlet
was introduced in version two of PowerShell. If you need to send an
email from version one of PowerShell, see Program: Send-MailMessage.
The Send-MailMessage
cmdlet
is the easiest way to send an email from PowerShell, but was introduced in
version two of PowerShell. If you need to send an email from version one
of PowerShell, you can use Example 12-5.
In addition to the fields shown in the script,
the System.Net.Mail.MailMessage
class
supports properties that let you add attachments, set message priority,
and much more. For more information about working with classes from the
.NET Framework, see Work with .NET Objects.
Example 12-5. Send-MailMessage.ps1
############################################################################## ## ## Send-MailMessage ## ## From Windows PowerShell Cookbook (O'Reilly) ## by Lee Holmes (http://www.leeholmes.com/guide) ## ## Illustrate the techniques used to send an email in PowerShell. ## In version two, use the Send-MailMessage cmdlet. ## ## Example: ## ## PS >$body = @" ## >> Hi from another satisfied customer of The PowerShell Cookbook! ## >> "@ ## >> ## PS >$to = "[email protected]" ## PS >$subject = "Thanks for all of the scripts." ## PS >$mailHost = "mail.leeholmes.com" ## PS >Send-MailMessage $to $subject $body $mailHost ## ############################################################################## param( ## The recipient of the mail message [string[]] $To = $(throw "Please specify the destination mail address"), ## The subject of the message [string] $Subject = "<No Subject>", ## The body of the message [string] $Body = $(throw "Please specify the message content"), ## The SMTP host that will transmit the message [string] $SmtpHost = $(throw "Please specify a mail server."), ## The sender of the message [string] $From = "$($env:UserName)@example.com" ) ## Create the mail message $email = New-Object System.Net.Mail.MailMessage ## Populate its fields foreach($mailTo in $to) { $email.To.Add($mailTo) } $email.From = $from $email.Subject = $subject $email.Body = $body ## Send the mail $client = New-Object System.Net.Mail.SmtpClient $smtpHost $client.UseDefaultCredentials = $true $client.Send($email)
For more information about running scripts, see Run Programs, Scripts, and Existing Tools.
Although it is 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.Web.Mail.SmtpMail
class for SMTP, the
System.Net.WebClient
class for HTTP,
and a few others. When the .NET Framework does not 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-6 shows how to receive
information about mail waiting in a remote POP3 mailbox, using the
Send-TcpRequest
script given in Example 12-7.
Example 12-6. Interacting with a remote POP3 mailbox
## 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 mail.myserver.com 110 $inbox = $output.Split("`n")[3] ## Parse the output for the number of messages waiting and total bytes $status = $inbox | Convert-TextObject -PropertyName "Response","Waiting","BytesTotal","Extra" "{0} messages waiting, totaling {1} bytes." -f $status.Waiting, $status.BytesTotal
In Example 12-6, 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-6 uses the Convert-TextObject
command, which is provided in
Program: Convert Text Streams to Objects.
Example 12-7 supports the core functionality of Example 12-6. It lets you easily work with plain-text TCP protocols.
Example 12-7. Send-TcpRequest.ps1
############################################################################## ## ## Send-TcpRequest ## ## From Windows 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 #> param( ## The computer to connect to [string] $ComputerName = "localhost", ## A switch to determine if you just want to test the connection [switch] $Test, ## The port to use [int] $Port = 80, ## A switch to determine if the connection should be made using SSL [switch] $UseSSL, ## The input string to send to the remote host [string] $InputObject, ## The delay, in milliseconds, to wait between commands [int] $Delay = 100 ) Set-StrictMode -Version Latest [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 { $socket = 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 = $socket.GetStream() ## If we wanted to use SSL, set up that portion of the connection if($UseSSL) { $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 Run Programs, Scripts, and Existing Tools.
3.133.130.199