Skip to main content

Windows

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()