Skip to main content

Windows

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.