Skip to main content

Resources

Visit Each Element of an Array

Problem

You want to work with each element of an array.

Solution

To access each item in an array onebyone, use the ForeachObject cmdlet:

PS >$myArray = 1,2,3 PS >$sum = 0 PS >$myArray | ForeachObject { $sum += $_ } PS >$sum 6

To access each item in an array in a more scriptlike fashion, use the foreach scripting keyword:

PS >$myArray = 1,2,3 PS >$sum = 0 PS >foreach($element in $myArray) { $sum += $element } PS >$sum 6

To access items in an array by position, use a for loop:

PS >$myArray = 1,2,3 PS >$sum = 0 PS >for($counter = 0; $counter lt $myArray.Count; $counter++) { >> $sum += $myArray[$counter] >> } >> PS >$sum 6

Discussion

PowerShell provides three main alternatives to working with elements in an array. The ForeachObject cmdlet and foreach scripting keyword techniques visit the items in an array one element at a time, while the for loop (and related looping constructs) lets you work with the items in an array in a less structured way.

Program: Add Extended File Properties to Files in PowerShell

Discussion

The Explorer shell provides useful information about a file when you click on its Properties dialog. It includes the authoring information, image information, music information, and more.

PowerShell doesn’t expose this information by default, but it is possible to obtain these properties from the Shell.Application COM object. Example 175 does just that—and adds this extended information as properties to the files returned by the GetChildItem cmdlet.

Example 175. AddExtendedFileProperties.ps1

############################################################################## ## ## AddExtendedFileProperties.ps1 ## ## Add the extended file properties normally shown in Explorer's ## "File Properties" tab. ## ## ie: ## ## PS >GetChildItem | AddExtendedFileProperties.ps1 | ## FormatTable Name,"Bit Rate" ## ##############################################################################

begin

{ ## Create the Shell.Application COM object that provides this ## functionality $shellObject = NewObject Com Shell.Application

## Store the property names and identifiers for all of the shell ## properties $itemProperties = @{

1 = "Size"; 2 = "Type"; 3 = "Date Modified"; 4 = "Date Created"; 5 = "Date Accessed"; 7 = "Status"; 8 = "Owner"; 9 = "Author"; 10 = "Title"; 11 = "Subject"; 12 = "Category"; 13 = "Pages"; 14 = "Comments"; 15 = "Copyright"; 16 = "Artist"; 17 = "Album Title"; 19 = "Track Number"; 20 = "Genre"; 21 = "Duration"; 22 = "Bit Rate"; 23 = "Protected"; 24 = "Camera Model"; 25 = "Date Picture Taken"; 26 = "Dimensions"; 30 = "Company"; 31 = "Description"; 32 = "File Version"; 33 = "Product Name"; 34 = "Product Version" }

}

process

{ ## Get the file from the input pipeline. If it is just a filename ## (rather than a real file,) piping it to the GetItem cmdlet will ## get the file it represents. $fileItem = $_ | GetItem

## Don't process directories if($fileItem.PsIsContainer) {

Example 175. AddExtendedFileProperties.ps1 (continued)

$fileItem return }

## Extract the file name and directory name $directoryName = $fileItem.DirectoryName $filename = $fileItem.Name

## Create the folder object and shell item from the COM object $folderObject = $shellObject.NameSpace($directoryName) $item = $folderObject.ParseName($filename)

## Now, go through each property and add its information as a ## property to the file we are about to return foreach($itemProperty in $itemProperties.Keys) {

$fileItem | AddMember NoteProperty $itemProperties[$itemProperty] ` $folderObject.GetDetailsOf($item, $itemProperty) }

## Finally, return the file with the extra shell information $fileItem }

List All IP Addresses for a Computer

Problem

You want to list all IP addresses for a computer.

Solution

To list IP addresses assigned to a computer, use the ipconfig application: PS >ipconfig

Discussion

The standard ipconfig application works well to manage network configuration options on a local machine. To view IP addresses on a remote computer, you have two options.

Use the Win32_NetworkAdapterConfiguration WMI class

To view IP addresses a remote computer, use the Win32_NetworkAdapterConfiguration WMI class. Since that lists all network adapters, use the WhereObject cmdlet to restrict the results to those with an IP address assigned to them:

PS >GetWmiObject Win32_NetworkAdapterConfiguration –Computer LEEDESK | >> WhereObject { $_.IpEnabled } >>

DHCPEnabled : True IPAddress : {192.168.1.100} DefaultIPGateway : {192.168.1.1}

DNSDomain
: hsd1.wa.comcast.net.

ServiceName
: USB_RNDIS

Description
: Linksys WirelessG USB Network Adapter with SpeedBooste

r v2 Packet Scheduler Miniport

Index
: 13

Run ipconfig on the remote computer

PS >InvokeRemoteExpression \\LEEDESK { ipconfig }

Download a File from the Internet

Problem

You want to download a file from a web site on the Internet.

Solution

Use the DownloadFile() method from the .NET Framework’s System.Net.WebClient class to download a file:

PS >$source = "http://www.leeholmes.com/favicon.ico" PS >$destination = "c:\temp\favicon.ico" PS > PS >$wc = NewObject System.Net.WebClient PS >$wc.DownloadFile($source, $destination)

Discussion

The System.Net.WebClient class from the .NET Framework lets you easily upload and download data from remote web servers.

The WebClient class acts much like a web browser, in that you can specify a user agent, proxy (if your outgoing connection requires one), and even credentials.

All web browsers send a user agent identifier along with their web request. This identifier tells the web site what application is making the request—such as Internet Explorer, Firefox, or an automated crawler from a search engine. Many web sites check this user agent identifier to determine how to display the page. Unfortunately, many fail entirely if they can’t determine the user agent for the incoming request. To make the System.Net.WebClient identify itself as Internet Explorer, use the following commands, instead:

$userAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2;)" $wc = NewObject System.Net.WebClient $wc.Headers.Add("useragent", $userAgent)

Notice that the solution uses a fully qualified path for the destination file. This is an important step, as the DownloadFile() method saves its files to the directory in which PowerShell.exe started (the root of your user profile directory by default) otherwise.

You can use the DownloadFile() method to download web pages just as easily as you download files—you need to supply only an URL as a source (such as http://blogs. msdn.com/powershell/rss.xml ) instead of a filename. If you ultimately intend to parse or read through the downloaded page, the DownloadString() method may be more appropriate.

Find All Files Modified Before a Certain Date

Problem

You want to find all files last modified before a certain date.

Solution

To find all files modified before a certain date, use the GetChildItem cmdlet to list the files in a directory, and then use the WhereObject cmdlet to compare the LastWriteTime property to the date you are interested in. For example, to find all files created before this year:

GetChildItem Recurse | WhereObject { $_.LastWriteTime lt "01/01/2007" }

Discussion

Acommon reason to compare files against a certain date is to find recently modified (or not recently modified) files. This looks almost the same as the example given by the solution, but your script can’t know the exact date to compare against.

In this case, the AddDays() method in the .NET Framework’s DateTime class gives you a way to perform some simple calendar arithmetic. If you have a DateTime object, you can add or subtract time from it to represent a different date altogether. For example, to find all files modified in the last 30 days:

$compareDate = (GetDate).AddDays(30) GetChildItem Recurse | WhereObject { $_.LastWriteTime ge $compareDate }

Similarly, to find all files more than 30 days old:

$compareDate = (GetDate).AddDays(30)

GetChildItem Recurse | WhereObject { $_.LastWriteTime lt $compareDate } In this example, the GetDate cmdlet returns an object that represents the current date and time. You call the AddDays() method to subtract 30 days from that time, which stores the date representing “30 days ago” in the $compareDate variable. Next, you compare that date against the LastWriteTime property of each file that the GetChildItem cmdlet returns.

The DateTime class is the administrator’s favorite calendar!

PS >[DateTime]::IsLeapYear(2008)

True

PS >$daysTillSummer = [DateTime] "06/21/2008" (GetDate)

PS >$daysTillSummer.Days

283

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

Get and List the Properties of a Computer Account

Problem

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

Solution

To list the properties of a computer account, use the [adsi] type shortcut to bind to the computer in Active Directory, and then pass the computer to the FormatList cmdlet:

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

$computer | FormatList *

Discussion

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

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

PS >$computer.OperatingSystem Windows Server 2003

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

PS >$user.Get("operatingSystem") Windows Server 2003

Measure Statistical Properties of a List in Windows PowerShell

Problem

You want to measure the numeric (minimum, maximum, sum, average) or textual (characters, words, lines) features of a list of objects.

Solution

Use the MeasureObject cmdlet to measure these statistical properties of a list. To measure the numeric features of a stream of objects, pipe those objects to the MeasureObject cmdlet: PS >1..10 | MeasureObject –Average Sum

Count : 10 Average : 5.5 Sum : 55 Maximum : Minimum : Property :

To measure the numeric features of a specific property in a stream of objects, supply that property name to the –Property parameter of the MeasureObject cmdlet. For example, in a directory with files:

PS >GetChildItem | MeasureObject Property Length Max Min Average Sum

Count : 427 Average : 10617025.4918033 Sum : 4533469885 Maximum : 647129088 Minimum : 0 Property : Length

To measure the textual features of a stream of objects, use the –Character, Word, and –Line parameters of the MeasureObject cmdlet:

PS >GetChildItem > output.txt PS >GetContent output.txt | MeasureObject Character Word Line

Lines
Words
Characters Property

964
6083
33484

Discussion

By default, the MeasureObject cmdlet counts only the number of objects it receives. If you want to measure additional properties (such as the maximum, minimum, average, sum, characters, words, or lines) of those objects, then you need to specify them as options to the cmdlet.

For the numeric properties, though, you usually don’t want to measure the objects themselves. Instead, you probably want to measure a specific property from the list—such as the Length property of a file. For that purpose, the MeasureObject cmdlet supports the –Property parameter to which you provide the property you want to measure.

Sometimes, you might want to measure a property that isn’t a simple number—such as the LastWriteTime property of a file. Since the LastWriteTime property is a DateTime, you can’t determine its average immediately. However, if any property allows you to convert it to a number and back in a meaningful way (such as the Ticks property of a DateTime), then you can still compute its statistical properties. Example 62 shows how to get the average LastWriteTime from a list of files.

Example 62. Using the Ticks property of the DateTime class to determine the average LastWriteTime of a list of files

PS >## Get the LastWriteTime from each file PS >$times = dir | ForeachObject { $_.LastWriteTime }

PS >## Measure the average Ticks property of those LastWriteTime

Example 62. Using the Ticks property of the DateTime class to determine the average LastWriteTime of a list of files (continued)

PS >$results = $times | MeasureObject Ticks Average

PS >## Create a new DateTime out of the average Ticks PS >NewObject DateTime $results.Average

Sunday, June 11, 2006 6:45:01 AM

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

Program: Add Inline C# to Your PowerShell Script

One of the natural languages to explore after learning PowerShell is C#. It uses many of the same programming techniques as PowerShell and uses the same classes and methods in the .NET Framework as PowerShell does, too. In addition, C# sometimes offers language features or performance benefits not available through PowerShell.

Rather than having to move to C# completely for these situations, Example 158 lets you write and invoke C# directly in your script.

Example 158. InvokeInline.ps1

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

## InvokeInline.ps1

## Library support for inline C#

##

## Usage

## 1) Define just the body of a C# method, and store it in a string. "Here

## strings" work great for this. The code can be simple:

##

## $codeToRun = "Console.WriteLine(Math.Sqrt(337));"

##

## or more complex:

##

## $codeToRun = @"

## string firstArg = (string) ((System.Collections.ArrayList) arg)[0];

## int secondArg = (int) ((System.Collections.ArrayList) arg)[1];

##

## Console.WriteLine("Hello {0} {1}", firstArg, secondArg );

##

## returnValue = secondArg * 3;

## "@

##

## 2) (Optionally) Pack any arguments to your function into a single object.

## This single object should be stronglytyped, so that PowerShell does

## not treat it as a PsObject.

## An ArrayList works great for multiple elements. If you have only one

## argument, you can pass it directly.

##

## [System.Collections.ArrayList] $arguments =

## NewObject System.Collections.ArrayList

## [void] $arguments.Add("World")

## [void] $arguments.Add(337)

##

## 3) Invoke the inline code, optionally retrieving the return value. You can

## set the return value in your inline code by assigning it to the

## "returnValue" variable as shown above.

##

## $result = InvokeInline $codeToRun $arguments

##

##

## If your code is simple enough, you can even do this entirely inline:

##

## InvokeInline "Console.WriteLine(Math.Pow(337,2));"

##

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

param( [string] $code = $(throw "Please specify the code to invoke"), [object] $arg, [string[]] $reference = @() )

## Stores a cache of generated inline objects. If this library is dotsourced ## from a script, these objects go away when the script exits.

Example 158. InvokeInline.ps1 (continued)

if(not (TestPath Variable:\Lee.Holmes.inlineCache)) { ${GLOBAL:Lee.Holmes.inlineCache} = @{} }

## The main function to execute inline C#. ## Pass the argument to the function as a stronglytyped variable. They will ## be available from C# code as the Object variable, "arg". ## Any values assigned to the "returnValue" object by the C# code will be ## returned to the caller as a return value.

function main

{ ## See if the code has already been compiled and cached $cachedObject = ${Lee.Holmes.inlineCache}[$code]

## The code has not been compiled or cached if($cachedObject eq $null) {

$codeToCompile = @" using System;

public class InlineRunner

{ public Object Invoke(Object arg) {

Object returnValue = null;

$code

return returnValue; } } "@

## Obtains an ICodeCompiler from a CodeDomProvider class. $provider = NewObject Microsoft.CSharp.CSharpCodeProvider

## Get the location for System.Management.Automation DLL $dllName = [PsObject].Assembly.Location

## Configure the compiler parameters $compilerParameters = NewObject System.CodeDom.Compiler.CompilerParameters

$assemblies = @("System.dll", $dllName) $compilerParameters.ReferencedAssemblies.AddRange($assemblies) $compilerParameters.ReferencedAssemblies.AddRange($reference) $compilerParameters.IncludeDebugInformation = $true $compilerParameters.GenerateInMemory = $true

## Invokes compilation.

Example 158. InvokeInline.ps1 (continued)

$compilerResults = $provider.CompileAssemblyFromSource($compilerParameters, $codeToCompile)

## Write any errors if generated. if($compilerResults.Errors.Count gt 0) {

$errorLines = "`n$codeToCompile" foreach($error in $compilerResults.Errors) {

$errorLines += "`n`t" + $error.Line + ":`t" + $error.ErrorText } WriteError $errorLines

} ## There were no errors. Store the resulting object in the object " ## cache. else {

${Lee.Holmes.inlineCache}[$code] = $compilerResults.CompiledAssembly.CreateInstance("InlineRunner") }

$cachedObject = ${Lee.Holmes.inlineCache}[$code] }

## Finally invoke the C# code if($cachedObject ne $null) {

return $cachedObject.Invoke($arg) } }

. Main

Get the Children of an Active Directory Container in PowerShell

Problem

You want to list all the children of an Active Directory container.

Solution

To list the items in a container, use the [adsi] type shortcut to bind to the OU in Active Directory, and then access the PsBase.Children property of that container:

$sales =

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

$sales.PsBase.Children

Discussion

The solution lists all the children of the Sales OU. This is the level of information you typically get from selecting a node in the ADSIEdit MMC snapin. If you want to filter this information to include only users, other organizational units, or more complex queries.

Make Decisions with Comparison and Logical Operators in Windows PowerShell

Problem

You want to compare some data with other data and make a decision based on that comparison.

Solution

Use PowerShell’s logical operators to compare pieces of data and make decisions based on them.

Comparison operators:

eq, ne, ge, gt, lt, le, like, notlike, match, notmatch, contains, notcontains, is, isnot

Logical operators:

and, or, xor, not

Discussion

PowerShell’s logical and comparison operators let you compare pieces of data, or test data for some condition. An operator either compares two pieces of data (a binary operator) or tests one piece of data (a unary operator). All comparison operators are binary operators (they compare two pieces of data), as are most of the logical operators. The only unary logical operator is the not operator, which returns the true/ false opposite of the data that it tests.

Comparison operators compare two pieces of data and return a result that depends on the specific comparison operator. For example, you might want to check whether a collection has at least a certain number of elements:

PS >(dir).Count ge 4 True

or, check whether a string matches a given regular expression:

PS >"Hello World" match "H.*World" True

Most comparison operators also adapt to the type of their input. For example, when you apply them to simple data such as a string, the like and match comparison operators determine whether the string matches the specified pattern. When you apply them to a collection of simple data, those same comparison operators return all elements in that collection that match the pattern you provide.

The match operator takes a regular expression as its argument. One of the more common regular expression symbols is the $ character, which represents the end of line. The $ character also represents the

start of a PowerShell variable, though! To prevent PowerShell from interpreting characters as language terms or escape sequences, place the string in single quotes rather than double quotes:

PS >"Hello World" match "Hello" True PS >"Hello World" match 'Hello$' False

Logical operators combine true or false statements and return a result that depends on the specific logical operator. For example, you might want to check whether a string matches the wildcard pattern you supply, and that it is longer than a certain number of characters:

PS >$data = "Hello World" PS >($data like "*llo W*") and ($data.Length gt 10) True PS >($data like "*llo W*") and ($data.Length gt 20) False

Some of the comparison operators actually incorporate aspects of the logical operators. Since using the opposite of a comparison (such as like) is so common, PowerShell provides comparison operators (such as notlike) that save you from having to use the not operator explicitly.

Comparison operators and logical operators (when combined with flow control statements) form the core of how we write a script or command that adapts to its data and input.

For more information about PowerShell’s operators, type GetHelp About_Operator.

Find Your Script’s Location in PowerShell

Problem

You want to know the location of the currently running script.

Solution

To determine the location of the currently executing script, use this function:

function GetScriptPath

{

SplitPath $myInvocation.ScriptName

}

Discussion

Once we know the full path to a script, the SplitPath cmdlet makes it easy to determine its location. Its sibling, the JoinPath cmdlet, makes it easy to form new paths from their components as well.

By accessing the $myInvocation.ScriptName variable in a function, we drastically simplify the logic it takes to determine the location of the currently running script.

Write to an Event Log in Windows PowerShell

Problem

You want to add an entry to an event log.

Solution

To write to an event log, use the –List parameter on the GetEventLog cmdlet to retrieve the proper event log. Then, set its source to a registered event log source and call its WriteEntry() method:

PS >$log = GetEventLog List | WhereObject { $_.Log eq "ScriptEvents" } PS >$log.Source = "PowerShellCookbook" PS >$log.WriteEntry("This is a message from my script.") PS > PS >GetEventLog ScriptEvents Newest 1 | Select Source,Message

Source
Message

PowerShellCookbook
This is a message from my script.

Discussion

As the solution mentions, you must set the event log’s Source property to a registered event log source before you can write information to the log. If you have not already registered an event log source on the system.

Program: Interactively Filter Lists of Objects in Windows PowerShell

There are times when the WhereObject cmdlet is too powerful. In those situations, the CompareProperty script provides a much simpler alternative. There are also times when the WhereObject cmdlet is too simple—when expressing your selection logic as code is more cumbersome than selecting it manually. In those situations, an interactive filter can be much more effective.

yet in the book, so feel free to just consider it a neat script for now. To learn more about a part that you don’t yet understand, look it up in the table of contents or the index.

Example 24. SelectFilteredObject.ps1

############################################################################## ## ## SelectFilteredObject.ps1 ## ## Provides an interactive window to help you select complex sets of objects. ## To do this, it takes all the input from the pipeline, and presents it in a ## notepad window. Keep any lines that represent objects you want to pass ## down the pipeline, delete the rest, then save the file and exit notepad. ##

Example 24. SelectFilteredObject.ps1 (continued)

## The script then passes the original objects that you kept along the ## pipeline. ## ## Example: ## GetProcess | SelectFilteredObject | StopProcess WhatIf ## ##############################################################################

## PowerShell runs your "begin" script block before it passes you any of the ## items in the pipeline. begin {

## Create a temporary file $filename = [System.IO.Path]::GetTempFileName()

## Define a header in a "herestring" that explains how to interact with ## the file $header = @"

############################################################ ## Keep any lines that represent objects you want to pass ## down the pipeline, and delete the rest. ## ## Once you finish selecting objects, save this file and ## exit. ############################################################

"@

## Place the instructions into the file $header > $filename

## Initialize the variables that will hold our list of objects, and ## a counter to help us keep track of the objects coming down the ## pipeline $objectList = @() $counter = 0

}

## PowerShell runs your "process" script block for each item it passes down ## the pipeline. In this block, the "$_" variable represents the current ## pipeline object process {

## Add a line to the file, using PowerShell's format (f) operator. ## When provided the ouput of GetProcess, for example, these lines look ## like: ## 30: System.Diagnostics.Process (powershell) "{0}: {1}" f $counter,$_.ToString() >> $filename

## Add the object to the list of objects, and increment our counter. $objectList += $_ $counter++

}

Example 24. SelectFilteredObject.ps1 (continued)

## PowerShell runs your "end" script block once it completes passing all ## objects down the pipeline. end {

## Start notepad, then call the process's WaitForExit() method to ## pause the script until the user exits notepad. $processStartInfo = NewObject System.Diagnostics.ProcessStartInfo "notepad" $processStartInfo.Arguments = $filename $process = [System.Diagnostics.Process]::Start($processStartInfo) $process.WaitForExit()

## Go over each line of the file foreach($line in (GetContent $filename)) {

## Check if the line is of the special format: numbers, followed by ## a colon, followed by extra text. if($line match "^(\d+?):.*") {

## If it did match the format, then $matches[1] represents the ## number a counter into the list of objects we saved during ## the "process" section. ## So, we output that object from our list of saved objects. $objectList[$matches[1]]

} }

## Finally, clean up the temporary file. RemoveItem $filename }

Write Culture-Aware Scripts in PowerShell

Problem

You want to ensure that your script works well on computers from around the world.

Solution

To write cultureaware scripts, keep the following guidelines in mind as you develop your scripts:

  • Create dates, times, and numbers using PowerShell’s language primitives.
  • Compare strings using PowerShell’s builtin operators.
  • Avoid treating user input as a collection of characters.
  • Use Parse() methods to convert user input to dates, times, and numbers.

Discussion

Writing cultureaware programs has long been isolated to the world of professional software developers. It’s not that users of simple programs and scripts can’t benefit from culture awareness though. It has just frequently been too difficult for nonprofessional programmers to follow the best practices. PowerShell makes this much easier than traditional programming languages however.

As your script travels between different cultures, several things change.

Date, time, and number formats

Most cultures have unique date, time, and number formats. To ensure that your script works in all cultures, PowerShell first ensures that its language primitives remain consistent no matter where your script runs. Even if your script runs on a machine in France (which uses a comma for its decimal separator), you can always rely on the statement $myDouble = 3.5 to create a number halfway between three and four. Likewise, you can always count on the statement $christmas = [DateTime] "12/ 25/2007" to create a date that represents Christmas in 2007—even in cultures that write dates in the order of day, month, year.

Culturally aware programs always display dates, times, and numbers using the preferences of that culture. This doesn’t break scripts as they travel between cultures and is an important aspect of writing cultureaware scripts. PowerShell handles this for you, as it uses the current culture’s preferences whenever it displays data.

If your script asks the user for a date, time, or number, make sure that you respect the format of the user’s culture’s when you do so. To convert user input to a specific type of data, use that type’s Parse() method.

$userInput = ReadHost "Please enter a date" $enteredDate = [DateTime]::Parse($userInput)

So, to ensure that your script remains cultureaware with respect to dates, times, and number formats, simply use PowerShell’s language primitives when you define them in your script. When you read them from the user, use Parse() methods when you convert them from strings.

Complexity of user input and file content

English is a rare language in that its alphabet is so simple. This leads to all kinds of programming tricks that treat user input and file content as arrays of bytes or simple plaintext (ASCII) characters. In most international languages, these tricks fail. In fact, many international symbols take up two characters’ worth of data in the string that contains them.

PowerShell uses the standard Unicode format for all stringbased operations: reading input from the user, displaying output to the user, sending data through the pipeline, and working with files.

Although PowerShell fully supports Unicode, the powershell.exe commandline host does not output some characters correctly, due to limitations in the Windows console system. Graphical PowerShell hosts

(such as the several thirdparty PowerShell IDEs) are not affected by these limitations however.

If you use PowerShell’s standard features when working with user input, you do not have to worry about its complexity. If you want to work with individual characters or words in the input, though, you will need to take special precautions. The System. Globalization.StringInfo class lets you do this in a culturally aware way. For more information about working with the StringInfo class, see http://msdn2.microsoft. com/enus/library/7h9tk6x8(vs.71).aspx.

So, to ensure that your script remains culturally aware with respect to user input, simply use PowerShell’s support for string operations whenever possible.

Capitalization rules

Acommon requirement in scripts is to compare user input against some predefined text (such as a menu selection). You normally want this comparison to be case insensitive, so that “QUIT” and “qUiT” mean the same thing.

The most common way to accomplish this is to convert the user input to uppercase or lowercase:

## $text comes from the user, and contains the value "quit" if($text.ToUpper() eq "QUIT") { ... }

Unfortunately, explicitly changing the capitalization of strings fails in subtle ways when run in different cultures, as many cultures have different capitalization and comparison rules. For example, the Turkish language includes two types of the letter “I”: one with a dot, and one without. The uppercase version of the lowercase letter “i” corresponds to the version of the capital “I” with a dot, not the capital “I” used in QUIT. That example causes the above string comparison to fail on a Turkish system.

To compare some input against a hardcoded string in a caseinsensitive manner, the better solution is to use PowerShell’s –eq operator without changing any of the casing yourself. The –eq operator is caseinsensitive and cultureneutral by default:

PS >$text1 = "Hello" PS >$text2 = "HELLO" PS >$text1 –eq $text2 True

So, to ensure that your script remains culturally aware with respect to capitalization rules, simply use PowerShell’s caseinsensitive comparison operators whenever possible.

Sorting rules

Sorting rules frequently change between cultures.

PS >UseCulture enUS { "Apple","Æble" | SortObject } Æble Apple PS >UseCulture daDK { "Apple","Æble" | SortObject } Apple Æble

To ensure that your script remains culturally aware with respect to sorting rules, assume that output is sorted correctly after you sort it—but don’t depend on the actual order of sorted output.

Other guidelines

For other resources on these factors for writing culturally aware programs, see http:// msdn2.microsoft.com/enus/library/h6270d0z(vs.71).aspx and http://www.microsoft. com/globaldev/getwr/steps/wrguide.mspx .

Program: Get Registry Items from Remote Machines

Discussion

Although PowerShell does not directly let you access and manipulate the registry of a remote computer, it still supports this by working with the .NET Framework. The functionality exposed by the .NET Framework is a bit more developeroriented than we want, so we can instead use a script to make it easier to work with.

Example 186 lets you list child items in a remote registry key, much like you do on the local computer. In order for this script to succeed, the target computer must have the remote registry service enabled and running.

Example 186. GetRemoteRegistryChildItem.ps1

############################################################################## ## ## GetRemoteRegistryChildItem.ps1 ## ## Get the list of subkeys below a given key. ## ## ie: ## ## PS >GetRemoteRegistryChildItem LEEDESK HKLM:\Software ## ##############################################################################

param( $computer = $(throw "Please specify a computer name."), $path = $(throw "Please specify a registry path") )

## Validate and extract out the registry key if($path match "^HKLM:\\(.*)") {

$baseKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey(

"LocalMachine", $computer) } elseif($path match "^HKCU:\\(.*)") {

$baseKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey

("CurrentUser", $computer) } else {

WriteError ("Please specify a fullyqualified registry path " + "(i.e.: HKLM:\Software) of the registry key to open.") return }

## Open the key $key = $baseKey.OpenSubKey($matches[1])

## Retrieve all of its children foreach($subkeyName in $key.GetSubKeyNames()) {

## Open the subkey $subkey = $key.OpenSubKey($subkeyName)

## Add information so that PowerShell displays this key like regular ## registry key $returnObject = [PsObject] $subKey

Example 186. GetRemoteRegistryChildItem.ps1 (continued)

$returnObject | AddMember NoteProperty PsChildName $subkeyName $returnObject | AddMember NoteProperty Property $subkey.GetValueNames()

## Output the key $returnObject

## Close the child key $subkey.Close() }

## Close the key and base keys $key.Close() $baseKey.Close()

List and Start Tasks

Problem

You want to list all the tasks allowed in a monitoring object, and then invoke one.

Solution

To retrieve cmdlets allowed for the current monitoring object, use the GetTask cmdlet:

$task = GetTask | WhereObject { $_.DisplayName –match "TaskName" }

Then use the StartTask cmdlet to start the task: $task | StartTask

Discussion

The GetTask cmdlet retrieves tasks specific to the monitoring object that applies to the current path. To change the list of tasks the cmdlet returns, navigate to the directory that represents the monitoring object you want to manage.

For more information about the GetTask cmdlet, type GetHelp GetTask. For more information about the StartTask cmdlet, type GetHelp StartTask.

Measure the Duration of a Windows PowerShell Command Problem

You want to know how long a command takes to execute.

Solution

To measure the duration of a command, use the MeasureCommand cmdlet: PS >MeasureCommand { StartSleep Milliseconds 337 }

Days : 0 Hours : 0 Minutes : 0 Seconds : 0 Milliseconds : 339 Ticks : 3392297 TotalDays : 3.92626967592593E06 TotalHours : 9.42304722222222E05 TotalMinutes : 0.00565382833333333 TotalSeconds : 0.3392297 TotalMilliseconds : 339.2297

Discussion

In interactive use, it is common to want to measure the duration of a command. An example of this might be running a performance benchmark on an application you’ve developed. The MeasureCommand cmdlet makes this easy to do. Because the command generates rich objectbased output, you can use its output for many daterelated tasks.

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

Access Elements of an Array

Problem

You want to access the elements of an array.

Solution

To access a specific element of an array, use PowerShell’s array access mechanism:

PS >$myArray = 1,2,"Hello World" PS >$myArray[1] 2

To access a range of array elements, use array ranges and array slicing:

PS >$myArray = 1,2,"Hello World" PS >$myArray[1..2 + 0] 2 Hello World 1

Discussion

PowerShell’s array access mechanisms provide a convenient way to access either specific elements of an array or more complex combinations of elements in that array. In PowerShell (as with most other scripting and programming languages), the item at index 0 represents the first item in the array.

Although working with the elements of an array by their numerical index is helpful, you may find it useful to refer to them by something else—such as their name, or even a custom label. This type of array is known as an associative array (or hashtable).

Set the ACL of a File or Directory in PowerShell

Problem

You want to change the ACL of a file or directory.

Solution

To change the ACL of a file, use the SetAcl cmdlet. This example prevents the Guest account from accessing a file:

$acl = GetAcl example.txt

$arguments = "LEEDESK\Guest","FullControl","Deny"

$accessRule =

NewObject System.Security.AccessControl.FileSystemAccessRule $arguments

$acl.SetAccessRule($accessRule)

$acl | SetAcl example.txt

Discussion

The SetAcl cmdlet sets the security descriptor of an item. This cmdlet doesn’t work only against the filesystem, however. Any provider (for example, the Registry provider) that supports the concept of security descriptors also supports the SetAcl cmdlet.

The SetAcl cmdlet requires that you provide it with an ACL to apply to the item. While it is possible to construct the ACL from scratch, it is usually easiest to retrieve it from the item beforehand (as demonstrated in the solution). To retrieve the ACL, use the GetAcl cmdlet. Once you’ve modified the access control rules on the ACL, simply pipe them to the SetAcl cmdlet to make them permanent.

In the solution, the $arguments list that we provide to the FileSystemAccessRule constructor explicitly sets a Deny rule on the Guest account of the LEEDESK computer for FullControl permission. For more information about working with classes (such as the FileSystemAccessRule class) from the .NET Framework.

Although the SetAcl command is powerful, you may already be familiar with commandline tools that offer similar functionality (such as cacls.exe). Although these tools generally do not work on the registry (or other providers that support PowerShell security descriptors), you can of course continue to use these tools from PowerShell.

For more information about the SetAcl cmdlet, type GetHelp SetAcl. For more information about the GetAcl cmdlet.

Assign a Static IP Address in Windows PowerShell

Problem

You want to assign a static IP address to a computer.

Solution

Use the Win32_NetworkAdapterConfiguration WMI class to manage network settings for a computer:

$description = "Linksys WirelessG USB" $staticIp = "192.168.1.100" $subnetMask = "255.255.255.0" $gateway = "192.168.1.1"

$adapter = GetWmiObject Win32_NetworkAdapterConfiguration –Computer LEEDESK |

WhereObject { $_.Description –match $description}

$adapter.EnableStatic($staticIp, $subnetMask)

$adapter.SetGateways($gateway, [UInt16] 1)

Discussion

When managing network settings for a computer, the Win32_NetworkAdapter Configuration WMI class requires that you know the description of the network adapter. Obtain that by reviewing the output of GetWmiObject Win32_ NetworkAdapterConfiguration –Computer :

PS >GetWmiObject Win32_NetworkAdapterConfiguration –Computer LEEDESK

(...) DHCPEnabled : True IPAddress : {192.168.1.100} DefaultIPGateway : {192.168.1.1} DNSDomain : hsd1.wa.comcast.net. ServiceName : USB_RNDIS Description : Linksys WirelessG USB Network Adapter with (...) Index : 13 (...)

Knowing which adapter you want to renew, you can now call methods on that object as illustrated in the solution. To enable DHCP on an adapter again, use the EnableDHCP() method:

PS >$adapter.EnableDHCP()