Skip to main content

Windows

Write a Function in Windows PowerShell

Problem

You have commands in your script that you want to call multiple times, or a section of your script that you consider to be a “helper” for the main purpose of your script.

Solution

Place this common code in a function, and then call that function instead. For example, this Celsius conversion code in a script:

param([double] $fahrenheit)

## Convert it to Celsius $celsius = $fahrenheit 32 $celsius = $celsius / 1.8

## Output the answer "$fahrenheit degrees Fahrenheit is $celsius degrees Celsius."

could be placed in a function (itself in a script):

param([double] $fahrenheit)

## Convert Fahrenheit to Celsius function ConvertFahrenheitToCelsius([double] $fahrenheit) {

$celsius = $fahrenheit 32 $celsius = $celsius / 1.8 $celsius

}

$celsius = ConvertFahrenheitToCelsius $fahrenheit

## Output the answer "$fahrenheit degrees Fahrenheit is $celsius degrees Celsius."

Although using a function arguably makes this specific script longer and more difficult to understand, the technique is extremely valuable (and used) in almost all nontrivial scripts.

Discussion

Once you define a function, any command after that definition can use it. This means that you must define your function before any part of your script that uses it. You might find this unwieldy if your script defines many functions, as the function definitions obscure the main logic portion of your script.

Acommon question that comes from those accustomed to batch scripting in cmd.exe is, “What is the PowerShell equivalent of a GOTO?” In situations where the GOTO is used to call subroutines or other iso

lated helper parts of the batch file, use a PowerShell function to accomplish that task. If the GOTO is used as a way to loop over something, PowerShell’s looping mechanisms are more appropriate.

In PowerShell, calling a function is designed to feel just like calling a cmdlet or a script. As a user, you should not have to know whether a little helper routine was written as a cmdlet, script, or function. When you call a function, simply add the parameters after the function name, with spaces separating each one (as shown in the solution). This is in contrast to the way that you call functions in many programming languages (such as C#), where you use parentheses after the function name and commas between each parameter.

Also, notice that the return value from a function is anything that it writes to the output pipeline (such as $celsius in the solution). You can write return $celsius if you want, but it is unnecessary.

Manage Files That Include Special Characters in PowerShell

Problem

You want to use a cmdlet that supports wildcarding but provide a filename that includes wildcard characters.

Solution

To prevent PowerShell from treating those characters as wildcard characters, use the cmdlet’s –LiteralPath (or similarly named) parameter if it defines one: GetChildItem LiteralPath '[My File].txt'

Discussion

One consequence of PowerShell’s advanced wildcard support is that the square brackets used to specify character ranges sometimes conflict with actual filenames. Consider the following example:

PS >GetChildItem | SelectObject Name

Name

[My File].txt

PS >GetChildItem '[My File].txt' | SelectObject Name PS >GetChildItem LiteralPath '[My File].txt' | SelectObject Name

Name

[My File].txt

The first command clearly demonstrates that we have a file called [My File].txt. When we try to retrieve it (passing its name to the GetChildItem cmdlet), we see no results. Since square brackets are wildcard characters in PowerShell (like * and ?), the text we provided turns into a search expression rather than a filename.

The –LiteralPath parameter (or a similarly named parameter in other cmdlets) tells PowerShell that the filename is named exactly—not a wildcard search term.

In addition to wildcard matching, filenames may sometimes run afoul of another topic—PowerShell escape sequences. For example, the backtick character (`)in PowerShell means the start of an escape sequence, such as `t (tab), `n (newline), or `a (alarm). To prevent PowerShell from interpreting a backtick as an escape sequence, surround that string in single quotes instead of double quotes.

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

For more information about PowerShell’s special characters, type GetHelp About_ Special_Characters.

Open or Close Ports in the Windows Firewall

Problem

You want to open or close ports in the Windows Firewall.

Solution

To open or close ports in the Windows Firewall, use the LocalPolicy. CurrentProfile.GloballyOpenPorts collection of the HNetCfg.FwMgr COM object.

To add a port, create a HNetCfg.FWOpenPort COM object to represent the port, and then add it to the GloballyOpenPorts collection:

$PROTOCOL_TCP = 6

$firewall = NewObject com HNetCfg.FwMgr

$port = NewObject com HNetCfg.FWOpenPort

$port.Name = "Webserver at 8080"

$port.Port = 8080

$port.Protocol = $PROTOCOL_TCP

$firewall.LocalPolicy.CurrentProfile.GloballyOpenPorts.Add($port) To close a port, remove it from the GloballyOpenPorts collection:

$PROTOCOL_TCP = 6

$firewall.LocalPolicy.CurrentProfile.GloballyOpenPorts.Remove(8080, $PROTOCOL_TCP)

Discussion

The HNetCfg.FwMgr COM object provides programmatic access to the Windows Firewall in Windows XP SP2 and later. The LocalPolicy.CurrentProfile property provides the majority of its functionality.

For more information about managing the Windows Firewall through its COM API, visit http://msdn.microsoft.com and search for “Using Windows Firewall API.” The documentation provides examples in VBScript but gives a useful overview of the functionality available.

If you are unfamiliar with the VBScriptspecific portions of the documentation, the Microsoft Script Center provides a useful guide to help you convert from VBScript to PowerShell. You can find that document at http://www.microsoft.com/technet/ scriptcenter/topics/winpsh/convert/default.mspx.

How to Parse and Manage Text-Based Logfiles in Windows PowerShell

Problem

You want to parse and analyze a textbased logfile using PowerShell’s standard object management commands.

Solution

Use the ConvertTextObject script to work with textbased logfiles. With your assistance, it con verts steams of text into streams of objects, which you can then easily work with using PowerShell’s standard commands.

The ConvertTextObject script primarily takes two arguments:

  1. A regular expression that describes how to break the incoming text into groups
  2. A list of property names that the script then assigns to those text groups

As an example, you can use patch logs from the Windows directory. These logs track the patch installation details from updates applied to the machine (except for Windows Vista). One detail included in these logfiles are the names and versions of the files modified by that specific patch, as shown in Example 71.

Example 71. Getting a list of files modified by hotfixes

PS >cd $env:WINDIR PS >$parseExpression = "(.*): Destination:(.*) \((.*)\)" PS >$files = dir kb*.log Exclude *uninst.log PS >$logContent = $files | GetContent | SelectString $parseExpression PS >$logContent

(...)

  1. Destination:C:\WINNT\system32\shell32.dll (6.0.3790.205)
  2. Destination:C:\WINNT\system32\wininet.dll (6.0.3790.218)
  3. Destination:C:\WINNT\system32\urlmon.dll (6.0.3790.218)
  4. Destination:C:\WINNT\system32\shlwapi.dll (6.0.3790.212)
  5. Destination:C:\WINNT\system32\shdocvw.dll (6.0.3790.214)
  6. Destination:C:\WINNT\system32\digest.dll (6.0.3790.0)
  7. Destination:C:\WINNT\system32\browseui.dll (6.0.3790.218) (...)

Like most logfiles, the format of the text is very regular but hard to manage. In this example, you have:

A number (the number of seconds since the patch started) The text, “: Destination:” The file being patched An open parenthesis The version of the file being patched A close parenthesis

You don’t care about any of the text, but the time, file, and file version are useful properties to track:

$properties = "Time","File","FileVersion" So now, you use the ConvertTextObject script to convert the text output into a stream of objects:

PS >$logObjects = $logContent | >> ConvertTextObject ParseExpression $parseExpression PropertyName $properties >>

We can now easily query those objects using PowerShell’s builtin commands. For example, you can find the files most commonly affected by patches and service packs, as shown by Example 72.

Example 72. Finding files most commonly affected by hotfixes

PS >$logObjects | GroupObject file | SortObject Descending Count | >> SelectObject Count,Name | FormatTable Auto >>

Count Name

152 C:\WINNT\system32\shdocvw.dll 147 C:\WINNT\system32\shlwapi.dll

Example 72. Finding files most commonly affected by hotfixes (continued)

128 C:\WINNT\system32\wininet.dll

116 C:\WINNT\system32\shell32.dll

92 C:\WINNT\system32\rpcss.dll

92 C:\WINNT\system32\olecli32.dll

92 C:\WINNT\system32\ole32.dll

84 C:\WINNT\system32\urlmon.dll (...)

Using this technique, you can work with most textbased logfiles.

Discussion

In Example 72, you got all the information you needed by splitting the input text into groups of simple strings. The time offset, file, and version information served their purposes as is. In addition to the features used by Example 72, however, the ConvertTextObject script also supports a parameter that lets you control the data types of those properties. If one of the properties should be treated as a number or a DateTime, you may get incorrect results if you work with that property as a string. For more information about this functionality, see the description of the –PropertyType parameter in the ConvertTextObject script.

Although most logfiles have entries designed to fit within a single line, some span multiple lines. When a logfile contains entries that span multiple lines, it includes some sort of special marker to separate log entries from each other. Take, for example:

PS >GetContent AddressBook.txt Name: Chrissy Phone: 5551212

Name: John

Phone: 5551213

The key to working with this type of logfile comes from two places. The first is the –Delimiter parameter of the GetContent cmdlet, which makes it split the file based on that delimiter instead of newlines. The second is to write a ParseExpression Regular Expression that ignores the newline characters that remain in each record.

PS >$records = gc AddressBook.txt Delimiter "" PS >$parseExpression = "(?s)Name: (\S*).*Phone: (\S*).*" PS >$records | ConvertTextObject ParseExpression $parseExpression

Property1 Property2

Chrissy 5551212

John 5551213 The parse expression in this example uses the single line option (?s) so that the (.*) portion of the regular expression accepts newline characters as well.

For extremely large logfiles, handwritten parsing tools may not meet your needs. In those situations, specialized log management tools can prove helpful. One example is Microsoft’s free Log Parser (http://www.logparser.com ). Another common alternative is to import the log entries to a SQL database, and then perform ad hoc queries on database tables, instead.

Sign a PowerShell Script or Formatting File

Problem

You want to sign a PowerShell script so that it may be run on systems that have their execution policy set to require signed scripts.

Solution

To sign the script with your standard codesigning certificate, use the SetAuthenticodeSignature cmdlet:

$cert = @(GetChildItem cert:\CurrentUser\My CodeSigning)[0] SetAuthenticodeSignature file.ps1 $cert

Alternatively, you may also use other traditional applications (such as signtool.exe) to sign PowerShell .ps1 and .ps1xml files.

Discussion

Signing a script or formatting file provides you and your customers with two primary benefits: publisher identification and file integrity. When you sign a script or formatting file, PowerShell appends your digital signature to the end of that file. This signature verifies that the file came from you and also ensures that nobody can tamper with the content in the file without detection. If you try to load a file that has been tampered with, PowerShell provides the following error message:

File C:\temp\test.ps1 cannot be loaded. The contents of file C:\temp\test.ps1 may have been tampered because the hash of the file does not match the hash stored in the digital signature. The script will not execute on the system. Please see "gethelp about_signing" for more details.. At line:1 char:10

+ .\test.ps1

When it comes to the signing of scripts and formatting files, PowerShell participates in the standard Windows Authenticode infrastructure. Because of that, techniques you may already know for signing files and working with their signatures continue to work with PowerShell scripts and formatting files. While the SetAuthenticodeSignature cmdlet is primarily designed to support scripts and formatting files, it also supports DLLs and other standard Windows executable file types.

To sign a file, the SetAuthenticodeSignature cmdlet requires that you provide it with a valid codesigning certificate. Most certification authorities provide Authenticode codesigning certificates for a fee. By using an Authenticode codesigning certificate from a reputable certification authority (such as VeriSign or Thawte), you can be sure that all users will be able to verify the signature on your script. Some online services offer extremely cheap codesigning certificates, but be aware that many machines may be unable to verify the digital signatures created by those certificates.

You can still gain many of the benefits of code signing on your own computers by generating your own codesigning certificate. While other computers will not be able to recognize the signature, it still provides tamperprotection on your own computer.

The –TimeStampServer parameter lets you sign your script or formatting file in a way that makes the signature on your script or formatting file valid even after your codesigning certificate expires.

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

Modify Properties of a Windows PowerShell User Account

Problem

You want to modify properties of a specific user account.

Solution

To modify a user account, use the [adsi] type shortcut to bind to the user in Active Directory, and then call the Put() method to modify properties. Finally, call the SetInfo() method to apply the changes.

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

$user.Put("Title", "Sr. Exec. Overlord") $user.SetInfo()

Discussion

The solution retrieves the MyerKen user from the Sales West OU. It then sets the user’s title to Sr. Exec. Overlord and applies those changes to Active Directory.

Place Special Characters in a String in PowerShell

Problem

You want to place special characters (such as tab and newline) in a string variable.

Solution

In an expanding string, use PowerShell’s escape sequences to include special characters such as tab and newline.

PS >$myString = "Report for Today`n" PS >$myString Report for Today

Discussion

Aliteral string uses single quotes around its text, while an expanding string uses double quotes around its 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 $ENV: SystemRoot) and escape sequences (such as `n) with their values (such as the SystemRoot environment variable and the newline character).

Unlike many languages that use a backslash character (\) for escape sequences, PowerShell uses a backtick (`) character. This stems from its focus on system administration, where backslashes are ubiquitous

in path names.

Insert Dynamic Information in a String in Windows PowerShell

Problem

You want to place dynamic information (such as the value of another variable) in a string.

Solution

In an expanding string, include the name of a variable in the string to insert the value of that variable.

PS >$header = "Report for Today" PS >$myString = "$header`n" PS >$myString Report for Today

To include information more complex than just the value of a variable, enclose it in a subexpression:

PS >$header = "Report for Today" PS >$myString = "$header`n$('' * $header.Length)" PS >$myString Report for Today

Discussion

Variable substitution in an expanding string is a simple enough concept, but subexpressions deserve a little clarification.

A subexpression is the dollar sign character, followed by a PowerShell command (or set of commands) contained in parentheses:

$(subexpression)

When PowerShell sees a subexpression in an expanding string, it evaluates the subexpression and places the result in the expanding string. In the solution, the expression '' * $header.Length tells PowerShell to make a line of dashes $header.Length long.

Another way to place dynamic information inside a string is to use PowerShell’s string formatting operator, which is based on the rules of the .NET string formatting:

PS >$header = "Report for Today" PS >$myString = "{0}`n{1}" f $header,('' * $header.Length)

PS >$myString Report for Today

For more information about PowerShell’s escape characters, type GetHelp About_Escape_Character or type GetHelp About_Special_ Character.

Interact with PowerShell’s Global Environment

Problem

You want to store information in the PowerShell environment so that other scripts have access to it.

Solution

To make a variable available to the entire PowerShell session, use a $GLOBAL: prefix when you store information in that variable:

## Create the web service cache, if it doesn't already exist

if(not (TestPath Variable:\Lee.Holmes.WebServiceCache))

{

${GLOBAL:Lee.Holmes.WebServiceCache} = @{}

}

If the main purpose of your script is to provide permanent functions and variables for its caller, treat that script as a library and have the caller dotsource the script:

PS >. LibraryDirectory PS >GetDirectorySize Directory size: 53,420 bytes

Discussion

The primary guidance when it comes to storing information in the session’s global environment to avoid it when possible. Scripts that store information in the global scope are prone to breaking other scripts and prone to being broken by other scripts.

It is a common practice in batch file programming, but script parameters and return values usually provide a much cleaner alternative.

If you do find yourself needing to write variables to the global scope, make sure that you create them with a name unique enough to prevent collisions with other scripts, as illustrated in the solution. Good options for naming prefixes are the script name, author’s name, or company name.

Stop a Windows PowerShell Process

Problem

You want to stop (or kill) a process on the system.

Solution

To stop a process, use the StopProcess cmdlet, as shown in Example 212.

Example 212. Stopping a process using the StopProcess cmdlet

PS >notepad PS >GetProcess Notepad

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

42 3 1276 3916 32 0.09 3520 notepad

PS >StopProcess ProcessName notepad PS >GetProcess Notepad

Example 212. Stopping a process using the StopProcess cmdlet (continued)

GetProcess : Cannot find a process with the name 'Notepad'. Verify the process name and call the cmdlet again. At line:1 char:12

+ GetProcess Notepad

Discussion

While the parameters of the StopProcess cmdlet are useful in their own right, PowerShell’s pipeline model lets you be even more precise. The StopProcess cmdlet stops any processes that you pipeline into it, so an advanced process set generated by GetProcess automatically turns into an advanced process set for the StopProcess cmdlet to operate on:

PS >GetProcess | WhereObject { $_.WorkingSet lt 10mb } | >> SortObject Descending Name | StopProcess WhatIf >> What if: Performing operation "StopProcess" on Target "svchost (1368)". What if: Performing operation "StopProcess" on Target "sqlwriter (1772)". What if: Performing operation "StopProcess" on Target "qttask (3672)". What if: Performing operation "StopProcess" on Target "Ditto (2892)". What if: Performing operation "StopProcess" on Target "ctfmon (3904)". What if: Performing operation "StopProcess" on Target "csrss (848)". What if: Performing operation "StopProcess" on Target "BrmfRsmg (1560)". What if: Performing operation "StopProcess" on Target "AutoHotkey (3460)". What if: Performing operation "StopProcess" on Target "alg (1084)".

Notice that this example uses the –WhatIf flag on the StopProcess cmdlet. This flag lets you see what would happen if you were to run the command but doesn’t actually perform the action.

For more information about the StopProcess cmdlet, type GetHelp StopProcess. For more information about the WhereObject cmdlet, type GetHelp WhereObject.

Access Environment Variables in Windows PowerShell

Problem

You want to use an environment variable (such as the system path, or current user’s name) in your script or interactive session.

Solution

PowerShell offers several ways to access environment variables. To list all environment variables, list the children of the env drive:

GetChildItem env: To get an environment variable using a more concise syntax, precede its name with

$env:

$env:variablename

i.e.: $env:username

To get an environment variable using its Provider path, supply env: or Environment:: to the GetChildItem cmdlet:

GetChildItem env:variablename

GetChildItem Environment::variablename

Discussion

PowerShell provides access to environment variables through its environment provider. Providers let you work with data stores (such as the registry, environment variables, and aliases) much as you would access the filesystem.

By default, PowerShell creates a drive (called env) that works with the environment provider to let you access environment variables. The environment provider lets you access items in the env: drive as you would any other drive: dir env:\variablename or dir env:variablename. If you want to access the provider directly (rather than go through its drive), you can also type dir Environment::variablename.

However, the most common (and easiest) way to work with environment variables is by typing $env:variablename. This works with any provider but is most typically used with environment variables.

This is because the environment provider shares something in common with several other providers—namely support for the *Content set of core cmdlets

Example 31. Working with content on different providers

PS >"hello world" > test PS >GetContent c:test hello world PS >GetContent variable:ErrorActionPreference Continue PS >GetContent function:more param([string[]]$paths); if(($paths ne $null) and ($paths.length ne 0)) { ...

GetContent $local:file | OutHost p } } else { $input | OutHost ... PS >GetContent env:systemroot C:\WINDOWS

For providers that support the content cmdlets, PowerShell lets you interact with this content through a special variable syntax

Example 32. Using PowerShell’s special variable syntax to access content

PS >$function:more param([string[]]$paths); if(($paths ne $null) and ($paths.length ne 0)) { …

GetContent $local:file | OutHost p } } else { $input | OutHost … PS >$variable:ErrorActionPreference Continue PS >$c:test hello world PS >$env:systemroot C:\WINDOWS

This variable syntax for content management lets you to both get and set content:

PS >$function:more = { $input | less.exe } PS >$function:more $input | less.exe

Now, when it comes to accessing complex provider paths using this method, you’ll quickly run into naming issues (even if the underlying file exists):

PS >$c:\temp\test.txt Unexpected token '\temp\test.txt' in expression or statement. At line:1 char:17

+ $c:\temp\test.txt

The solution to that lies in PowerShell’s escaping support for complex variable names. To define a complex variable name, enclose it in braces:

PS >${1234123!@#$!@#$12$!@#$@!} = "Crazy Variable!" PS >${1234123!@#$!@#$12$!@#$@!} Crazy Variable! PS >dir variable:\1*

Name
Value

1234123!@#$!@#$12$!@#$@!
Crazy Variable!

… and the content equivalent (assuming that the file exists):

PS >${c:\temp\test.txt} hello world Since environment variable names do not contain special characters, this GetContent variable syntax is the best (and easiest) way to access environment variables.