Skip to main content

Windows

Create a String in PowerShell

Problem

You want to create a variable that holds text.

Solution

Use PowerShell string variables to give you a way to store and work with text.

To define a string that supports variable expansion and escape characters in its definition, surround it with double quotes:

$myString = "Hello World"

To define a literal string (that does not support variable expansion or escape characters), surround it with single quotes:

$myString = 'Hello World'

Discussion

String literals come in two varieties: literal (nonexpanding) and expanding strings. To create a literal string, place single quotes ($myString = 'Hello World') around the text. To create an expanding string, place double quotes ($myString = "Hello World") around the text.

In a literal string, all the text between the single quotes becomes part of your string. In an expanding string, 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, respectively).

One exception to the “all text in a literal string is literalrule comes from the quote characters themselves. In either type of string, PowerShell lets you to place two of that string’s quote characters together to add 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.'

This helps prevent escaping atrocities that would arise when you try to include a single quote in a singlequoted string. For example:

$myString = 'This string includes ' + "'" + 'single quotes' + "'"

This example shows how easy PowerShell makes it to create new strings by adding other strings together. This is an attractive way to build a formatted report in a script but should be used with caution.

Due to the way that the .NET Framework (and therefore PowerShell) manages strings, adding information to the end of a large string this way causes noticeable performance problems. 

Get the Current Location in Windows PowerShell

Problem

You want to determine the current location.

Solution

To determine the current location, use the GetLocation cmdlet: PS >GetLocation

Path

C:\temp PS >$currentLocation = (GetLocation).Path PS >$currentLocation C:\temp

Discussion

One problem that sometimes impacts scripts that work with the .NET Framework is that PowerShell’s concept of “current location” isn’t always the same as the PowerShell.exe process’s “current directory.” Take, for example:

PS >GetLocation

Path

C:\temp

PS >GetProcess | ExportCliXml processes.xml PS >$reader = NewObject Xml.XmlTextReader processes.xml PS >$reader.BaseURI file:///C:/Documents and Settings/Lee/processes.xml

PowerShell keeps these concepts separate because it supports multiple pipelines of execution. The processwide current directory affects the entire process, so you would risk corrupting the environment of all background tasks as you navigate around the shell if that changed the process’s current directory.

When you use filenames in most .NET methods, the best practice is to use fully qualified pathnames. The ResolvePath cmdlet makes this easy:

PS >GetLocation

Path

C:\temp

PS >GetProcess | ExportCliXml processes.xml PS >$reader = NewObject Xml.XmlTextReader (ResolvePath processes.xml) PS >$reader.BaseURI file:///C:/temp/processes.xml

If you want to access a path that doesn’t already exist, use the JoinPath in combination with the GetLocation cmdlet: PS >JoinPath (GetLocation) newfile.txt

C:\temp\newfile.txt

List Currently Running Windows PowerShell Processes

Problem

You want to see which processes are running on the system.

Solution

To retrieve the list of currently running processes, use the GetProcess cmdlet: PS >GetProcess

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

274 6 1328 3940 33 1084 alg 85 4 3816 6656 57 5.67 3460 AutoHotkey 50 2 2292 1980 14 384.25 1560 BrmfRsmg 71 3 2520 4680 35 0.42 2592 cmd

946 7 3676 6204 32 848 csrss 84 4 732 2248 22 3144 csrss 68 4 936 3364 30 0.38 3904 ctfmon

243 7 3648 9324 48 2.02 2892 Ditto (...)

Discussion

The GetProcess cmdlet retrieves information about all processes running on the system. Because these are rich .NET objects (of the type System.Diagnostics.Process), advanced filters and operations are easier than ever before.

For example, to find all processes using more than 100 MB of memory:

PS >GetProcess | WhereObject { $_.WorkingSet gt 100mb }

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

1458 29 83468 105824 273 323.80 3992 BigBloatedApp

To group processes by company:

PS >GetProcess | GroupObject Company

Count Name Group

39 {alg, csrss, csrss, dllhost...} 4 {AutoHotkey, Ditto, gnuserv, mafwTray} 1 Brother Industries, Ltd. {BrmfRsmg}

19 Microsoft Corporation {cmd, ctfmon, EXCEL, explorer...} 1 Free Software Foundation {emacs} 1 Microsoft (R) Corporation {FwcMgmt}

(...)

Or perhaps to sort by start time (with the most recent first):

PS >GetProcess | Sort Descending StartTime | SelectObject First 10

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

1810 39 53616 33964 193 318.02 1452 iTunes 675 6 41472 50180 146 49.36 296 powershell 1240 35 48220 58860 316 167.58 4012 OUTLOOK

305 8 5736 2460 105 21.22 3384 WindowsSearch... 464 7 29704 30920 153 6.00 3680 powershell

1458 29 83468 105824 273 324.22 3992 iexplore 478 6 24620 23688 143 17.83 3548 powershell 222 8 8532 19084 144 20.69 3924 EXCEL

14 2 396 1600 15 0.06 2900 logon.scr 544 18 21336 50216 294 180.72 2660 WINWORD

These advanced tasks become incredibly simple due to the rich amount of information that PowerShell returns for each process. For more information about the GetProcess cmdlet, type GetHelp GetProcess.

Variables and Objects in Windows PowerShell

As touched on in Chapter 2, Pipelines , PowerShell makes life immensely easier by keeping information in its native form: objects. Users expend most of their effort in traditional shells just trying to resuscitate information that the shell converted from its native form to plain text. Tools have evolved that ease the burden of working with plain text, but that job is still significantly more difficult than it needs to be.

Since PowerShell builds on Microsoft’s .NET Framework, native information comes in the form of .NET objects—packages of information, and functionality closely related to that information.

Let’s say that you want to get a list of running processes on your system. In other shells, your command (such as tlist.exe or /bin/ps) generates a plaintext report of the running processes on your system. To work with that output, you send it through a bevy of text processing tools—if you are lucky enough to have them available.

PowerShell’s GetProcess cmdlet generates a list of the running processes on your system. In contrast to other shells, though, these are fullfidelity System.Diagnostics. Process objects straight out of the .NET Framework. The .NET Framework documentation describes them as objects that “... [provide] access to local and remote processes, and [enable] you to start and stop local system processes.With those objects in hand, PowerShell makes it trivial for you to access properties of objects (such as their process name or memory usage) and to access functionality on these objects (such as stopping them, starting them, or waiting for them to exit).

Program: Add a Graphical User Interface to Your Script

While the techniques provided in the rest of this chapter are usually all you need, it is sometimes helpful to provide a graphical user interface to interact with the user.

Since PowerShell fully supports traditional executables, simple programs can usually fill this need. If creating a simple program in an environment such as Visual Studio is inconvenient, you can often use PowerShell to create these applications directly.

Example 129 demonstrates the techniques you can use to develop a Windows Forms application using PowerShell scripting alone.

Example 129. SelectGraphicalFilteredObject.ps1

############################################################################## ## ## SelectGraphicalFilteredObject.ps1 ## ## Display a Windows Form to help the user select a list of items piped in. ## Any selected items get passed along the pipeline. ## ## ie: ## ## PS >dir | SelectGraphicalFilteredObject ##

Example 129. SelectGraphicalFilteredObject.ps1 (continued)

## Directory: Microsoft.PowerShell.Core\FileSystem::C:\

##

##

## Mode
LastWriteTime Length Name

##

## d
10/7/2006
4:30 PM
Documents and Settings

## d
3/18/2007
7:56 PM
Windows

##

 

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

$objectArray = @($input)

## Ensure that they've piped information into the script if($objectArray.Count eq 0) {

WriteError "This script requires pipeline input." return }

## Load the Windows Forms assembly [void] [Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

## Create the main form $form = NewObject Windows.Forms.Form $form.Size = NewObject Drawing.Size @(600,600)

## Create the listbox to hold the items from the pipeline $listbox = NewObject Windows.Forms.CheckedListBox $listbox.CheckOnClick = $true $listbox.Dock = "Fill" $form.Text = "Select the list of objects you wish to pass down the pipeline" $listBox.Items.AddRange($objectArray)

## Create the button panel to hold the OK and Cancel buttons $buttonPanel = NewObject Windows.Forms.Panel $buttonPanel.Size = NewObject Drawing.Size @(600,30) $buttonPanel.Dock = "Bottom"

## Create the Cancel button, which will anchor to the bottom right $cancelButton = NewObject Windows.Forms.Button $cancelButton.Text = "Cancel" $cancelButton.DialogResult = "Cancel" $cancelButton.Top = $buttonPanel.Height $cancelButton.Height 5 $cancelButton.Left = $buttonPanel.Width $cancelButton.Width 10 $cancelButton.Anchor = "Right"

## Create the OK button, which will anchor to the left of Cancel $okButton = NewObject Windows.Forms.Button $okButton.Text = "Ok" $okButton.DialogResult = "Ok" $okButton.Top = $cancelButton.Top $okButton.Left = $cancelButton.Left $okButton.Width 5 $okButton.Anchor = "Right"

Example 129. SelectGraphicalFilteredObject.ps1 (continued)

## Add the buttons to the button panel $buttonPanel.Controls.Add($okButton) $buttonPanel.Controls.Add($cancelButton)

## Add the button panel and list box to the form, and also set ## the actions for the buttons $form.Controls.Add($listBox) $form.Controls.Add($buttonPanel) $form.AcceptButton = $okButton $form.CancelButton = $cancelButton $form.Add_Shown( { $form.Activate() } )

## Show the form, and wait for the response $result = $form.ShowDialog()

## If they pressed OK (or Enter,) go through all the ## checked items and send the corresponding object down the pipeline if($result eq "OK") {

foreach($index in $listBox.CheckedIndices) { $objectArray[$index] } }

Discover Registry Settings for Programs in Windows PowerShell

Problem

You want to automate the configuration of a program, but that program does not document its registry configuration settings.

Solution

To discover a registry setting for a program, use Sysinternals’ Process Monitor to observe registry access by that program. Process Monitor is available from http:// www.microsoft.com/technet/sysinternals/FileAndDisk/processmonitor.mspx.

Discussion

In an ideal world, all programs would fully support commandline administration and configuration through PowerShell cmdlets. Many programs do not, however, so the solution is to look through their documentation in the hope that they list the registry keys and properties that control their settings. While many programs document their registry configuration settings, many still do not.

Although these programs may not document their registry settings, you can usually observe their registry access activity to determine the registry paths they use. To illustrate this, we will use the Sysinternals’ Process Monitor to discover PowerShell’s execution policy configuration keys. Although PowerShell documents these keys and makes its automated configuration a breeze, it illustrates the general technique.

Tell Process Monitor to begin capturing information

Switch to the Process Monitor window, and then press CtrlE (or click the magnifying glass icon). Process Monitor now captures all registry access for the program in question.

Manually set the configuration option

Click OK, Apply, or whatever action it takes to actually complete the program’s configuration. For the PowerShell example, this means pressing Enter.

Tell Process Monitor to stop capturing information

Switch again to the Process Monitor window, and then press CtrlE (or click the magnifying glass icon). Process Monitor now no longer captures the application’s activity.

Review the capture logs for registry modification

The Process Monitor window now shows all registry keys that the application interacted with when it applied its configuration setting.

Press CtrlF (or click the binoculars icon); then search for RegSetValue. Process Monitor highlights the first modification to a registry key.

Press Enter (or doubleclick the highlighted row) to see the details about this specific registry modification. In this example, we can see that PowerShell changed the value of the ExecutionPolicy property (under HKLM:\Software\Microsoft\PowerShell\1\ ShellIds\Microsoft.PowerShell)to RemoteSigned. Press F3 to see the next entry that corresponds to a registry modification.

Automate these registry writes

Now that you know all registry writes that the application performed when it updated its settings, judgment and experimentation will help you determine which modifications actually represent this setting. Since PowerShell only performed one registry write (to a key that very obviously represents the execution policy), the choice is pretty clear in this example.

Once you’ve discovered the registry keys, properties, and values that the application uses to store its configuration data, you can use the techniques discussed in Article

PS >$key = "HKLM:\Software\Microsoft\PowerShell\1\" + >> "ShellIds\Microsoft.PowerShell" >> PS >SetItemProperty $key ExecutionPolicy AllSigned PS >GetExecutionPolicy AllSigned PS >SetItemProperty $key ExecutionPolicy RemoteSigned PS >GetExecutionPolicy RemoteSigned

Access and Manage Your Console History in Windows PowerShell

Problem

After working in the shell for awhile, you want to invoke commands from your history, view your command history, and save your command history.

Solution

To get the most recent commands from your session, use the GetHistory cmdlet:

GetHistory

To rerun a specific command from your session history, provide its Id to the InvokeHistory cmdlet: InvokeHistory Id

To increase (or limit) the number of commands stored in your session history, assign a new value to the $MaximumHistoryCount variable: $MaximumHistoryCount = Count

To save your command history to a file, pipe the output of GetHistory to the ExportCliXml cmdlet:

GetHistory | ExportCliXml Filename

To add a previously saved command history to your current session history, call the ImportCliXml cmdlet and then pipe that output to the AddHistory cmdlet:

ImportCliXml Filename | AddHistory

Discussion

Unlike the console history hotkeys the GetHistory cmdlet produces rich objects that represent information about items in your history. Each object contains that item’s ID, command line, start of execution time, and end of execution time.

Once you know the ID of a history item (as shown in the output of GetHistory), you can pass it to InvokeHistory to execute that command again.

The IDs provided by the GetHistory cmdlet differ from the IDs given by the Windows console common history hotkeys (such as F7), because their history management techniques differ.

By default, PowerShell stores only the last 64 entries of your command history. If you want to raise or lower this amount, set the $MaximumHistoryCount variable to the size you desire. To make this change permanent, set the variable in your PowerShell profile script. To clear your history, either restart the shell, or temporarily set the $MaximumHistoryCount variable to 1.

Determine Whether an Array Contains an Item

Problem

You want to determine whether an array or list contains a specific item.

Solution

To determine whether a list contains a specific item, use the –contains operator:

PS >"Hello","World" contains "Hello" True PS >"Hello","World" contains "There" False

Discussion

The –contains operator is a useful way to quickly determine whether a list contains a specific element. To search a list for items that instead match a pattern, use the –match or –like operators.

Program: Create a ZIP Archive

Discussion

When transporting or archiving files, it is useful to store those files in an archive. ZIP archives are the most common type of archive, so it would be useful to have a script to help manage them.

For many purposes, traditional commandline ZIP archive utilities may fulfill your needs. If they do not support the level of detail or interaction that you need for administrative tasks, a more programmatic alternative is attractive.

Example 177 lets you create ZIP archives simply by piping files into them. It requires that you have the SharpZipLib installed, which you can obtain from http:// www.icsharpcode.net/OpenSource/SharpZipLib/ .

Example 177. NewZipFile.ps1

############################################################################## ## ## NewZipFile.ps1 ## ## Create a Zip file from any files piped in. Requires that ## you have the SharpZipLib installed, which is available from ## http://www.icsharpcode.net/OpenSource/SharpZipLib/ ## ## ie: ## ## PS >dir *.ps1 | NewZipFile scripts.zip d:\bin\ICSharpCode.SharpZipLib.dll ## PS >"readme.txt" | NewZipFile docs.zip d:\bin\ICSharpCode.SharpZipLib.dll ## ##############################################################################

Example 177. NewZipFile.ps1 (continued)

param( $zipName = $(throw "Please specify the name of the file to create."), $libPath = $(throw "Please specify the path to ICSharpCode.SharpZipLib.dll.") )

## Load the Zip library [void] [Reflection.Assembly]::LoadFile($libPath) $namespace = "ICSharpCode.SharpZipLib.Zip.{0}"

## Create the Zip File $zipName =

$executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($zipName) $zipFile = NewObject ($namespace f "ZipOutputStream") ([IO.File]::Create($zipName)) $zipFullName = (ResolvePath $zipName).Path

[byte[]] $buffer = NewObject byte[] 4096

## Go through each file in the input, adding it to the Zip file ## specified foreach($file in $input) {

## Skip the current file if it is the zip file itself if($file.FullName eq $zipFullName) {

continue }

## Convert the path to a relative path, if it is under the current location $replacePath = [Regex]::Escape( (GetLocation).Path + "\" ) $zipName = ([string] $file) replace $replacePath,""

## Create the zip entry, and add it to the file $zipEntry = NewObject ($namespace f "ZipEntry") $zipName $zipFile.PutNextEntry($zipEntry)

$fileStream = [IO.File]::OpenRead($file.FullName) [ICSharpCode.SharpZipLib.Core.StreamUtils]::Copy($fileStream, $zipFile, $buffer) $fileStream.Close()

}

## Close the file $zipFile.Close()

Manage an Exchange 2007 Server

Pointandclick management has long been the stereotype of Windows administration. While it has always been possible to manage portions of Windows (or other applications) through the command line, support usually comes as an afterthought. Once all the administration support has been added to the user interface, the developers of an application might quickly cobble together a COM API or commandline tool if you are lucky. If you aren’t lucky, they might decide only to publish some registry key settings, or perhaps nothing at all.

This inequality comes almost naturally from implementing a management model as an afterthought: with a fully functional user interface complete, very few application developers deem fully functional scriptable administration to be a high priority.

And then there’s Exchange 2007.

In contrast to those who cobble together a management model only after completing the user interface, the Exchange 2007 team wrote their management infrastructure first. In Exchange 2007, it’s not only a firstclass feature—it’s a way of life. Exchange 2007 includes nearly 400 cmdlets to let you manage Exchange systems. Not only is the magnitude stunning, but its breadth and depth is as well. To guarantee full coverage by way of PowerShell cmdlets, the Exchange Management Console user interface builds itself completely on top of PowerShell cmdlets.

Any command that affects the Exchange environment does so through one of the included Exchange Management cmdlets.

The benefit of this model is immense. It doesn’t matter whether you are working with users, groups, mailboxes, or anything else in Exchange: you can automate it all.