Skip to main content

Windows

Get Windows PowerShell Help on a Command

Problem

You want to learn about how a specific command works and how to use it.

Solution

The command that provides help and usage information about a command is called GetHelp. It supports several different views of the help information, depending on your needs.

To get the summary of help information for a specific command, provide the command’s name as an argument to the GetHelp cmdlet. This primarily includes its synopsis, syntax, and detailed description:

GetHelp CommandName

or

CommandName ? To get the detailed help information for a specific command, supply the –Detailed flag to the GetHelp cmdlet. In addition to the summary view, this also includes its parameter descriptions and examples:

GetHelp CommandName Detailed To get the full help information for a specific command, supply the –Full flag to the GetHelp cmdlet. In addition to the detailed view, this also includes its full parameter descriptions and additional notes:

GetHelp CommandName Full To get only the examples for a specific command, supply the –Examples flag to the GetHelp cmdlet: GetHelp CommandName Examples

Discussion

The GetHelp cmdlet is the primary way to interact with the help system in PowerShell. Like the GetCommand cmdlet, the GetHelp cmdlet supports wildcards. If you want to list all commands that match a certain pattern (for example, *process*), you can simply type GetHelp *process*.

To generate a list of all cmdlets along with a brief synopsis, run the following command:

GetHelp * | SelectObject Name,Synopsis | FormatTable Auto

If the pattern matches only a single command, PowerShell displays the help for that command.

The GetHelp cmdlet is one of the three commands you will use most commonly as you explore Windows PowerShell. The other two commands are GetCommand and GetMember.

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

Access Arguments of a Script, Function, or Script Block in Windows PowerShell

Problem

You want to access the arguments provided to a script, function, or script block.

Solution

To access arguments by name, use a param statement:

param($firstNamedArgument, [int] $secondNamedArgument = 0)

"First named argument is: $firstNamedArgument" "Second named argument is: $secondNamedArgument"

To access unnamed arguments by position, use the $args array:

"First positional argument is: " + $args[0] "Second positional argument is: " + $args[1]

You can use these techniques in exactly the same way with scripts, functions, and script blocks, as illustrated by Example 106.

Example 106. Working with arguments in scripts, functions, and script blocks

############################################################################## ## GetArguments.ps1 ## ## Use commandline arguments ############################################################################## param($firstNamedArgument, [int] $secondNamedArgument = 0)

## Display the arguments by name "First named argument is: $firstNamedArgument" "Second named argument is: $secondNamedArgument"

function GetArgumentsFunction { ## We could use a param statement here, as well ## param($firstNamedArgument, [int] $secondNamedArgument = 0)

## Display the arguments by position "First positional function argument is: " + $args[0] "Second positional function argument is: " + $args[1] }

GetArgumentsFunction One Two

$scriptBlock = { param($firstNamedArgument, [int] $secondNamedArgument = 0)

## We could use $args here, as well "First named scriptblock argument is: $firstNamedArgument" "Second named scriptblock argument is: $secondNamedArgument"

}

& $scriptBlock First One Second 4.5

Example 106 produces the following output:

PS >GetArguments First 2 First named argument is: First Second named argument is: 2 First positional function argument is: One Second positional function argument is: Two First named scriptblock argument is: One Second named scriptblock argument is: 4

Discussion

Although PowerShell supports both the param keyword and the $args array, you will most commonly want to use the param keyword to define and access script, function, and script block parameters.

In most languages, the most common reason to access parameters through an $argsstyle array is to determine the name of the currently running script. For information about how to do this in PowerShell.

When you use the param keyword to define your parameters, PowerShell provides your script or function with many useful features that allow users to work with your script much like they work with cmdlets:

  • Users need only to specify enough of the parameter name to disambiguate it from other parameters.
  • Users can understand the meaning of your parameters much more clearly.
  • You can specify the type of your parameters, which PowerShell uses to convert input if required.
  • You can specify default values for your parameters.

The $args array is sometimes helpful, however, as a way to deal with all arguments at once. For example:

function Reverse

{ $argsEnd = $args.Length 1 $args[$argsEnd..0]

}

produces

PS >Reverse 1 2 3 4 4 3 2 1

Program: Get the MD5 or SHA1 Hash of a File

Discussion

File hashes provide a useful way to check for damage or modification to a file. Adigital hash acts like the fingerprint of a file and detects even minor modifications. If the content of a file changes, then so does its hash. Many online download services provide the hash of a file on that file’s download page so that you can determine whether the transfer somehow corrupts the file.

There are three common ways to generate the hash of a file: MD5, SHA1, SHA256. The two most common are MD5, followed by SHA1. While popular, these hash types can be trusted to detect only accidental file modification. They can be fooled if somebody wants to tamper with the file without changing its hash. The SHA256 algorithm can be used to protect against even intentional file tampering.

Example 173 lets you determine the hash of a file (or of multiple files if provided by the pipeline).

Example 173. GetFileHash.ps1

############################################################################## ## ## GetFileHash.ps1 ## ## Get the hash of an input file. ## ## ie: ## ## PS >GetFileHash myFile.txt ## PS >dir | GetFileHash ## PS >GetFileHash myFile.txt Hash SHA1 ## ##############################################################################

param( $path, $hashAlgorithm = "MD5" )

## Create the hash object that calculates the hash of our file. If they ## provide an invalid hash algorithm, provide an error message. if($hashAlgorithm eq "MD5") {

$hasher = [System.Security.Cryptography.MD5]::Create() } elseif($hashAlgorithm eq "SHA1") {

$hasher = [System.Security.Cryptography.SHA1]::Create() } elseif($hashAlgorithm eq "SHA256") {

$hasher = [System.Security.Cryptography.SHA256]::Create() } else {

$errorMessage = "Hash algorithm $hashAlgorithm is not valid. Valid " +

"algorithms are MD5, SHA1, and SHA256." WriteError $errorMessage return

}

Example 173. GetFileHash.ps1 (continued)

## Create an array to hold the list of files $files = @()

## If they specified the file name as a parameter, add that to the list ## of files to process if($path) {

$files += $path } ## Otherwise, take the files that they piped in to the script. ## For each input file, put its full name into the file list else {

$files += @($input | ForeachObject { $_.FullName }) }

## Go through each of the items in the list of input files foreach($file in $files) {

## Convert it to a fullyqualified path $filename = (ResolvePath $file ErrorAction SilentlyContinue).Path

## If the path does not exist (or is not a file,) just continue if((not $filename) or (not (TestPath $filename Type Leaf))) {

continue }

## Use the ComputeHash method from the hash object to calculate ## the hash $inputStream = NewObject IO.StreamReader $filename $hashBytes = $hasher.ComputeHash($inputStream.BaseStream) $inputStream.Close()

## Convert the result to hexadecimal $builder = NewObject System.Text.StringBuilder $hashBytes | ForeachObject { [void] $builder.Append($_.ToString("X2")) }

## Return a custom object with the important details from the ## hashing $output = NewObject PsObject $output | AddMember NoteProperty Path ([IO.Path]::GetFileName($file)) $output | AddMember NoteProperty HashAlgorithm $hashAlgorithm $output | AddMember NoteProperty HashValue ([string] $builder.ToString()) $output

}

Retrieve Printer Information

Problem

You want to get information about printers on the current system.

Solution

To retrieve information about printers attached to the system, use the Win32_Printer WMI class:

PS >GetWmiObject Win32_Printer | SelectObject Name,PrinterStatus

Name
PrinterStatus

Microsoft Office Document Image Wr...
3

Microsoft Office Document Image Wr...
3

CutePDF Writer
3

Brother DCP1000
3

To retrieve information about a specific printer, apply a filter based on its name:

PS >$device = GetWmiObject Win32_Printer Filter "Name='Brother DCP1000'" PS >$device | FormatList *

Status : Unknown Name : Brother DCP1000 Attributes : 588 Availability : AvailableJobSheets : AveragePagesPerMinute : 0 Capabilities : {4, 2, 5} CapabilityDescriptions : {Copies, Color, Collate} Caption : Brother DCP1000 (...)

To retrieve specific properties, access as you would access properties on other PowerShell objects:

PS >$device.VerticalResolution 600 PS >$device.HorizontalResolution 600

Discussion

The example in the solution uses the Win32_Printer WMI class to retrieve information about installed printers on the computer. While the Win32_Printer class gives access to most commonly used information, WMI supports several other printerrelated classes: Win32_TCPIPPrinterPort, Win32_PrinterDriver, CIM_Printer, Win32_PrinterConfiguration, Win32_PrinterSetting, Win32_PrinterController, Win32_PrinterShare, and Win32_PrinterDriverDll.

Structured Files in Windows PowerShell

In the world of textonly system administration, managing structured files is often a pain. For example, working with (or editing) an XML file means either loading it into an editor to modify by hand, or writing a custom tool that can do that for you. Even worse, it may mean modifying the file as though it were plain text while hoping to not break the structure of the XML itself.

In that same world, working with a file in CSV format means going through the file yourself, splitting each line by the commas in it. It’s a seemingly great approach, until you find yourself faced with anything but the simplest of data.

Structure and structured files don’t come only from other programs, either. When writing scripts, one common goal is to save structured data so that you can use it later. In most scripting (and programming) languages, this requires that you design a data structure to hold that data, design a way to store and retrieve it from disk, and bring it back to a usable form when you want to work with it again.

Fortunately, working with XML, CSVs, and even your own structured files becomes much easier with PowerShell at your side.

Securely Handle Sensitive Information in Windows PowerShell

Problem

You want to request sensitive information from the user, but want to do this as securely as possible.

Solution

To securely handle sensitive information, store it in a SecureString whenever possible. The ReadHost cmdlet (with the –AsSecureString parameter) lets you prompt the user for (and handle) sensitive information by returning the user’s response as a SecureString:

PS >$secureInput = ReadHost AsSecureString "Enter your private key" Enter your private key: ******************* PS >$secureInput System.Security.SecureString

Discussion

When you use any string in the .NET Framework (and therefore PowerShell), it retains that string so that it can efficiently reuse it later. Unlike most .NET data, unused strings persist even after you finish using them. When this data is in memory, there is always the chance that it could get captured in a crash dump, or swapped to disk in a paging operation. Because some data (such as passwords and other confidential information) may be sensitive, the .NET Framework includes the SecureString class—a container for text data that the framework encrypts when it stores it in memory. Code that needs to interact with the plaintext data inside a SecureString does so as securely as possible.

When a cmdlet author asks you for sensitive data (for example, an encryption key), the best practice is to designate that parameter as a SecureString to help keep your information confidential. You can provide the parameter with a SecureString variable as input, or the host prompts you for the SecureString if you do not provide one. PowerShell also supports two cmdlets (ConvertToSecureString and ConvertFromSecureString) that allow you to securely persist this data to disk.

Credentials are a common source of sensitive information.

By default, the SecureString cmdlets use Windows’ data protection API when they convert your SecureString to and from its text representation. The key it uses to encrypt your data is based on your Windows logon credentials, so only you can decrypt the data that you’ve encrypted. If you want the exported data to work on another system or separate user account, you can use the cmdlet options that let you provide an explicit key. PowerShell treats this sensitive data as an opaque blob—and so should you.

However, there are many instances when you may want to automatically provide the SecureString input to a cmdlet rather than have the host prompt you for it. In these situations, the ideal solution is to use the ConvertToSecureString cmdlet to import a previously exported SecureString from disk. This retains the confidentiality of your data and still lets you automate the input.

If the data is highly dynamic (for example, coming from a CSV), then the ConvertToSecureString cmdlet supports an –AsPlainText parameter:

$secureString = ConvertToSecureString "Kinda Secret" AsPlainText –Force

Since you’ve already provided plaintext input in this case, placing this data in a SecureString no longer provides a security benefit. To prevent a false sense of security, the cmdlet requires the Force parameter to convert plaintext data into a SecureString.

Once you have data in a SecureString, you may want to access its plaintext representation. PowerShell doest’t provide a direct way to do this, as that defeats the purpose of a SecureString. If you still want to convert a SecureString to plain text, you have two options:

1. Use the GetNetworkCredential() method of the PsCredential class

$secureString = ReadHost AsSecureString $temporaryCredential = NewObject ` System.Management.Automation.PsCredential "TempUser",$secureString $unsecureString = $temporaryCredential.GetNetworkCredential().Password

2. Use the .NET Framework’s Marshal class

$secureString = ReadHost AsSecureString $unsecureString = [Runtime.InteropServices.Marshal]::PtrToStringAuto( [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureString))

Modify PowerShell Properties of a Security or Distribution Group

Problem

You want to modify properties of a specific security or distribution group.

Solution

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

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

PS >$group.Put("Description", "Managers in the Sales West Organization") PS >$group.SetInfo()

Discussion

The solution retrieves the Management group from the Sales West OU. It then sets the description to Managers in the Sales West Organization, and then applies those changes to Active Directory.

Replace Text in a String in Windows PowerShell

Problem

You want to replace a portion of a string with another string.

Solution

PowerShell provides several options to help you replace text in a string with other text.

Use the Replace() method on the string itself to perform simple replacements:

PS >"Hello World".Replace("World", "PowerShell")

Hello PowerShell Use PowerShell’s regular expression –replace operator to perform more advanced regular expression replacements:

PS >"Hello World" replace '(.*) (.*)','$2 $1' World Hello

Discussion

The Replace() method and the –replace operator both provide useful ways to replace text in a string. The Replace() method is the quickest but also the most constrained. It replaces every occurrence of the exact string you specify with the exact replacement string that you provide. The –replace operator provides much more flexibility, since its arguments are regular expressions that can match and replace complex patterns.

The regular expressions that you use with the –replace operator often contain characters that PowerShell normally interprets as variable names or escape characters. To prevent PowerShell from interpreting

these characters, use a nonexpanding string (single quotes) as shown by the solution.

Program: Search for WMI Classes

Along with WMI’s huge scope comes a related problem: finding the WMI class that accomplishes your task. If you want to dig a little deeper, though, Example 152 lets you search for WMI classes by name, description, property name, or property description.

Example 152. SearchWmiNamespace.ps1

############################################################################## ## ## SearchWmiNamespace.ps1 ##

Example 152. SearchWmiNamespace.ps1 (continued)

## Search the WMI classes installed on the system for the provided match text. ## ## ie: ## ## PS >SearchWmiNamespace Registry ## PS >SearchWmiNamespace Process ClassName,PropertyName ## PS >SearchWmiNamespace CPU Detailed ## ##############################################################################

param( [string] $pattern = $(throw "Please specify a search pattern."), [switch] $detailed, [switch] $full,

## Supports any or all of the following match options: ## ClassName, ClassDescription, PropertyName, PropertyDescription [string[]] $matchOptions = ("ClassName","ClassDescription")

)

## Helper function to create a new object that represents ## a Wmi match from this script function NewWmiMatch {

param( $matchType, $className, $propertyName, $line )

$wmiMatch = NewObject PsObject $wmiMatch | AddMember NoteProperty MatchType $matchType $wmiMatch | AddMember NoteProperty ClassName $className $wmiMatch | AddMember NoteProperty PropertyName $propertyName $wmiMatch | AddMember NoteProperty Line $line

$wmiMatch }

## If they've specified the detailed or full options, update ## the match options to provide them an appropriate amount of detail if($detailed) {

$matchOptions = "ClassName","ClassDescription","PropertyName" }

if($full) { $matchOptions = "ClassName","ClassDescription","PropertyName","PropertyDescription" }

## Verify that they specified only valid match options foreach($matchOption in $matchOptions) {

$fullMatchOptions =

Example 152. SearchWmiNamespace.ps1 (continued)

"ClassName","ClassDescription","PropertyName","PropertyDescription"

if($fullMatchOptions notcontains $matchOption) {

$error = "Cannot convert value {0} to a match option. " + "Specify one of the following values and try again. " + "The possible values are ""{1}""."

$ofs = ", " throw ($error f $matchOption, ([string] $fullMatchOptions)) } }

## Go through all of the available classes on the computer foreach($class in GetWmiObject List) {

## Provide explicit get options, so that we get back descriptions ## as well $managementOptions = NewObject System.Management.ObjectGetOptions $managementOptions.UseAmendedQualifiers = $true $managementClass =

NewObject Management.ManagementClass $class.Name,$managementOptions

## If they want us to match on class names, check if their text ## matches the class name if($matchOptions contains "ClassName") {

if($managementClass.Name match $pattern) { NewWmiMatch "ClassName" ` $managementClass.Name $null $managementClass.__PATH } }

## If they want us to match on class descriptions, check if their text ## matches the class description if($matchOptions contains "ClassDescription") {

$description = $managementClass.PsBase.Qualifiers |

foreach { if($_.Name eq "Description") { $_.Value } } if($description match $pattern) {

NewWmiMatch "ClassDescription" ` $managementClass.Name $null $description } }

## Go through the properties of the class foreach($property in $managementClass.PsBase.Properties) {

Example 152. SearchWmiNamespace.ps1 (continued)

## If they want us to match on property names, check if their text ## matches the property name if($matchOptions contains "PropertyName") {

if($property.Name match $pattern) { NewWmiMatch "PropertyName" ` $managementClass.Name $property.Name $property.Name } }

## If they want us to match on property descriptions, check if ## their text matches the property name if($matchOptions contains "PropertyDescription") {

$propertyDescription = $property.Qualifiers |

foreach { if($_.Name eq "Description") { $_.Value } } if($propertyDescription match $pattern) {

NewWmiMatch "PropertyDescription" ` $managementClass.Name $property.Name $propertyDescription } } } }

Manage a Running PowerShell Service

Problem

You want to manage a running service.

Solution

To stop a service, use the StopService cmdlet:

PS >StopService AudioSrv WhatIf What if: Performing operation "StopService" on Target "Windows Audio (Audi oSrv)".

Likewise, use the SuspendService, RestartService, and ResumeService cmdlets to suspend, restart, and resume services, respectively.

For other tasks (such as setting the startup mode), use the GetWmiObject cmdlet:

$service = GetWmiObject Win32_Service |

WhereObject { $_.Name eq "AudioSrv" } $service.ChangeStartMode("Manual") $service.ChangeStartMode("Automatic")

Discussion

The StopService cmdlet lets you stop a service either by name or display name.

Notice that the solution uses the –WhatIf flag on the StopService cmdlet. This parameter 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 StopService cmdlet, type GetHelp StopService.If you want to suspend, restart, or resume a service, see the SuspendService, RestartService, and ResumeService cmdlets, respectively.