Chapter 12. Internet-Enabled Scripts

Introduction

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.

Download a File from the Internet

Problem

You want to download a file from a website on the Internet.

Solution

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)

Discussion

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.

Download a Web Page from the Internet

Problem

You want to download a web page from the Internet and work with the content as a plain string.

Solution

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)

Discussion

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.

Note

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("&nbsp&nbsp", "")
}

. Main

For more information about running scripts, see Run Programs, Scripts, and Existing Tools.

Program: Get-PageUrls

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.

Connect to a Web Service

Problem

You want to connect to and interact with an Internet web service.

Solution

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

Discussion

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 -UseDefaultCredential 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.

Note

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.

Program: Connect-WebService

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.

Export Command Output as a Web Page

Problem

You want to export the results of a command as a web page so that you can post it to a web server.

Solution

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

Discussion

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 -PostContent 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.

Send an Email

Problem

You want to send an email.

Solution

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

Discussion

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.

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.

Program: Interact with Internet Protocols

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.130.199