Skip to main content

Resources

List the PowerShell Users in an Organizational Unit

Problem

You want to list all the users in an OU.

Solution

To list the users in an OU, use the [adsi] type shortcut to bind to the OU in Active Directory. Create a new System.DirectoryServices.DirectorySearcher for that OU, and then set its Filter property to (objectClass=User). Finally, call the searcher’s FindAll() method to perform the search.

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

$searcher = NewObject System.DirectoryServices.DirectorySearcher $sales $searcher.Filter = '(objectClass=User)' $searcher.FindOne()

Discussion

The solution lists all users in the Sales OU. It does this through the System. DirectoryServices.DirectorySearcher class from the .NET Framework, which lets you query Active Directory. The Filter property specifies an LDAP filter string.

By default, a DirectorySearcher searches the given container and all containers below it. Set the SearchScope property to change this behavior. Avalue of Base searches only the current container, while a value

of OneLevel searches only the immediate children.

Calculations and Math in Windows PowerShell

Math is an important feature in any scripting language. Math support in a language includes addition, subtraction, multiplication, and division of course, but extends further into more advanced mathematical operations. So, it should not surprise you that PowerShell provides a strong suite of mathematical and calculationoriented features.

Since PowerShell provides full access to its scripting language from the command line, though, this keeps a powerful and useful commandline calculator always at your fingertips!

In addition to its support for traditional mathematical operations, PowerShell also caters to system administrators by working natively with concepts such as megabytes and gigabytes, simple statistics (such as sum and average), and conversions between bases.

Access Windows Performance Counters

Problem

You want to access system performance counter information from PowerShell.

Solution

To retrieve information about a specific performance counter, use the System. Diagnostics.PerformanceCounter class from the .NET Framework, as shown in Example 156.

Example 156. Accessing performance counter data through the System.Diagnostics. PeformanceCounter class

PS >$arguments = "System","System Up Time" PS >$counter = NewObject System.Diagnostics.PerformanceCounter $arguments PS > PS >[void] $counter.NextValue() PS >NewObject TimeSpan 0,0,0,$counter.NextValue()

Days
: 0

Hours
: 18

Minutes
: 51

Seconds
: 17

Milliseconds
: 0

Ticks
: 678770000000

TotalDays
: 0.785613425925926

TotalHours
: 18.8547222222222

TotalMinutes
: 1131.28333333333

TotalSeconds
: 67877

TotalMilliseconds : 67877000

Alternatively, WMI’s Win32_Perf* set of classes support many of the most common performance counters:

GetWmiObject Win32_PerfFormattedData_Tcpip_NetworkInterface

Discussion

The System.Diagnostics.PerformanceCounter class from the .NET Framework provides access to the different performance counters you might want to access on a Windows system. Example 156 illustrates working with a performance counter from a specific category. In addition, the constructor for the PerformanceCounter class also lets you specify instance names, and even a machine name for the performance counter you want to retrieve.

The first time you access a performance counter, the NextValue() method returns 0. At that point, the system begins to sample the performance information and returns a current value the next time you call the NextValue() method.

Get the Properties of an Organizational Unit from Windows PowerShell

Problem

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

Solution

To list the properties of an OU, use the [adsi] type shortcut to bind to the OU in Active Directory, and then pass the OU to the FormatList cmdlet: $organizationalUnit = [adsi] "LDAP://localhost:389/ou=West,ou=Sales,dc=Fabrikam,dc=COM"

$organizationalUnit | FormatList *

Discussion

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

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

PS >$organizationalUnit.wWWHomePage http://fabrikam.com/sales/west

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

PS >$organizationalUnit.Get("name") West

Add Custom Methods and Properties to Objects

Problem

You have an object and want to add your own custom properties or methods (members) to that object.

Solution

Use the AddMember cmdlet to add custom members to an object.

Discussion

The AddMember cmdlet is extremely useful in helping you add custom members to individual objects. For example, imagine that you want to create a report from the files in the current directory, and that report should include each file’s owner. The Owner property is not standard on the objects that GetChildItem produces, but you could write a small script to add them, as shown in

Example 34. A script that adds custom properties to its output of file objects

############################################################################## ## GetOwnerReport.ps1 ## ## Gets a list of files in the current directory, but with their owner added ## to the resulting objects. ## ## Example: ## GetOwnerReport ## GetOwnerReport | FormatTable Name,LastWriteTime,Owner ##############################################################################

$files = GetChildItem foreach($file in $files) {

$owner = (GetAcl $file).Owner

$file | AddMember NoteProperty Owner $owner

$file }

Although it is most common to add static information (such as a NoteProperty), the AddMember cmdlet supports several other property and method types—including AliasProperty, ScriptProperty, CodeProperty, CodeMethod, and ScriptMethod. For a more detailed description of these other property types, see “Working with the .NET Framework”, as well as the help documentation for the AddMember cmdlet.

Although the AddMember cmdlet lets you to customize specific objects, it does not let you to customize all objects of that type.

Calculated properties

Calculated properties are another useful way to add information to output objects. If your script or command uses a FormatTable or SelectObject command to generate its output, you can create additional properties by providing an expression that generates their value. For example:

GetChildItem | SelectObject Name, @{Name="Size (MB)"; Expression={ "{0,8:0.00}" f ($_.Length / 1MB) } }

In this command, we get the list of files in the directory. We use the SelectObject command to retrieve its name and a calculated property called Size (MB). This calculated property returns the size of the file in megabytes, rather than the default (which is bytes).

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

For more information about adding calculated properties, type GetHelp SelectObject or GetHelp FormatTable.

Program: Investigate the InvocationInfo Variable

When experimenting with the information available through the $myInvocation variable, it is helpful to see how this information changes between scripts, functions, and script blocks. For a useful deep dive into the resources provided by the $myInvocation variable, review the output of Example 141.

Example 141. GetInvocationInfo.ps1

############################################################################## ## ## GetInvocationInfo.ps1 ## ## Display the information provided by the $myInvocation variable ## ############################################################################## param([switch] $preventExpansion)

## Define a helper function, so that we can see how $myInvocation changes ## when it is called, and when it is dotsourced function HelperFunction {

" MyInvocation from function:" ""*50 $myInvocation

" Command from function:" ""*50 $myInvocation.MyCommand

}

## Define a script block, so that we can see how $myInvocation changes ## when it is called, and when it is dotsourced $myScriptBlock = {

" MyInvocation from script block:" ""*50 $myInvocation

" Command from script block:" ""*50 $myInvocation.MyCommand

}

## Define a helper alias SetAlias gii GetInvocationInfo

Example 141. GetInvocationInfo.ps1 (continued)

## Illustrate how $myInvocation.Line returns the entire line that the ## user typed. "You invoked this script by typing: " + $myInvocation.Line

## Show the information that $myInvocation returns from a script "MyInvocation from script:" ""*50 $myInvocation

"Command from script:" ""*50 $myInvocation.MyCommand

## If we were called with the PreventExpansion switch, don't go ## any further if($preventExpansion) {

return }

## Show the information that $myInvocation returns from a function "Calling HelperFunction" ""*50 HelperFunction

## Show the information that $myInvocation returns from a dotsourced ## function "DotSourcing HelperFunction" ""*50 . HelperFunction

## Show the information that $myInvocation returns from an aliased script "Calling aliased script" ""*50 gii PreventExpansion

## Show the information that $myInvocation returns from a script block "Calling script block" ""*50 & $myScriptBlock

## Show the information that $myInvocation returns from a dotsourced ## script block "DotSourcing script block" ""*50 . $myScriptBlock

## Show the information that $myInvocation returns from an aliased script "Calling aliased script" ""*50 gii –PreventExpansion

Back Up an Event Log

Problem

You want to store the information in an event log in a file for storage or later review.

Solution

To store event log entries in a file, use the GetEventLog cmdlet to retrieve the entries in the event log, and then pipe them to the ExportCliXml cmdlet to store them in a file.

GetEventLog System | ExportCliXml c:\temp\SystemLogBackup.clixml

Discussion

Once you’ve exported the events from an event log, you can archive them, or use the ImportCliXml cmdlet to review them on any machine that has PowerShell installed:

PS >$archivedLogs = ImportCliXml c:\temp\SystemLogBackup.clixml

PS >$archivedLogs | Group Source

Count Name
Group

856 Service Control Manager
{LEEDESK, LEEDESK, LEEDESK, LEEDESK...

640 TermServDevices
{LEEDESK, LEEDESK, LEEDESK, LEEDESK...

91 Print
{LEEDESK, LEEDESK, LEEDESK, LEEDESK...

100 WMPNetworkSvc
{LEEDESK, LEEDESK, LEEDESK, LEEDESK...

123 Tcpip
{LEEDESK, LEEDESK, LEEDESK, LEEDESK...

(...)

For more information about the GetEventLog cmdlet, type GetHelp GetEventLog. For more information about the ExportCliXml and ImportCliXml cmdlets, type GetHelp ExportCliXml and GetHelp ImportCliXml, respectively.

Filter Items in a List or Windows PowerShell Command Output

Problem

You want to filter the items in a list or command output.

Solution

Use the WhereObject cmdlet (which has the standard aliases, where and ?) to select items in a list (or command output) that match a condition you provide.

To list all running processes that have "search" in their name, use the like operator to compare against the process’s Name property:

GetProcess | WhereObject { $_.Name like "*Search*" }

To list all directories in the current location, test the PsIsContainer property:

GetChildItem | WhereObject { $_.PsIsContainer }

To list all stopped services, use the eq operator to compare against the service’s Status property:

GetService | WhereObject { $_.Status eq "Stopped" }

Discussion

For each item in its input (which is the output of the previous command), the WhereObject cmdlet evaluates that input against the script block that you specify. If the script block returns True, then the WhereObject cmdlet passes the object along. Otherwise, it does not. Ascript block is a series of PowerShell commands enclosed by the { and } characters. You can write any PowerShell commands inside the script block. In the script block, the $_ variable represents the current input object. For each item in the incoming set of objects, PowerShell assigns that item to the $_ variable, and then runs your script block. In the preceding examples, this incoming object represents the process, file, or service that the previous cmdlet generated.

This script block can contain a great deal of functionality, if desired. It can combine multiple tests, comparisons, and much more.

For simple filtering, the syntax of the WhereObject cmdlet may sometimes seem overbearing.

For complex filtering (for example, the type you would normally rely on a mouse to do with files in an Explorer window), writing the script block to express your intent may be difficult or even infeasible.

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

Display Messages and Output to the User in PowerShell

Problem

You want to display messages and other information to the user.

Solution

To ensure that the output actually reaches the screen, call the WriteHost (or OutHost) cmdlet:

PS >function GetDirectorySize >> { >> $size = (GetChildItem | MeasureObject Sum Length).Sum >> WriteHost ("Directory size: {0:N0} bytes" f $size) >> } >> PS >GetDirectorySize Directory size: 46,581 bytes PS >$size = GetDirectorySize Directory size: 46,581 bytes

If you want a message to help you (or the user) diagnose and debug your script, use the WriteDebug cmdlet. If you want a message to provide detailed tracetype output, use the WriteVerbose cmdlet, as shown in Example 122.

Example 122. A function that provides debug and verbose output

PS >function GetDirectorySize >> { >> WriteDebug "Current Directory: $(GetLocation)" >> >> WriteVerbose "Getting size" >> $size = (GetChildItem | MeasureObject Sum Length).Sum >> WriteVerbose "Got size: $size" >> >> WriteHost ("Directory size: {0:N0} bytes" f $size) >> } >> PS >$DebugPreference = "Continue" PS >GetDirectorySize DEBUG: Current Directory: D:\lee\OReilly\Scripts\Programs Directory size: 46,581 bytes PS >$DebugPreference = "SilentlyContinue" PS >$VerbosePreference = "Continue" PS >GetDirectorySize VERBOSE: Getting size VERBOSE: Got size: 46581 Directory size: 46,581 bytes PS >$VerbosePreference = "SilentlyContinue"

Discussion

Most scripts that you write will output richly structured data, such as the actual count of bytes in a directory. That way, other scripts can use the output of that script as a building block for their functionality.

When you do want to provide output specifically to the user, use the WriteHost, WriteDebug, and WriteVerbose cmdlets.

However, be aware that this type of output bypasses normal file redirection, and is therefore difficult for the user to capture. In the case of the WriteHost cmdlet, use it only when your script already generates other structured data that the user would want to capture in a file or variable.

Most script authors eventually run into the problem illustrated by Example 123 when their script tries to output formatted data to the user.

Example 123. An error message caused by formatting statements

PS >## Get the list of items in a directory, sorted by length PS >function GetChildItemSortedByLength($path = (GetLocation)) >> { >> GetChildItem $path | FormatTable | Sort Length >> }

Example 123. An error message caused by formatting statements (continued)

>> PS >GetChildItemSortedByLength outlineoutput : Object of type "Microsoft.PowerShell.Commands.Internal.Fo rmat.FormatEntryData" is not legal or not in the correct sequence. This is likely caused by a userspecified "format*" command which is conflicting with the default formatting.

This happens because the Format* cmdlets actually generate formatting information for the OutHost cmdlet to consume. The OutHost cmdlet (which PowerShell adds automatically to the end of your pipelines) then uses this information to generate formatted output. To resolve this problem, always ensure that formatting commands are the last commands in your pipeline, as shown in Example 124.

Example 124. A function that does not generate formatting errors

PS >## Get the list of items in a directory, sorted by length PS >function GetChildItemSortedByLength($path = (GetLocation)) >> { >> ## Problematic version >> ## GetChildItem $path | FormatTable | Sort Length >> >> ## Fixed version >> GetChildItem $path | Sort Length | FormatTable >> } >> PS >GetChildItemSortedByLength

(...)

Mode
LastWriteTime
Length Name

a
3/11/2007
3:21 PM
59 LibraryProperties.ps1

a
3/6/2007
10:27 AM
150 GetTomorrow.ps1

a
3/4/2007
3:10 PM
194 ConvertFromFahrenheitWithout

Function.ps1

a
3/4/2007
4:40 PM
257 LibraryTemperature.ps1

a
3/4/2007
4:57 PM
281 ConvertFromFahrenheitWithLib

rary.ps1

a
3/4/2007
3:14 PM
337 ConvertFromFahrenheitWithFunc

tion.ps1

(...)

When it comes to producing output for the user, a common reason is to provide progress messages. PowerShell actually supports this in a much richer way, through its WriteProgress cmdlet. 

Set the ACL of a Registry Key

Problem

You want to change the ACL of a registry key.

Solution

To set the ACL on a registry key, use the SetAcl cmdlet. This example grants an account write access to a registry key under HKLM:\Software. This is especially useful for programs that write to administratoronly regions of the registry, which prevents them from running under a nonadministrator account.

cd HKLM:\Software\MyProgram $acl = GetAcl . $arguments = "LEEDESK\Lee","FullControl","Allow" $accessRule = NewObject System.Security.AccessControl.RegistryAccessRule $arguments $acl.SetAccessRule($accessRule) $acl | SetAcl .

Discussion

The SetAcl cmdlet sets the security descriptor of an item. This cmdlet doesn’t only work against the registry, however. Any provider (for example, the filesystem 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 RegistryAccessRule constructor explicitly sets an Allow rule on the Lee account of the LEEDESK computer for FullControl permission.

Although the SetAcl command is powerful, you may already be familiar with commandline tools that offer similar functionality (such as SubInAcl.exe). You can of course continue to use these tools from PowerShell.

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

Get, Install, and Uninstall Management Packs

Problem

You want to automate the deployment or configuration of management packs.

Solution

To retrieve information about installed management packs, use the GetManagementPack cmdlet, as shown in Example 261.

Example 261. Using the GetManagementPack cmdlet

PS Monitoring:\Oxford.contoso.com >$mp = GetManagementPack | WhereObject { $_.DisplayName eq "Health Internal Library" } PS Monitoring:\Oxford.contoso.com >$mp

Name : System.Health.Internal TimeCreated : 5/22/2007 9:38:40 AM LastModified : 5/22/2007 9:38:40 AM KeyToken : 31bf3856ad364e35 Version : 6.0.5000.0 Id : 9395a1eb63221c8b71ad7ab6955c7e11 VersionId : dfaeece9437e7d46edce260bd77a8667 References : {System.Library, System.Health.Library} Sealed : True

Example 261. Using the GetManagementPack cmdlet (continued)

ContentReadable
: False

FriendlyName
: System Health Internal Library

DisplayName
: Health Internal Library

Description
: System Health Interal Library: This Management Pack (...)

DefaultLanguageCode : ENU LockObject : System.Object

Use the UninstallManagementPack cmdlet to remove a management pack:

$mp = GetManagementPack | WhereObject { $_.DisplayName eq "Management Pack Name" } $mp | UninstallManagementPack

To install a management pack, provide its path to the InstallManagementPack cmdlet:

InstallManagementPack

Discussion

For more information about the GetManagementPack cmdlet, type GetHelp GetManagementPack. For more information about the InstallManagementPack cmdlet, type GetHelp InstallManagementPack. For more information about the UninstallManagementPack cmdlet, type GetHelp UninstallManagementPack.

How to Get the System Date and Time in Windows PowerShell

Problem

You want to get the system date.

Solution

To get the system date, run the command GetDate.

Discussion

The GetDate command generates rich objectbased output, so you can use its result for many daterelated tasks. For example, to determine the current day of the week:

PS >$date = GetDate PS >$date.DayOfWeek Sunday

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

Create an Array or List of Items

Problem

You want to create an array or list of items.

Solution

To create an array that holds a given set of items, separate those items with commas:

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

To create an array of a specific size, use the NewObject cmdlet:

PS >$myArray = NewObject string[] 10 PS >$myArray[5] = "Hello" PS >$myArray[5] Hello

To store the output of a command that generates a list, use variable assignment:

PS >$myArray = GetProcess PS >$myArray

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

274 6 1316 3908 33 3164 alg 983 7 3636 7472 30 688 csrss 69 4 924 3332 30 0.69 2232 ctfmon 180 5 2220 6116 37 2816 dllhost (...)

To create an array that you plan to modify frequently, use an ArrayList, as shown by Example 111.

Example 111. Using an ArrayList to manage a dynamic collection of items

PS >$myArray = NewObject System.Collections.ArrayList PS >[void] $myArray.Add("Hello") PS >[void] $myArray.AddRange( ("World","How","Are","You") ) PS >$myArray Hello World How Are You PS >$myArray.RemoveAt(1) PS >$myArray Hello How Are You

Discussion

Aside from the primitive data types (such as strings, integers, and decimals), lists of items are a common concept in the scripts and commands that you write. Most commands generate lists of data: the GetContent cmdlet generates a list of strings in a file, the GetProcess cmdlet generates a list of processes running on the system, and the GetCommand cmdlet generates a list of commands, just to name a few.

The solution shows how to store the output of a command that generates a list. If a command outputs only one item (such as a single line from a file, a single process, or a single command), then that output is

no longer a list. If you want to treat that output as a list even when it is not, use the list evaluation syntax ( @() ) to force PowerShell to interpret it as an array:

$myArray = @(GetProcess Explorer)

Move a File or Directory in PowerShell

Problem

You want to move a file or directory.

Solution

To move a file or directory, use the MoveItem cmdlet: PS >MoveItem example.txt c:\temp\example2.txt

Discussion

The MoveItem cmdlet moves an item from one location to another. Like the other *Item cmdlets, the MoveItem doesn’t work only against the filesystem. Any providers that support the concept of items automatically support this cmdlet as well.

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

Although the MoveItem cmdlet works in every provider, you cannot move items between providers. For more information about the MoveItem cmdlet, type GetHelp MoveItem.

Program: Summarize System Information in Windows PowerShell

WMI provides an immense amount of information about the current system or remote systems. In fact, the msinfo32.exe application traditionally used to gather system information is based largely on WMI.

The script shown in Example 246 summarizes the most common information, but WMI provides a great deal more than that.

Example 246. GetDetailedSystemInformation.ps1

############################################################################## ## ## GetDetailedSystemInformation.ps1 ## ## Get detailed information about a system. ## ## ie: ## ## PS >GetDetailedSystemInformation LEEDESK > output.txt ## ##############################################################################

param( $computer = "." )

"#"*80 "System Information Summary" "Generated $(GetDate)" "#"*80 "" ""

"#"*80 "Computer System Information" "#"*80 GetWmiObject Win32_ComputerSystem Computer $computer | FormatList *

"#"*80 "Operating System Information" "#"*80 GetWmiObject Win32_OperatingSystem Computer $computer | FormatList *

"#"*80 "BIOS Information" "#"*80 GetWmiObject Win32_Bios Computer $computer | FormatList *

Example 246. GetDetailedSystemInformation.ps1 (continued)

"#"*80 "Memory Information" "#"*80 GetWmiObject Win32_PhysicalMemory Computer $computer | FormatList *

"#"*80 "Physical Disk Information" "#"*80 GetWmiObject Win32_DiskDrive Computer $computer | FormatList *

"#"*80 "Logical Disk Information" "#"*80 GetWmiObject Win32_LogicalDisk Computer $computer | FormatList *

Import Structured Data from a CSV File

Problem

You want to import structured data that has been stored in a CSV file. This is helpful when you want to use structured data created by another program, or structured data modified by a person.

Solution

Use PowerShell’s ImportCsv cmdlet to import structured data from a CSV file.

For example, imagine that you previously exported an inventory of the patches applied to a preVista system by KB number:

cd $env:WINDIR GetChildItem KB*.log | ExportCsv c:\temp\patch_log.csv

Somebody reviewed the CSV, and kept only lines from patch logs that they would like to review further. You would like to copy those actual patch logs to a directory so that you can share them.

PS >ImportCsv C:\temp\patch_log_reviewed.csv | ForeachObject { >> CopyItem –LiteralPath $_.FullName –Destination c:\temp\sharedlogs\ } >>

Discussion

As mentioned the CSV file format is one of the most common formats for exchanging semistructured data between programs and systems.

PowerShell’s ImportCsv cmdlet provides an easy way to import semistructured data to the PowerShell environment from other programs. When PowerShell imports your data from the CSV, it creates a new object for each row in the CSV. For each object, PowerShell creates properties on the object from the values of the columns in the CSV.

The preceding solution uses the ForeachObject cmdlet to pass each object to the CopyItem cmdlet. For each item, it uses the incoming object’s FullName property as the source path, and uses c:\temp\

sharedlogs\ as the destination. However, the CSV includes a PSPath property that represents the source, and most cmdlets support PSPath as an alternative (alias) parameter name for –LiteralPath. Because of this, we could have also written

PS >ImportCsv C:\temp\patch_log_reviewed.csv | >> CopyItem Destination c:\temp\sharedlogs\ >>

One thing to keep in mind is that the CSV file format supports only plain strings for property values. When you import data from a CSV, properties that look like dates will still only be strings. Properties that look like numbers will only be strings. Properties that look like any sort of rich data type will only be strings. That means that sorting on any property will always be an alphabetical sort, which is usually not the same as the sorting rules for the rich data types that the property might look like.

If your ultimate goal is to load rich unmodified data from something that you’ve previously exported from PowerShell, the ImportCliXml cmdlet provides a much better alternative.

Access User and Machine Certificates

Problem

You want to retrieve information about certificates for the current user or local machine.

Solution

To browse and retrieve certificates on the local machine, use PowerShell’s certificate drive. This drive is created by the certificate provider, as shown in Example 165.

Example 165. Exploring certificates in the certificate provider

PS >SetLocation cert:\CurrentUser\ PS >$cert = GetChildItem Rec CodeSign PS >$cert | FormatList

Subject : CN=PowerShell User Issuer : CN=PowerShell Local Certificate Root Thumbprint : FD48FAA9281A657DBD089B5A008FAFE61D3B32FD FriendlyName : NotBefore : 4/22/2007 12:32:37 AM NotAfter : 12/31/2039 3:59:59 PM Extensions : {System.Security.Cryptography.Oid, System.Security.Cryptogr

aphy.Oid}

Discussion

The certificate drive provides a useful way to navigate and view certificates for the current user or local machine. For example, if your execution policy requires the use of digital signatures, the following command tells you which publishers are trusted to run scripts on your system:

GetChildItem cert:\CurrentUser\TrustedPublisher

The certificate provider is probably most commonly used to select a codesigning certificate for the SetAuthenticodeSignature cmdlet. The following command selects the “best” code signing certificate—that being the one that expires last:

$certificates = GetChildItem Cert:\CurrentUser\My CodeSign $signingCert = @($certificates | Sort Desc NotAfter)[0] In this CodeSign parameter lets you search for certificates in the certificate store that support code signing.

Although the certificate provider is useful for browsing and retrieving information from the computer’s certificate stores, it does not lets you add or remove items from these locations. If you want to manage certificates in the certificate store, the System.Security.Cryptography.X509Certificates.X509Store class (and other related classes from the System.Security.Cryptography.X509Certificates namespace) from the .NET Framework support that functionality.

For more information about the certificate provider, type GetHelp Certificate.

List the Members of a Group in Windows PowerShell

Problem

You want to list all the members in a group.

Solution

To list the members of a group, use the [adsi] type shortcut to bind to the group in Active Directory, and then access the Member property:

$group =

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

$group.Member

Discussion

The solution lists all members of the Management group in the Sales West OU. Since Active Directory stores this information as a property of the group, this is simply a specific case of retrieving information about the group.

Program: Convert Text Streams to Objects in Windows PowerShell

One of the strongest features of PowerShell is its objectbased pipeline. You don’t waste your energy creating, destroying, and recreating the object representation of your data. In other shells, you lose the fullfidelity representation of data when the pipeline converts it to pure text. You can regain some of it through excessive text parsing, but not all of it.

However, you still often have to interact with lowfidelity input that originates from outside PowerShell. Textbased data files and legacy programs are two examples.

PowerShell offers great support for two of the three textparsing staples:

Sed

Replaces text. For that functionality, PowerShell offers the replace operator.

Grep

Searches text. For that functionality, PowerShell offers the SelectString cmdlet, among others.

The third traditional textparsing tool, Awk, lets you to chop a line of text into more intuitive groupings. PowerShell offers the Split() method on strings, but that lacks some of the power you usually need to break a string into groups.

The ConvertTextObject script presented in Example 57 lets you convert text streams into a set of objects that represent those text elements according to the rules you specify. From there, you can use all of PowerShell’s objectbased tools, which gives you even more power than you would get with the textbased equivalents.

Example 57. ConvertTextObject.ps1

############################################################################## ## ## ConvertTextObject.ps1 Convert a simple string into a custom PowerShell ## object.

##

##
Parameters:

##

##
[string] Delimiter

##
If specified, gives the .NET Regular Expression with which to

##
split the string. The script generates properties for the

##
resulting object out of the elements resulting from this split.

##
If not specified, defaults to splitting on the maximum amount

##
of whitespace: "\s+", as long as ParseExpression is not

##
specified either.

##

##
[string] ParseExpression

##
If specified, gives the .NET Regular Expression with which to

##
parse the string. The script generates properties for the

##
resulting object out of the groups captured by this regular

##
expression.

##

Example 57. ConvertTextObject.ps1 (continued)

##
** NOTE ** Delimiter and ParseExpression are mutually exclusive.

##

##
[string[]] PropertyName

##
If specified, the script will pair the names from this object

##
definition with the elements from the parsed string. If not

##
specified (or the generated object contains more properties

##
than you specify,) the script uses property names in the

##
pattern of Property1,Property2,...,PropertyN

##

##
[type[]] PropertyType

##
If specified, the script will pair the types from this list with

##
the properties from the parsed string. If not specified (or the

##
generated object contains more properties than you specify,) the

##
script sets the properties to be of type [string]

##

##

##
Example usage:

##
"Hello World" | ConvertTextObject

##
Generates an Object with "Property1=Hello" and "Property2=World"

##

##
"Hello World" | ConvertTextObject Delimiter "ll"

##
Generates an Object with "Property1=He" and "Property2=o World"

##

##
"Hello World" | ConvertTextObject ParseExpression "He(ll.*o)r(ld)"

##
Generates an Object with "Property1=llo Wo" and "Property2=ld"

##

##
"Hello World" | ConvertTextObject PropertyName FirstWord,SecondWord

##
Generates an Object with "FirstWord=Hello" and "SecondWord=World

##

##
"123 456" | ConvertTextObject PropertyType $([string],[int])

##
Generates an Object with "Property1=123" and "Property2=456"

##
The second property is an integer, as opposed to a string

##

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

param( [string] $delimiter, [string] $parseExpression, [string[]] $propertyName, [type[]] $propertyType )

function Main( $inputObjects, $parseExpression, $propertyType, $propertyName, $delimiter)

{ $delimiterSpecified = [bool] $delimiter $parseExpressionSpecified = [bool] $parseExpression

## If they've specified both ParseExpression and Delimiter, show usage if($delimiterSpecified and $parseExpressionSpecified)

Example 57. ConvertTextObject.ps1 (continued)

{ Usage return

}

## If they enter no parameters, assume a default delimiter of whitespace if(not $($delimiterSpecified or $parseExpressionSpecified)) {

$delimiter = "\s+" $delimiterSpecified = $true }

## Cycle through the $inputObjects, and parse it into objects foreach($inputObject in $inputObjects) {

if(not $inputObject) { $inputObject = "" } foreach($inputLine in $inputObject.ToString()) {

ParseTextObject $inputLine $delimiter $parseExpression ` $propertyType $propertyName } } }

function Usage

{ "Usage: " " ConvertTextObject" " ConvertTextObject ParseExpression parseExpression " +

"[PropertyName propertyName] [PropertyType propertyType]" " ConvertTextObject Delimiter delimiter " + "[PropertyName propertyName] [PropertyType propertyType]" return }

## Function definition ParseTextObject. ## Perform the heavylifting parse a string into its components. ## for each component, add it as a note to the Object that we return function ParseTextObject {

param( $textInput, $delimiter, $parseExpression, $propertyTypes, $propertyNames)

$parseExpressionSpecified = not $delimiter

$returnObject = NewObject PSObject

$matches = $null $matchCount = 0 if($parseExpressionSpecified) {

Example 57. ConvertTextObject.ps1 (continued)

## Populates the matches variable by default [void] ($textInput match $parseExpression) $matchCount = $matches.Count

} else {

$matches = [Regex]::Split($textInput, $delimiter) $matchCount = $matches.Length }

$counter = 0 if($parseExpressionSpecified) { $counter++ } for(; $counter lt $matchCount; $counter++) {

$propertyName = "None" $propertyType = [string]

## Parse by Expression if($parseExpressionSpecified) {

$propertyName = "Property$counter"

## Get the property name if($counter le $propertyNames.Length) {

if($propertyName[$counter 1]) { $propertyName = $propertyNames[$counter 1] } }

## Get the property value if($counter le $propertyTypes.Length) {

if($types[$counter 1]) { $propertyType = $propertyTypes[$counter 1] }

} } ## Parse by delimiter else {

$propertyName = "Property$($counter + 1)"

## Get the property name if($counter lt $propertyNames.Length) {

if($propertyNames[$counter]) { $propertyName = $propertyNames[$counter] } }

Example 57. ConvertTextObject.ps1 (continued)

## Get the property value if($counter lt $propertyTypes.Length) {

if($propertyTypes[$counter]) { $propertyType = $propertyTypes[$counter] } } }

AddNote $returnObject $propertyName ` ($matches[$counter] as $propertyType) }

$returnObject }

## Add a note to an object function AddNote ($object, $name, $value) {

$object | AddMember NoteProperty $name $value }

Main $input $parseExpression $propertyType $propertyName $delimiter

Generate Large Reports and Text Streams in Windows PowerShell

Problem

You want to write a script that generates a large report or large amount of data

Solution

The best approach to generating a large amount of data is to take advantage of PowerShell’s streaming behavior whenever possible. Opt for solutions that pipeline data between commands:

GetChildItem C:\ *.txt Recurse | OutFile c:\temp\AllTextFiles.txt

rather than collect the output at each stage:

$files = GetChildItem C:\ *.txt –Recurse $files | OutFile c:\temp\AllTextFiles.txt

If your script generates a large text report (and streaming is not an option), use the StringBuilder class:

$output = NewObject System.Text.StringBuilder

GetChildItem C:\ *.txt Recurse |

ForeachObject { [void] $output.Append($_.FullName + "`n") }

$output.ToString()

rather than simple text concatenation:

$output = "" GetChildItem C:\ *.txt Recurse | ForeachObject { $output += $_.FullName } $output

Discussion

In PowerShell, combining commands in a pipeline is a fundamental concept. As scripts and cmdlets generate output, PowerShell passes that output to the next command in the pipeline as soon as it can. In the solution, the GetChildItem commands that retrieve all text files on the C: drive take a very long time to complete. However, since they begin to generate data almost immediately, PowerShell can pass that data onto the next command as soon as the GetChildItem cmdlet produces it. This is true of any commands that generate or consume data and is called streaming. The pipeline completes almost as soon as the GetChildItem cmdlet finishes producing its data and uses memory very efficiently as it does so.

The second GetChildItem example (that collects its data) prevents PowerShell from taking advantage of this streaming opportunity. It first stores all the files in an array, which, because of the amount of data, takes a long time and enormous amount of memory. Then, it sends all those objects into the output file, which takes a long time as well.

However, most commands can consume data produced by the pipeline directly, as illustrated by the OutFile cmdlet. For those commands, PowerShell provides streaming behavior as long as you combine the commands into a pipeline. For commands that do not support data coming from the pipeline directly, the ForeachObject cmdlet (with the aliases of foreach and %) lets you to still work with each piece of data as the previous command produces it, as shown in the StringBuilder example.

Creating large text reports

When you generate large reports, it is common to store the entire report into a string, and then write that string out to a file once the script completes. You can usually accomplish this most effectively by streaming the text directly to its destination (a file or the screen), but sometimes this is not possible.

Since PowerShell makes it so easy to add more text to the end of a string (as in $output += $_.FullName), many initially opt for that approach. This works great for smalltomedium strings, but causes significant performance problems for large strings.

As an example of this performance difference, compare the following:

PS >MeasureCommand { >> $output = NewObject Text.StringBuilder

>> 1..10000 | >> ForeachObject { $output.Append("Hello World") } >> } >>

(...) TotalSeconds : 2.3471592

PS >MeasureCommand { >> $output = "" >> 1..10000 | ForeachObject { $output += "Hello World" } >> } >>

(...) TotalSeconds : 4.9884882

In the .NET Framework (and therefore PowerShell), strings never change after you create them. When you add more text to the end of a string, PowerShell has to build a new string by combining the two smaller strings. This operation takes a long time for large strings, which is why the .NET Framework includes the System.Text. StringBuilder class. Unlike normal strings, the StringBuilder class assumes that you will modify its data—an assumption that allows it to adapt to change much more efficiently.

Program: Query a SQL Data Source

It is often helpful to perform ad hoc queries and commands against a data source such as a SQL server, Access database, or even an Excel spreadsheet. This is especially true when you want to take data from one system and put it in another, or when you want to bring the data into your PowerShell environment for detailed interactive manipulation or processing.

Although you can directly access each of these data sources in PowerShell (through its support of the .NET Framework), each data source requires a unique and hard to remember syntax. Example 155 makes working with these SQLbased data sources both consistent and powerful.

Example 155. InvokeSqlCommand.ps1

############################################################################## ## ## InvokeSqlCommand.ps1 ## ## Return the results of a SQL query or operation ## ## ie:

##

##
## Use Windows authentication

##
InvokeSqlCommand.ps1 Sql "SELECT TOP 10 * FROM Orders"

##

##
## Use SQL Authentication

##
$cred = GetCredential

##
InvokeSqlCommand.ps1 Sql "SELECT TOP 10 * FROM Orders" Cred $cred

##

##
## Perform an update

##
$server = "MYSERVER"

##
$database = "Master"

##
$sql = "UPDATE Orders SET EmployeeID = 6 WHERE OrderID = 10248"

##
InvokeSqlCommand $server $database $sql

##

##
$sql = "EXEC SalesByCategory 'Beverages'"

##
InvokeSqlCommand Sql $sql

##

##
## Access an access database

##
InvokeSqlCommand (ResolvePath access_test.mdb) Sql "SELECT * from Users"

##

##
## Access an excel file

##
InvokeSqlCommand (ResolvePath xls_test.xls) Sql 'SELECT * from [Sheet1$]'

##

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

param( [string] $dataSource = ".\SQLEXPRESS", [string] $database = "Northwind", [string] $sqlCommand = $(throw "Please specify a query."), [System.Management.Automation.PsCredential] $credential

)

## Prepare the authentication information. By default, we pick ## Windows authentication $authentication = "Integrated Security=SSPI;"

## If the user supplies a credential, then they want SQL ## authentication if($credential) {

$plainCred = $credential.GetNetworkCredential() $authentication = ("uid={0};pwd={1};" f $plainCred.Username,$plainCred.Password) }

Example 155. InvokeSqlCommand.ps1 (continued)

## Prepare the connection string out of the information they ## provide $connectionString = "Provider=sqloledb; " +

"Data Source=$dataSource; " + "Initial Catalog=$database; " + "$authentication; "

## If they specify an Access database or Excel file as the connection ## source, modify the connection string to connect to that data source if($dataSource match '\.xls$|\.mdb$') {

$connectionString = "Provider=Microsoft.Jet.OLEDB.4.0; Data Source=$dataSource; "

if($dataSource match '\.xls$') { $connectionString += 'Extended Properties="Excel 8.0;"; '

## Generate an error if they didn't specify the sheet name properly if($sqlCommand notmatch '\[.+\$\]') {

$error = 'Sheet names should be surrounded by square brackets, and ' +

'have a dollar sign at the end: [Sheet1$]' WriteError $error return

} } }

## Connect to the data source and open it $connection = NewObject System.Data.OleDb.OleDbConnection $connectionString $command = NewObject System.Data.OleDb.OleDbCommand $sqlCommand,$connection $connection.Open()

## Fetch the results, and close the connection $adapter = NewObject System.Data.OleDb.OleDbDataAdapter $command $dataset = NewObject System.Data.DataSet [void] $adapter.Fill($dataSet) $connection.Close()

## Return all of the rows from their query $dataSet.Tables | SelectObject Expand Rows