## With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

No credit card required

# Introduction

Above all else, the design of Windows PowerShell places priority on its use as an efficient and powerful interactive shell. Even its scripting language plays a critical role in this effort, as it too heavily favors interactive use.

What surprises most people when they first launch PowerShell is its similarity to the command prompt that has long existed as part of Windows. Familiar tools continue to run. Familiar commands continue to run. Even familiar hotkeys are the same. Supporting this familiar user interface, though, is a powerful engine that lets you accomplish once cumbersome administrative and scripting tasks with ease.

This chapter introduces PowerShell from the perspective of its interactive shell.

# Run Programs, Scripts, and Existing Tools

## Problem

You rely on a lot of effort invested in your current tools. You have traditional executables, Perl scripts, VBScript, and of course, a legacy build system that has organically grown into a tangled mess of batch files. You want to use PowerShell, but you don’t want to give up everything you already have.

## Solution

To run a program, script, batch file, or other executable command in the system’s path, enter its filename. For these executable types, the extension is optional:

Program.exe arguments
ScriptName.ps1 arguments
BatchFile.cmd arguments

To run a command that contains a space in its name, enclose its filename in single-quotes (') and precede the command with an ampersand (&), known in PowerShell as the invoke operator:

& 'C:\Program Files\Program\Program.exe' arguments

To run a command in the current directory, place .\ in front of its filename:

.\Program.exe arguments

To run a command with spaces in its name from the current directory, precede it with both an ampersand and .\:

& '.\Program With Spaces.exe' arguments

## Discussion

In this case, the solution is mainly to use your current tools as you always have. The only difference is that you run them in the PowerShell interactive shell, rather than cmd.exe.

The final three tips in the solution merit special attention. They are the features of PowerShell that many new users stumble on when it comes to running programs. The first is running commands that contain spaces. In cmd.exe, the way to run a command that contains spaces is to surround it with quotes:

"C:\Program Files\Program\Program.exe"

In PowerShell, though, placing text inside quotes is part of a feature that lets you evaluate complex expressions at the prompt, as shown in Example 1-1.

Example 1-1. Evaluating expressions at the PowerShell prompt

PS > 1 + 1
2
PS > 26 * 1.15
29.9
PS > "Hello" + " World"
Hello World
PS > "Hello World"
Hello World
PS > "C:\Program Files\Program\Program.exe"
C:\Program Files\Program\Program.exe
PS >

So, a program name in quotes is no different from any other string in quotes. It’s just an expression. As shown previously, the way to run a command in a string is to precede that string with the invoke operator (&). If the command you want to run is a batch file that modifies its environment, see Program: Retain Changes to Environment Variables Set by a Batch File.

### Note

By default, PowerShell’s security policies prevent scripts from running. Once you begin writing or using scripts, though, you should configure this policy to something less restrictive. For information on how to configure your execution policy, see Enable Scripting Through an Execution Policy.

The second command that new users (and seasoned veterans before coffee!) sometimes stumble on is running commands from the current directory. In cmd.exe, the current directory is considered part of the path: the list of directories that Windows searches to find the program name you typed. If you are in the C:\Programs directory, cmd.exe looks in C:\Programs (among other places) for applications to run.

PowerShell, like most Unix shells, requires that you explicitly state your desire to run a program from the current directory. To do that, you use the .\Program.exe syntax, as shown previously. This prevents malicious users on your system from littering your hard drive with evil programs that have names similar to (or the same as) commands you might run while visiting that directory.

To save themselves from having to type the location of commonly used scripts and programs, many users put these utilities along with their PowerShell scripts in a “tools” directory, which they add to their system’s path. If PowerShell can find a script or utility in your system’s path, you do not need to explicitly specify its location.

### Note

Scripts and examples from this book are available at http://www.oreilly.com/catalog/9780596801519.

To learn how to write a PowerShell script, see Write a Script.

# Resolve Errors Calling Native Executables

## Problem

You have a command line that works from cmd.exe, and want to resolve errors that occur from running that command in PowerShell.

## Solution

Enclose any affected command arguments in single quotes to prevent them from being interpreted by PowerShell, and replace any single quotes in the command with two single quotes.

PS > cmd /c echo '!"#$%&''()*+,-./09:;<=>?@AZ[\]^_az{|}~' !"#$%&'()*+,-./09:;<=>?@AZ[\]^_az{|}~

## Discussion

One of PowerShell’s primary goals has always been command consistency. Because of this, cmdlets are very regular in the way that they accept parameters. Native executables write their own parameter parsing, so you never know what to expect when working with them. In addition, PowerShell offers many features that make you more efficient at the command line: command substitution, variable expansion, and more. Since many native executables were written before PowerShell was developed, they may use special characters that conflict with these features.

### Note

Unlike it does with cmdlets, PowerShell doesn’t apply special parameter processing to native executables. However, version one of PowerShell did incorrectly process some arguments that contained the colon character if the argument looked like parameters for a cmdlet. If you’ve experienced this (or perhaps formed a superstition that “calling native executables doesn’t work properly”), try again.

As an example, the command given in the Solution uses all the special characters available on a typical keyboard. Without the quotes, PowerShell treats some of them as language features, as shown in Table 1-1.

Table 1-1. Sample of special characters

Special characterMeaning

"

The beginning (or end) of quoted text

#

The beginning of a comment

$ The beginning of a variable & Reserved for future use ( ) Parentheses used for subexpressions ; Statement separator { } Script block | Pipeline separator  Escape character When surrounded by single quotes, PowerShell accepts these characters as written, without the special meaning. Despite these precautions, you may still sometimes run into a command that doesn’t seem to work when called from PowerShell. To see exactly what PowerShell passes to that command, you can view the output of the trace source called NativeCommandParameterBinder: PS > Trace-Command NativeCommandParameterBinder { cmd /c echo '!"#$%&''()*+,-./09:;<=>?@AZ[\]^_az{|}~'
} -PsHost

DEBUG: NativeCommandParameterBinder Information: 0 :  WriteLine
Argument 0: /c
DEBUG: NativeCommandParameterBinder Information: 0 :  WriteLine
Argument 1: echo
DEBUG: NativeCommandParameterBinder Information: 0 :  WriteLine
Argument 2: !#$%&'()*+,-./09:;<=>?@AZ[\]^_az{|}~ !"#$%&'()*+,-./09:;<=>?@AZ[\]^_az{|}~

If the command arguments shown in this output don’t match the arguments you expect, they have special meaning to PowerShell and should be escaped.

Get-Help Trace-Command

Appendix A

# Run a PowerShell Command

## Problem

You want to run a PowerShell command.

## Solution

To run a PowerShell command, type its name at the command prompt. For example:

PS > Get-Process

Handles  NPM(K)    PM(K)    WS(K)   VM(M)  CPU(s)    Id ProcessName
-------  ------    -----    -----   -----  ------    -- -----------
133       5    11760     7668      46          1112 audiodg
184       5    33248      508      93          1692 avgamsvr
143       7    31852      984      97          1788 avgemc

## Discussion

The Get-Process command is an example of a native PowerShell command, called a cmdlet. As compared to traditional commands, cmdlets provide significant benefits to both administrators and developers:

• They share a common and regular command-line syntax.

• They support rich pipeline scenarios (using the output of one command as the input of another).

• They produce easily manageable object-based output, rather than error-prone plain text output.

Because the Get-Process cmdlet generates rich object-based output, you can use its output for many process-related tasks.

The Get-Process cmdlet is just one of the many that PowerShell supports. See Find a Command to Accomplish a Task to learn techniques for finding additional commands that PowerShell supports.

# Invoke a Long-Running or Background Command

## Problem

You want to invoke a long-running command on a local or remote computer.

## Solution

Invoke the command as a Job to have PowerShell run it in the background:

PS > Start-Job { while($true) { Get-Random; Start-Sleep 5 } } -Name Sleeper Id Name State HasMoreData Location -- ---- ----- ----------- -------- 1 Sleeper Running True localhost PS > Receive-Job Sleeper 671032665 1862308704 PS > Stop-Job Sleeper ## Discussion PowerShell’s job cmdlets provide a consistent way to create and interact with background tasks. In the Solution, we use the Start-Job cmdlet to launch a background job on the local computer. We give it the name of Sleeper, but otherwise we don’t customize much of its execution environment. In addition to allowing you to customize the job name, the Start-Job cmdlet also lets you launch the job under alternate user credentials or as a 32-bit process (if run originally from a 64-bit process). Once you have launched a job, you can use the other Job cmdlets to interact with it: Get-Job Gets all jobs associated with the current session. Wait-Job Waits for a job until it has output ready to be retrieved. Receive-Job Retrieves any output the job has generated since the last call to Receive-Job. Stop-Job Stops a job. Remove-Job Removes a job from the list of active jobs. ### Note In addition to the Start-Job cmdlet, you can also use the -AsJob parameter in many cmdlets to have them perform their tasks in the background. Two of the most useful examples are the Invoke-Command cmdlet (when operating against remote computers) and the set of WMI-related cmdlets. If your job generates an error, the Receive-Job cmdlet will display it to you when you receive the results, as shown in Example 1-2. If you want to investigate these errors further, the object returned by Get-Job exposes them through the Error property. Example 1-2. Retrieving errors from a Job PS > Start-Job -Name ErrorJob { Write-Error Error! } WARNING: column "Command" does not fit into the display and was removed. Id Name State HasMoreData Location -- ---- ----- ----------- -------- 1 ErrorJob Running True localhost PS > Receive-Job ErrorJob Error! + CategoryInfo : NotSpecified: (:) [Write-Error], WriteError Exception + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorExc eption,Microsoft.PowerShell.Commands.WriteErrorCommand PS >$job = Get-Job ErrorJob
PS > $job | Format-List * State : Completed HasMoreData : False StatusMessage : Location : localhost Command : Write-Error Error! JobStateInfo : Completed Finished : System.Threading.ManualResetEvent InstanceId : 801e932c-5580-4c8b-af06-ddd1024840b7 Id : 1 Name : ErrorJob ChildJobs : {Job2} Output : {} Error : {} Progress : {} Verbose : {} Debug : {} Warning : {} PS >$job.ChildJobs[0] | Format-List *

State         : Completed
StatusMessage :
HasMoreData   : False
Location      : localhost
Runspace      : System.Management.Automation.RemoteRunspace
Command       :  Write-Error Error!
JobStateInfo  : Completed
InstanceId    : 60fa85da-448b-49ff-8116-6eae6c3f5006
Id            : 2
Name          : Job2
ChildJobs     : {}
Output        : {}
Error         : {Microsoft.PowerShell.Commands.WriteErrorException,Microso
ft.PowerShell.Commands.WriteErrorCommand}
Progress      : {}
Verbose       : {}
Debug         : {}
Warning       : {}

PS > Register-TemporaryEvent $job StateChanged -Action { [Console]::Beep(100,100) Write-Host "Job #$($sender.Id) ($($sender.Name)) complete." } PS > Job #6 (TenSecondSleep) complete. PS > ## Discussion When a job completes, it raises a StateChanged event to notify subscribers that its state has changed. We can use PowerShell’s event handling cmdlets to register for notifications about this event, but they are not geared toward this type of one-time event handling. To solve that, we use the Register-TemporaryEvent command given in Create a Temporary Event Subscription. In our example action block in that solution, we simply emit a beep and write a message saying that the job is complete. As another option, you can also update your prompt function to highlight jobs that are complete but still have output you haven’t processed: $psJobs = @(Get-Job -State Completed | ? { $_.HasMoreData }) if($psJobs.Count -gt 0) { ($psJobs | Out-String).Trim() | Write-Host -Fore Yellow } For more information about events and this type of automatic event handling, see Chapter 31. # Customize Your Shell, Profile, and Prompt ## Problem You want to customize PowerShell’s interactive experience with a personalized prompt, aliases, and more. ## Solution When you want to customize aspects of PowerShell, place those customizations in your personal profile script. PowerShell provides easy access to this profile script by storing its location in the $profile variable.

### Note

By default, PowerShell’s security policies prevent scripts (including your profile) from running. Once you begin writing scripts, though, you should configure this policy to something less restrictive. For information on how to configure your execution policy, see Enable Scripting Through an Execution Policy.

To create a new profile (and overwrite one if it already exists):

New-Item -type file -force $profile To edit your profile: notepad$profile

Get-ChildItem $profile Once you create a profile script, you can add a function called Prompt that returns a string. PowerShell displays the output of this function as your command-line prompt. function Prompt { "PS [$env:COMPUTERNAME] >"
}

This example prompt displays your computer name, and looks like PS [LEE-DESK]>.

You may also find it helpful to add aliases to your profile. Aliases let you refer to common commands by a name that you choose. Personal profile scripts let you automatically define aliases, functions, variables, or any other customizations that you might set interactively from the PowerShell prompt. Aliases are among the most common customizations, as they let you refer to PowerShell commands (and your own scripts) by a name that is easier to type.

### Note

If you want to define an alias for a command but also need to modify the parameters to that command, then define a function instead.

For example:

Set-Alias new New-Object
Set-Alias iexplore 'C:\Program Files\Internet Explorer\iexplore.exe'

. $profile Functions are also very common customizations, with the most popular being the Prompt function. ## Discussion The Solution discusses three techniques to make useful customizations to your PowerShell environment: aliases, functions, and a hand-tailored prompt. You can (and will often) apply these techniques at any time during your PowerShell session, but your profile script is the standard place to put customizations that you want to apply to every session. ### Note To remove an alias or function, use the Remove-Item cmdlet: Remove-Item function:\MyCustomFunction Remove-Item alias:\new Although the Prompt function returns a simple string, you can also use the function for more complex tasks. For example, many users update their console window title (by changing the $host.UI.RawUI.WindowTitle variable) or use the Write-Host cmdlet to output the prompt in color. If your prompt function handles the screen output itself, it still needs to return a string (for example, a single space) to prevent PowerShell from using its default. If you don’t want this extra space to appear in your prompt, add an extra space at the end of your Write-Host command and return the backspace ("b") character, as shown in Example 1-3.

Example 1-3. An example PowerShell prompt

##############################################################################
##
## From Windows PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
##############################################################################

Set-StrictMode -Version Latest

function Prompt
{
$id = 1$historyItem = Get-History -Count 1
if($historyItem) {$id = $historyItem.Id + 1 } Write-Host -ForegroundColor DarkGray "n[$(Get-Location)]"
Write-Host -NoNewLine "PS:$id > "$host.UI.RawUI.WindowTitle = "$(Get-Location)" "b" } In addition to showing the current location, this prompt also shows the ID for that command in your history. This lets you locate and invoke past commands with relative ease: [C:\] PS:73 >5 * 5 25 [C:\] PS:74 >1 + 1 2 [C:\] PS:75 >Invoke-History 73 5 * 5 25 [C:\] PS:76 > Although the profile referenced by $profile is the one you will almost always want to use, PowerShell actually supports four separate profile scripts. For further details on these scripts (along with other shell customization options), see Common Customization Points.

# Find a Command to Accomplish a Task

## Problem

You want to accomplish a task in PowerShell but don’t know the command or cmdlet to accomplish that task.

## Solution

Use the Get-Command cmdlet to search for and investigate commands.

To get the summary information about a specific command, specify the command name as an argument:

Get-Command CommandName

To get the detailed information about a specific command, pipe the output of Get-Command to the Format-List cmdlet:

Get-Command CommandName | Format-List

To search for all commands with a name that contains text, surround the text with asterisk characters:

Get-Command *text*

To search for all commands that use the Get verb, supply Get to the -Verb parameter:

Get-Command -Verb Get

To search for all commands that act on a service, use Service as the value of the -Noun parameter:

Get-Command -Noun Service

## Discussion

One of the benefits that PowerShell provides administrators is the consistency of its command names. All PowerShell commands (called cmdlets) follow a regular Verb-Noun pattern, for example: Get-Process, Get-EventLog, and Set-Location. The verbs come from a relatively small set of standard verbs (as listed in Appendix J) and describe what action the cmdlet takes. The nouns are specific to the cmdlet and describe what the cmdlet acts on.

Knowing this philosophy, you can easily learn to work with groups of cmdlets. If you want to start a service on the local machine, the standard verb for that is Start. A good guess would be to first try Start-Service (which in this case would be correct), but typing Get-Command -Verb Start would also be an effective way to see what things you can start. Going the other way, you can see what actions are supported on services by typing Get-Command -Noun Service.

See Get Help on a Command for a way to list all commands along with a brief description of what they do.

The Get-Command cmdlet is one of the three commands you will use most commonly as you explore Windows PowerShell. The other two commands are Get-Help and Get-Member.

There is one important point when it comes to looking for a PowerShell command to accomplish a particular task. Many times, that PowerShell command does not exist, because the task is best accomplished the same way it always was: ipconfig.exe to get IP configuration information, netstat.exe to list protocol statistics and current TCP/IP network connections, and many more.

For more information about the Get-Command cmdlet, type Get-Help Get-Command.

Get Help on a Command

# Get Help on a Command

## Problem

You want to learn how a specific command works and how to use it.

## Solution

The command that provides help and usage information about a command is called Get-Help. It supports several different views of the help information, depending on your needs.

To get the summary of help information for a specific command, provide the command’s name as an argument to the Get-Help cmdlet. This primarily includes its synopsis, syntax, and detailed description:

Get-Help CommandName

or

CommandName -?

To get the detailed help information for a specific command, supply the -Detailed flag to the Get-Help cmdlet. In addition to the summary view, this also includes its parameter descriptions and examples:

Get-Help CommandName -Detailed

To get the full help information for a specific command, supply the -Full flag to the Get-Help cmdlet. In addition to the detailed view, this also includes its full parameter descriptions and additional notes:

Get-Help CommandName -Full

To get only the examples for a specific command, supply the -Examples flag to the Get-Help cmdlet:

Get-Help CommandName -Examples

To retrieve the most up-to-date online version of a command’s help topic, supply the -Online flag to the Get-Help cmdlet:

Get-Help CommandName -Online

To find all help topics that contain a given keyword, provide that keyword as an argument to the Get-Help cmdlet. If the keyword isn’t also the name of a specific help topic, this returns all help topics that contain the keyword, including its name, category, and synopsis:

Get-Help Keyword

## Discussion

The Get-Help cmdlet is the primary way to interact with the help system in PowerShell. Like the Get-Command cmdlet, the Get-Help cmdlet supports wildcards. If you want to list all commands that match a certain pattern (for example, *process*), you can simply type Get-Help *process*.

### Note

To generate a list of all cmdlets and aliases (along with their brief synopsis), run the following command:

Get-Help * -Category Cmdlet | Select-Object Name,Synopsis | Format-Table -Auto

If the pattern matches only a single command, PowerShell displays the help for that command. Although command wildcarding and keyword searching is a helpful way to search PowerShell help, see Program: Search Help for Text for a script that lets you search the help content for a specified pattern.

In addition to console-based help, PowerShell also offers online and Compiled Help (CHM) versions of its help content. The Solution demonstrates how to quickly access online help content, but accessing the CHM version of help is slightly more difficult.

• If you are working within PowerShell’s Integrated Scripting Environment (ISE), accessing the CHM help is as easy as pressing F1.

• If you are working on Windows 7, you can access the CHM help through the Windows PowerShell Help option in PowerShell’s jump list (Figure 1-1). To open PowerShell’s jump list, either right-click on the taskbar icon or click the arrow beside PowerShell’s icon in the start menu.

• If you are working within the PowerShell console, you can access the CHM help by launching the .CHM file directly. Program: View PowerShell’s HTML Help demonstrates how to do this.

The Get-Help cmdlet is one of the three commands you will use most commonly as you explore Windows PowerShell. The other two commands are Get-Command and Get-Member.

For more information about the Get-Help cmdlet, type Get-Help Get-Help.

# Program: Search Help for Text

Both the Get-Command and Get-Help cmdlets let you search for command names that match a given pattern. However, when you don’t know exactly what portions of a command name you are looking for, you will more often have success searching through the help content for an answer. On Unix systems, this command is called Apropos.

The Get-Help cmdlet automatically searches the help database for keyword references when it can’t find a help topic for the argument you supply. In addition to that, you might want to extend this even further to search for text patterns or even help topics that talk about existing help topics. PowerShell’s help facilities don’t support this type of search.

That doesn’t need to stop us, though, as we can write the functionality ourselves.

To run this program, supply a search string to the Search-Help script (given in Example 1-4). The search string can be either simple text or a regular expression. The script then displays the name and synopsis of all help topics that match. To see the help content for that topic, use the Get-Help cmdlet.

Example 1-4. Search-Help.ps1

##############################################################################
##
## Search-Help
##
## From Windows PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
##############################################################################

<#

.SYNOPSIS

Search the PowerShell help documentation for a given keyword or regular
expression.

.EXAMPLE

Search-Help hashtable
Searches help for the term 'hashtable'

.EXAMPLE

Search-Help "(datetime|ticks)"
Searches help for the term datetime or ticks, using the regular expression
syntax.

#>

param(
## The pattern to search for
[Parameter(Mandatory = $true)]$Pattern
)

Set-StrictMode -Version Latest

$helpNames =$(Get-Help * | Where-Object { $_.Category -ne "Alias" }) ## Go through all of the help topics foreach($helpTopic in $helpNames) { ## Get their text content, search for the specified pattern$content = Get-Help -Full $helpTopic.Name | Out-String if($content -match "(.{0,30}$pattern.{0,30})") {$helpTopic | Add-Member NoteProperty Match $matches[0].Trim()$helpTopic | Select-Object Name,Match
}
}

Run Programs, Scripts, and Existing Tools

# Program: View PowerShell’s HTML Help

PowerShell’s compiled help (CHM) offers many useful features: a table of contents, an index, full-text search, and more. While easy to launch from the Integrated Scripting Environment (ISE) and the Windows 7 jump list, discoverability is still a problem from console windows.

To easily launch PowerShell’s CHM help, use the Show-HtmlHelp script shown in Example 1-5.

Example 1-5. Show-HtmlHelp.ps1

##############################################################################
##
## Show-HtmlHelp
##
## From Windows PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
##############################################################################

<#

.SYNOPSIS

Launches the CHM version of PowerShell help.

.EXAMPLE

Show-HtmlHelp

#>

Set-StrictMode -Version Latest

$path = (Resolve-Path c:\windows\help\mui\*\WindowsPowerShellHelp.chm).Path hh "$path::/html/defed09e-2acd-4042-bd22-ce4bf92c2f24.htm"

Run Programs, Scripts, and Existing Tools

# Launch PowerShell at a Specific Location

## Problem

You want to launch a PowerShell session in a specific location.

## Solution

Both Windows and PowerShell offer several ways to launch PowerShell in a specific location:

• PowerShell’s command-line arguments

• Community extensions

## Discussion

If you are browsing the filesystem with Windows Explorer, typing PowerShell into the address bar launches PowerShell in that location (as shown in Figure 1-2).

The resulting session lacks the console window customizations defined by PowerShell’s Start menu link (as does launching PowerShell from the StartRun dialog), but Customize the Shell to Improve Your Productivity shows how to make even these PowerShell windows display in PowerShell’s signature blue.

For another way to launch PowerShell from Windows Explorer, several members of the PowerShell community have written power toys and Windows Explorer extensions that provide a “Launch PowerShell Here” option when you right-click on a folder from Windows Explorer. An Internet search for “PowerShell Here” turns up several.

If you aren’t browsing the desired folder with Windows Explorer, use PowerShell’s -NoExit parameter, along with the implied -Command parameter. In the -Command parameter, call the Set-Location cmdlet to initially move to your desired location. From StartRun (or any other means of launching an application), type:

PowerShell -NoExit Set-Location 'C:\Program Files'

# Invoke a PowerShell Command or Script from Outside PowerShell

## Problem

You want to invoke a PowerShell command or script from a batch file, a logon script, a scheduled task, or any other non-PowerShell application.

## Solution

To invoke a PowerShell command, use the -Command parameter:

PowerShell -Command Get-Process; Read-Host

To launch a PowerShell script, use the -File parameter:

PowerShell -File 'full path to script' arguments

For example:

PowerShell -File 'c:\shared scripts\Get-Report.ps1' Hello World

## Discussion

By default, any arguments to PowerShell.exe get interpreted as commands to run. PowerShell runs the command as though you had typed it in the interactive shell, and then exits. You can customize this behavior by supplying other parameters to PowerShell.exe, such as -NoExit, -NoProfile, and more.

Since launching a script is so common, PowerShell provides the -File parameter to eliminate the complexities that arise from having to invoke a script from the -Command parameter. This technique lets you invoke a PowerShell script as the target of a logon script, advanced file association, scheduled task, and more.

### Note

When PowerShell detects that its input or output streams have been redirected, it suppresses any prompts that it might normally display. If you want to host an interactive PowerShell prompt inside another application (such as Emacs), use - as the argument for the -File parameter. In PowerShell (as with traditional Unix shells), this implies “taken from standard input.”

powershell -File -

If the script is for background automation or a scheduled task, these scripts can sometimes interfere with (or become influenced by) the user’s environment. For these situations, three parameters come in handy:

-NoProfile

Runs the command or script without loading user profile scripts. This makes the script launch faster, but it primarily prevents user preferences (e.g., aliases and preference variables) from interfering with the script’s working environment.

-WindowStyle

Runs the command or script with the specified window style—most commonly Hidden. When run with a window style of Hidden, PowerShell hides its main window immediately. For more ways to control the window style from within PowerShell, see Launch a Process.

-ExecutionPolicy

Runs the command or script with a specified execution policy applied only to this instance of PowerShell. This lets you write PowerShell scripts to manage a system without having to change the system-wide execution policy. For more information about scoped execution policies, see Enable Scripting Through an Execution Policy.

### Note

If you are the author of the program that needs to run PowerShell scripts or commands, PowerShell lets you call these scripts and commands much more easily than calling its command-line interface. For more information about this approach, see Add PowerShell Scripting to Your Own Program.

If the arguments to the -Command parameter become complex, special character handling in the application calling PowerShell (such as cmd.exe) might interfere with the command you want to send to PowerShell. For this situation, PowerShell supports an EncodedCommand parameter: a Base64-encoded representation of the Unicode string you want to run. Example 1-6 demonstrates how to convert a string containing PowerShell commands to a Base64-encoded form.

Example 1-6. Converting PowerShell commands into a Base64-encoded form

$commands = '1..10 | % { "PowerShell Rocks" }'$bytes = [System.Text.Encoding]::Unicode.GetBytes($commands)$encodedString = [Convert]::ToBase64String($bytes) Once you have the encoded string, you can use it as the value of the EncodedCommand parameter, as shown in Example 1-7. Example 1-7. Launching PowerShell with an encoded command from cmd.exe Microsoft Windows [Version 6.0.6000] Copyright (c) 2006 Microsoft Corporation. All rights reserved. C:\Users\Lee>PowerShell -EncodedCommand MQAuAC4AMQAwACAAfAAgACUAIAB7ACAAIgBQAG8AdwBlAHIAUwBoA GUAbABsACAAUgBvAGMAawBzACIAIAB9AA== PowerShell Rocks PowerShell Rocks PowerShell Rocks PowerShell Rocks PowerShell Rocks PowerShell Rocks PowerShell Rocks PowerShell Rocks PowerShell Rocks PowerShell Rocks For more information about running scripts, see Run Programs, Scripts, and Existing Tools. # Customize the Shell to Improve Your Productivity ## Problem You want to use the PowerShell console more efficiently for copying, pasting, history management, and scrolling. ## Solution Run the commands shown in Example 1-8 to permanently customize your PowerShell console windows and make many tasks easier. Example 1-8. Set-ConsoleProperties.ps1 Push-Location Set-Location HKCU:\Console New-Item '.\%SystemRoot%_system32_WindowsPowerShell_v1.0_powershell.exe' Set-Location '.\%SystemRoot%_system32_WindowsPowerShell_v1.0_powershell.exe' New-ItemProperty . ColorTable00 -type DWORD -value 0x00562401 New-ItemProperty . ColorTable07 -type DWORD -value 0x00f0edee New-ItemProperty . FaceName -type STRING -value "Lucida Console" New-ItemProperty . FontFamily -type DWORD -value 0x00000036 New-ItemProperty . FontSize -type DWORD -value 0x000c0000 New-ItemProperty . FontWeight -type DWORD -value 0x00000190 New-ItemProperty . HistoryNoDup -type DWORD -value 0x00000000 New-ItemProperty . QuickEdit -type DWORD -value 0x00000001 New-ItemProperty . ScreenBufferSize -type DWORD -value 0x0bb80078 New-ItemProperty . WindowSize -type DWORD -value 0x00320078 Pop-Location These commands customize the console color, font, history storage properties, QuickEdit mode, buffer size, and window size. With these changes in place, you can also improve your productivity by learning some of the hotkeys for common tasks, listed in Table 1-2. PowerShell uses the same input facilities as cmd.exe, and so it brings with it all the input features that you are already familiar with—and some that you aren’t! Table 1-2. Partial list of Windows PowerShell hotkeys Hotkey Meaning Up arrow Scan backward through your command history. Down arrow Scan forward through your command history. PgUp Display the first command in your command history. PgDown Display the last command in your command history. Left arrow Move cursor one character to the left on your command line. Right arrow Move cursor one character to the right on your command line. Home Move the cursor to the beginning of the command line. End Move the cursor to the end of the command line. Ctrl + Left arrow Move the cursor one word to the left on your command line. Ctrl + Right arrow Move the cursor one word to the right on your command line. ## Discussion When you launch PowerShell from the link on your Windows Start menu, it customizes several aspects of the console window: • Foreground and background color, to make the console more visually appealing • QuickEdit mode, to make copying and pasting with the mouse easier • Buffer size, to make PowerShell retain the output of more commands in your console history By default, these customizations do not apply when you run PowerShell from the StartRun dialog. The commands given in the solution section improve the experience by applying these changes to all PowerShell windows that you open. The hotkeys do, however, apply to all PowerShell windows (and any other application that uses Windows’ cooked input mode). The most common are given in the solution section, but Common Customization Points provides the full list. ## See Also Common Customization Points # Program: Learn Aliases for Common Commands In interactive use, full cmdlet names (such as Get-ChildItem) are cumbersome and slow to type. Although aliases are much more efficient, it takes a while to discover them. To learn aliases more easily, you can modify your prompt to remind you of the shorter version of any aliased commands that you use. This involves two steps: 1. Add the program Get-AliasSuggestion.ps1, shown in Example 1-9, to your tools directory or another directory. Example 1-9. Get-AliasSuggestion.ps1 ############################################################################## ## ## Get-AliasSuggestion ## ## From Windows PowerShell Cookbook (O'Reilly) ## by Lee Holmes (http://www.leeholmes.com/guide) ## ############################################################################## <# .SYNOPSIS Get an alias suggestion from the full text of the last command. Intended to be added to your prompt function to help learn aliases for commands. .EXAMPLE Get-AliasSuggestion Remove-ItemProperty Suggestion: An alias for Remove-ItemProperty is rp #> param( ## The full text of the last command$LastCommand
)

Set-StrictMode -Version Latest

$helpMatches = @() ## Find all of the commands in their last input$tokens = [Management.Automation.PSParser]::Tokenize(
$lastCommand, [ref]$null)
$commands =$tokens | Where-Object { $_.Type -eq "Command" } ## Go through each command foreach($command in $commands) { ## Get the alias suggestions foreach($alias in Get-Alias -Definition $command.Content) {$helpMatches += "Suggestion: An alias for " +
"$($alias.Definition) is $($alias.Name)"
}
}

$helpMatches 2. Add the text from Example 1-10 to the Prompt function in your profile. If you do not yet have a Prompt function, see Customize Your Shell, Profile, and Prompt to learn how to add one. Example 1-10. A useful prompt to teach you aliases for common commands function Prompt { ## Get the last item from the history$historyItem = Get-History -Count 1

## If there were any history items
if($historyItem) { ## Get the training suggestion for that item$suggestions = @(Get-AliasSuggestion $historyItem.CommandLine) ## If there were any suggestions if($suggestions)
{
## For each suggestion, write it to the screen
foreach($aliasSuggestion in$suggestions)
{
Write-Host "$aliasSuggestion" } Write-Host "" } } ## Rest of prompt goes here "PS [$env:COMPUTERNAME] >"
}

# Program: Learn Aliases for Common Parameters

## Problem

You want to learn aliases defined for command parameters.

## Solution

Use the Get-ParameterAlias script to return all aliases for parameters used by the previous command in your session history.

Example 1-11. Get-ParameterAlias.ps1

##############################################################################
##
## Get-ParameterAlias
##
## From Windows PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
##############################################################################

<#

.SYNOPSIS

Looks in the session history, and returns any aliases that apply to
parameters of commands that were used.

.EXAMPLE

PS >dir -ErrorAction SilentlyContinue
PS >Get-ParameterAlias
An alias for the 'ErrorAction' parameter of 'dir' is ea

#>

Set-StrictMode -Version Latest

## Get the last item from their session history
$history = Get-History -Count 1 if(-not$history)
{
return
}

## And extract the actual command line they typed
$lastCommand =$history.CommandLine

## Use the Tokenizer API to determine which portions represent
## commands and parameters to those commands
$tokens = [System.Management.Automation.PsParser]::Tokenize($lastCommand, [ref] $null)$currentCommand = $null ## Now go through each resulting token foreach($token in $tokens) { ## If we've found a new command, store that. if($token.Type -eq "Command")
{
$currentCommand =$token.Content
}

## If we've found a command parameter, start looking for aliases
if(($token.Type -eq "CommandParameter") -and ($currentCommand))
{
## Remove the leading "-" from the parameter
$currentParameter =$token.Content.TrimStart("-")

## Determine all of the parameters for the current command.
(Get-Command $currentCommand).Parameters.GetEnumerator() | ## For parameters that start with the current parameter name, Where-Object {$_.Key -like "$currentParameter*" } | ## return all of the aliases that apply. We use "starts with" ## because the user might have typed a shortened form of ## the parameter name. Foreach-Object {$_.Value.Aliases | Foreach-Object {

# Program: Create Scripts from Session History

After interactively experimenting at the command line for a while to solve a multistep task, you’ll often want to keep or share the exact steps you used to eventually solve the problem. The script smiles at you from your history buffer, but it is unfortunately surrounded by many more commands that you don’t want to keep.

To solve this problem, use the Get-History cmdlet to view the recent commands that you’ve typed. Then, call Copy-History with the IDs of the commands you want to keep, as shown in Example 1-12.

Example 1-12. Copy-History.ps1

##############################################################################
##
## Copy-History
##
## From Windows PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
##############################################################################

<#

.SYNOPSIS

Copy selected commands from the history buffer into the clipboard as a script.

.EXAMPLE

Copy-History
Copies the entire contents of the history buffer into the clipboard.

.EXAMPLE

Copy-History -5
Copies the last five commands into the clipboard.

.EXAMPLE

Copy-History 2,5,8,4
Copies commands 2,5,8, and 4.

.EXAMPLE

Copy-History (1..10+5+6)
Copies commands 1 through 10, then 5, then 6, using PowerShell's array
slicing syntax.

#>

param(
## The range of history IDs to copy
[int[]] $Range ) Set-StrictMode -Version Latest$history = @()

## If they haven't specified a range, assume it's everything
if((-not $range) -or ($range.Count -eq 0))
{
$history = @(Get-History -Count ([Int16]::MaxValue)) } ## If it's a negative number, copy only that many elseif(($range.Count -eq 1) -and ($range[0] -lt 0)) {$count = [Math]::Abs($range[0])$history = (Get-History -Count $count) } ## Otherwise, go through each history ID in the given range ## and add it to our history list. else { foreach($commandId in $range) { if($commandId -eq -1) { $history += Get-History -Count 1 } else {$history += Get-History -Id $commandId } } } ## Finally, export the history to the clipboard.$history | Foreach-Object { $_.CommandLine } | clip.exe For more information about running scripts, see Run Programs, Scripts, and Existing Tools. ## See Also Run Programs, Scripts, and Existing Tools # Invoke a Command from Your Session History ## Problem You want to run a command from the history of your current session. ## Solution To invoke a specific command by its ID: Invoke-History ID To search through your history for a command containing text: PS > #text<TAB> To repopulate your command with the text of a previous command by its ID: PS > #ID<TAB> ## Discussion Once you’ve had your shell open for a while, your history buffer quickly fills with useful commands. The history management hotkeys described in Customize the Shell to Improve Your Productivity show one way to navigate your history, but this type of history navigation works only for command lines you’ve typed in that specific session. If you keep a persistent command history (as shown in Save State Between Sessions), these shortcuts do not apply. The Invoke-History cmdlet illustrates the simplest example of working with your command history. Given a specific history ID (perhaps shown in your prompt function), calling Invoke-History with that ID will run that command again. For more information about this technique, see Customize Your Shell, Profile, and Prompt. As part of its tab-completion support, PowerShell gives you easy access to previous commands as well. If you prefix your command with the # character, tab completion takes one of two approaches: ID completion If you type a number, tab completion finds the entry in your command history with that ID, and then replaces your command line with the text of that history entry. This is especially useful when you want to slightly modify a previous history entry, since Invoke-History by itself doesn’t support that. Pattern completion If you type anything else, tab completion searches for entries in your command history that contain that text. Under the hood, PowerShell uses the -like operator to match your command entries, so you can use all of the wildcard characters supported by that operator. For more information on searching text for patterns, see Search a String for Text or a Pattern. PowerShell’s tab completion is largely driven by the fully customizable TabExpansion function. You can easily change this function to include more advanced functionality, or even just customize specific behaviors to suit your personal preferences. For more information, see Tab Completion. # Program: Search Formatted Output for a Pattern While PowerShell’s built-in filtering facilities are incredibly flexible (for example, the Where-Object cmdlet), they generally operate against specific properties of the incoming object. If you are searching for text in the object’s formatted output, or don’t know which property contains the text you are looking for, simple text-based filtering is sometimes helpful. To solve this problem, you can pipe the output into the Out-String cmdlet before passing it to the Select-String cmdlet. Select-TextOutput (shown in Example 1-13) does exactly this, and it lets you search for a pattern in the visual representation of command output. Example 1-13. Select-TextOutput.ps1 ############################################################################## ## ## Select-TextOutput ## ## From Windows PowerShell Cookbook (O'Reilly) ## by Lee Holmes (http://www.leeholmes.com/guide) ## ############################################################################## <# .SYNOPSIS Searches the textual output of a command for a pattern. .EXAMPLE Get-Service | Select-TextOutput audio Finds all references to "Audio" in the output of Get-Service #> param( ## The pattern to search for$Pattern
)

Set-StrictMode -Version Latest
$input | Out-String -Stream | Select-String$pattern

Run Programs, Scripts, and Existing Tools

# Interactively View and Process Command Output

## Problem

You want to graphically explore and analyze the output of a command.

## Solution

Use the Out-GridView cmdlet to interactively explore the output of a command.

## Discussion

The Out-GridView cmdlet is one of the rare PowerShell cmdlets that displays a graphical user interface. While the Where-Object and Sort-Object cmdlets are the most common way to sort and filter lists of items, the Out-GridView cmdlet is very effective at the style of repeated refinement that sometimes helps you develop complex queries. Figure 1-3 shows the Out-GridView cmdlet in action.

Out-GridView lets you primarily filter your command output in two ways: a quick filter expression and a criteria filter.

Quick filters are fairly simple. As you type text in the topmost “Filter” window, Out-GridView filters the list to contain only items that match that text. If you want to restrict this text filtering to specific columns, simply provide a column name before your search string and separate the two with a colon. You can provide multiple search strings, in which case Out-GridView returns only rows that match all of the required strings.

### Note

Unlike most filtering cmdlets in PowerShell, the quick filters in the Out-GridView cmdlet do not support wildcards or regular expressions. For this type of advanced query, criteria-based filtering can help.

Criteria filters give fine-grained control over the filtering used by the Out-GridView cmdlet. To apply a criteria filter, click the “Add criteria” button and select a property to filter on. Out-GridView adds a row below the quick filter field and lets you pick one of several operations to apply to this property:

• Less than or equal to

• Greater than or equal to

• Between

• Equals

• Does not equal

• Contains

• Does not contain

In addition to these filtering options, Out-GridView also lets you click and rearrange the header columns to sort by them.

### Processing output

Once you’ve sliced and diced your command output, you can select any rows you want to keep and press Ctrl-C to copy them to the clipboard. Out-GridView copies the items to the clipboard as tab-separated data, so you can easily paste the information into a spreadsheet or other file for further processing.

Despite its clipboard output, exporting items to the Out-GridView cmdlet is primarily a one-way operation. While you can process items somewhat further by exporting them to a spreadsheet or text file, there is no way to access the results of sorting and filtering as full-fidelity objects. For an approach that supports this type of full-fidelity filtering, see Program: Interactively Filter Lists of Objects. Additionally, a graphical version is shown in Program: Add a Graphical User Interface to Your Script.

# Store the Output of a 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 Out-File cmdlet or one of the redirection operators.

Out-File:

Get-ChildItem | Out-File unicodeFile.txt
Get-Content filename.cs | Out-File -Encoding ASCII file.txt
Get-ChildItem | Out-File -Width 120 unicodeFile.cs

Redirection operators:

Get-ChildItem > files.txt
Get-ChildItem 2> errors.txt

## Discussion

The Out-File cmdlet and redirection operators share a lot in common. 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 Out-File 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 PowerShell-formatted 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 PowerShell-formatted 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 Out-File cmdlet.

The default output encoding sometimes causes unexpected results because PowerShell creates all files using the UTF-16 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 on 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 Out-File cmdlet.

For more information about the Out-File cmdlet, type Get-Help Out-File. For a full list of supported redirection operators, see Capturing Output.

Capturing Output

# Add Information to the End of a File

## Problem

You want to redirect the output of a pipeline into a file but add the information to the end of that file.

## Solution

To redirect the output of a command into a file, use either the -Append parameter of the Out-File cmdlet or one of the appending redirection operators described in Capturing Output. Both support options to append text to the end of a file.

Out-File:

Get-ChildItem | Out-File -Append files.txt

Redirection operators:

Get-ChildItem >> files.txt

## Discussion

The Out-File cmdlet and redirection operators share a lot in common. For the most part, you can use either. See the discussion in Store the Output of a Command into a File for a more detailed comparison of the two approaches, including reasons that you would pick one over the other.

# Record a Transcript of Your Shell Session

## Problem

You want to record a log or transcript of your shell session.

## Solution

To record a transcript of your shell session, run the command Start-Transcript. It has an optional -Path parameter that defaults to a filename based on the current system time. By default, PowerShell places this file in the My Documents directory. To stop recording the transcript of your shell system, run the command Stop-Transcript.

## Discussion

Although the Get-History cmdlet is helpful, it does not record the output produced during your PowerShell session. To accomplish that, use the Start-Transcript cmdlet. In addition to the Path parameter described previously, the Start-Transcript cmdlet also supports parameters that let you control how PowerShell interacts with the output file.

## Problem

You want to use PowerShell cmdlets, providers, or script-based extensions written by a third party.

## Solution

Use the Import-Module command to import third-party commands into your PowerShell session.

To import a registered module by name:

Import-Module Name

To import a module from a specific directory:

Import-Module c:\path\to\module

To import a module from a specific file (module, script, or assembly):

Import-Module c:\path\to\module\file.ext

## Discussion

PowerShell supports two sets of commands that enable additional cmdlets and providers: *-Module and *-PsSnapin. Snapins were the packages for extensions in version one of PowerShell. They supported only compiled extensions and had onerous installation requirements.

Version two of PowerShell introduces modules that support everything that snapins support (and more) without the associated installation pain.

The most common way to import a module is by name. PowerShell searches through every directory listed in the PSModulePath environment variable, looking for subdirectories that match the name you specify. Inside those directories, it looks for the module (*.psd1, *.psm1, and *.dll) with the same name and loads it.

### Note

When you install a module on your own system, the most common place to put it is in the WindowsPowerShell\Modules directory in your My Documents directory.

To have PowerShell look in another directory for modules, add it to your personal PSModulePath environment variable, just as you would add a Tools directory to your personal Path.

If you want to load a module from a directory not in PSModulePath, you can provide the entire directory name and module name to the Import-Module command. For example, for a module named Test, use Import-Module c:\path\to\Test. As with loading modules by name, PowerShell looks in c:\temp\path\to for a module (*.psd1, *.psm1, or *.dll) named Test and loads it.

If you know the specific module file you want to load, you can also specify the full path to that module.

One popular source of additional commands is the PowerShell Community Extensions project, located at http://www.codeplex.com/PowerShellCX.

If you want to import these commands for every PowerShell session, add a call to Import-Module to your PowerShell Profile.

# Use Commands from Customized Shells

## Problem

You want to use the commands from a PowerShell-based product that launches a customized version of the PowerShell console, but in a regular PowerShell session.

## Solution

Launch the customized version of the PowerShell console, and then use the Get-Module and Get-PsSnapin commands to see what additional modules and/or snapins it loaded.

## Discussion

As described in Extend Your Shell with Additional Commands, PowerShell modules and snapins are the two ways that third parties can distribute and add additional PowerShell commands. Products that provide customized versions of the PowerShell console do this by calling PowerShell.exe with one of three parameters:

• -PSConsoleFile, to load a console file that provides a list of snapins to load.

• -Command, to specify an initial startup command (that then loads a snapin or module)

• -File, to specify an initial startup script (that then loads a snapin or module)

Regardless of which one it used, you can examine the resulting set of loaded extensions to see which ones you can import into your other PowerShell sessions.

The Get-PsSnapin command returns all snapins loaded in the current session. It always returns the set of core PowerShell snapins, but it will also return any additional snapins loaded by the customized environment. For example, if the name of a snapin you recognize is Product.Feature.Commands, you can load that into future PowerShell sessions by typing Add-PsSnapin Product.Feature.Commands. To automate this, add the command into your PowerShell profile.

If you are uncertain of which snapin to load, you can also use the Get-Command command to discover which snapin defines a specific command:

PS > Get-Command Get-Counter | Select PsSnapin

PSSnapIn
--------
Microsoft.PowerShell.Diagnostics

Like the Get-PsSnapin command, the Get-Module command returns all modules loaded in the current session. It returns any modules you’ve added so far into that session, but it will also return any additional modules loaded by the customized environment. For example, if the name of a module you recognize is ProductModule, you can load that into future PowerShell sessions by typing Import-Module ProductModule. To automate this, add the command into your PowerShell profile.

If you are uncertain of which module to load, you can also use the Get-Command command to discover which module defines a specific command:

PS > Get-Command Start-BitsTransfer | Select Module

Module
------
BitsTransfer

# Save State Between Sessions

## Problem

You want to save state or history between PowerShell sessions.

## Solution

Subscribe to the PowerShell.Exiting engine event to have PowerShell invoke a script or script block that saves any state you need.

To have PowerShell save your command history, place a call to Enable-HistoryPersistence in your profile, as in Example 1-14.

Example 1-14. Enable-HistoryPersistence.ps1

##############################################################################
##
## Enable-HistoryPersistence
##
## From Windows PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
##############################################################################

<#

.SYNOPSIS

Reloads any previously saved command history and registers for the
PowerShell.Exiting engine event to save new history when the shell
exits.

#>

Set-StrictMode -Version Latest

$GLOBAL:maximumHistoryCount = 32767$historyFile = (Join-Path (Split-Path $profile) "commandHistory.clixml") if(Test-Path$historyFile)
{
Import-CliXml $historyFile | Add-History } ## Register for the engine shutdown event$null = Register-EngineEvent -SourceIdentifier 
([System.Management.Automation.PsEngineEvent]::Exiting) -Action {

## Save our history
$historyFile = (Join-Path (Split-Path$profile) "commandHistory.clixml")
$maximumHistoryCount = 1kb ## Get the previous history items$oldEntries = @()
if(Test-Path $historyFile) {$oldEntries = Import-CliXml $historyFile -ErrorAction SilentlyContinue } ## And merge them with our changes$currentEntries = Get-History -Count $maximumHistoryCount$additions = Compare-Object $oldEntries$currentEntries
-Property CommandLine | Where-Object { $_.SideIndicator -eq "=>" } | Foreach-Object {$_.CommandLine }

$newEntries =$currentEntries | ? { $additions -contains$_.CommandLine }

## Keep only unique command lines. First sort by CommandLine in
## descending order (so that we keep the newest entries,) and then
## re-sort by StartExecutionTime.
$history = @($oldEntries + $newEntries) | Sort -Unique -Descending CommandLine | Sort StartExecutionTime ## Finally, keep the last 100 Remove-Item$historyFile
$history | Select -Last 100 | Export-CliXml$historyFile
}

## Discussion

PowerShell provides easy script-based access to a broad variety of system, engine, and other events. You can register for notification of these events and even automatically process any of those events. In this example, we subscribe to the only one currently available, which is called PowerShell.Exiting. PowerShell generates this event when you close a session.

For PowerShell to handle this event, you must use the exit keyword to close your session, rather than the X button at the top right of the console window. In the Integrated Scripting Environment, the close button generates this event as well.

This script could do anything, but in this example we have it save our command history and restore it when we launch PowerShell. Why would we want to do this? Well, with a rich history buffer, we can more easily find and reuse commands we’ve previously run. For two examples of doing this, see Recipes and .

Example 1-14 takes two main actions. First, we load our stored command history (if any exists). Then, we register an automatic action to be processed whenever the engine generates its PowerShell.Exiting` event. The action itself is relatively straightforward, although exporting our new history does take a little finesse. If you have several sessions open at the same time, each will update the saved history file when it exits. Since we don’t want to overwrite the history saved by the other shells, we first reload the history from disk and combine it with the history from the current shell.

Once we have the combined list of command lines, we sort them and pick out the unique ones before storing them back in the file.