Skip to main content

Resources

Retrieve a Specific Event Log Entry

Problem

You want to retrieve a specific event log entry.

Solution

To retrieve a specific event log entry, use the GetEventLog cmdlet to retrieve the entries in the event log, and then pipe them to the WhereObject cmdlet to filter them to the one you are looking for.

PS >GetEventLog System | WhereObject { $_.Index –eq 2920 }

Index Time Type Source EventID Message

2920 May 06 09:18 Info Service Control M... 7036 The Logical Disk...

Discussion

If you’ve listed the items in an event log or searched it for entries that have a message with specific text, you often want to get more details about a specific event log entry.

Since the GetEventLog cmdlet retrieves rich objects that represent event log entries, you can pipe them to the WhereObject cmdlet for equally rich filtering.

By default, PowerShell’s default table formatting displays a summary of event log entries. If you are retrieving a specific entry, however, you are probably interested in seeing more details about the entry. In this case, use the FormatList cmdlet to format these entries in a more detailed list view, as shown in Example 204.

Example 204. A detailed list view of an event log entry

PS > GetEventLog System | WhereObject { $_.Index –eq 2920 } | >> FormatList >>

Index
: 2920

EntryType
: Information

EventID
: 7036

Message
: The Logical Disk Manager Administrative Service servi

ce entered the stopped state.

Category
: (0)

CategoryNumber
: 0

ReplacementStrings : {Logical Disk Manager Administrative Service, stopped

} Source : Service Control Manager TimeGenerated : 5/6/2007 9:18:25 AM TimeWritten : 5/6/2007 9:18:25 AM UserName :

Index : 2919 (...)

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

Use Console Files to Load and Save Sets of Snapins in Windows PowerShell

Problem

You want to load PowerShell with a set of additional snapins, but do not want to modify your (or the user’s) profile.

Solution

Once you register a snapin on your system, you can add its snapin identifier to a PowerShell console file to load it. When you specify that file as the PsConsoleFile parameter of PowerShell.exe, PowerShell loads all snapins defined by the console file into the new session.

Save the list of currently loaded snapins to a console file:

ExportConsole Filename.psc1

Load PowerShell with the set of snapins defined in the file Filename.psc1:

PowerShell PsConsoleFile Filename.psc1

Discussion

PowerShell console files are simple XML files that list the identifiers of alreadyinstalled snapins to load. A typical console file looks like

Example 111. A typical PowerShell console file

1.0

Console files should be saved with the file extension .psc1.

Although it is common to load the console file with PowerShell’s commandline options (in scripts and automated tasks), you can also doubleclick on the console file to load it interactively.

Read a Key of User Input in Windows PowerShell

Problem

You want your script to get a single keypress from the user.

Solution

For most purposes, use the [Console]::ReadKey() method to read a key: PS >$key = [Console]::ReadKey($true)

PS >$key

KeyChar
Key
Modifiers

h
H
Alt

For highly interactive use (for example, when you care about key down and key up), use:

PS >$key = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") PS >$key

VirtualKeyCode Character ControlKeyState KeyDown 16 ...ssed, NumLockOn True

PS >$key.ControlKeyState ShiftPressed, NumLockOn

Discussion

For most purposes, the [Console]::ReadKey() is the best way to get a keystroke from a user, as it accepts simple keypresses—as well as more complex keypresses that might include the Ctrl, Alt, and Shift keys.

The following function emulates the DOS pause command:

function Pause

{ WriteHost NoNewLine "Press any key to continue . . . " [Console]::ReadKey($true) | OutNull WriteHost

}

If you need to capture individual key down and key up events (including those of the Ctrl, Alt, and Shift keys), use the $host.UI.RawUI.ReadKey() method.

Program: Search the Windows Registry

Discussion

While the Windows Registry Editor is useful for searching the registry, it sometimes may not provide the power you need. For example, the registry editor does not support searches with wildcards or regular expressions.

In the filesystem, we have the SelectString cmdlet to search files for content. PowerShell does not have that for other stores, but we can write a script to do it. The key here is to think of registry key values like you think of content in a file:

  • Directories have items; items have content.
  • Registry keys have properties; properties have values.

Example 184 goes through all registry keys (and their values) for a search term and returns information about the match.

Example 184. SearchRegistry.ps1

############################################################################## ## ## SearchRegistry.ps1 ## ## Search the registry for keys or properties that match a specific value. ## ## ie: ## ## PS >SetLocation HKCU:\Software\Microsoft\ ## PS >SearchRegistry Run ## ##############################################################################

param([string] $searchText = $(throw "Please specify text to search for."))

## Helper function to create a new object that represents ## a registry match from this script function NewRegistryMatch {

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

$registryMatch = NewObject PsObject $registryMatch | AddMember NoteProperty MatchType $matchType $registryMatch | AddMember NoteProperty KeyName $keyName $registryMatch | AddMember NoteProperty PropertyName $propertyName $registryMatch | AddMember NoteProperty Line $line

$registryMatch }

## Go through each item in the registry foreach($item in GetChildItem Recurse ErrorAction SilentlyContinue) {

## Check if the key name matches if($item.Name match $searchText) {

NewRegistryMatch "Key" $item.Name $null $item.Name }

## Check if a key property matches foreach($property in (GetItemProperty $item.PsPath).PsObject.Properties) {

## Skip the property if it was one PowerShell added if(($property.Name eq "PSPath") or ($property.Name eq "PSChildName")) {

continue }

## Search the text of the property $propertyText = "$($property.Name)=$($property.Value)" if($propertyText match $searchText) {

Example 184. SearchRegistry.ps1 (continued)

NewRegistryMatch "Property" $item.Name $property.Name $propertyText } } }

Manage Operations Manager Agents

Problem

You want to manage Operations Manager agents on remote machines.

Solution

To retrieve information about installed agents, use the GetAgent cmdlet:

PS Monitoring:\Oxford.contoso.com >GetAgent | SelectObject DisplayName

DisplayName

Ibiza.contoso.com Denver.contoso.com Sydney.contoso.com

To remove an agent, use the UninstallAgent cmdlet:

PS Monitoring:\Oxford.contoso.com >GetAgent | WhereObject { $_.DisplayName match "Denver" } | >> UninstallAgent

To install an agent on a specific computer, use the InstallAgentByName function:

PS Monitoring:\Oxford.contoso.com >InstallAgentByName Oxford.contoso.com

Discussion

The GetAgent cmdlet returns a great deal of information about each agent it retrieves. The example in the solution filters this to show only the DisplayName, but you may omit the SelectObject cmdlet to retrieve all information about that agent.

If you need more control over the agent installation process, examine the content of the InstallAgentByName function:

PS Monitoring:\Oxford.contoso.com

>GetContent Function:\InstallAgentByName

The function simplifies the most common scenario for installing agents, but the InstallAgent cmdlet that supports it provides additional functionality.

For more information about the GetAgent cmdlet, type GetHelp GetAgent. For more information about the InstallAgent cmdlet, type InstallAgent. For more information about the UninstallAgent cmdlet, type GetHelp UninstallAgent.

Invoke a PowerShell Script From Outside PowerShell

Problem

You want to invoke a PowerShell script from a batch file, a logon script, scheduled task, or any other nonPowerShell application.

Solution

Launch PowerShell.exe in the following way:

PowerShell "& 'full path to script' arguments"

For example,

PowerShell "& 'c:\shared scripts\GetReport.ps1' Hello World"

Discussion

Supplying a single string argument to PowerShell.exe invokes PowerShell, runs the command as though you had typed it in the interactive shell, and then exits. Since the path to a script often contains spaces, you invoke the script by placing its name between single quotes, and after the & character. If the script name does not contain spaces, you can omit the single quotes and & character. This technique lets you invoke a PowerShell script as the target of a logon script, advanced file association, scheduled task and more.

If you are the author of the program that needs to run PowerShell scripts or commands, PowerShell lets you call these scripts and commands much more easily than calling its commandline interface.

If the command becomes much more complex than a simple script call, special characters in the application calling PowerShell (such as cmd.exe) might interfere with the command you want to send to PowerShell. For this situation, PowerShell supports an EncodedCommand parameter: a Base64 encoded representation of the Unicode string. erShell commands to a Base64 encoded form.

Example 14. Converting PowerShell commands into a Base64 encoded form

$commands = '1..10 | % { "PowerShell Rocks" }' $bytes = [System.Text.Encoding]::Unicode.GetBytes($commands) $encodedString = [Convert]::ToBase64String($bytes)

Once you have the encoded string, you can use it as the value of the EncodedCommand parameter, as shown in Example 15.

Example 15. Launching PowerShell with an encoded command from cmd.exe

Microsoft Windows [Version 6.0.6000] Copyright (c) 2006 Microsoft Corporation. All rights reserved.

C:\Users\Lee>PowerShell EncodedCommand MQAuAC4AMQAwACAAfAAgACUAIAB7ACAAIgBQAG8A↵ dwBlAHIAUwBoAGUAbABsACAAUgBvAGMAawBzACIAIAB9AA== PowerShell Rocks PowerShell Rocks PowerShell Rocks PowerShell Rocks PowerShell Rocks PowerShell Rocks PowerShell Rocks PowerShell Rocks PowerShell Rocks PowerShell Rocks

Write Pipeline-Oriented Scripts with Cmdlet Keywords

Problem

Your script, function, or script block primarily takes input from the pipeline, and you want to write it in a way that makes this intention both easy to implement and easy to read.

Solution

To cleanly separate your script into regions that deal with the initialization, perrecord processing, and cleanup portions, use the begin, process, and end keywords, respectively.

Example 108. A pipelineoriented script that uses cmdlet keywords

function InputCounter

{ begin {

$count = 0 }

## Go through each element in the pipeline, and add up ## how many elements there were. process {

WriteDebug "Processing element $_" $count++ }

end { $count } }

This produces the following output:

PS >$debugPreference = "Continue" PS >dir | InputCounter DEBUG: Processing element CompareProperty.ps1 DEBUG: Processing element ConnectWebService.ps1 DEBUG: Processing element ConvertTextObject.ps1 DEBUG: Processing element ConvertFromFahrenheitWithFunction.ps1 DEBUG: Processing element ConvertFromFahrenheitWithLibrary.ps1 DEBUG: Processing element ConvertFromFahrenheitWithoutFunction.ps1 DEBUG: Processing element GetAliasSuggestion.ps1 (...) DEBUG: Processing element SelectFilteredObject.ps1 DEBUG: Processing element SetConsoleProperties.ps1 20

Discussion

If your script, function, or script block deals primarily with input from the pipeline, the begin, process, and end keywords let you express your solution most clearly. Readers of your script (including you!) can easily see which portions of your script deal with initialization, perrecord processing, and cleanup. In addition, separating your code into these blocks lets your script to consume elements from the pipeline as soon as the previous script produces them.

Take, for example, the GetInputWithForeach and GetInputWithKeyword functions shown in Example 109. The first visits each element in the pipeline with a foreach statement over its input, while the second uses the begin, process, and end keywords.

Example 109. Two functions that take different approaches to processing pipeline input

## Process each element in the pipeline, using a ## foreach statement to visit each element in $input function GetInputWithForeach($identifier) {

WriteHost "Beginning InputWithForeach (ID: $identifier)"

foreach($element in $input)

{ WriteHost "Processing element $element (ID: $identifier)" $element

}

WriteHost "Ending InputWithForeach (ID: $identifier)" }

## Process each element in the pipeline, using the ## cmdletstyle keywords to visit each element in $input function GetInputWithKeyword($identifier) {

begin { WriteHost "Beginning InputWithKeyword (ID: $identifier)" }

process

{ WriteHost "Processing element $_ (ID: $identifier)" $_

}

end { WriteHost "Ending InputWithKeyword (ID: $identifier)" } }

Both of these functions act the same when run individually, but the difference becomes clear when we combine them with other scripts or functions that take pipeline input. When a script uses the $input variable, it must wait until the previous script finishes producing output before it can start. If the previous script takes a long time to produce all its records (for example, a large directory listing), then your user must wait until the entire directory listing completes to see any results, rather than seeing results for each item as the script generates it.

If a script, function, or script block uses the cmdletstyle keywords, it must place all its code (aside from comments or its param statement if it uses one) inside one of the three blocks. If your code needs to define

and initialize variables or define functions, place them in the begin block. Unlike most blocks of code contained within curly braces, the code in the begin, process, and end blocks has access to variables and functions defined within the blocks before it.

When we chain together two scripts that process their input with the begin, process, and end keywords, the second script gets to process input as soon as the first script produces it.

PS >1,2,3 | GetInputWithKeyword 1 | GetInputWithKeyword 2 Beginning InputWithKeyword (ID: 1) Beginning InputWithKeyword (ID: 2) Processing element 1 (ID: 1) Processing element 1 (ID: 2) 1 Processing element 2 (ID: 1) Processing element 2 (ID: 2) 2 Processing element 3 (ID: 1) Processing element 3 (ID: 2) 3 Ending InputWithKeyword (ID: 1) Ending InputWithKeyword (ID: 2)

When we chain together two scripts that process their input with the $input variable, the second script can’t start until the first completes.

PS >1,2,3 | GetInputWithForeach 1 | GetInputWithForeach 2 Beginning InputWithForeach (ID: 1) Processing element 1 (ID: 1) Processing element 2 (ID: 1) Processing element 3 (ID: 1) Ending InputWithForeach (ID: 1) Beginning InputWithForeach (ID: 2) Processing element 1 (ID: 2) 1 Processing element 2 (ID: 2) 2 Processing element 3 (ID: 2) 3 Ending InputWithForeach (ID: 2)

When the first script uses the cmdletstyle keywords, and the second scripts uses the $input variable, the second script can’t start until the first completes.

PS >1,2,3 | GetInputWithKeyword 1 | GetInputWithForeach 2 Beginning InputWithKeyword (ID: 1) Processing element 1 (ID: 1) Processing element 2 (ID: 1) Processing element 3 (ID: 1)

Ending InputWithKeyword (ID: 1) Beginning InputWithForeach (ID: 2) Processing element 1 (ID: 2) 1 Processing element 2 (ID: 2) 2 Processing element 3 (ID: 2) 3 Ending InputWithForeach (ID: 2)

When the first script uses the $input variable and the second script uses the cmdletstyle keywords, the second script gets to process input as soon as the first script produces it.

PS >1,2,3 | GetInputWithForeach 1 | GetInputWithKeyword 2 Beginning InputWithKeyword (ID: 2) Beginning InputWithForeach (ID: 1) Processing element 1 (ID: 1) Processing element 1 (ID: 2) 1 Processing element 2 (ID: 1) Processing element 2 (ID: 2) 2 Processing element 3 (ID: 1) Processing element 3 (ID: 2) 3 Ending InputWithForeach (ID: 1) Ending InputWithKeyword (ID: 2)

Remove a File or Directory in PowerShell

Problem

You want to remove a file or directory.

Solution

To remove a file or directory, use the RemoveItem cmdlet:

PS >TestPath NewDirectory True PS >RemoveItem NewDirectory PS >TestPath NewDirectory False

Discussion

The RemoveItem cmdlet removes an item from the location you provide. The RemoveItem cmdlet doesn’t work only against the filesystem, however. Any providers that support the concept of items automatically support this cmdlet as well.

The RemoveItem cmdlet lets you specify multiple files through its Path, Include, Exclude, and Filter parameters.

If the item is a container (for example, a directory), PowerShell warns you that your action will also remove anything inside that container. You can provide the –Recurse flag if you want to prevent this message.

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

Manage Printers and Print Queues

Problem

You want to clear pending print jobs from a printer.

Solution

To manage printers attached to the system, use the Win32_Printer WMI class. By default, the WMI class lists all printers:

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 clear the print queue of a specific printer, apply a filter based on its name and call the CancelAllJobs() method: PS >$device = GetWmiObject Win32_Printer Filter "Name='Brother DCP1000'" PS >$device.CancelAllJobs()

__GENUS : 2 __CLASS : __PARAMETERS __SUPERCLASS : __DYNASTY : __PARAMETERS __RELPATH : __PROPERTY_COUNT : 1 __DERIVATION : {} __SERVER : __NAMESPACE : __PATH : ReturnValue : 5

Discussion

The example in the solution uses the Win32_Printer WMI class to cancel all jobs for a printer. In addition to cancelling all print jobs, the Win32_Printer class supports other tasks:

PS >$device | GetMember MemberType Method

TypeName: System.Management.ManagementObject#root\cimv2\Win32_Printer

Name MemberType Definition

CancelAllJobs Method System.Management.ManagementBaseObject Can... Pause Method System.Management.ManagementBaseObject Pau... PrintTestPage Method System.Management.ManagementBaseObject Pri... RenamePrinter Method System.Management.ManagementBaseObject Ren... Reset Method System.Management.ManagementBaseObject Res... Resume Method System.Management.ManagementBaseObject Res... SetDefaultPrinter Method System.Management.ManagementBaseObject Set... SetPowerState Method System.Management.ManagementBaseObject Set...

Easily Import and Export Your Structured Data

Problem

You have a set of data (such as a hashtable or array) and want to save it to disk so that you can use it later. Conversely, you have saved structured data to a file and want to import it so that you can use it.

Solution

Use PowerShell’s ExportCliXml cmdlet to save structured data to disk, and the ImportCliXml cmdlet to import it again from disk.

For example, imagine storing a list of your favorite directories in a hashtable, so that you can easily navigate your system with a “Favorite CD” function. Example 85 shows this function.

Example 85. A function that requires persistent structured data

PS >$favorites = @{} PS >$favorites["temp"] = "c:\temp" PS >$favorites["music"] = "h:\lee\my music" PS >function fcd { >> param([string] $location) SetLocation $favorites[$location] >> } >> PS >GetLocation

Path

HKLM:\software

PS >fcd temp PS >GetLocation

Path

C:\temp

Unfortunately, the $favorites variable vanishes whenever you close PowerShell.

To get around this, you could recreate the $favorites variable in your profile, but another way is to export it directly to a file. This command assumes that you have already created a profile, and places the file in the same location as that profile:

PS >$filename = JoinPath (SplitPath $profile) favorites.clixml PS >$favorites | ExportCliXml $filename PS >$favorites = $null PS >$favorites PS >

Once it’s on disk, you can reload it using the ImportCliXml cmdlet, as shown in Example 86.

Example 86. Restoring structured data from disk

PS >$favorites = ImportCliXml $filename PS >$favorites

Name
Value

music
h:\lee\my music

temp
c:\temp

PS >fcd music PS >GetLocation

Path

H:\lee\My Music

Discussion

PowerShell provides the ExportCliXml and ImportCliXml cmdlets to let you easily move structured data into and out of files. These cmdlets accomplish this in a very datacentric and futureproof way—by storing only the names, values, and basic data types for the properties of that data.

By default, PowerShell stores one level of data: all directly accessible simple properties (such as the WorkingSet of a process) but a plaintext representation for anything deeper (such as a process’s

Threads collection). For information on how to control the depth of this export, type GetHelp ExportCliXml and see the explanation of the –Depth parameter.

After you import data saved by ExportCliXml, you again have access to the properties and values from the original data. PowerShell converts some objects back to their fully featured objects (such as System.DateTime objects), but for the most part does not retain functionality (for example, methods) from the original objects.

Program: Start a Process As Another User in PowerShell

Discussion

If your script requires user credentials, you will want to store those credentials in a PowerShell PsCredential object. This lets you securely store those credentials, or pass them to other commands that accept PowerShell credentials. When you write a script that accepts credentials, consider letting the user to supply either a username or a preexisting credential. Example 164 demonstrates a useful approach that allows that. As the framework for this demonstration, the script lets you start a process as another user.

Example 164. StartProcessAsUser.ps1

############################################################################## ## ## StartProcessAsUser.ps1 ## ## Launch a process under alternate credentials, providing functionality ## similar to runas.exe. ## ## ie: ## ## PS >$file = JoinPath ([Environment]::GetFolderPath("System")) certmgr.msc ## PS >StartProcessAsUser Administrator mmc $file ## ## ## ##############################################################################

param( $credential = (GetCredential), [string] $process = $(throw "Please specify a process to start."), [string] $arguments = "" )

## Create a real credential if they supplied a username if($credential is "String") {

$credential = GetCredential $credential }

## Exit if they canceled out of the credential dialog if(not ($credential is "System.Management.Automation.PsCredential")) {

return }

## Prepare the startup information (including username and password) $startInfo = NewObject Diagnostics.ProcessStartInfo $startInfo.Filename = $process

Example 164. StartProcessAsUser.ps1 (continued)

$startInfo.Arguments = $arguments

## If we're launching as ourselves, set the "runas" verb if(($credential.Username eq "$ENV:Username") or ($credential.Username eq "\$ENV:Username")) {

$startInfo.Verb = "runas" } else {

$startInfo.UserName = $credential.Username $startInfo.Password = $credential.Password $startInfo.UseShellExecute = $false

}

## Start the process [Diagnostics.Process]::Start($startInfo)

Remove a Windows PowerShell User from a Security or Distribution Group

Problem

You want to remove a user from a security or distribution group.

Solution

To remove a user from a security or distribution group, use the [adsi] type shortcut to bind to the group in Active Directory, and then call the Remove() method:

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

$user = "LDAP://localhost:389/cn=MyerKen,ou=West,ou=Sales,dc=Fabrikam,dc=COM" $management.Remove($user)

Discussion

The solution removes the MyerKen user from a group named Management in the Sales West OU.

Trim a String in Windows PowerShell

Problem

You want to remove leading or trailing spaces from a string or user input.

Solution

Use the Trim() method of the string to remove all leading and trailing whitespace characters from that string.

PS >$text = " `t Test String`t `t" PS >"|" + $text.Trim() + "|" |Test String|

Discussion

The Trim() method cleans all whitespace from the beginning and end of a string. If you want just one or the other, you can also call the TrimStart() or TrimEnd() method to remove whitespace from the beginning or the end of the string, respectively. If you want to remove specific characters from the beginning or end of a string, the Trim(), TrimStart(), and TrimEnd() methods provide options to support that. To trim a list of specific characters from the end of a string, provide that list to the method, as shown in Example 55.

Example 55. Trimming a list of characters from the end of a string

PS >"Hello World".TrimEnd('d','l','r','o','W',' ') He

At first blush, the following command that attempts to trim the text "World" from the end of a string appears to work incorrectly:

PS >"Hello World".TrimEnd(" World")

He This happens because the TrimEnd() method takes a list of characters to remove from the end of a string. PowerShell automatically converts a string to a list of characters if required, so this command is in fact the same as the command in Example 55.

If you want to replace text anywhere in a string (and not just from the beginning or end)

Convert a VBScript WMI Script to PowerShell

Problem

You want to perform a WMI task in PowerShell, but can find only VBScript examples that demonstrate the solution to the problem.

Solution

To accomplish the task of a script that retrieves data from a computer, use the GetWmiObject cmdlet: foreach($printer in GetWmiObject –Computer COMPUTER Win32_Printer) { ## Work with the properties $printer.Name

} To accomplish the task of a script that calls methods on an instance, use the [Wmi] or [WmiSearcher] accelerators to retrieve the instances, and then call methods on the instances like you would call any other PowerShell method:

$service = [Wmi] 'Win32_Service.Name="winmgmt"'

$service.ChangeStartMode("Manual")

$service.ChangeStartMode("Automatic")

To accomplish the task of a script that calls methods on a class, use the [WmiClass] accelerator to retrieve the class, and then call methods on the class like you would call any other PowerShell method:

$class = [WmiClass] "Win32_Process"

$class.Create("Notepad")

Discussion

For many years, VBScript has been the preferred language that administrators use to access WMI data. Because of that, the vast majority of scripts available in books and on the Internet come written in VBScript.

These scripts usually take one of three forms: retrieving data and accessing properties, calling methods of an instance, and calling methods of a class.

Although most WMI scripts on the Internet accomplish unique tasks, PowerShell supports many of the traditional WMI tasks natively. If you want to translate a WMI example to PowerShell, first check that

there aren’t any PowerShell cmdlets that might accomplish the task directly.

Retrieving data

One of the most common uses of WMI is for data collection and system inventory tasks. A typical VBScript that retrieves data looks like Example 153.

Example 153. Retrieving printer information from WMI using VBScript

strComputer = "." Set objWMIService = GetObject("winmgmts:" _ & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")

Set colInstalledPrinters = objWMIService.ExecQuery _ ("Select * from Win32_Printer")

For Each objPrinter in colInstalledPrinters Wscript.Echo "Name: " & objPrinter.Name Wscript.Echo "Location: " & objPrinter.Location Wscript.Echo "Default: " & objPrinter.Default

Next

The first three lines prepare a WMI connection to a given computer and namespace. The next two lines of code prepare a WMI query that requests all instances of a class. The For Each block loops over all the instances, and the objPrinter.Property statements interact with properties on those instances.

In PowerShell, the GetWmiObject cmdlet takes care of most of that, by retrieving all instances of a class from the computer and namespace that you specify. The first five lines of code then become:

$installedPrinters = GetWmiObject Win32_Printer If you need to specify a different computer, namespace, or query restriction, the GetWmiObject cmdlets supports those through optional parameters.

In PowerShell, the For Each block becomes: foreach($printer in $installedPrinters) { $printer.Name $printer.Location $printer.Default }

Notice that we spend the bulk of the PowerShell conversion of this script showing how to access properties. If you don’t actually need to work with the properties (and only want to display them for reporting purposes), PowerShell’s formatting commands simplify that even further:

GetWmiObject Win32_Printer | FormatList Name,Location,Default

Calling methods on an instance

Although data retrieval scripts form the bulk of WMI management examples, another common task is to call methods of an instance that invoke actions.

For example, Example 154 changes the startup type of a service.

Example 154. Changing the startup type of a service from WMI using VBScript

strComputer = "." Set objWMIService = GetObject("winmgmts:" _

& "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")

Set colServiceList = objWMIService.ExecQuery _ ("Select * from Win32_Service where StartMode = 'Manual'")

For Each objService in colServiceList

errReturnCode = objService.ChangeStartMode("Disabled") Next

The first three lines prepare a WMI connection to a given computer and namespace. The next two lines of code prepare a WMI query that requests all instances of a class and adds an additional filter (StartMode = 'Manual') to the query. The For Each block loops over all the instances, and the objService.Change(...) statement calls the Change() method on the service.

In PowerShell, the GetWmiObject cmdlet takes care of most of the setup, by retrieving all instances of a class from the computer and namespace that you specify. The first five lines of code then become:

$services = GetWmiObject Win32_Service –Filter "StartMode = 'Manual'" If you need to specify a different computer or namespace, the GetWmiObject cmdlets supports those through optional parameters.

In PowerShell, the For Each block becomes: foreach($service in $services) { $service.ChangeStartMode("Disabled") }

Calling methods on a class

Although less common than calling methods on an instance, it is sometimes helpful to call methods on a WMI class. PowerShell makes this work almost exactly like calling methods on an instance.

For example, a script that creates a process on a remote computer looks like this:

strComputer = "COMPUTER"

Set objWMIService = GetObject _

("winmgmts:\\" & strComputer & "\root\cimv2:Win32_Process")

objWMIService.Create("notepad.exe")

The first three lines prepare a WMI connection to a given computer and namespace. The final line calls the Create() method on the class.

In PowerShell, the [WmiClass] accelerator lets you easily access WMI classes. The first three lines of code then become:

$processClass = [WmiClass] "\\COMPUTER\Root\Cimv2:Win32_Process"

In PowerShell, calling the method on the class is nearly identical:

$processClass.Create("notepad.exe") 

Windows PowerShell Active Directory

By far, the one thing that makes system administration on the Windows platform most unique is its interaction with Active Directory. As the centralized authorization, authentication, and information store for Windows networks, Active Directory automation forms the core of many enterprise administration tasks.

While PowerShell doesn’t include either Active Directory cmdlets or an Active Directory provider, its access through the .NET Framework provides support for the broad range of Active Directory administration.

Use a COM Object

Problem

You want to create a COM object to interact with its methods and properties.

Solution

Use the NewObject cmdlet (with the –ComObject parameter) to create a COM object from its ProgID. You can then interact with the methods and properties of the COM object as you would any other object in PowerShell.

$object = NewObject ComObject ProgId

For example:

PS >$sapi = NewObject Com Sapi.SpVoice PS >$sapi.Speak("Hello World")

Discussion

Historically, many applications have exposed their scripting and administration interfaces as COM objects. While .NET APIs (and PowerShell cmdlets) are becoming more common, interacting with COM objects is still a common administrative task.

As with classes in the .NET Framework, it is difficult to know what COM objects you can use to help you accomplish your system administration tasks. For a handpicked list of the COM objects most useful to system administrators.

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

Environmental Awareness in Windows PowerShell

While many of your scripts will be designed to work in isolation, you will often find it helpful to give your script information about its execution environment: its name, current working directory, environment variables, common system paths, and more.

PowerShell offers several ways to get at this information—from its cmdlets, to builtin variables, to features that it offers from the .NET Framework.

Find Event Log Entries with Specific Text

Problem

You want to retrieve all event log entries that contain a given term.

Solution

To find specific event log entries, use the GetEventLog cmdlet to retrieve the items, and then pipe them to the WhereObject cmdlet to filter them, as shown in Example 202.

Example 202. Searching the event log for entries that mention the term “disk”

PS >GetEventLog System | WhereObject { $_.Message match "disk" }

Index Time Type Source EventID Message

2920 May 06 09:18 Info Service Control M... 7036 The Logical Disk... 2919 May 06 09:17 Info Service Control M... 7036 The Logical Disk... 2918 May 06 09:17 Info Service Control M... 7035 The Logical Disk... 2884 May 06 00:28 Erro sr 1 The System Resto... 2333 Apr 03 00:16 Erro Disk 11 The driver detec... 2332 Apr 03 00:16 Erro Disk 11 The driver detec... 2131 Mar 27 13:59 Info Service Control M... 7036 The Logical Disk... 2127 Mar 27 12:48 Info Service Control M... 7036 The Logical Disk... 2126 Mar 27 12:48 Info Service Control M... 7035 The Logical Disk... 2123 Mar 27 12:31 Info Service Control M... 7036 The Logical Disk... 2122 Mar 27 12:29 Info Service Control M... 7036 The Logical Disk... 2121 Mar 27 12:29 Info Service Control M... 7035 The Logical Disk...

Discussion

Since the GetEventLog cmdlet retrieves rich objects that represent event log entries, you can pipe them to the WhereObject cmdlet for equally rich filtering.

By default, PowerShell’s default table formatting displays a summary of event log entries. If you are searching the event log message, however, you are probably interested in seeing more details about the message itself. In this case, use the FormatList cmdlet to format these entries in a more detailed list view. Example 203 shows this view.

Example 203. A detailed list view of an event log entry

PS >GetEventLog System | WhereObject { $_.Message match "disk" } | >> FormatList >>

Index
: 2920

EntryType
: Information

EventID
: 7036

Message
: The Logical Disk Manager Administrative Service servi

ce entered the stopped state.

Category
: (0)

CategoryNumber
: 0

ReplacementStrings : {Logical Disk Manager Administrative Service, stopped

} Source : Service Control Manager TimeGenerated : 5/6/2007 9:18:25 AM TimeWritten : 5/6/2007 9:18:25 AM UserName :

Index : 2919 (...)

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

Extend Your Windows PowerShell with Additional Snapins

Problem

You want to use PowerShell cmdlets and providers written by a third party.

Solution

In PowerShell, extensions that contain additional cmdlets and providers are called snapins. The author might distribute them with an automated installer but can also distribute them as a standalone PowerShell assembly. PowerShell identifies each snapin by the filename of its assembly and by the snapin name that its author provides.

To use a snapin:

  1. Obtain the snapin assembly.
  2. Copy it to a secure location on your computer. Since snapins are equivalent to executable programs, pick a location (such as the Program Files directory) that provides users read access but not write access.
  3. Register the snapin. From the directory that contains the snapin assembly, run InstallUtil SnapinFilename.dll. This command lets all users on the computer load and run commands defined by the snapin. You can find the InstallUtil utility in the .NET Framework’s installation directory—commonly C:\WINDOWS\ Microsoft.NET\Framework\v2.0.50727\InstallUtil.exe.
  4. Add the snapin. At a PowerShell prompt (or in your profile file), run the command AddPsSnapin SnapinIdentifier. To see all available snapin identifiers, review the names listed in the output of the command:

GetPsSnapin Registered

5. Use the cmdlets and providers contained in that snapin.

To remove the snapin registration from your system, type InstallUtil /u SnapinFilename.dll. Once uninstalled, you may delete the files associated with the snapin.

Discussion

For interactive use (or in a profile), the AddPsSnapin cmdlet is the most common way to load an individual snapin.

One popular source of additional snapins is the PowerShell Community Extensions project, located at http://www.codeplex.com/PowerShellCX .

Read a Line of User Input in Windows PowerShell

Problem

You want to use input from the user in your script.

Solution

To obtain user input, use the ReadHost cmdlet:

PS >$directory = ReadHost "Enter a directory name" Enter a directory name: C:\MyDirectory PS >$directory C:\MyDirectory

Discussion

The ReadHost cmdlet reads a single line of input from the user. If the input contains sensitive data, the cmdlet supports an –AsSecureString parameter to read this input as a SecureString.

If the user input represents a date, time, or number, be aware that most cultures represent these data types differently.

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