Skip to main content

Resources

Modify or Remove a Registry Key Value

Problem

You want to modify or remove a property of a specific registry key.

Solution

To set the value of a registry key, use the SetItemProperty cmdlet:

PS >(GetItemProperty .).MyProgram c:\temp\MyProgram.exe PS >SetItemProperty . MyProgram d:\Lee\tools\MyProgram.exe PS >(GetItemProperty .).MyProgram d:\Lee\tools\MyProgram.exe

To remove the value of a registry key, use the RemoveItemProperty cmdlet:

PS >RemoveItemProperty . MyProgram PS >(GetItemProperty .).MyProgram

Discussion

In the registry provider, PowerShell treats registry keys as items and key values as properties of those items. To change the value of a key property, use the SetItemProperty cmdlet. The SetItemProperty cmdlet has the standard alias, sp.To remove a key property altogether, use the RemoveItemProperty cmdlet.

As always, use caution when changing information in the registry. Deleting or changing the wrong item can easily render your system unbootable.

For more information about the GetItemProperty cmdlet, type GetHelp GetItemProperty. For information about the SetItemProperty and RemoveItemProperty cmdlets, type GetHelp SetItemProperty or GetHelp RemoveItemProperty, respectively. For more information about the registry provider, type GetHelp Registry.

Manage Mailboxes

Problem

You want to get and modify information about mailboxes from the Exchange Management Shell.

Solution

To retrieve information about a mailbox (or multiple mailboxes), use the GetMailbox cmdlet:

$user = GetMailbox *preeda*

$user | FormatList * To modify information about a mailbox, use the SetMailbox cmdlet. This example prevents Preeda from sending mail when her mailbox goes over 2 GB, and then verifies it:

$user | SetMailbox –ProhibitSendQuota 2GB $user | GetMailbox

Discussion

In addition to the common task of retrieving and modifying mailbox information, another useful mailboxrelated command is the MoveMailbox cmdlet. For example, to move all users from one storage group to another database:

GetMailbox |

WhereObject { $_.Database –like "*SMBEX01\First Storage Group" } |

MoveMailbox –TargetDatabase "Mailbox Database 3"

After a few moments, that command displays the progress of the bulk mailbox move.

For more information about the GetMailbox cmdlet, type GetHelp GetMailbox. For more information about the SetMailbox cmdlet, type GetHelp SetMailbox. For more information about the MoveMailbox cmdlet, type GetHelp MoveMailbox.

Run a PowerShell Command in Windows PowerShell

Problem

You want to run a PowerShell command.

Solution

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

PS >GetProcess

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 GetProcess 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 commandline syntax.
  • They support rich pipeline scenarios (using the output of one command as the input of another).
  • They produce easily manageable objectbased output, rather than errorprone plain text output.

Because the GetProcess cmdlet generates rich objectbased output, you can use its output for many processrelated tasks.

The GetProcess cmdlet is just one of the many that PowerShell supports.

Write a Script Block in Windows PowerShell

Problem

You have a section of your script that works nearly the same for all input, aside from a minor change in logic.

Solution

As shown in Example 103, place the minor logic differences in a script block, and then pass that script block as a parameter to the code that requires it. Use the invoke operator (&) to execute the script block.

Example 103. A script that applies a script block to each element in the pipeline

############################################################################## ## MapObject.ps1 ## ## Apply the given mapping command to each element of the input ## ## Example: ## 1,2,3 | MapObject { $_ * 2 } ############################################################################## param([ScriptBlock] $mapCommand)

process { & $mapCommand }

Discussion

Imagine a script that needs to multiply all the elements in a list by two:

function MultiplyInputByTwo

{ process {

$_ * 2 } }

but it also needs to perform a more complex calculation:

function MultiplyInputComplex

{ process {

($_ + 2) * 3 } }

These two functions are strikingly similar, except for the single line that actually performs the calculation. As we add more calculations, this quickly becomes more evident. Adding each new seven line function gives us only one unique line of value!

PS >1,2,3 | MultiplyInputByTwo 2 4 6 PS >1,2,3 | MultiplyInputComplex

9 12 15

If we instead use a script block to hold this “unknown” calculation, we don’t need to keep on adding new functions:

PS >1,2,3 | MapObject { $_ * 2 } 2 4 6 PS >1,2,3 | MapObject { ($_ + 2) * 3 } 9 12 15 PS >1,2,3 | MapObject { ($_ + 3) * $_ } 4 10 18

In fact, the functionality provided by MapObject is so helpful that it is a standard PowerShell cmdlet—called ForeachObject.

Program: Get Disk Usage Information

Discussion

When disk space starts running low, you’ll naturally want to find out where to focus your cleanup efforts. Sometimes, you may tackle this by looking for large directories (including the directories in them), but other times, you may solve this by looking for directories that are large simply from the files they contain.

Example 172 collects both types of data. It also demonstrates an effective use of calculated properties. Like the AddMember cmdlet, calculated properties let you add properties to output objects by specifying the expression that generates their data.

Example 172. GetDiskUsage.ps1

############################################################################## ## ## GetDiskUsage.ps1 ## ## Retrieve information about disk usage in the current directory and all ## subdirectories. If you specify the IncludeSubdirectories flag, this ## script accounts for the size of subdirectories in the size of a directory. ## ## ie: ## ## PS >GetDiskUsage ## PS >GetDiskUsage IncludeSubdirectories ## ##############################################################################

param( [switch] $includeSubdirectories )

## If they specify the IncludeSubdirectories flag, then we want to account ## for all subdirectories in the size of each directory if($includeSubdirectories) {

GetChildItem | WhereObject { $_.PsIsContainer } |

SelectObject Name, @{ Name="Size"; Expression={ ($_ | GetChildItem Recurse |

MeasureObject Sum Length).Sum + 0 } } } ## Otherwise, we just find all directories below the current directory, ## and determine their size else {

GetChildItem Recurse | WhereObject { $_.PsIsContainer } |

SelectObject FullName, @{ Name="Size"; Expression={ ($_ | GetChildItem |

MeasureObject Sum Length).Sum + 0 } } }

Program: List All Installed Software in PowerShell

The best place to find information about currently installed software is actually from the place that stores information about how to uninstall it: the HKLM:\SOFTWARE\ Microsoft\Windows\CurrentVersion\Uninstall registry key.

Each child of that registry key represents a piece of software you can uninstall—traditionally through the Add/Remove Programs entry in the Control Panel. In addition to the DisplayName of the application, other useful properties usually exist (depending on the application). Examples include Publisher, UninstallString, and HelpLink.

To see all the properties available from software installed on your system, type the following:

$properties = GetInstalledSoftware | ForeachObject { $_.PsObject.Properties }

$properties | SelectObject Name | SortObject Unique Name

This lists all properties mentioned by at least one installed application (although very few are shared by all installed applications).

To work with this data, though, you first need to retrieve it. Example 243 provides a script to list all installed software on the current system, returning all information as properties of PowerShell objects.

Example 243. GetInstalledSoftware.ps1

############################################################################## ## ## GetInstalledSoftware.ps1 ## ## List all installed software on the current computer. ## ## ie: ##

Example 243. GetInstalledSoftware.ps1 (continued)

## PS >GetInstalledSoftware PowerShell ## ##############################################################################

param( $displayName = ".*" )

## Get all the listed software in the Uninstall key $keys = GetChildItem HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall

## Get all of the properties from those items $items = $keys | ForeachObject { GetItemProperty $_.PsPath }

## For each of those items, display the DisplayName and Publisher foreach($item in $items) {

if(($item.DisplayName) and ($item.DisplayName match $displayName)) { $item } }

Parse and Manage Binary Files in Windows PowerShell

Problem

You want to work with binary data in a file.

Solution

Two main techniques are used when working with binary data in a file. The first is to read the file using the Byte encoding, so that PowerShell does not treat the content as text. The second is to use the BitConverter class to translate these bytes back and forth into numbers that you more commonly care about.

Example 73 displays the “characteristics” of a Windows executable. The beginning section of any executable (a .DLL, .EXE, and several others) starts with a binary section known as the PE (portable executable) header. Part of this header includes characteristics about that file—such as whether the file is a DLL.

For more information about the PE header format, see http://www.microsoft.com/ whdc/system/platform/firmware/PECOFF.mspx.

Example 73. GetCharacteristics.ps1

############################################################################## ## ## GetCharacteristics.ps1 ## ## Get the file characteristics of a file in the PE Executable File Format. ## ## ie: ## ## PS >GetCharacteristics $env:WINDIR\notepad.exe ## IMAGE_FILE_LOCAL_SYMS_STRIPPED ## IMAGE_FILE_RELOCS_STRIPPED ## IMAGE_FILE_EXECUTABLE_IMAGE

Example 73. GetCharacteristics.ps1 (continued)

## IMAGE_FILE_32BIT_MACHINE ## IMAGE_FILE_LINE_NUMS_STRIPPED ## ##############################################################################

param([string] $filename = $(throw "Please specify a filename."))

## Define the characteristics used in the PE file file header. ## Taken from http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx $characteristics = @{} $characteristics["IMAGE_FILE_RELOCS_STRIPPED"] = 0x0001 $characteristics["IMAGE_FILE_EXECUTABLE_IMAGE"] = 0x0002 $characteristics["IMAGE_FILE_LINE_NUMS_STRIPPED"] = 0x0004 $characteristics["IMAGE_FILE_LOCAL_SYMS_STRIPPED"] = 0x0008 $characteristics["IMAGE_FILE_AGGRESSIVE_WS_TRIM"] = 0x0010 $characteristics["IMAGE_FILE_LARGE_ADDRESS_AWARE"] = 0x0020 $characteristics["RESERVED"] = 0x0040 $characteristics["IMAGE_FILE_BYTES_REVERSED_LO"] = 0x0080 $characteristics["IMAGE_FILE_32BIT_MACHINE"] = 0x0100 $characteristics["IMAGE_FILE_DEBUG_STRIPPED"] = 0x0200 $characteristics["IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP"] = 0x0400 $characteristics["IMAGE_FILE_NET_RUN_FROM_SWAP"] = 0x0800 $characteristics["IMAGE_FILE_SYSTEM"] = 0x1000 $characteristics["IMAGE_FILE_DLL"] = 0x2000 $characteristics["IMAGE_FILE_UP_SYSTEM_ONLY"] = 0x4000 $characteristics["IMAGE_FILE_BYTES_REVERSED_HI"] = 0x8000

## Get the content of the file, as an array of bytes $fileBytes = GetContent $filename ReadCount 0 Encoding byte

## The offset of the signature in the file is stored at location 0x3c. $signatureOffset = $fileBytes[0x3c]

## Ensure it is a PE file $signature = [char[]] $fileBytes[$signatureOffset..($signatureOffset + 3)] if([String]::Join('', $signature) ne "PE`0`0") {

throw "This file does not conform to the PE specification." }

## The location of the COFF header is 4 bytes into the signature $coffHeader = $signatureOffset + 4

## The characteristics data are 18 bytes into the COFF header. The BitConverter ## class manages the conversion of the 4 bytes into an integer. $characteristicsData = [BitConverter]::ToInt32($fileBytes, $coffHeader + 18)

## Go through each of the characteristics. If the data from the file has that ## flag set, then output that characteristic. foreach($key in $characteristics.Keys)

Example 73. GetCharacteristics.ps1 (continued)

{ $flag = $characteristics[$key] if(($characteristicsData band $flag) eq $flag) {

$key } }

Discussion

For most files, this technique is the easiest way to work with binary data. If you actually modify the binary data, then you will also want to use the Byte encoding when you send it back to disk:

$fileBytes | SetContent modified.exe Encoding Byte

For extremely large files, though, it may be unacceptably slow to load the entire file into memory when you work with it. If you begin to run against this limit, the solution is to use file management classes from the .NET Framework. These classes include BinaryReader, StreamReader, and others.

Program: Create a Self-Signed Certificate in PowerShell

Discussion

It is possible to benefit from the tamperprotection features of signed scripts without having to pay for an official codesigning certificate. You do this by creating a selfsigned certificate. Scripts signed with a selfsigned certificate will not be recognized as valid on other computers, but still lets you sign scripts on your own computer.

When Example 161 runs, it prompts you for a password. Windows uses this pass word to prevent malicious programs from automatically signing files on your behalf.

Example 161. NewSelfSignedCertificate.ps1

############################################################################## ## ## NewSelfSignedCertificate.ps1 ## ## Generate a new selfsigned certificate. The certificate generated by these ## commands allow you to sign scripts on your own computer for protection ## from tampering. Files signed with this signature are not valid on other ## computers. ## ## ie: ## ## PS >NewSelfSignedCertificate.ps1 ## ##############################################################################

if(not (GetCommand makecert.exe ErrorAction SilentlyContinue)) { $errorMessage = "Could not find makecert.exe. " + "This tool is available as part of Visual Studio, or the Windows SDK."

WriteError $errorMessage return }

$keyPath = JoinPath ([IO.Path]::GetTempPath()) "root.pvk"

## Generate the local certification authority

makecert n "CN=PowerShell Local Certificate Root" a sha1 ` eku 1.3.6.1.5.5.7.3.3 r sv $keyPath root.cer ` ss Root sr localMachine

## Use the local certification authority to generate a selfsigned ## certificate makecert pe n "CN=PowerShell User" ss MY a sha1 `

eku 1.3.6.1.5.5.7.3.3 iv $keyPath ic root.cer

## Remove the private key from the filesystem. RemoveItem $keyPath

Example 161. NewSelfSignedCertificate.ps1 (continued)

## Retrieve the certificate GetChildItem cert:\currentuser\my codesign | WhereObject { $_.Subject match "PowerShell User" }

Create a Security or Distribution Group in Windows PowerShell

Problem

You want to create a security or distribution group.

Solution

To create a security or distribution group, use the [adsi] type shortcut to bind to a container in Active Directory, and then call the Create() method:

$salesWest =

[adsi] "LDAP://localhost:389/ou=West,ou=Sales,dc=Fabrikam,dc=COM" $management = $salesWest.Create("Group", "CN=Management") $management.SetInfo()

Discussion

The solution creates a group named Management in the Sales West OU.

When you run this script against a real Active Directory deployment (as opposed to an ADAM instance), be sure to update the sAMAccountName property, or you’ll get an autogenerated default.

When you create a group in Active Directory, it is customary to also set the type of group by defining the groupType attribute on that group. To specify a group type, use the –bor operator to combine group flags and use the resulting value as the groupType property. Example 233 defines the group as a global, securityenabled group.

Example 233. Creating an Active Directory security group with a custom groupType

$ADS_GROUP_TYPE_GLOBAL_GROUP = 0x00000002 $ADS_GROUP_TYPE_DOMAIN_LOCAL_GROUP = 0x00000004 $ADS_GROUP_TYPE_LOCAL_GROUP = 0x00000004 $ADS_GROUP_TYPE_UNIVERSAL_GROUP = 0x00000008 $ADS_GROUP_TYPE_SECURITY_ENABLED = 0x80000000

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

$groupType = $ADS_GROUP_TYPE_SECURITY_ENABLED bor $ADS_GROUP_TYPE_GLOBAL_GROUP

$management = $salesWest.Create("Group", "CN=Management") $management.Put("groupType", $groupType) $management.SetInfo()

If you need to create groups in bulk from the data in a CSV, the ImportADUser script. To make the script create groups instead of users, change this line:

$newUser = $userContainer.Create("User", "CN=$username")

to this:

$newUser = $userContainer.Create("Group", "CN=$username")

If you change the script to create groups in bulk, it is helpful to also change the variable names ($user, $users, $username, and $newUser) to correspond to grouprelated names: $group, $groups, $groupname, and $newgroup.

Prevent a String from Including Dynamic Information in Windows PowerShell

Problem

You want to prevent PowerShell from interpreting special characters or variable names inside a string.

Solution

Use a nonexpanding string to have PowerShell interpret your string exactly as entered. A nonexpanding uses the single quote character around its text.

PS >$myString = 'Useful PowerShell characters include: $, `, " and { }' PS >$myString Useful PowerShell characters include: $, `, " and { }

If you want to include newline characters as well, use a nonexpanding here string, as in Example 52.

Example 52. A nonexpanding here string that includes newline characters

PS >$myString = @' >> Tip of the Day >> >> Useful PowerShell characters include: $, `, ', " and { } >> '@ >> PS >$myString Tip of the Day

Useful PowerShell characters include: $, `, ', " and { }

Discussion

In a literal string, all the text between the single quotes becomes part of your string. This is in contrast to an expanding string, where PowerShell expands variable names (such as $myString) and escape sequences (such as `n) with their values (such as the content of $myString and the newline character).

Nonexpanding strings are a useful way to manage files and folders that contain special characters that might otherwise be interpreted as escape sequences.

“Create a String, ” one exception to the “all text in a literal string is literal” rule comes from the quote characters themselves. In either type of string, PowerShell let you place two of that string’s quote characters together to include the quote character itself:

$myString = "This string includes ""double quotes"" because it combined quote characters." $myString = 'This string includes ''single quotes'' because it combined quote characters.'

Extend the Reach of Windows PowerShell

The PowerShell environment is phenomenally comprehensive. It provides a great surface of cmdlets to help you manage your system, a great scripting language to let you automate those tasks, and direct access to all the utilities and tools you already know.

The cmdlets, scripting language, and preexisting tools are just part of what makes PowerShell so comprehensive, however. In addition to these features, PowerShell provides access to a handful of technologies that drastically increase its capabilities: the .NET Framework, Windows Management Instrumentation (WMI), COM automation objects, native Windows API calls, and more.

Not only does PowerShell give you access to these technologies, but it also gives you access to them in a consistent way. The techniques you use to interact with properties and methods of PowerShell objects are the same techniques that you use to interact with properties and methods of .NET objects. In turn, those are the same techniques that you use to work with WMI and COM objects, too.

Working with these techniques and technologies provides another huge benefit— knowledge that easily transfers to working in .NET programming languages such as C#.

Program: Invoke a PowerShell Expression on a Remote Machine

Example 214 lets you start processes and invoke PowerShell expressions on remote machines. It uses PsExec (from http://www.microsoft.com/technet/sysinternals/utilities/ psexec.mspx) to support the actual remote command execution.

This script offers more power than just remote command execution, however. As Example 213 demonstrates, it leverages PowerShell’s capability to import and export strongly structured data, so you can work with the command output using many of the same techniques you use to work with command output on the local system. Example 213 demonstrates this power by filtering command output on the remote system but sorting it on the local system.

Example 213. Invoking a PowerShell expression on a remote machine

PS >$command = { GetProcess | WhereObject { $_.Handles gt 1000 } } PS >InvokeRemoteExpression \\LEEDESK $command | Sort Handles

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

1025
8
3780
3772
32
134.42
848 csrss

1306
37
50364
64160
322
409.23
4012 OUTLOOK

1813
39
54764
36360
321
340.45
1452 iTunes

2316
273
29168
41164
218
134.09
1244 svchost

Since this strongly structured data comes from objects on another system, PowerShell does not regenerate the functionality of those objects (except in rare cases).

Example 214. InvokeRemoteExpression.ps1

############################################################################## ## ## InvokeRemoteExpression.ps1 ## ## Invoke a PowerShell expression on a remote machine. Requires PsExec from ## http://www.microsoft.com/technet/sysinternals/utilities/psexec.mspx ## ## ie: ## ## PS >InvokeRemoteExpression \\LEEDESK { GetProcess } ## PS >(InvokeRemoteExpression \\LEEDESK { GetDate }).AddDays(1) ## PS >InvokeRemoteExpression \\LEEDESK { GetProcess } | Sort Handles ## ##############################################################################

param( $computer = "\\$ENV:ComputerName", [ScriptBlock] $expression = $(throw "Please specify an expression to invoke."), [switch] $noProfile )

## Prepare the command line for PsExec. We use the XML output encoding so ## that PowerShell can convert the output back into structured objects. $commandLine = "echo . | powershell Output XML "

if($noProfile) { $commandLine += "NoProfile " }

## Convert the command into an encoded command for PowerShell $commandBytes = [System.Text.Encoding]::Unicode.GetBytes($expression) $encodedCommand = [Convert]::ToBase64String($commandBytes) $commandLine += "EncodedCommand $encodedCommand"

Example 214. InvokeRemoteExpression.ps1 (continued)

## Collect the output and error output $errorOutput = [IO.Path]::GetTempFileName() $output = psexec /acceptEula $computer cmd /c $commandLine 2>$errorOutput

## Check for any errors $errorContent = GetContent $errorOutput RemoveItem $errorOutput if($errorContent match "Access is denied") {

$OFS = "`n" $errorMessage = "Could not execute remote expression. " $errorMessage += "Ensure that your account has administrative " +

"privileges on the target machine.`n" $errorMessage += ($errorContent match "psexec.exe :")

WriteError $errorMessage }

## Return the output to the user $output

Control Access and Scope of Variables and Other Items in Windows PowerShell

Problem

You want to control how you define (or interact with) the visibility of variables, aliases, functions, and drives.

Solution

PowerShell offers several ways to access variables. To create a variable with a specific scope, supply that scope before the variable name:

$SCOPE:variable = value

To access a variable at a specific scope, supply that scope before the variable name:

$SCOPE:variable To create a variable that remains even after the script exits, create it in the GLOBAL scope: $GLOBAL:variable = value To change a scriptwide variable from within a function, supply SCRIPT as its scope name:

$SCRIPT:variable = value

Discussion

PowerShell controls access to variables, functions, aliases, and drives through a mechanism known as scoping. The scope of an item is another term for its visibility. You are always in a scope (called the current or local scope), but some actions change what that means.

When your code enters a nested prompt, script, function, or script block, PowerShell creates a new scope. That scope then becomes the local scope. When it does this, PowerShell remembers the relationship between your old scope and your new scope. From the view of the new scope, the old scope is called the parent scope. From the view of the old scope, the new scope is called a child scope. Child scopes get access to all the variables in the parent scope, but changing those variables in the child scope doesn’t change the version in the parent scope.

Trying to change a scriptwide variable from a function is often a “gotcha,” because a function is a new scope. As mentioned previously, changing something in a child scope (the function) doesn’t

affect the parent scope (the script). The rest of this discussion describes ways to change the value for the entire script.

When your code exits a nested prompt, script, function, or script block, the opposite happens. PowerShell removes the old scope, then changes the local scope to be the scope that originally created it—the parent of that old scope.

Some scopes are so common that PowerShell gives them special names:

Global

The outermost scope. Items in the global scope are visible from all other scopes.

Script

The scope that represents the current script. Items in the script scope are visible from all other scopes in the script.

Local

The current scope.

When you define the scope of an item, PowerShell supports two additional scope names that act more like options: Private and AllScope. When you define an item to have a Private scope, PowerShell does not make that item directly available to child scopes. PowerShell does not hide it from child scopes, though, as child scopes can still use the Scope parameter of the GetVariable cmdlet to get variables from parent scopes. When you specify the AllScope option for an item (through one of the *Variable, *Alias,or *Drive cmdlets), child scopes that change the item also affect the value in parent scopes.

With this background, PowerShell provides several ways for you to control access and scope of variables and other items.

Variables

To define a variable at a specific scope (or access a variable at a specific scope), use its scope name in the variable reference. For example:

$SCRIPT:myVariable = value

Functions

To define a function at a specific scope (or access a function at a specific scope), use its scope name when creating the function. For example:

function $GLOBAL:MyFunction { ... } GLOBAL:MyFunction args

Aliases and drives

To define an alias or drive at a specific scope, use the Option parameter of the *Alias and *Drive cmdlets. To access an alias or drive at a specific scope, use the Scope parameter of the *Alias and *Drive cmdlets.

For more information about scopes, type GetHelp AboutScope.

Handle Warnings, Errors, and Terminating Errors

Problem

You want to handle warnings, errors, and terminating errors generated by scripts or other tools that you call.

Solution

To control how your script responds to warning messages, set the $warningPreference variable. In this example, to ignore them: $warningPreference = "SilentlyContinue"

To control how your script responds to nonterminating errors, set the $errorActionPreference variable. In this example, to ignore them:

$errorActionPreference = "SilentlyContinue" To control how your script responds to terminating errors, use the trap statement. In this example, to output a message and continue with the script:

trap [DivideByZeroException] { "Don't divide by zero!"; continue }

Discussion

PowerShell defines several preference variables that help you control how your script reacts to warnings, errors, and terminating errors. As an example of these error management techniques, consider the following script:

############################################################################## ## ## GetWarningsAndErrors.ps1 ## ## Demonstrates the functionality of the WriteWarning, WriteError, and throw ## statements ## ##############################################################################

WriteWarning "Warning: About to generate an error" WriteError "Error: You are running this script" throw "Could not complete operation."

You can now see how a script might manage those separate types of errors:

PS >$warningPreference = "Continue" PS >GetWarningsAndErrors.ps1 WARNING: Warning: About to generate an error .. GetWarningsAndErrors.ps1 : Error: You are

running this script At line:1 char:27

+

GetWarningsAndErrors.ps1 Could not complete operation. At .. GetWarningsAndErrors.ps1:12 char:6

+

throw "Could not complete operation."

Once you modify the warning preference, the original warning message gets suppressed:

PS >$warningPreference = "SilentlyContinue" PS >GetWarningsAndErrors.ps1 .. GetWarningsAndErrors.ps1 : Error: You are

running this script At line:1 char:27

+

GetWarningsAndErrors.ps1 Could not complete operation. At .. GetWarningsAndErrors.ps1:12 char:6

+

throw "Could not complete operation."

When you modify the error preference, you suppress errors and exceptions, as well:

PS >$errorActionPreference = "SilentlyContinue" PS >GetWarningsAndErrors.ps1 PS >

An addition to the $errorActionPreference variable, all cmdlets allow you to specify your preference during an individual call:

PS >$errorActionPreference = "Continue" PS >GetChildItem IDoNotExist GetChildItem : Cannot find path '...\IDoNotExist' because it does not exist. At line:1 char:14

+ GetChildItem IDoNotExist PS >GetChildItem IDoNotExist ErrorAction SilentlyContinue PS >

If you reset the error preference back to Continue, you can see the impact of a trap statement. The message from the WriteError call makes it through, but the exception does not:

PS >$errorActionPreference = "Continue" PS >trap { "Caught an error"; continue }; GetWarningsAndErrors .. GetWarningsAndErrors.ps1 : Error: You are

running this script At line:1 char:61

+ trap { "Caught an error"; continue }; GetWarningsAndErrors Caught an error

Determine the Differences Between Two Files

Problem

You want to determine the differences between two files.

Solution

To determine simple differences in the content of each file, store their content in variables, and then use the CompareObject cmdlet to compare those variables:

PS >"Hello World" > c:\temp\file1.txt PS >"Hello World" > c:\temp\file2.txt PS >"More Information" >> c:\temp\file2.txt PS >$content1 = GetContent c:\temp\file1.txt PS >$content2 = GetContent c:\temp\file2.txt PS >CompareObject $content1 $content2

InputObject SideIndicator

More Information =>

Discussion

The primary focus of the CompareObject cmdlet is to compare two unordered sets of objects. Although those sets of objects can be strings (as in the content of two files), the output of CompareObject when run against files is usually counterintuitive due to the content losing its order.

When comparing large files (or files where the order of comparison matters), you can still use traditional file comparison tools such as diff.exe or the WinDiff application that comes with both the Windows Support Tools and Visual Studio.

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

How to Record a Transcript of Your in Windows PowerShell 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 StartTranscript Path. Path is optional and 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 StopTranscript.

Discussion

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

Remove Elements from an Array

Problem

You want to remove all elements from an array that match a given item or term— either exactly, by pattern, or by regular expression.

Solution

To remove all elements from an array that match a pattern, use the –ne, notlike, and –notmatch comparison operators as shown in Example 112.

Example 112. Removing elements from an array using the –ne, notlike, and –notmatch operators

PS >$array = "Item 1","Item 2","Item 3","Item 1","Item 12" PS >$array ne "Item 1" Item 2 Item 3 Item 12 PS >$array notlike "*1*" Item 2 Item 3 PS >$array notmatch "Item .." Item 1 Item 2 Item 3 Item 1

To actually remove the items from the array, store the results back in the array:

PS >$array = "Item 1","Item 2","Item 3","Item 1","Item 12" PS >$array = $array ne "Item 1" PS >$array Item 2 Item 3 Item 12

Discussion

The eq, like, and match operators are useful ways to find elements in a collection that match your given term. Their opposites—the –ne, notlike, and –notmatch operators—return all elements that do not match that given term.

To remove all elements from an array that match a given pattern, then, you can save all elements that do not match that pattern.

View a Registry Key

Problem

You want to view the value of a specific registry key.

Solution

To retrieve the value(s) of a registry key, use the GetItemProperty cmdlet, as shown in Example 181.

Example 181. Retrieving properties of a registry key

PS >SetLocation HKCU: PS >SetLocation \Software\Microsoft\Windows\CurrentVersion\Run PS >GetItemProperty .

PSPath
: Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_U

SER\Software\Microsoft\Windows\CurrentVersion\Run

PSParentPath
: Microsoft.PowerShell.Core\Registry::HKEY_CURRENT_U

SER\Software\Microsoft\Windows\CurrentVersion

PSChildName
: Run

PSDrive
: HKCU

PSProvider
: Microsoft.PowerShell.Core\Registry

FolderShare
: "C:\Program Files\FolderShare\FolderShare.exe" /ba

ckground

TaskSwitchXP
: d:\lee\tools\TaskSwitchXP.exe

ctfmon.exe
: C:\WINDOWS\system32\ctfmon.exe

Ditto
: C:\Program Files\Ditto\Ditto.exe

QuickTime Task
: "C:\Program Files\QuickTime Alternative\qttask.exe

" atboottime

H/PC Connection Agent : "C:\Program Files\Microsoft ActiveSync\wcescomm.exe"

Discussion

In the registry provider, PowerShell treats registry keys as items and key values as properties of those items. To get the properties of an item, use the GetItemProperty cmdlet. The GetItemProperty cmdlet has the standard alias, gp.

Example 181 lists all property values associated with that specific key. To retrieve the value of a specific item, access it as though you would access a property on a .NET object, or anywhere else in PowerShell:

PS >$item = GetItemProperty . PS >$item.TaskSwitchXp d:\lee\tools\TaskSwitchXP.exe

If you want to do this all at once, the command looks like:

PS >$runKey = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" PS >(GetItemProperty $runKey).TaskSwitchXp d:\lee\tools\TaskSwitchXP.exe

For more information about the GetItemProperty cmdlet, type GetHelp GetItemProperty. For more information about the registry provider, type GetHelp Registry.

Manage Exchange Users

Problem

You want to get and modify information about user domain accounts from the Exchange Management Shell.

Solution

To get and set information about Active Directory users, use the GetUser and SetUser cmdlets, respectively:

$user = GetUser *preeda*

$user | FormatList *

To update Preeda’s title to Senior Vice President:

$user | SetUser –Title "Senior Vice President" $user.Title

Discussion

Active Directory accounts are an integral part of working with Exchange 2007. While you will often want to modify the mailboxes of domain users, GetUser and SetUser lets you work with the user accounts themselves.

For more information about the GetUser cmdlet, type GetHelp GetUser. For more information about the SetUser cmdlet, type GetHelp SetUser.

How to Run Programs, Scripts, and Existing Tools in Windows PowerShell

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 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 singlequotes (') 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.

Example 11. 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.

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.

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.