Skip to main content

Resources

How to Search a File for Text or a Pattern in Windows PowerShell

Problem

You want to find a string or regular expression in a file.

Solution

To search a file for an exact (but case insensitive) match, use the –Simple parameter of the SelectString cmdlet:

PS >SelectString –Simple SearchText file.txt To search a file for a regular expression, provide that pattern to the SelectString cmdlet:

PS >SelectString "\(...\) ......." phone.txt To Recursively search all *.txt files for a regular expression, pipe the results of GetChildItem to the SelectString cmdlet:

PS >GetChildItem Filter *.txt Recurse | SelectString pattern

Discussion

The SelectString cmdlet is the easiest way to search files for a pattern or specific string. In contrast to the traditional textmatching utilities (such as grep) that support the same type of functionality, the matches returned by the SelectString cmdlet include detailed information about the match itself.

PS >$matches = SelectString "output file" transcript.txt PS >$matches | Select LineNumber,Line

LineNumber Line 7 Transcript started, output file... If you want to search multiple files of a specific extension, the SelectString cmdlet lets you use wildcards (such as *.txt) on the filename. For more complicated lists of files (which includes searching all files in the directory), it is usually more useful to use the GetChildItem cmdlet to generate the list of files as shown previously.

By default, the SelectString cmdlet outputs the filename, line number, and matching line for every match it finds. In some cases, this output may be too much detail— such as when you are searching for which binary file contains a specific string. Binary files rarely make sense when displayed as text, so your screen quickly fills with apparent garbage.

The solution to this problem comes from the SelectString’s –Quiet switch. It simply returns True or False, depending on whether the file contains the string. So, to find the DLL in the current directory that contains the text "Debug":

GetChildItem | Where { $_ | SelectString "Debug" Quiet } Two other common tools used to search files for text are the –match operator and the switch statement with the –file option.

Enable PowerShell Scripting Through an Execution Policy

Problem

PowerShell provides an error message when you try to run a script:

PS>.\Test.ps1 File C:\temp\test.ps1 cannot be loaded because the execution of scripts is disa bled on this system. Please see "gethelp about_signing" for more details. At line:1 char:10

+ .\Test.ps1

Solution

To prevent this error message, use the SetExecutionPolicy cmdlet to change the PowerShell execution policy to one of the policies that allow scripts to run:

SetExecutionPolicy RemoteSigned

Discussion

As normally configured, PowerShell operates strictly as an interactive shell. By disabling the execution of scripts by default, PowerShell prevents malicious PowerShell scripts from affecting users who have PowerShell installed, but who may never have used (or even heard of!) PowerShell.

You (as a reader of this book) are not part of that target audience, however, so you will want to configure PowerShell to run under one of the following four execution policies:

Restricted

PowerShell operates as an interactive shell only. Attempting to run a script generates an error message. This is PowerShell’s default execution policy.

AllSigned

PowerShell only runs scripts that contain a digital signature. When you attempt to run a script signed by a publisher that PowerShell hasn’t seen before, PowerShell asks whether you trust that publisher to run scripts on your system.

RemoteSigned (recommended)

PowerShell runs most scripts without prompting, but requires that scripts that originate from the Internet contain a digital signature. As in AllSigned mode, PowerShell asks whether you trust that publisher to run scripts on your system when you run a script signed by a publisher it hasn’t seen before. PowerShell considers a script to have come from the Internet when it has been downloaded to your computer by a popular communications programs such as Internet Explorer, Outlook, or Messenger.

Unrestricted

PowerShell does not require a digital signature on any script, but (like Windows Explorer) warns you when a script originates from the Internet.

Run the SetExecutionPolicy cmdlet as an administrator to configure the system’s execution policy. If you want to configure your execution policy on Windows Vista, rightclick the Windows PowerShell link for the option to launch PowerShell as Administrator.

Just because a script is signed, it does not mean that the script is safe! The signature on a script gives you a way to verify who the script came from, but not that you can trust its author to run commands on your

system. You need to make that decision for yourself, which is why PowerShell asks you.

Alternatively, you may directly modify the registry key that PowerShell uses to store its execution policy. This is the ExecutionPolicy property under the registry path HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell.

In an enterprise setting, PowerShell also lets you override this local preference through Group Policy.

When using an execution policy that detects Internetbased scripts, you may want to stop PowerShell from treating those scripts as remote. To do that, rightclick on the file from Windows Explorer, select Properties, and then click Unblock.

In an enterprise setting, PowerShell sometimes warns of the dangers of Internetbased scripts even if they are located only on a network share. If unblocking the file does not resolve the issue, your machine has likely been configured to restrict access to network shares. This is common with Internet Explorer’s Enhanced Security Configuration mode. To prevent this message, add the path of the network share to Internet Explorer’s Intranet or Trusted Sites zone.

For more information about script signing in PowerShell, type GetHelp about_ signing. For more information about the SetExecutionPolicy cmdlet, type GetHelp SetExecutionPolicy.

Get and List the Properties of a PowerShell User Account

Problem

You want to get and list the properties of a specific user account.

Solution

To list the properties of a user account, use the [adsi] type shortcut to bind to the user in Active Directory, and then pass the user to the FormatList cmdlet: $user = [adsi] "LDAP://localhost:389/cn=MyerKen,ou=West,ou=Sales,dc=Fabrikam,dc=COM"

$user | FormatList *

Discussion

The solution retrieves the MyerKen user from the Sales West OU. By default, the FormatList cmdlet shows only the distinguished name of the user, so we type FormatList * to display all properties.

If you know the property for which you want the value, specify it by name:

PS >$user.DirectReports CN=SmithRobin,OU=West,OU=Sales,DC=Fabrikam,DC=COM CN=DoeJane,OU=West,OU=Sales,DC=Fabrikam,DC=COM

Unlike users, some types of Active Directory objects don’t allow you to retrieve their properties by name this way. Instead, you must call the Get() method to retrieve specific properties:

PS >$user.Get("userPrincipalName") Ken.Myer@fabrikam.com

Create a Multiline or Formatted String in Windows PowerShell

Problem

You want to create a variable that holds text with newlines or other explicit formatting.

Solution

Use a PowerShell here string to store and work with text that includes newlines and other formatting information.

$myString = @" This is the first line of a very long string. A "here string" lets you to create blocks of text that span several lines. "@

Discussion

PowerShell begins a here string when it sees the characters @" followed by a newline. It ends the string when it sees the characters "@ on their own line. These seemingly odd restrictions allow you to create strings that include quote characters, newlines, and other symbols that you commonly use when you create large blocks of preformatted text.

These restrictions, while useful, can sometimes cause problems when you copy and paste PowerShell examples from the Internet. Web pages often add spaces at the end of lines, which can interfere with the

strict requirements of the beginning of a here string. If PowerShell produces an error when your script defines a here string, check that the here string does not include an errant space after its first quote character.

Like string literals, here strings may be literal (and use single quotes) or expanding (and use double quotes).

In addition to their usefulness in preformatted text variables, here strings also provide a useful way to temporarily disable lines in your script. Since PowerShell does not provide a firstclass multiline comment, you can use a here string as you would use a multiline comment in other scripting or programming languages. Example 51 demonstrates this technique.

Example 51. Using here strings for multiline comments

## This is a regular comment $null = @" function MyTest {

Example 51. Using here strings for multiline comments (continued)

"This should not be considered a function" }

$myVariable = 10; "@

## This is regular script again

Using $null for the variable name tells PowerShell to not retain the information for your later use.

Safely Build File Paths Out of Their Components

Problem

You want to build a new path out of a combination of subpaths.

Solution

To join elements of a path together, use the JoinPath cmdlet:

PS >JoinPath (GetLocation) newfile.txt C:\temp\newfile.txt

Discussion

The usual way to create new paths is by combining strings for each component, placing a path separator between them:

PS >"$(GetLocation)\newfile.txt" C:\temp\newfile.txt

Unfortunately, this approach suffers from a handful of problems:

  • What if the directory returned by GetLocation already has a slash at the end?
  • What if the path contains forward slashes instead of backslashes?
  • What if we are talking about registry paths instead of filesystem paths? Fortunately, the JoinPath cmdlet resolves these issues and more. For more information about the JoinPath cmdlet, type GetHelp JoinPath.

Launch a Windows PowerShell Process

Problem

You want to launch a new process on the system, but also want to configure its startup environment.

Solution

To launch a new process, use the [System.Diagnostics.Process]::Start() method. To control its startup environment, supply it with a System.Diagnostics. ProcessStartInfo object that you prepare, as shown in Example 211.

Example 211. Configuring the startup environment of a new process

$credential = GetCredential

## Prepare the startup information (including username and password) $startInfo = NewObject Diagnostics.ProcessStartInfo $startInfo.UserName = $credential.Username $startInfo.Password = $credential.Password $startInfo.Filename = "powershell"

## Start the process $startInfo.UseShellExecute = $false [Diagnostics.Process]::Start($startInfo)

Discussion

Normally, launching a process in PowerShell is as simple as typing the program name:

PS >notepad c:\temp\test.txt

However, you may sometimes need detailed control over the process details, such as its credentials, startup directory, environment variables, and more. In those situations, use the [System.Diagnostics.Process]::Start() method to provide that functionality.

The following function acts like the cmd.exe start command and like the Start | Run dialog in Windows:

PS >function start { [Diagnostics.Process]::Start($args) }

PS >start www.msn.com

Store Information in Variables in Windows PowerShell

Problem

You want to store the output of a pipeline or command for later use, or to work with it in more detail.

Solution

To store output for later use, store the output of the command in a variable. You can access this information later, or even pass it down the pipeline as though it was the output of the original command:

PS >$result = 2 + 2 PS >$result 4 PS >$processes = GetProcess PS >$processes.Count 85 PS >$processes | WhereObject { $_.ID eq 0 }

Handles
NPM(K)
PM(K)
WS(K) VM(M)
CPU(s)
Id ProcessName

0
0
0
16
0

0 Idle

Discussion

Variables in PowerShell (and all other scripting and programming languages) let you store the output of something so that you can use it later. Avariable name starts with a dollar sign ($) and can be followed by nearly any character. Asmall set of characters have special meaning to PowerShell, so PowerShell provides a way to make variable names that include even these.

You can store the result of any pipeline or command in a variable to use it later. If that command generates simple data (such as a number or string), then the variable contains simple data. If the command generates rich data (such as the objects that represent system processes from the GetProcess cmdlet), then the variable contains that list of rich data. If the command (such as a traditional executable) generates plain text (such as the output of traditional executable), then the variable contains plain text.

If you’ve stored a large amount of data into a variable, but no longer need that data, assign the value $null (or anything else) to that variable so that PowerShell can release the memory it was using to store

that data.

In addition to variables that you create, PowerShell automatically defines several variables that represent things such as the location of your profile file, the process ID of PowerShell, and more.

Tracing and Error Management in Windows PowerShell

What if it doesn’t all go according to plan? This is the core question behind error management in any system and plays a large part in writing PowerShell scripts as well.

While it is a core concern in many systems, PowerShell’s support for error management provides several unique features designed to make your job easier: the primary benefit being a distinction between terminating and nonterminating errors.

When running a complex script or scenario, the last thing you want is for your world to come crashing down because a script can’t open one of the 1,000 files it is operating on. Although it should make you aware of the failure, the script should still continue to the next file. That is an example of a nonterminating error. But what if the script runs out of disk space while running a backup? That should absolutely be an error that causes the script to exit—also known as a terminating error.

Given this helpful distinction, PowerShell provides several features that allow you to manage errors generated by scripts and programs, and also allows you to generate them yourself.

Comparing Datain Windows PowerShell

When working in PowerShell, it is common to work with collections of objects. Most PowerShell commands generate objects, as do many of the methods that you work with in the .NET Framework. To help work with these object collections, PowerShell introduces the CompareObject cmdlet. The CompareObject cmdlet provides functionality similar to wellknown diff commands, but with an objectoriented flavor.

Store the Output of a Windows PowerShell Command into a File

Problem

You want to redirect the output of a pipeline into a file.

Solution

To redirect the output of a command into a file, use either the OutFile cmdlet or one of the redirection operators.

OutFile:

GetChildItem | OutFile unicodeFile.txt GetContent filename.cs | OutFile Encoding ASCII file.txt GetChildItem | OutFileWidth 120 unicodeFile.cs

Redirection operators:

GetChildItem > files.txt GetChildItem 2> errors.txt

Discussion

The OutFile cmdlet and redirection operators share a lot in common—and for the most part, you can use either. The redirection operators are unique because they give the greatest amount of control over redirecting individual streams. The OutFile cmdlet is unique primarily because it lets you easily configure the formatting width and encoding.

The default formatting width and the default output encoding are two aspects of output redirection that can sometimes cause difficulty.

The default formatting width sometimes causes problems because redirecting PowerShellformatted output into a file is designed to mimic what you see on the screen. If your screen is 80 characters wide, the file will be 80 characters wide as well. Examples of PowerShellformatted output include directory listings (that are implicitly formatted as a table) as well as any commands that you explicitly format using one of the Format* set of cmdlets. If this causes problems, you can customize the width of the file with the —Width parameter on the OutFile cmdlet.

The default output encoding sometimes causes unexpected results because PowerShell creates all files using the UTF16 Unicode encoding by default. This allows PowerShell to fully support the entire range of international characters, cmdlets, and output. Although this is a great improvement to traditional shells, it may cause an unwanted surprise when running large search and replace operations on ASCII source code files, for example. To force PowerShell to send its output to a file in the ASCII encoding, use the —Encoding parameter on the OutFile cmdlet.

For more information about the OutFile cmdlet, type GetHelp OutFile.

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