Skip to main content

Windows

Combine Two Arrays

Problem

You have two arrays and want to combine them into one.

Solution

To combine PowerShell arrays, use the addition operator (+):

PS >$firstArray = "Element 1","Element 2","Element 3","Element 4" PS >$secondArray = 1,2,3,4 PS > PS >$result = $firstArray + $secondArray PS >$result Element 1 Element 2 Element 3 Element 4 1 2 3 4

Discussion

One common reason to combine two arrays is when you want to add data to the end of one of the arrays. For example:

PS >$array = 1,2 PS >$array = $array + 3,4 PS >$array 1 2 3 4

You can write this more clearly as:

PS >$array = 1,2 PS >$array += 3,4 PS >$array 1 2 3 4

When written in the second form, however, you might think that PowerShell simply adds the items to the end of the array while keeping the array itself intact. This is not true, since arrays in PowerShell (like most other languages) stay the same length once you create them. To combine two arrays, PowerShell creates a new array large enough to hold the contents of both arrays and then copies both arrays into the destination array.

If you plan to add and remove data from an array frequently, the System. Collections.ArrayList class provides a more dynamic alternative.

The Windows Registry

As the configuration store for the vast majority of applications, the registry plays a central role in system administration. It is also generally hard to manage.

While commandline tools (such as reg.exe) exist to help you work with the registry, their interfaces are usually inconsistent and confusing. While the Registry Editor graphical user interface is easy to use, it does not support scripted administration.

PowerShell tackles this problem by exposing the Windows Registry as a navigation provider—a data source that you navigate and manage in exactly the same way that you work with the filesystem.

Experiment with Exchange Management Shell

Problem

You want to experiment with the features and functionality of the Exchange Management Shell without working on a production system.

Solution

To explore the Exchange Management Shell, use the shellfocused Exchange 2007 Microsoft Virtual Lab.

Discussion

Microsoft recently introduced virtual labs as a core technology to help you experiment with new technologies. Exchange 2007 offers several of these labs. The one that applies to the Exchange Management Shell is called “Exchange Server 2007: Using the Exchange Server 2007 Management Console and Shell Virtual Lab.”

To launch this lab, visit the Technet Virtual Labs home page at http://www.microsoft. com/technet/traincert/virtuallab/default.mspx . In the Virtual Labs by Product section, click Exchange Server; then select Exchange Server 2007: Using the Exchange Server 2007 Management Console and Shell.

Program: Send an Email in Windows PowerShell

Example 94 shows how to easily send email messages from your scripts.

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.

Example 94. SendMailMessage.ps1

############################################################################## ## ## SendMailMessage.ps1 ## ## Illustrate the techniques used to send an email in PowerShell. ## ## Example: ## ## PS >$body = @" ## >> Hi from another satisfied customer of The PowerShell Cookbook! ## >> "@ ## >> ## PS >$to = "guide_feedback@leeholmes.com" ## PS >$subject = "Thanks for all of the scripts." ## PS >$mailHost = "mail.leeholmes.com" ## PS >SendMailMessage $to $subject $body $mailHost ## ##############################################################################

param( [string[]] $to = $(throw "Please specify the destination mail address"), [string] $subject = "", [string] $body = $(throw "Please specify the message content"), [string] $smtpHost = $(throw "Please specify a mail server."), [string] $from = "$($env:UserName)@example.com"

)

## Create the mail message $email = NewObject 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 = NewObject System.Net.Mail.SmtpClient $smtpHost $client.UseDefaultCredentials = $true $client.Send($email)

Program: Interact with Internet Protocols in Windows PowerShell

While it is common to work at an abstract level with web sites and web services, an entirely separate style of Internetenabled 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 allow you to 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 95 shows how to receive information about mail waiting in a remote POP3 mailbox, using the SendTcpRequest script given in Example 96.

Example 95. Interacting with a remote POP3 mailbox

## Get the user credential if(not (TestPath Variable:\mailCredential)) {

$mailCredential = GetCredential } $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 | SendTcpRequest mail.myserver.com 110 $inbox = $output.Split("`n")[3]

## Parse the output for the number of messages waiting and total bytes $status = $inbox |

ConvertTextObject PropertyName "Response","Waiting","BytesTotal","Extra" "{0} messages waiting, totaling {1} bytes." f $status.Waiting, $status.BytesTotal

In Example 95, 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 95 uses the ConvertTextObject command

Example 96 supports the core functionality of Example 95. It lets you easily work with plaintext TCP protocols.

Example 96. SendTcpRequest.ps1

############################################################################## ## SendTcpRequest.ps1 ## ## 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 | SendTcpRequest search.msn.com 80

##############################################################################

param( [string] $remoteHost = "localhost", [int] $port = 80, [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) {

WriteHost "Connecting to $remoteHost on port $port" }

trap { WriteError "Could not connect to remote computer: $_"; exit } $socket = NewObject System.Net.Sockets.TcpClient($remoteHost, $port)

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

$stream = $socket.GetStream() $writer = NewObject System.IO.StreamWriter($stream)

Example 96. SendTcpRequest.ps1 (continued)

## Create a buffer to receive the response $buffer = NewObject System.Byte[] 1024 $encoding = NewObject System.Text.AsciiEncoding

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() StartSleep 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")) {

WriteHost $line } $SCRIPT:output = ""

}

## Read the user's command, quitting if they hit ^D $command = ReadHost 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()

Example 96. SendTcpRequest.ps1 (continued)

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

$output } }

## Read output from a remote host function GetOutput {

$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 StartSleep m 1000

## Read what data is available $foundmore = $false while($stream.DataAvailable) {

$read = $stream.Read($buffer, 0, 1024) $outputBuffer += ($encoding.GetString($buffer, 0, $read)) $foundmore = $true

} } while($foundmore)

$outputBuffer }

. Main

Code Reuse in PowerShell

What surprises many people is how much you can accomplish in PowerShell from the interactive prompt alone. Since PowerShell makes it so easy to join its powerful commands together into even more powerful combinations, enthusiasts grow to relish this brevity. In fact, there is a special place in the heart of most scripting enthusiasts set aside entirely for the most compact expressions of power: oneliners.

Despite its interactive efficiency, you obviously don’t want to retype all your brilliant ideas anew each time you need them. When you want to save or reuse the commands that you’ve written, PowerShell provides many avenues to support you: scripts, libraries, functions, script blocks, and more.

Get the Files in a Directory in Windows PowerShell

Problem

You want to get or list the files in a directory.

Solution

To retrieve the list of files in a directory, use the GetChildItem cmdlet. To get a specific item, use the GetItem cmdlet:

  • To list all items in the current directory, use the GetChildItem cmdlet: GetChildItem
  • To list all items that match a wildcard, supply a wildcard to the GetChildItem cmdlet:

GetChildItem *.txt

    • To list all files that match a wildcard in the current directory (and all its chil
    • dren), use the –Include and –Recurse parameters of the GetChildItem cmdlet: GetChildItem –Include *.txt Recurse
  • To list all directories in the current directory, use the WhereObject cmdlet to test

the PsIsContainer property: GetChildItem | Where { $_.PsIsContainer }

• To get information about a specific item, use the GetItem cmdlet: GetItem test.txt

Discussion

Although most commonly used on the filesystem, the GetChildItem and GetItem cmdlets in fact work against any items in any of the PowerShell drives. In addition to

A: through Z: (the standard file system drives), they also work on Alias:, Cert:, Env:, Function:, HKLM:, HKCU:, and Variable:.

One example lists files that match a wildcard in a directory and all its children. That example works on any PowerShell provider. However, PowerShell can retrieve your results more quickly if you use a provider specific filter.

The solution demonstrates some simple wildcard scenarios that the GetChildItem cmdlet supports, but PowerShell in fact enables several more advanced scenarios.

In the filesystem, these cmdlets return objects from the .NET Framework that represent files and directories—instances of the System.IO.FileInfo and System.IO. DirectoryInfo classes, respectively. Each provides a great deal of useful information: attributes, modification times, full name, and more. Although the default directory listing exposes a lot of information, PowerShell provides even more.

Program: List Startup or Shutdown Scripts for a Machine

The Group Policy system in Windows stores startup and shutdown scripts under the registry keys HKLM:\SOFTWARE\Policies\Microsoft\Windows\System\Scripts\Startup and HKLM:\SOFTWARE\Policies\Microsoft\Windows\System\Scripts\Shutdown. Each key has a subkey for each group policy object that applies. Each of those child keys has another level of keys that correspond to individual scripts that apply to the machine.

Example 242 allows you to easily retrieve and access the startup and shutdown scripts for a machine.

Example 242. GetMachineStartupShutdownScript.ps1

############################################################################## ## ## GetMachineStartupShutdownScript.ps1 ## ## Get the startup or shutdown scripts assigned to a machine ## ## ie: ## ## PS >GetMachineStartupShutdownScript Startup ## ##############################################################################

param( $scriptType = $(throw "Please specify the script type") )

## Verify that they've specified a correct script type $scriptOptions = "Startup","Shutdown" if($scriptOptions notcontains $scriptType) {

$error = "Cannot convert value {0} to a script type. " + "Specify one of the following values and try again. " + "The possible values are ""{1}""."

$ofs = ", " throw ($error f $scriptType, ([string] $scriptOptions)) }

## Store the location of the group policy scripts for the machine $registryKey = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\System\Scripts"

Example 242. GetMachineStartupShutdownScript.ps1 (continued)

## Go through each of the policies in the specified key foreach($policy in GetChildItem $registryKey\$scriptType) {

## For each of the scripts in that policy, get its script name ## and parameters foreach($script in GetChildItem $policy.PsPath) {

GetItemProperty $script.PsPath | Select Script,Parameters } }

Get the Content of a File in Windows PowerShell

Problem

You want to get the content of a file.

Solution

Provide the filename as an argument to the GetContent cmdlet:

PS >$content = GetContent c:\temp\file.txt Place the filename in a ${ } section to use the cmdlet GetContent variable syntax:

PS >$content = ${c:\temp\file.txt} Provide the filename as an argument to the ReadAllText() method to use the System. IO.File class from the .NET Framework:

PS >$content = [System.IO.File]::ReadAllText("c:\temp\file.txt")

Discussion

PowerShell offers three primary ways to get the content of a file. The first is the GetContent cmdlet—the cmdlet designed for this purpose. In fact, the GetContent cmdlet works on any PowerShell drive that supports the concept of items with content. This includes Alias:, Function:, and more. The second and third ways are the GetContent variable syntax, and the ReadAllText() method.

When working against files, the GetContent cmdlet returns the content of the file linebyline. When it does this, PowerShell supplies additional information about that output line. This information, which PowerShell attaches as properties to each output line, includes the drive and path from where that line originated, among other things.

If you want PowerShell to split the file content based on a string that you choose (rather than the default of newlines), the GetContent cmdlet’s –Delimiter parameter lets you provide one.

While useful, having PowerShell attach this extra information when you are not using it can sometimes slow down scripts that operate on large files. If you need to process a large file more quickly, the GetContent cmdlet’s ReadCount parameter lets you control how many lines PowerShell reads from the file at once. With a ReadCount of 1 (which is the default), PowerShell returns each line onebyone. With a ReadCount of 2, PowerShell returns two lines at a time. With a ReadCount of less than 1, PowerShell returns all lines from the file at once.

Beware of using a ReadCount of less than 1 for extremely large files. One of the benefits of the GetContent cmdlet is its streaming behavior. No matter how large the file, you will still be able to process each

line of the file without using up all your system’s memory. Since a ReadCount of less than 1 reads the entire file before returning any results, large files have the potential to use up your system’s memory.

If performance is a primary concern, the [File]::ReadAllText() method from the .NET Framework reads a file most quickly from the disk. Unlike the GetContent cmdlet, it does not split the file into newlines, attach any additional information, or work against any other PowerShell drives. Like the GetContent cmdlet with a ReadCount of less than 1, it reads all the content from the file before it returns it to you—so be cautious when using it on extremely large files.

For more information about the GetContent cmdlet, type GetHelp GetContent. For information on how to work with more structured files (such as XML and CSV), see Chapter 8, Structured Files .

Security and Script Signing of Windows PowerShell

Security plays two important roles in PowerShell. The first role is the security of PowerShell itself: scripting languages have long been a vehicle of emailbased malware on Windows, so PowerShell’s security features have been carefully designed to thwart this danger. The second role is the set of securityrelated tasks you are likely to encounter when working with your computer: script signing, certificates, and credentials, just to name a few.

When it comes to talking about security in the scripting and commandline world, a great deal of folklore and superstition clouds the picture. One of the most common misconceptions is that that scripting languages and commandline shells somehow lets users bypass the security protections of the Windows graphical user interface.

The Windows security model (as with any security model that actually provides security) protects resources—not the way you get to them. That is because programs that you run, in effect, are you. If you can do it, so can a program. If a program can do it, then you can do it without having to use that program. For example, consider the act of changing critical data in the Windows Registry. If you use the Windows Registry Editor graphical user interface, it provides an error message when you attempt to perform an operation that you do not have permission for.

The Registry Editor provides this error message because it is unable to delete that key, not because it wanted to prevent you from doing it. Windows itself protects the registry keys, not the programs you use to access them.

Likewise, PowerShell provides an error message when you attempt to perform an operation that you do not have permission for. Not because PowerShell contains

extra security checks for that operation, but because it is also simply unable to perform the operation:

PS >NewItem "HKLM:\Software\Microsoft\Windows\CurrentVersion\Run\New" NewItem : Requested registry access is not allowed. At line:1 char:9

+ NewItem "HKLM:\Software\Microsoft\Windows\CurrentVersion\Run\New"

While perhaps clear after explanation, this misunderstanding often gets used as a reason to prevent users from running command shells or scripting languages altogether.

Add PowerShell Scripting to Your Own Program

Problem

You want to provide your users with an easy way to automate your program, but don’t want to write a scripting language on your own.

Discussion

One of the fascinating aspects of PowerShell is how easily it lets you add many of its capabilities to your own program. This is because PowerShell is, at its core, a powerful engine that any application can use. The PowerShell console application is in fact just a textbased interface to this engine.

While a full discussion of the PowerShell hosting model is outside the scope of this book, the following example illustrates the techniques behind exposing features of your application for your users to script.

To frame Example 1513, imagine an email application that lets you run rules when it receives an email. While you will want to design a standard interface that allows users to create simple rules, you will also want to provide a way for users to write incredibly complex rules. Rather than design a scripting language yourself, you can simply use PowerShell’s scripting language. In the following example, we provide userwritten scripts with a variable called $message that represents the current message and then runs their commands.

PS >GetContent VerifyCategoryRule.ps1

if($message.Body match "book")

{

[Console]::WriteLine("This is a message about the book.")

}

else

{

[Console]::WriteLine("This is an unknown message.")

}

PS >.\RulesWizardExample.exe (ResolvePath VerifyCategoryRule.ps1)

This is a message about the book.

For more information on how to host PowerShell in your own application, see the MSDN topic, “How to Create a Windows PowerShell Hosting Application,” available at http://msdn2.microsoft.com/enus/library/ms714661.aspx.

Step 1: Download the Windows SDK

The Windows SDK contains samples, tools, reference assemblies, templates, documentation, and other information used when developing PowerShell cmdlets. It is available by searching for “Microsoft Windows SDK” on http://download.microsoft.com .

Step 2: Create a file to hold the hosting source code

Create a file called RulesWizardExample.cs with the content from Example 1513, and save it on your hard drive.

Example 1513. RulesWizardExample.cs

using System; using System.Management.Automation; using System.Management.Automation.Runspaces;

namespace Template

{ // Define a simple class that represents a mail message public class MailMessage {

public MailMessage(string to, string from, string body)

{ this.To = to; this.From = from; this.Body = body;

}

public String To; public String From; public String Body;

}

public class RulesWizardExample

{ public static void Main(string[] args) {

// Ensure that they've provided some script text if(args.Length == 0) {

Console.WriteLine("Usage:"); Console.WriteLine(" RulesWizardExample

Search for a Windows PowerShell User Account

Problem

You want to search for a specific user account, but don’t know the user’s distinguished name (DN).

Solution

To search for a user in Active Directory, use the [adsi] type shortcut to bind to a container that holds the user account, and then use the System.DirectoryServices. DirectorySearcher class from the .NET Framework to search for the user:

$domain = [adsi] "LDAP://localhost:389/dc=Fabrikam,dc=COM" $searcher = NewObject System.DirectoryServices.DirectorySearcher $domain $searcher.Filter = '(&(objectClass=User)(displayName=Ken Myer))'

$userResult = $searcher.FindOne() $user = $userResult.GetDirectoryEntry()

Discussion

When you don’t know the full DN of a user account, the System.DirectoryServices. DirectorySearcher class from the .NET Framework lets you search for it.

You provide an LDAP filter (in this case, searching for users with the display name of Ken Myer), and then call the FindOne() method. The FindOne() method returns the first search result that matches the filter, so we retrieve its actual Active Directoryentry. Although the solution searches on the user’s display name, you can search on any field in Active Directory—the userPrincipalName and sAMAccountName are two other good choices.

When you do this search, always try to restrict it to the lowest level of the domain possible. If we know that Ken Myer is in the Sales OU, it would be better to bind to that OU instead:

$domain = [adsi] "LDAP://localhost:389/ou=Sales,dc=Fabrikam,dc=COM"

For more information about the LDAP search filter syntax, search http://msdn. microsoft.com for “Search Filter Syntax.”