Working with objects - PowerShell fundamentals - PowerShell in Depth, Second Edition (2015)

PowerShell in Depth, Second Edition (2015)

Part 1. PowerShell fundamentals

Chapter 7. Working with objects

This chapter covers

· Using objects in PowerShell

· Understanding object properties, methods, and events

· Working with objects in the pipeline

When you work with Windows PowerShell for, say, 10 minutes or so, you start to suspect that it isn’t quite the standard command-line interface it appears to be. Many administrators, relying on previous experience with shells like Cmd.exe or Bash, struggle to use PowerShell efficiently and effectively. The reason for this is that—despite PowerShell doing its best to disguise this fact—it’s an object-oriented shell, which is a significant difference from the text-based shells of yesterday. Wrapping your head around this paradigm shift, you can see that PowerShell’s object-oriented nature is crucial to using the shell effectively.

Note

PowerShell is object oriented but that doesn’t mean you have to become a programmer to use it. You need to learn enough about objects to get the most of the shell—enough to use the tool effectively.

Although Don’s proverb “PowerShell hates text” isn’t exactly true, it does give you an idea how important it is to embrace object-based operations over text-based operations. This is directly analogous to using SQL for set-based operations on a database as opposed to using a sequential programming technique. You use the tool in the best way by using it correctly.

7.1. Introduction to objects

If you have some experience with object-oriented programming, skip down to section 7.2. If you have no idea what “object oriented” means, or if you’re starting to get concerned that this is a programming book (believe us, it’s not), then definitely read this section before proceeding.

People make a big deal of objects when it comes to programming, although there’s no reason to. You’ve probably used a spreadsheet before—probably Microsoft Excel. If you have, then you’re completely prepared to deal with objects.

Open an Excel spreadsheet and type some column names into the first row. Perhaps you could use new user information, with columns named UserName, FirstName, LastName, Department, City, and Title. That’s the example we’ll go with, and you can see our spreadsheet in figure 7.1. We’ve also added some data rows underneath the first, and you should go ahead and do that, too.

Figure 7.1. Creating a simple database as an Excel spreadsheet

If you thought of this spreadsheet as a database, you wouldn’t be far off. It isn’t a complicated database, but it certainly stores data in a columnar format, which is how most databases are presented—the innards of the database require another book. It’d be better if you thought of this as adata structure, which is a more generic term than database. This data structure has some visible features associated with it. There are six columns, for example, and five rows (the first row, which contains column names, doesn’t count). Each row contains data for each of the six columns.

We could’ve put this information into lots of other data structures. For example, we could’ve put it into a SQL Server database, which would look visually similar, although it’d be physically quite different if you looked at how the data was saved to disk. The point is that they’re both data structures. It’s true that they use different names. For example, what Excel calls a column could be called either a column or a domain in SQL Server; what Excel refers to as a row would be called a row, a tuple, or an entity in SQL Server. Excel has a sheet, whereas SQL Server would have atable. The names are only names, and they don’t affect what’s being stored in those structures.

Objects, it turns out, are another kind of data structure. As with Excel or SQL Server, you don’t ever get to see how objects physically store their data, nor do you need to. You only need to know that objects are a way of storing data, usually in memory, so that you can work with the data. Objects use a slightly different terminology than Excel or SQL Server:

· Excel stores a bunch of things on sheets, and SQL Server stores them in tables. In object lingo, a bunch of things is called a collection.

· Excel uses a row to represent a single thing, such as a user in our example. In object-speak, a row is an object.

· In Excel and SQL Server, you have columns to store the individual bits of data about a thing. In the world of objects, they’re called properties.

Try to mentally visualize objects as looking like an Excel spreadsheet, with some minor changes in terminology. Instead of a sheet containing rows and columns, you have a collection consisting of objects, which have properties.

At this point, most object tutorials will indulge in a noncomputer, real-world analogy, and we’re obligated by tradition to do the same. Let’s say you wander onto a used car lot—you’re standing in the middle of a collection of car objects. It’s worth nothing that objects come in many differenttypes. For example, a car object would look entirely different from a television object. All of those car objects have various properties, which describe the objects: color, number of doors, type of engine, and so forth. Some of these properties you can change, such as the color, and some you can’t, such as the manufacturer. Thus some properties you can read and write and some are read-only. All this will become more relevant when you start working with PowerShell objects. But for now you can imagine making a spreadsheet, with columns for the color, doors, and so on, and with each row representing a single car on the lot.

7.2. Members: properties, methods, and events

It’s obvious that objects have a lot of things associated with them, and this is where the spreadsheet analogy will start to break down, so we’ll stick with cars. And maybe televisions, because who doesn’t like a nice TV show now and again?

Consider some of the things you might use to describe a television or a car; these are the properties of the objects. Obviously, each different type of object will have a different set of properties, so one of the things you’ll always want to keep in mind is the type name of the object you’re working with. Table 7.1 provides some examples.

Table 7.1. Example properties for a car type and for a television type

TypeName: Car

TypeName: Television

Manufacturer

Manufacturer

Model

Model

Color

Size

EngineType

Resolution

Length

CurrentChannel

Both of these types of objects can perform various actions, which in the world of objects are called methods. Specifically, a method is something that you can tell an object to do or have done to it. Thinking in terms of cars and televisions, look at table 7.2 for some examples.

Table 7.2. Example methods for a car type and for a television type

TypeName: Car

TypeName: Television

Turn

ChangeChannel

Accelerate

PowerOn

Brake

PowerOff

DeployAirbags

RaiseVolume

Sell

LowerVolume

In the world of Windows, consider a service. What kinds of actions can a service take? You can stop them, start them, pause them (sometimes), resume them (from pause), and so on. Therefore, if you were looking at an object of the type Service, you might expect to find methods named Start, Stop, Pause, Resume, and so on.

Collectively, the properties and methods of an object are referred to as its members, as if the object type is some kind of exclusive country club and the properties and methods belong to it.

There’s one other type of member, called events. You don’t work with events in PowerShell a whole lot, but you’ll see them, so we want you to know what they’re for.

Note

Their lack of use isn’t a PowerShell deficiency, because you’ll find many good cmdlets for working with WMI-, .NET-, and PowerShell-related events. We’ll cover these more in later chapters. Based on our experiences, most administrators haven’t explored working with events, which is somewhat complicated and often drifts into the world of .NET or systems programming. The adoption of PowerShell is a bit like the exploration of an unknown area. The pioneers push into unknown territory, such as events, whereas the bulk of the population slowly follows the trails they’ve created. Events are on the fringes of explored territory for most IT pros.

Basically, an event is a notification from the object to you that something has happened. A car object might have a “Crashed” event, letting you know that something bad happened. A service object might have a “FinishedStarting” event, letting you know that it was done starting. When you use events, you’re simply writing commands that you want to run in response to the event—that is, when the car crashes, run the command to call for emergency services. Or sometimes you just want to see the notification.

PowerShell has a convenient way of showing you the members of an object: the Get-Member cmdlet. You should be using this cmdlet so much that you get tired of typing Get-Member and want to use the shorter alias, gm, instead. Go right ahead—this is going to be an important part of your life with PowerShell, so you should become familiar with it right away. To use Get-Member, run any command that creates output. That output goes into PowerShell’s pipeline, and that output is always in the form of objects. You can pipe those objects to gm to see what members the objects have. Here’s an example:

PS C:\> Get-Service | Get-Member

TypeName: System.ServiceProcess.ServiceController

Name MemberType Definition

---- ---------- ----------

Name AliasProperty Name = ServiceName

RequiredServices AliasProperty RequiredServices = ServicesDepen...

Disposed Event System.EventHandler Disposed(Sys...

Close Method void Close()

Continue Method void Continue()

CreateObjRef Method System.Runtime.Remoting.ObjRef C...

Dispose Method void Dispose(), void IDisposable...

Equals Method bool Equals(System.Object obj)

ExecuteCommand Method void ExecuteCommand(int command)

GetHashCode Method int GetHashCode()

GetLifetimeService Method System.Object GetLifetimeService()

GetType Method type GetType()

InitializeLifetimeService Method System.Object InitializeLifetime...

Pause Method void Pause()

Refresh Method void Refresh()

Start Method void Start(), void Start(string[...

Stop Method void Stop()

WaitForStatus Method void WaitForStatus(System.Servic...

CanPauseAndContinue Property bool CanPauseAndContinue {get;}

CanShutdown Property bool CanShutdown {get;}

CanStop Property bool CanStop {get;}

Container Property System.ComponentModel.IContainer...

DependentServices Property System.ServiceProcess.ServiceCo...

DisplayName Property string DisplayName {get;set;}

MachineName Property string MachineName {get;set;}

ServiceHandle Property System.Runtime.InteropServices.S...

ServiceName Property string ServiceName {get;set;}

ServicesDependedOn Property System.ServiceProcess.ServiceCon...

ServiceType Property System.ServiceProcess.ServiceTyp...

Site Property System.ComponentModel.ISite Site...

Status Property System.ServiceProcess.ServiceCon...

ToString ScriptMethod System.Object ToString();

You can see all of the members in this output:

· Properties, which come in several variations, like AliasProperty and plain-old Property properties. Functionally, there’s no difference in how you use any of them, so we’ll generically refer to them as properties.

· Methods, like Start, Stop, Pause, and so on. Don’t worry about trying to run these methods now. When the time comes, hopefully there will be cmdlets such as Stop-Service you can run that will wrap up the method.

· Events—well, one event—like Disposed. We have no idea what this does. Okay, we do but for our purposes you can ignore it. A lot of this information comes from the .NET Framework so more is exposed than most IT pros care to see.

The important, and easy-to-overlook, information is the TypeName, which in this case is System.ServiceProcess.ServiceController. You can punch that entire TypeName into an internet search engine to find Microsoft’s detailed documentation on this kind of object, which is where you’d go if you wanted to figure out what Disposed is for.

Property types

As you explore different objects with Get-Member, you’re likely to come across a number of property types. These will be listed under MemberType. Items that are Property should be what you find when reading the MSDN documentation for that object type. Some of these property names aren’t necessarily intuitive for an IT pro, so PowerShell or the cmdlet developer often adds an AliasProperty. This is an alternative for the “official” property name. For example, the members of Get-Service show an AliasProperty of Name. When you use that property name, PowerShell will “redirect” to the original property name of ServiceName. Most Windows admins would think of the Name of a service and not a ServiceName.

You may also come across ScriptProperty. This is another PowerShell-added property that uses a PowerShell command to calculate a property value. A NoteProperty is a static property name, often added by Select-Object or Add-Member. Finally, you might also seePropertySet. Think of this as a prepackaged bundle of properties. These are defined by PowerShell or cmdlet developers—for example, a process object as a PSResources property set. So instead of typing

Get-Process | Select Name,ID,HandleCount,WorkingSet,PagedMemorySize,

PrivateMemorySize,VirtualMemorySize,TotalProcessorTime

you can simply type

Get-Process | select PSResources

Note

Microsoft updates its documentation as new versions of .NET are released, but be careful to match the documentation version to the version of .NET you’re using. How do you know that? Type $psversiontable at a PowerShell prompt and use the first two numbers given in theCLRVersion property. On a system using PowerShell v2, expect something like 2.0. For PowerShell v3 or v4 you should see a version starting with 4.0.

Get-Member is even smart enough to deal with multiple types of objects at once. For example, when you run Dir, you’re potentially producing both Directory and File objects. They’re similar but not exactly the same. A Directory, for example, won’t have some of the data that aFile would have, such as a length (size in bytes).

PS C:\windows> dir | get-member

TypeName: System.IO.DirectoryInfo

Name MemberType Definition

---- ---------- ----------

Mode CodeProperty System.String Mode{get=Mode;}

Create Method System.Void Create(System.Secur...

CreateObjRef Method System.Runtime.Remoting.ObjRef ...

CreateSubdirectory Method System.IO.DirectoryInfo CreateS...

Delete Method System.Void Delete(), System.Vo...

...

PSChildName NoteProperty System.String PSChildName=ADWS

PSDrive NoteProperty System.Management.Automation.PS...

PSIsContainer NoteProperty System.Boolean PSIsContainer=True

PSParentPath NoteProperty System.String PSParentPath=Micr...

PSPath NoteProperty System.String PSPath=Microsoft....

PSProvider NoteProperty System.Management.Automation.Pr...

Attributes Property System.IO.FileAttributes Attrib...

CreationTime Property System.DateTime CreationTime {g...

CreationTimeUtc Property System.DateTime CreationTimeUtc...

Exists Property System.Boolean Exists {get;}

Extension Property System.String Extension {get;}

FullName Property System.String FullName {get;}

LastAccessTime Property System.DateTime LastAccessTime ...

LastAccessTimeUtc Property System.DateTime LastAccessTimeU...

LastWriteTime Property System.DateTime LastWriteTime {...

LastWriteTimeUtc Property System.DateTime LastWriteTimeUt...

Name Property System.String Name {get;}

Parent Property System.IO.DirectoryInfo Parent ...

Root Property System.IO.DirectoryInfo Root {g...

BaseName ScriptProperty System.Object BaseName {get=$th...

TypeName: System.IO.FileInfo

Name MemberType Definition

---- ---------- ----------

Mode CodeProperty System.String Mode{get=Mode;}

AppendText Method System.IO.StreamWriter AppendTe...

CopyTo Method System.IO.FileInfo CopyTo(strin...

Create Method System.IO.FileStream Create()

CreateObjRef Method System.Runtime.Remoting.ObjRef ...

...

PSChildName NoteProperty System.String PSChildName=bfsvc...

PSDrive NoteProperty System.Management.Automation.PS...

PSIsContainer NoteProperty System.Boolean PSIsContainer=False

PSParentPath NoteProperty System.String PSParentPath=Micr...

PSPath NoteProperty System.String PSPath=Microsoft....

PSProvider NoteProperty System.Management.Automation.Pr...

Attributes Property System.IO.FileAttributes Attrib...

CreationTime Property System.DateTime CreationTime {g...

CreationTimeUtc Property System.DateTime CreationTimeUtc...

Directory Property System.IO.DirectoryInfo Directo...

DirectoryName Property System.String DirectoryName {get;}

Exists Property System.Boolean Exists {get;}

Extension Property System.String Extension {get;}

FullName Property System.String FullName {get;}

IsReadOnly Property System.Boolean IsReadOnly {get;...

LastAccessTime Property System.DateTime LastAccessTime ...

LastAccessTimeUtc Property System.DateTime LastAccessTimeU...

LastWriteTime Property System.DateTime LastWriteTime {...

LastWriteTimeUtc Property System.DateTime LastWriteTimeUt...

Length Property System.Int64 Length {get;}

Name Property System.String Name {get;}

BaseName ScriptProperty System.Object BaseName {get=if ...

VersionInfo ScriptProperty System.Object VersionInfo {get=...

Note

We removed some of the command’s output to save space. We may do that from time to time when the output isn’t germane to the discussion, but we’ll stick in an ellipsis (...) so that you’ll know we left some stuff out.

Keep this trick in mind: Any command that produces output can be piped to Get-Member to see what members that output had. But once you’ve done this, your output is removed and replaced with Get-Member’s own output. In other words, Get-Member usually needs to be the last thing on the command line, because piping its output to something else doesn’t usually make sense or do anything useful (Select-Object is sometimes useful if you need the member names, for instance).

Sometimes, PowerShell lies, but only for good. For example, take a look at the first few lines of output created by Get-Process:

PS C:\> get-process

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

------- ------ ----- ----- ----- ------ -- -----------

87 8 2208 7780 79 1.06 1100 conhost

33 5 980 3068 46 0.02 1820 conhost

30 4 828 2544 41 0.00 2532 conhost

Guess what? There’s no property named “NPM(K).” Of those eight columns, only Handles, Id, and ProcessName have the correct column headers. The rest of them were created by PowerShell for display purposes, but they’re not the real property names. So, if you wanted to work with the information in those columns, you’d need to find the property names. Remember that you can use any property name you see from Get-Member in cmdlets like Where-Object and Select-Object. Don’t assume a command’s default output is all there is to the object or that those are the actual property names.

Note

Go ahead and open PowerShell and run Get-Process | Get-Member. See if you can identify the properties that were used to create those other five columns.

7.3. Sorting objects

Once you know the properties that an object contains, you can start to have fun with those objects. For example, by default Get-Process produces a list that’s sorted by process name. What if you wanted to sort the list by Virtual Memory size instead?

This is where PowerShell’s object orientation proves to be vastly superior to older text-based shells. In a Unix operating system, for example, you’d have to do some fancy text manipulation. You’d need to know that the Virtual Memory column started at character 27 and went on for five character columns. If the output of the command ever changed, you’d be out of luck and would have to rewrite all your commands that depended on Virtual Memory being in characters 27 through 31. In PowerShell, you don’t need to worry about it. Because the data isn’t text until the command has finished running, you can take advantage of the flexibility of the object data structure and run something like the following:

PS C:\> Get-Process | Sort-Object -Property vm

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

------- ------ ----- ----- ----- ------ -- -----------

0 0 0 24 0 0 Idle

486 0 108 304 3 4 System

29 2 348 1020 5 0.05 228 smss

48 4 824 2688 14 0.02 1408 svchost

144 8 2284 4068 18 0.02 484 lsm

68 6 1356 4244 29 0.05 2828 svchost

261 18 3180 7424 31 0.16 712 svchost

233 13 3848 7720 34 1.15 468 services

96 13 2888 4868 34 0.00 1352 ismserv

125 13 2324 5712 35 0.05 1468 dfssvc

We hope you used Get-Member to discover that the “VM(M)” column is being produced from the VM property (if you didn’t, here’s a hint: look at the members with a member type of AliasProperty). We took the output of Get-Process and passed the information to Sort-Object. The Sort-Object cmdlet has a parameter, -Property, that lets you specify one or more properties—that is, columns—on which to sort. As you can see from the first few lines of output, it’s now sorting on the VM property. Note that sorting is in ascending order by default; if you want descending order, there’s another parameter for that:

PS C:\> Get-Process | Sort-Object -Property vm –Descending

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

------- ------ ----- ----- ----- ------ -- -----------

256 38 88116 74268 692 1.89 2188 powershell_ise

403 21 56032 54320 566 2.31 2656 powershell

248 39 38920 35684 545 0.84 1248 Microsoft.Acti...

146 24 29336 21924 511 0.37 164 PresentationFo...

985 43 19344 35604 387 8.52 848 svchost

1118 103 21460 27752 358 2.87 476 lsass

Now, we have to be honest and tell you that you won’t see most people run the command that way. They’ll usually use aliases instead of the full cmdlet names. They’ll often know that the –Property parameter is positional, meaning you don’t have to type the parameter name as long as your list of sort properties appears immediately after the cmdlet name or alias. When you type less, you can specify the descending option, because it’s the only parameter that starts with the letters “desc.” In other words, the following is more common:

PS C:\> Get-Process | sort vm –desc

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

------- ------ ----- ----- ----- ------ -- -----------

256 38 88116 74268 692 1.89 2188 powershell_ise

400 21 86920 87024 567 3.32 2656 powershell

248 39 38920 35684 545 0.84 1248 Microsoft.Acti...

146 24 29336 21924 511 0.37 164 PresentationFo...

998 44 19536 35712 389 8.52 848 svchost

Keep in mind that the property name—vm, in this case—can’t be shortened in any way. The shortening bit only applies to parameter names, not their values. Although you didn’t type the parameter name (it’s –Property), vm is still being passed to that parameter as a value. You can tell because it’s vm and not –vm.

7.4. Selecting objects

The next cmdlet we’ll discuss is Select-Object. This cmdlet can do several distinct things, and it can do some of them at the same time. Because we find that newcomers to PowerShell get very confused about this command’s functionality, we recommend you pay close attention to what we’re describing.

7.4.1. Use 1: choosing properties

Select-Object includes a –Property parameter, which accepts a comma-separated list of properties that you want to display. You use this to override the default display for that object type. PowerShell will still control how this information is formatted (we’ll show you how to control formatting in chapter 9).

Tip

Remember, if you’re curious about what properties are available to be selected, pipe the object to Get-Member. Don’t assume that the formatted column headers you may have seen are the property names.

PS C:\> Get-Process | Select-Object -Property Name,ID,VM,PM

Name Id VM PM

---- -- -- --

conhost 1100 82726912 2166784

conhost 1820 48480256 1003520

conhost 2532 42979328 847872

csrss 324 45211648 2027520

csrss 372 74035200 31252480

dfsrs 1288 363728896 14540800

dfssvc 1468 36069376 2326528

As you can see from those first few lines of output, you got exactly the “columns” (remember our spreadsheet example?) you asked for. Also note that PowerShell isn’t terribly sensitive about case—it displayed “Id” even though you typed it as “ID”.

Here’s something interesting about Select-Object:

PS C:\> Get-Process | Select Name,ID,VM,PM | Get-Member

TypeName: Selected.System.Diagnostics.Process

Name MemberType Definition

---- ---------- ----------

Equals Method bool Equals(System.Object obj)

GetHashCode Method int GetHashCode()

GetType Method type GetType()

ToString Method string ToString()

Id NoteProperty System.Int32 Id=1100

Name NoteProperty System.String Name=conhost

PM NoteProperty System.Int32 PM=2162688

VM NoteProperty System.Int32 VM=82726912

Tip

Select-Object can be shortened to its alias, Select. Its –Property parameter is positional (like it was for Sort), so in that last example you provided the list of properties in the correct position.

After Select-Object runs, the objects it sends to the pipeline have only the properties you specified. Everything else is gone. You can see that the objects’ type names have changed too, indicating that they’ve been “Selected.” That’s a cue to you that this isn’t a complete process object; it’s a subset of information that would normally be available. This behavior creates some interesting problems for newbies. Can you tell the difference between these two commands?

1. Get-Process | Select Name,Id,PM,NPM | Sort VM –Descending

2. Get-Process | Sort VM –Descending | Select Name,Id,PM,NPM

Here’s the difference:

1. The process objects are generated first. Then, you’re selecting a subset of their columns (that is, properties), including only Name, ID, PM, and NPM. Only those four properties will exist in the output. Yet the final command is trying to sort on the VM property, which wasn’t one of the ones you picked. This command will run without error, but you won’t get the results you were expecting.

2. The process objects are generated first, and then you sort them on the VM property. This will work, because at this stage the objects still have a VM property. Then you choose the properties you want to see.

These two examples illustrate how important it is to think about what each command is doing, about what each command is outputting, and about what the next command is going to do with that output—think in pipelines, not commands. This is another situation where Get-Member is invaluable. If you run a command and don’t get the result you expect, break your command into small commands and pipe each part to Get-Member so you can verify exactly what type of object PowerShell is writing to the pipeline.

7.4.2. Use 2: choosing a subset of objects

The other, and almost completely unrelated, thing that Select-Object can do is select a subset of “rows” or objects. It can select a chunk of objects either from the beginning of the set, or from the end, or even from the middle. But that’s it.

Note

This is a big “gotcha” for newcomers. Select-Object doesn’t apply any intelligence when it’s grabbing a chunk of rows. It’s either “the first 10,” or “the last 5,” or something like that. It doesn’t care about the data—it’s merely counting off a specified number of rows. That’s it.

In this example, you grab the five processes that are using the most virtual memory:

PS C:\> Get-Process | sort VM -Descending | select -first 5

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

------- ------ ----- ----- ----- ------ -- -----------

256 38 88116 74268 692 1.89 2188 powershell_ise

483 21 69624 70160 567 3.87 2656 powershell

248 38 38880 35648 543 0.84 1248 Microsoft.Acti...

146 24 29336 21924 511 0.37 164 PresentationFo...

993 43 19352 35620 387 8.52 848 svchost

Or maybe you’d like to see the five using the least amount of paged memory:

PS C:\> Get-Process | sort PM -Descending | select -last 5

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

------- ------ ----- ----- ----- ------ -- -----------

30 4 828 2544 41 0.00 2532 conhost

48 4 824 2688 14 0.02 1408 svchost

29 2 348 1020 5 0.05 228 smss

485 0 108 304 3 4 System

0 0 0 24 0 0 Idle

Or perhaps—and this gets tricky—you want to see the five biggest consumers of paged memory, skipping the top three:

PS C:\> Get-Process | sort PM -Descending | select -skip 3 -first 5

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

------- ------ ----- ----- ----- ------ -- -----------

525 21 69980 70776 567 4.10 2656 powershell

248 39 38928 35684 545 0.84 1248 Microsoft.Acti...

606 42 32704 39540 192 1.98 1788 explorer

206 14 30520 19504 71 1.40 372 csrss

146 24 29336 21924 511 0.37 164 PresentationFo...

Keep in mind that it doesn’t matter in which order you specify the parameters to Select-Object; it will always execute –Skip first and then grab whatever –First or -Last you specify.

When used in this context, Select-Object writes the original object to the pipeline. So in this example if you piped the command to Get-Member you’d see PowerShell is writing System.Diagnostics.Process to the pipeline.

Note

We want to remind you again that –First and –Last don’t care about the data in your objects. They’re only grabbing the “top five consumers of memory” because you’d first sorted them on that data. After sorting you need to blindly grab the first five or last five or whatever—the first or last “n” that are presented to Select-Object by the pipeline. That’s the extent of Select-Object’s ability to filter out some of the objects you’ve produced.

An important change was made to Select-Object starting in PowerShell v3. In PowerShell v2, when you selected, say, the first 5 objects, PowerShell continued to get all the remaining objects. For small data sets this was no big deal. But if your command was returning 5,000 objects and you just wanted the first 5, you had to wait until all 5,000 were processed—hardly performance friendly. Starting in v3, once PowerShell gets the first or last X number of objects, it stops processing, which means a much faster performing expression. If for some reason you’d like to revert to the v2 approach, use the –Wait parameter. If you don’t believe us, you can test this for yourself with Measure-Command.

PS C:\> measure-command {1..5000 | select -first 5 -wait}

Days : 0

Hours : 0

Minutes : 0

Seconds : 0

Milliseconds : 98

Ticks : 983903

TotalDays : 1.13877662037037E-06

TotalHours : 2.73306388888889E-05

TotalMinutes : 0.00163983833333333

TotalSeconds : 0.0983903

TotalMilliseconds : 98.3903

PS C:\> measure-command {1..5000 | select -first 5}

Days : 0

Hours : 0

Minutes : 0

Seconds : 0

Milliseconds : 0

Ticks : 6005

TotalDays : 6.95023148148148E-09

TotalHours : 1.66805555555556E-07

TotalMinutes : 1.00083333333333E-05

TotalSeconds : 0.0006005

TotalMilliseconds : 0.6005

In the first command, which simulates the v2 approach, it took 98 milliseconds. But the optimized approach introduced in v3 only took .6 milliseconds. That’s a performance gain we can all get behind.

7.4.3. Use 3: making custom properties

This is a super-cool feature: Select-Object’s –Property parameter accepts a combination of property names, which you’ve seen us do, and custom properties, which are properties you define on the fly using a special syntax. We’ll admit up front that the syntax is kind of ugly and involves a lot of punctuation, but it’s worth learning. Let’s start with a one-line example:

PS C:\> Get-Process | Select –Property

Name,ID,@{name="TotalMemory";expression={$_.PM + $_.VM}}

Name Id TotalMemory

---- -- -----------

conhost 1100 84889600

conhost 1820 49483776

conhost 2532 43827200

csrss 324 47239168

Here you’re creating a new property called TotalMemory. The value for this property comes from adding the PM and VM properties of each object in the collection.

Hmm, looking at this it turns out the VM and PM properties are in bytes. You’re used to seeing them in kilobytes and megabytes, because PowerShell’s default output obviously tweaks them. Because you’re working with the raw values, you’ll have to do some math. The following is a one-line command that wraps on the printed page:

PS C:\> Get-Process | Select -Property

Name,ID,@{Name="TotalMemory(M)";Expression={($_.PM + $_.VM) /

1MB -as [int]}}

Name Id TotalMemory(M)

---- -- --------------

conhost 1100 81

conhost 1820 47

conhost 2532 42

csrss 324 45

csrss 372 100

dfsrs 1288 361

Note

Keep your math skills in mind. You needed to enclose the addition operation in parentheses to force it to occur first. Otherwise, math rules dictate that PowerShell run $_.VM / 1MB first and then add $_.PM, which would still be in bytes. Also, remember that converting the value to an integer causes it to be rounded to the nearest integer.

Okay, so what the heck is all that?

· The structure starting with an @ sign is called a hash table (also referred to as dictionaries or associative arrays). Hash tables consist of one or more key-value pairs. In this case, we’ve used two pairs. Each pair is separated by a semicolon. See the About_Hash_Tables help topic andchapter 16.

· The first key is Name, and it’s a key that Select-Object has been designed to look for. We didn’t make this up—it’s listed in the examples in the help file for Select-Object. The value that goes along with this key is what you want to appear in the column header for your new custom property.

· The second key is Expression, and we didn’t make that up, either. Again, it’s a special key that the command is hardcoded to look for. Its value goes in curly brackets, and everything inside the curly brackets is run by PowerShell to create the value for this column for each row. You can have as much PowerShell code as you need between the curly braces.

· The $_ is a placeholder that PowerShell looks for in special situations. This is one of those situations. PowerShell will replace $_ with whatever object is in the row that’s currently being produced. So, $_ will represent one process object at a time. Because this placeholder trips up many people, in PowerShell v3 and later you can also use $psitem in its place and achieve the same result.

· You don’t want to work with the entire process object—it has more than 65 properties! You only want to work with one of those properties at a time. In other words, you want to work with a piece, or a fraction, of the object. In math, what character indicates a fraction? A decimal point! So you follow $_ with a period, or decimal point, to indicate that you’re going to specify the portion of the object you want. In the first case, it’s the PM property, and in the second it’s the VM property.

· Each @ structure represents a single property in your output. You can specify as many of those structures as you want, as part of the comma-separated property list, to create additional columns.

One use for this trick is to come up with new column names that you like better than the originals:

PS C:\> Get-Process | Select -Property Name,ID,

@{Name="VirtMem";Expression={$psitem.vm}},

@{Name="PhysMem";Expression={$psitem.pm}}

Name Id VirtMem PhysMem

---- -- ------- -------

conhost 1100 82726912 2166784

conhost 1820 48480256 1003520

conhost 2532 42979328 847872

csrss 324 45211648 2027520

In this example we opted for the newer $psitem instead of $_ to indicate the current object in the pipeline. As with all other cases where you use Select-Object and its -Property parameter, the output objects contain only the properties you specified:

PS C:\> Get-Process | Select -Property Name,ID,

@{Name="VirtMem";Expression={$psitem.vm}},

@{Name="PhysMem";Expression={$psitem.pm}} | get-member

TypeName: Selected.System.Diagnostics.Process

Name MemberType Definition

---- ---------- ----------

Equals Method bool Equals(System.Object obj)

GetHashCode Method int GetHashCode()

GetType Method type GetType()

ToString Method string ToString()

Id NoteProperty System.Int32 Id=1100

Name NoteProperty System.String Name=conhost

PhysMem NoteProperty System.Int32 PhysMem=2166784

VirtMem NoteProperty System.Int32 VirtMem=82726912

You need to remember the following about these custom properties:

· The key Name can be replaced with n or N as a shortcut. You could also use Label, L or l (the letter L). We don’t like using a lowercase L all by itself, because it’s easily mistaken for the number 1. If you do abbreviate use L.

· The key expression can be replaced with e or E as a shortcut.

· Use $_ or $psitem to reference the current object in the pipeline.

Note

Why does PowerShell let you use name, n, label, or l? In v1 of PowerShell, some cmdlets accepted custom properties and required Name. Other cmdlets that did the same thing used Label instead. It made no sense, so beginning with v2 Microsoft let all of those cmdlets use any of those keys.

7.4.4. Use 4: extracting and expanding properties

Okay, this will get a bit cerebral, but bear with us while we walk you through some examples. We’re going to focus on exclusively using the –ExpandProperty parameter of Select-Object for all of these.

Extracting Property Values

First, let’s suppose you have a bunch of computers in Active Directory. Hopefully that isn’t too hard to imagine. Now, let’s say you want to get a list of running processes from every computer in the WebFarm organizational unit (OU) of the company.pri domain.

Note

In these examples we’re assuming you’re using the Microsoft AD cmdlets, which aren’t part of PowerShell out of the box but must be added separately, usually by installing Remote Server Administration Tools (RSAT). If that’s not the case, you’ll need to modify them to fit with your toolset.

You run the following to get the computers themselves:

Get-ADComputer –Filter * -SearchBase "ou=WebFarm,dc=company,dc=pri"

This command produces a bunch of computer objects, each of which will have a Name property containing the computer’s name. The Get-Process cmdlet accepts multiple computer names on its –ComputerName parameter, so in theory it might seem like you could do this:

Get-Process –computerName (

Get-ADComputer –Filter * -SearchBase "ou=WebFarm,dc=company,dc=pri"

)

The problem is, Get-ADComputer produces objects of the type Computer, whereas the -ComputerName parameter wants input of the type String (it says so in the command’s help file). So that won’t work. Instead, you need to extract only the contents of the Name property from those computers, and that’s where the –ExpandProperty parameter comes in:

Get-Process –computerName (

Get-ADComputer –Filter * -SearchBase "ou=WebFarm,dc=company,dc=pri" |

Select-Object –ExpandProperty Name

)

Note

We’re writing these commands in a more formatted style to make them more readable in the book. You can type them exactly as they’re written (press Enter on a blank line when you’re finished to run them), or type them all into a single line. It’s your choice.

When PowerShell executes this expression, the command within the parentheses is evaluated first. This expression is getting all computer objects from the WebFarm organizational unit and expanding the Name property. This has the effect of writing a collection of strings, such as the computer name, to the pipeline, instead of a bunch of computer objects. Using -ExpandProperty is also a handy technique when you want to save a property value to a variable.

For example, let’s say you have some code to use the DisplayName property from a service object. This works, but it’s a little complicated:

PS C:\> $svc = Get-Service spooler | select displayname

PS C:\> Write-Host "Checking $($svc.Displayname)"

Checking Print Spooler

The Get-Service cmdlet wrote a service object to $svc so you need to use a subexpression to access the DisplayName property. Or you can expand the property:

PS C:\> $svc = Get-Service spooler | Select -ExpandProperty Displayname

PS C:\> Write-Host "Checking $svc"

Checking Print Spooler

Depending on your situation this might be easier to understand. You can expand only a single property, but you can do it for a bunch of objects. This is a great way of creating an array of simple values.

PS C:\> $sources = Get-Eventlog system -Newest 1000 |

select -Unique -ExpandProperty Source

PS C:\> $sources | sort | select -first 10

BTHUSB

DCOM

disk

EventLog

HTTP

Microsoft-Windows-Dhcp-Client

Microsoft-Windows-DHCPv6-Client

Microsoft-Windows-DNS-Client

Microsoft-Windows-DriverFrameworks-UserMode

Microsoft-Windows-FilterManager

With this technique, $sources is a collection of strings and not eventlog objects.

Starting in PowerShell v3, you can also take a shortcut and have PowerShell implicitly expand a property:

PS C:\> (Get-Service m*).Displayname

Multimedia Class Scheduler

Windows Firewall

Distributed Transaction Coordinator

Microsoft iSCSI Initiator Service

Windows Installer

Microsoft Keyboard Filter

This is much easier to type than:

PS C:\> Get-Service m* | select -ExpandProperty Displayname

Multimedia Class Scheduler

Windows Firewall

Distributed Transaction Coordinator

Microsoft iSCSI Initiator Service

Windows Installer

Microsoft Keyboard Filter

Service names

Services have a Name property and a DisplayName property. The two are usually different. Consider this example:

(Get-Service m*).Displayname

You’re filtering on Name but listing DisplayName. The differences between Name and DisplayName are easily shown:

PS C:\> Get-Service m* | Format-Table Name, DisplayName -AutoSize

Name DisplayName

---- -----------

MMCSS Multimedia Class Scheduler

MpsSvc Windows Firewall

MSDTC Distributed Transaction Coordinator

MSiSCSI Microsoft iSCSI Initiator Service

msiserver Windows Installer

MsKeyboardFilter Microsoft Keyboard Filter

When working with Services, just be careful which type of name you’re specifying.

although it’s perhaps not necessarily easier to understand, especially if you’re new to PowerShell. But when used interactively in the console, you can get the same results with a single command that previously took several steps:

PS C:\> (Get-Eventlog system -Newest 1000).Source | sort | Get-Unique |

select -first 10

BTHUSB

DCOM

disk

EventLog

HTTP

Microsoft-Windows-Dhcp-Client

Microsoft-Windows-DHCPv6-Client

Microsoft-Windows-DNS-Client

Microsoft-Windows-DriverFrameworks-UserMode

Microsoft-Windows-FilterManager

If you use syntax like this in a PowerShell script, be sure to clearly document the expression.

Expanding collections

Sometimes, you’ll find that a property is a collection of other objects. For example, the DependentServices property of a service object is a collection of other services:

PS C:\> Get-Service | Select-Object -Property Name,DependentServices

Name DependentServices

---- -----------------

ADWS {}

AeLookupSvc {}

ALG {}

AppIDSvc {}

Appinfo {}

AppMgmt {}

AudioEndpointBuilder {AudioSrv}

AudioSrv {}

BFE {SharedAccess, RemoteAccess, Polic...

These collections get printed in curly brackets as you see here, but you can use -ExpandProperty to “expand” them into their full, stand-alone objects. This is often useful when you’re getting a single top-level object, such as getting the BFE service (which appears to have several dependent services):

PS C:\> Get-Service -Name BFE | Select -Expand DependentServices

Status Name DisplayName

------ ---- -----------

Running WdNisSvc Windows Defender Network Inspection...

Running WdNisDrv Windows Defender Network Inspection...

Stopped SharedAccess Internet Connection Sharing (ICS)

Stopped RemoteAccess Routing and Remote Access

Running PolicyAgent IPsec Policy Agent

Stopped NcaSvc Network Connectivity Assistant

Running MpsSvc Windows Firewall

Stopped IKEEXT IKE and AuthIP IPsec Keying Modules

Now you can see the contents of the DependentServices property. Those contents are themselves services, so they have the same familiar-looking output.

You can also use the enumeration trick we showed earlier:

PS C:\> (Get-Service BFE).DependentServices

Status Name DisplayName

------ ---- -----------

Running WdNisSvc Windows Defender Network Inspection...

Running WdNisDrv Windows Defender Network Inspection...

Stopped SharedAccess Internet Connection Sharing (ICS)

Stopped RemoteAccess Routing and Remote Access

Running PolicyAgent IPsec Policy Agent

Stopped NcaSvc Network Connectivity Assistant

Running MpsSvc Windows Firewall

Stopped IKEEXT IKE and AuthIP IPsec Keying Modules

You can even continue the enumeration to the next level:

PS C:\> (Get-Service BFE).DependentServices.Displayname

Windows Defender Network Inspection Service

Windows Defender Network Inspection System Driver

Internet Connection Sharing (ICS)

Routing and Remote Access

IPsec Policy Agent

Network Connectivity Assistant

Windows Firewall

IKE and AuthIP IPsec Keying Modules

7.4.5. Use 5: choosing properties and a subset of objects

You can also combine the “first or last” functionality with the ability to pick the columns (properties) that you want:

PS C:\> Get-Process | sort PM -Descending |

select -Skip 3 -First 5 -Property name,id,pm,vm

Name Id PM VM

---- -- -- --

powershell 2656 72245248 594939904

Microsoft.Activ... 1248 39858176 571244544

explorer 1788 33488896 200929280

csrss 372 31252480 74035200

PresentationFon... 164 30040064 536096768

Note

Normally, you’ll see people specify the properties they want without using the –Property parameter name: Select-Object Name,ID,PM,VM. We didn’t do that, because we didn’t specify the list of properties in the first position. When you use parameter names, as you did in this example, you can put the parameters in any order you want. We’re big fans of using parameter names, specifically because you don’t have to remember any special order. If you omit the parameter names and only provide values, then it’s on you to make sure you get the order right, and it’s easy to get that wrong.

7.5. Filtering objects

We howed you how Select-Object can grab a subset of objects, but we took pains to point out that it’s non-intelligent, grabbing hunks of objects from the beginning or end of the set. The Where-Object cmdlet, on the other hand, has much more powerful capabilities for truly filtering out objects you don’t want.

7.5.1. Simplified syntax

PowerShell v3 introduced a new, simplified syntax for Where-Object, so we’ll cover that first. PowerShell v3 and later still support the full syntax, which is all that’ll work in older versions of PowerShell, and we’ll cover that last.

To use this syntax, you’ll need to know two things:

· The name of the property that contains the data you want to filter on

· The property values that you want to keep (everything else will be discarded)

Here’s an example:

PS C:\> Get-Service | Where Status –ne Running

Status Name DisplayName

------ ---- -----------

Stopped AeLookupSvc Application Experience

Stopped ALG Application Layer Gateway Service

Stopped AppIDSvc Application Identity

Stopped Appinfo Application Information

Stopped AppMgmt Application Management

Stopped AudioEndpointBu... Windows Audio Endpoint Builder

We included the first few lines of output. As you can see, we used the Where alias instead of the Where-Object cmdlet name; you’ll also see people use the alias ? (this is harder for newcomers to understand so we don’t recommend it), as in this example:

PS C:\> Get-Service | ? Status –ne Running

Status Name DisplayName

------ ---- -----------

Stopped AeLookupSvc Application Experience

Stopped ALG Application Layer Gateway Service

Stopped AppIDSvc Application Identity

Stopped Appinfo Application Information

Stopped AppMgmt Application Management

Stopped AudioEndpointBu... Windows Audio Endpoint Builder

After the command (or alias), you type the name of the property you want the command to look at. In this case, we chose the Status property. Then, you specify one of PowerShell’s comparison operators, which we covered in the previous chapter. Finally, you specify the value that identifies objects you want to keep—we wanted to keep all services that didn’t have a status of “Running.”

To use this simplified syntax, you need to know a few rules:

· You can only perform a single comparison. In other words, you can’t look for services that have a service type of “Win32OwnProcess” and a status of “stopped”—you can only do one of those things. You could use two consecutive Where-Object commands to achieve that goal, though: get-service | where status -eq stopped | where servicetype -eq Win32OwnProcess.

· You can only use the core comparison operators specifically supported by the command—read its help for a full list.

If you need to do anything more complicated, you’ll have to switch to the full syntax for the command.

Note

Keep in mind that the simplified syntax was new for PowerShell v3. You’re likely to run across lots of examples that seem like they could use the simplified syntax but instead use the full syntax. It’s likely those examples were written for older versions of PowerShell, or that the examples’ authors are used to the full syntax. It’s okay, because that full syntax still works fine in v3 and later.

7.5.2. Full syntax

The full syntax for Where-Object involves a script block, which is basically a comparison contained within curly brackets. This syntax uses the same $_ or $psitem that you could’ve used in Select-Object. Remember, PowerShell looks for $_ (or $psitem) in special instances, and the script block of Where-Object is one of those. If you don’t need to work with the entire piped-in object (and you rarely will), use a period to specify a single property. For example, here are three versions of the exact same command. We’ll start with the fullest possible syntax and work down to the briefest using aliases and positional parameters:

· Get-Process | Where-Object -FilterScript {$_.workingset -gt 1mb -AND $_.company -notmatch "Microsoft"}

· Get-Process | Where {$_.workingset -gt 1mb -AND $_.company -notmatch "Microsoft"}

· ps | ? {$_.ws -gt 1mb -AND $_.company -notmatch "Microsoft"}

These all do exactly the same thing, and you’re welcome to run them in PowerShell to see what you get. You’ll notice that, with this full syntax, you’re able to specify multipart comparisons using some of the Boolean operators that we introduced in chapter 6.

If you’re writing a script that might be executed on a system running PowerShell v2, you’ll need to stick with $_. Otherwise you can use $psitem, but the only thing you gain is potential clarity.

7.5.3. The Where method

PowerShell v4 introduced another way to filter a collection of objects. This method isn’t well documented, nor is it easily discoverable, yet it’s relatively easy to use and performs well. Basically, you can use Where as a method:

PS C:\> (Get-Service m*).Where{$_.status -eq "stopped"}

Status Name DisplayName

------ ---- -----------

Stopped MozillaMaintenance Mozilla Maintenance Service

Stopped MSDTC Distributed Transaction Coordinator

Stopped MSiSCSI Microsoft iSCSI Initiator Service

Stopped msiserver Windows Installer

When you use Where in this way, you must include $psitem or $_ to indicate a pipelined object. You aren’t required to use parentheses, but we think you should get in the habit anyway. This command will give you the same results:

(Get-Service m*).Where({$psitem.status -eq "stopped"})

The reason is that there’s a second parameter to the Where method. You can specify any one of the following: First, Last, SkipUntil, Until, or Split.

PS C:\> $p = (Get-Process).Where({$_.ws -gt 100mb},"split")

We retrieved all processes and split them into two parts based on the filter. The first element of the array will contain those processes where the WorkingSet (the property is abbreviated to ws by Get-Process) is greater than 100 MB:

PS C:\> $p[0].count

13

PS C:\> $p[0][0]

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

------- ------ ----- ----- ----- ------ -- -----------

201 67 86224 102668 261 197.91 1240 chrome

The second element of the array ($p) will contain everything else:

PS C:\> $p[1].count

75

Here are a few other ways to use this cool trick:

PS C:\> (Get-Process).Where({$_.ws -gt 100mb},"First",3)

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

------- ------ ----- ----- ----- ------ -- -----------

414 39 126472 141540 476 1,279.25 2196 chrome

1764 102 164760 216800 480 3,609.66 3856 chrome

814 54 99444 107804 504 383.08 7260 chrome

PS C:\> (Get-Process).Where({$_.ws -gt 100mb},"Last",2)

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

------- ------ ----- ----- ----- ------ -- -----------

778 39 85344 103384 160 2,350.08 1308 svchost

680 96 142976 176404 409 31.45 3252 thunderbird

The number is the number of objects to return. Yes, you could get the same results with more traditional expressions like this:

PS C:\> Get-Process | Where {$_.ws -gt 100mb} | Select -First 3

For a small data set like this, you may not notice much of a performance difference. But in a large set you will:

PS C:\> measure-command {(1..1000).where({$psitem%2})}

Days : 0

Hours : 0

Minutes : 0

Seconds : 0

Milliseconds : 41

Ticks : 413475

TotalDays : 4.78559027777778E-07

TotalHours : 1.14854166666667E-05

TotalMinutes : 0.000689125

TotalSeconds : 0.0413475

TotalMilliseconds : 41.3475

PS C:\> measure-command {(1..1000) | where {$psitem%2}}

Days : 0

Hours : 0

Minutes : 0

Seconds : 0

Milliseconds : 118

Ticks : 1185579

TotalDays : 1.37219791666667E-06

TotalHours : 3.293275E-05

TotalMinutes : 0.001975965

TotalSeconds : 0.1185579

TotalMilliseconds : 118.5579

Here we filtered to get the odd numbers between 1 and 1000. Using the traditional syntax took 118 ms whereas the newer v4 syntax took only 41 ms.

We haven’t covered the Skip and SkipUntil options yet. The best way to think of them is that they provide an additional filter that supplies the data that matches the condition or the data that doesn’t match. Let’s start by looking at the complete set of processes:

PS C:\> Get-Process | sort Handles

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

------- ------ ----- ----- ----- ------ -- -----------

0 0 0 24 0 0 Idle

43 6 664 2988 40 0.00 2932 TabTip32

44 2 280 620 4 2.55 364 smss

72 8 992 4600 70 0.03 3148 jusched

75 8 1916 6544 57 1.16 7088 conhost

81 7 968 1308 44 0.00 1488 armsvc

...

810 44 38500 83228 492 4.59 6060 EXCEL

838 50 97772 183120 627 17.27 412 WINWORD

843 45 13884 19960 105 12.09 380 svchost

872 32 21636 24776 132 13.31 932 svchost

971 46 27568 33388 137 97.02 980 svchost

1010 156 155984 206096 549 26.56 6668 WWAHost

1073 39 34984 50056 878 22.28 4292 livecomm

1095 0 128 9196 13 4,138.06 4 System

1164 27 8692 25700 288 0.56 2120 UcMapi

1324 20 8400 13340 54 38.69 652 lsass

1327 50 67388 95364 537 20.25 5680 ONENOTE

1983 61 44424 45640 449 59.27 1008 svchost

2015 89 53904 113196 570 81.89 3140 explorer

2244 64 72928 117012 664 7.25 2820 lync

If you want to see only the data where the Handles property is greater than 1000, use the following:

PS C:\> Get-Process | sort Handles | Where Handles -gt 1000

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

------- ------ ----- ----- ----- ------ -- -----------

1010 156 155984 206096 549 26.56 6668 WWAHost

1078 39 34996 50788 878 22.48 4292 livecomm

1094 0 128 9196 13 4,140.78 4 System

1170 28 8720 25720 289 0.56 2120 UcMapi

1314 48 67304 95364 535 20.27 5680 ONENOTE

1322 20 8316 13296 53 38.72 652 lsass

1980 61 44372 45608 449 59.27 1008 svchost

2015 88 53904 113208 570 82.88 3140 explorer

2236 64 72884 116980 663 7.36 2820 lync

You could modify this as follows:

PS C:\> (Get-Process | sort Handles).Where({$_.Handles -gt 1000})

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

------- ------ ----- ----- ----- ------ -- -----------

1010 156 155984 206096 549 26.56 6668 WWAHost

1116 0 128 9196 13 4,141.44 4 System

1126 42 36836 53436 889 23.36 4292 livecomm

1174 28 8728 25728 289 0.56 2120 UcMapi

1332 50 67536 95420 537 20.34 5680 ONENOTE

1337 20 8396 13332 54 38.89 652 lsass

1984 61 44372 45608 449 59.27 1008 svchost

2106 93 54676 113484 576 83.33 3140 explorer

2236 64 72912 116996 664 7.38 2820 lync

To see the data that doesn’t match, you have to modify the filter or use the Until parameter:

PS C:\> (Get-Process | sort Handles).Where({$_.Handles -gt 1000}, "Until")

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

------- ------ ----- ----- ----- ------ -- -----------

0 0 0 24 0 0 Idle

43 6 664 2988 40 0.00 2932 TabTip32

44 2 280 620 4 2.55 364 smss

72 8 992 4600 70 0.03 3148 jusched

75 8 1932 6568 57 1.55 7088 conhost

81 7 968 1308 44 0.00 1488 armsvc

...

812 44 38500 83260 492 4.64 6060 EXCEL

841 50 62776 145108 586 87.06 412 WINWORD

842 45 13884 19964 105 12.09 380 svchost

871 32 21112 24180 132 13.38 932 svchost

971 46 27568 33388 137 97.33 980 svchost

If you don’t sort the processes, data will be displayed until the first process is met that has a Handles property with a value greater than 1000.

If you add an integer as a third property:

(Get-Process | sort Handles).Where({$_.Handles -gt 1000}, "Until", 3)

you’ll see the first three processes that don’t have a Handles property greater than 1000. Unfortunately there isn’t a way to get the last N processes that don’t match the criterion.

The SkipUntil parameter is the opposite of Until. It skips all processes that don’t match the filter. These options yield the same result:

(Get-Process | sort Handles).Where({$_.Handles -gt 1000}, "First")

(Get-Process | sort Handles).Where({$_.Handles -gt 1000}, "First", 1)

(Get-Process | sort Handles).Where({$_.Handles -gt 1000}, "SkipUntil", 1)

So why would you use SkipUntil? The best reason is that it’s a bit faster than the alternatives, so you get your data sooner.

Tip

Because this syntax can be so efficient, it’s great for interactive sessions. Normally, we hesitate to use such a shortcut in a script, but because of the performance gains it’s hard to argue against it. So if you include syntax like this in a PowerShell script, we encourage you to document and clearly explain it, especially if the script will be used by other people.

This syntax can be confusing, so we recommend that you practice with it by building on the examples we’ve provided.

7.6. Grouping objects

Most of the time, the PowerShell pipeline handles groups of objects fine. But sometimes you want to take matters into your own hands. The Group-Object cmdlet takes a bunch of objects and puts them into buckets, or groups, based on a key property:

PS C:\> Get-Service | Group-Object –property Status

Count Name Group

----- ---- -----

95 Running {System.ServiceProcess.ServiceController, Sy...

99 Stopped {System.ServiceProcess.ServiceController, Sy...

Here you’re taking all the service objects and piping them to Group-Object, organizing them into groups based on the Status property. What you get back is a different object. Even though you started with service objects, Group-Object writes a different type of object to the pipeline. You can verify this by piping your command to Get-Member:

PS C:\> Get-Service | group status | Get-Member

TypeName: Microsoft.PowerShell.Commands.GroupInfo

Name MemberType Definition

---- ---------- ----------

Equals Method bool Equals(System.Object obj)

GetHashCode Method int GetHashCode()

GetType Method type GetType()

ToString Method string ToString()

Count Property System.Int32 Count {get;}

Group Property System.Collections.ObjectModel.Collection`1[[Syst...

Name Property System.String Name {get;}

Values Property System.Collections.ArrayList Values {get;}

You can simplify your typing by taking advantage of aliases and positional parameters. As you can see, you have something called a Microsoft.PowerShell.Commands.Group-Info object, which has properties of Group, Name, and Values. The Group property is the collection of objects. In this example, that will be service objects.

Let’s run that command again:

PS C:\> $services = Get-Service | group status

PS C:\> $services

Count Name Group

----- ---- -----

100 Stopped {System.ServiceProcess.ServiceController, Sy...

94 Running {System.ServiceProcess.ServiceController, Sy...

The variable $services contains the GroupInfo objects. Let’s look at the Group property of the first element of $services and display the first few items:

PS C:\> $services[0].group[0..5]

Status Name DisplayName

------ ---- -----------

Stopped AeLookupSvc Application Experience

Stopped ALG Application Layer Gateway Service

Stopped AppIDSvc Application Identity

Stopped Appinfo Application Information

Stopped AppMgmt Application Management

Stopped aspnet_state ASP.NET State Service

Grouping objects can come in handy when you’re more interested in the collective results. For example, suppose you want to find out what types of files are in a given folder. We’ll look at a local folder, but this would easily translate to a shared folder on one of your file servers:

PS C:\> $files = dir c:\work -recurse –file | Group Extension

PS C:\> $files | sort Count -descending | select -first 5 Count,Name

Count Name

----- ----

215 .txt

90 .ps1

42 .csv

40 .xml

15 .abc

We’ll deal with formatting later, but at a glance you can see which file types are most in use. If you were curious about the files themselves, you could get them in the Group property.

Depending on what you need to do with the grouped objects, you might find it easier to work with a hash table. Group-Object can help you with the –AsHashTable parameter:

PS C:\> $services = Get-WmiObject –Class Win32_Service |

group StartMode –AsHashTable

PS C:\> $services

Name Value

---- -----

Manual {\\SERENITY\root\cimv2:Win32_Service.Name...

Unknown {\\SERENITY\root\cimv2:Win32_Service.Name...

Auto {\\SERENITY\root\cimv2:Win32_Service.Name...

Disabled {\\SERENITY\root\cimv2:Win32_Service.Nam....

PS C:\> $services.Disabled.count

11

PS C:\> $services.Disabled.displayname

Bluetooth Support Service

HomeGroup Listener

HomeGroup Provider

Net.Tcp Port Sharing Service

Routing and Remote Access

Remote Registry

Smart Card

Internet Connection Sharing (ICS)

Windows Biometric Service

Windows Media Player Network Sharing Service

Family Safety

Finally, sometimes you don’t care about the grouped items themselves, only the results of the grouping. In those situations you can use the –NoElement parameter, which omits the Group property:

PS C:\> dir c:\scripts -file | group extension -NoElement |

sort count -desc | select -first 5

Count Name

----- ----

1170 .ps1

313 .txt

57 .zip

26 .xml

25 .csv

Here we got all files from the Scripts folder, grouped by extension but omitting the files themselves.

7.7. Measuring objects

Sometimes, you need to know how many objects you have, and PowerShell is happy to help. Allow us to introduce the Measure-Object cmdlet and its alias, Measure:

PS C:\> Get-Command -Verb Get | Measure-Object

Count : 508

Average :

Sum :

Maximum :

Minimum :

Property :

You can also get count information with Group-Object as we showed previously. But as the previous output implies, Measure-Object can do more than count, if you give it a single property that you know contains numeric data:

PS C:\> Get-Process | Measure-Object -Property PM -Average -Sum -Min -Max

Count : 45

Average : 14478631.8222222

Sum : 651538432

Maximum : 90230784

Minimum : 0

Property : PM

Like Group-Object, this cmdlet writes a new object to the pipeline with properties like Sum and Average. But as you can imagine, this is certainly a useful tool. Let’s continue with our group of file extensions and find out how much space they’re using:

PS C:\> $files | sort count -descending |

select -first 5 Count,Name,@{Name="Size";Expression={

($_.Group | Measure-Object Length -sum).sum}}

Count Name Size

----- ---- ----

215 .txt 22386907

90 .ps1 663866

42 .csv 7928346

40 .xml 26003578

15 .abc 4564272

Here you created a custom property called Size that took the Group property from each GroupInfo object and piped it to Measure-Object to get the sum of the length property. This is a nice example of the PowerShell’s flexibility and capability. You started out by running a simpleDIR command and ended up with completely different but extremely valuable output.

7.8. Enumerating objects

Enumerating basically means “going through a whole bunch of things, one at a time.” In other words, imagine that you have a big stack of comic books and need to pick out all the ones that Neil Gaiman worked on. You’re going to go through that stack, one at a time, open each one up, and look to see if Neil’s mentioned in the credits. It’s going to be time-consuming, and you’re going to wish you could get your little brother to do it, but it’s what you have to do.

PowerShell does this with the ForEach-Object cmdlet. As with Where-Object, PowerShell v3 introduced a simplified syntax—but this time, we’ll start with the full syntax.

7.8.1. Full syntax

You’ll usually pass a script block to its –Process parameter, and in that script block you’ll have access to good-old $_, which will represent a single piped-in object, or use the newer $psitem. For example, let’s say that you were fed up with work and needed to shut down every computer in the domain. We’re not saying you should do that, but if you needed to, this would accomplish it:

Get-ADComputer –filter * | ForEach-Object –Process {

Stop-Computer –computerName $_.Name

}

In real life, if someone were crazy enough to do this, you’d probably see it written with an alias as well as abbreviated and positional parameters:

Get-ADComputer –filter * | ForEach {

Stop-Computer –comp $_.Name

}

You might even see a shorter alias used. This isn’t one we care for, because it gets hard to read scripts that have a lot of these, but people use it a lot:

Get-ADComputer –filter * | % {

Stop-Computer –comp $_.Name

}

You could type any of those all on one line, but you could also type them exactly as we did in the example. Press Enter on a blank line when you’re finished, and PowerShell will start shutting everything down.

Before you do that (and possibly tank your career), we want to revisit this concept of enumerating. We’re not going to pretend you’ll never need to use this cmdlet, because you will. We’re not even going to tell you that using it should be a rare occurrence, because it might not be. But any time you do use ForEach-Object, sit down and ask yourself if you really have to. For example, we notice that the Stop-Computer cmdlet has a –ComputerName parameter, which accepts data of the type string[], which means it can accept more than one computer name at a time. Thus, the following would work:

Stop-Computer –computername (

Get-ADComputer –Filter * | Select –Expand Name)

See? No need to use the confusing old ForEach-Object at all, with its curly brackets and $_ and whatnot. In many cases, a properly used cmdlet can work against several things at once, without you needing to go through them one at a time. It’s as if you had a magic scanner into which you could pour your comic collection and have it spit out the ones you want without you having to manually look at each one.

7.8.2. Simplified syntax

The simplified syntax for ForEach-Object is a bit restrictive in what it lets you do, but it does away with the ugly $_ or $psitem placeholder. The simplified syntax of Foreach-Object has similar restrictions to Where-Object in that you can only use a single simple command. As an example, you can use calc.exe:

PS> Start-Process calc

PS> Get-Process calc

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

------- ------ ----- ----- ----- ------ -- -----------

72 18 6152 11060 85 0.11 2320 calc

A new calculator process is started. Richard used to use Notepad for this sort of example until a reviewer complained because the code shut down Notepad and destroyed his notes. We trust you aren’t. In the full syntax, you’d use

Get-Process calc | foreach {$_.Kill()}

In the real world you’d use Get-Process calc | Stop-Process, but the explanation wouldn’t work as well.

With the simplified syntax, you’d use

Get-Process calc | foreach Kill

But something like this won’t work:

Get-Process | foreach if (Id -eq 2120){Kill}

You need to use the full syntax:

Get-Process | foreach {if ($_.Id -eq 2120){$_.Kill()}}

The simplified syntax is probably of more use with Where-Object, but having options is always good, and you should take the opportunity to type less code to achieve the desired result where applicable.

7.8.3. The ForEach method

In PowerShell v4 you also get a ForEach method, which like the Where method we discussed earlier, isn’t easy to discover. But the syntax is essentially the same:

PS C:\> ("don","jeff","richard").foreach({$_.toupper()})

DON

JEFF

RICHARD

As with Where, you can use $_ or $psitem. The method offers performance gains. Here’s the traditional way to use it:

PS C:\> measure-command {(1..1000) | foreach {$_ *3}}

Days : 0

Hours : 0

Minutes : 0

Seconds : 0

Milliseconds : 92

Ticks : 929776

TotalDays : 1.07612962962963E-06

TotalHours : 2.58271111111111E-05

TotalMinutes : 0.00154962666666667

TotalSeconds : 0.0929776

TotalMilliseconds : 92.9776

And here’s the new syntax:

PS C:\> measure-command {(1..1000).foreach({$_ *3})}

Days : 0

Hours : 0

Minutes : 0

Seconds : 0

Milliseconds : 40

Ticks : 400475

TotalDays : 4.63512731481481E-07

TotalHours : 1.11243055555556E-05

TotalMinutes : 0.000667458333333333

TotalSeconds : 0.0400475

TotalMilliseconds : 40.0475

The larger the data set, the greater the gain:

PS C:\> (measure-command {(1..100000) | foreach {$_ *3}}).TotalMilliseconds

2854.1627

PS C:\> (measure-command {(1..100000).foreach({$_ *3})}).TotalMilliseconds

801.3789

As before, if you use this syntax in a script be sure to clearly document it.

7.9. Importing, exporting, and converting objects

Now we’re going to cover PowerShell’s core, built-in commands for getting data in and out of the shell and various other formats. First, let’s define the four verbs you’ll be working with:

· Import refers to the process of reading data from some external format, usually a file, and bringing that data into the shell in the form of objects. So this is a two-step process: read the data, and then convert the data into objects.

· Export refers to the process of taking objects in the shell, converting them to some other data structure, and then writing that data out to an external form—usually a file. As with import, this is a two-step process: convert the data, and then write it out.

· ConvertTo refers to the process of taking objects in the shell, changing them into some other data structure, and then leaving that converted data in the shell so that other commands can work with it.

· ConvertFrom refers to the process of taking some data structure and converting it into the object data structure that the shell uses. The objects remain in the shell for other commands to work with.

Warning

We see folks get confused about these four verbs all the time. Remember, Import and Export deal with external files; ConvertTo and ConvertFrom deal entirely with data that’s contained within PowerShell.

Here’s a quick rundown of some of the cmdlets you’ll find yourself using. This isn’t an exhaustive list, but it’s a good one to start with:

· ConvertTo-HTML

· ConvertTo-CSV

· Export-CSV

· Import-CSV

· Export-CliXML

· Import-CliXML

Let’s work with the CSV cmdlets first. Technically, they only create comma-separated value (CSV) data structures by default; using their –Delimiter parameter, you can also have them create files that use delimiters other than a comma. We’ve seen people create tab-delimited format (TDF) files using these cmdlets, for example.

Here’s the first example:

PS C:\> Get-Process | select name,id,vm,pm | ConvertTo-Csv

#TYPE Selected.System.Diagnostics.Process

"Name","Id","VM","PM"

"conhost","1100","82726912","2883584"

"conhost","1820","48480256","1003520"

"conhost","2532","42979328","847872"

"csrss","324","45211648","2027520"

"csrss","372","74035200","31252480"

"dfsrs","1288","364253184","14684160"

"dfssvc","1468","36069376","2326528"

"dllhost","1016","58023936","4202496"

"dns","1324","122880000","86794240"

"dwm","1964","55918592","1712128"

"explorer","1788","200929280","33488896"

As you can see, this obviously took the objects we had and made them into a CSV representation—not a CSV file, mind you, because there’s no file involved. The data was converted from objects (one kind of data structure) to CSV (another kind of data structure), but the data stayed in the shell, which is what the ConvertTo verb means.

Note

The first line in the CSV is a comment, indicating what type of data was converted. You can eliminate this, if necessary, by using a parameter of the ConvertTo-CSV cmdlet. We’ll let you read the help file for the cmdlet to find that parameter. It’s for your own good.

So what if you need that in a file? Simple: redirect the output using the legacy console redirection characters.

PS C:\> Get-Process | select name,id,vm,pm | ConvertTo-Csv > procs.csv

That’s an alternative to the Out-File cmdlet, so you could use that instead:

PS C:\> Get-Process | select name,id,vm,pm | ConvertTo-Csv |

Out-File procs.csv

If you’re a fan of shortcuts, then you’re going to love Export-CSV. As implied by the Export verb, it basically combines ConvertTo-CSV and Out-File into a single, handy utility:

PS C:\> Get-Process | select name,id,vm,pm | Export-Csv procs.csv

But be careful. Run the following command in PowerShell:

PS C:\> Get-Process | Export-Csv myprocs.csv

Then open the CSV file in Notepad or view it in PowerShell:

PS C:\> Get-Content .\myprocs.csv | select -first 2

#TYPE System.Diagnostics.Process

"__NounName","Name","Handles","VM","WS","PM","NPM","Path","Company","CPU","

FileVersion","ProductVersion","Description","Product","BasePriority","ExitC

ode","HasExited","ExitTime","Handle","HandleCount","Id","MachineName","Main

WindowHandle","MainWindowTitle","MainModule","MaxWorkingSet","MinWorkingSet

","Modules","NonpagedSystemMemorySize","NonpagedSystemMemorySize64","PagedM

emorySize","PagedMemorySize64","PagedSystemMemorySize","PagedSystemMemorySi

ze64","PeakPagedMemorySize","PeakPagedMemorySize64","PeakWorkingSet","PeakW

orkingSet64","PeakVirtualMemorySize","PeakVirtualMemorySize64","PriorityBoo

stEnabled","PriorityClass","PrivateMemorySize","PrivateMemorySize64","Privi

legedProcessorTime","ProcessName","ProcessorAffinity","Responding","Session

Id","StartInfo","StartTime","SynchronizingObject","Threads","TotalProcessor

Time","UserProcessorTime","VirtualMemorySize","VirtualMemorySize64","Enable

RaisingEvents","StandardInput","StandardOutput","StandardError","WorkingSet

","WorkingSet64","Site","Container"

What happened? Exactly what you told PowerShell to do: get all processes on the local computer and export them to a CSV file. Don’t assume that exporting, or converting for that matter, works on the cmdlet’s default output. The export or convert cmdlet processes all objects and all their properties. If that isn’t what you want, you’ll need to select the properties you’re interested in:

PS C:\> Get-Process | Select Name,ID,WS,VM,PM,Path | Export-Csv myprocs.csv

PS C:\> Get-Content .\myprocs.csv | select -first 3

#TYPE Selected.System.Diagnostics.Process

"Name","Id","WS","VM","PM","Path"

"cmd","2036","208896","79732736","5902336","C:\Windows\system32\cmd.exe"

That’s more like it. The other fact to keep in mind when exporting or converting to the CSV format is that properties that are nested objects don’t translate well:

PS C:\> Get-Service | Select Name,Displayname,DependentServices,status |

Convertto-CSV | Select -first 4

#TYPE Selected.System.ServiceProcess.ServiceController"Name","DisplayName",

"DependentServices","Status"

"AeLookupSvc","Application

Experience","System.ServiceProcess.ServiceController[]","Stopped"

"ALG","Application Layer Gateway

Service","System.ServiceProcess.ServiceController[]","Stopped"

The DependentServices property is a collection of nested service objects. When you attempt to turn this into a CSV-formatted item, you end up with System.Service-Process.ServiceController[], which is hardly meaningful. The bottom line is that when using the CSV format, stick with properties that have simple values. We’ll show you how to handle these nested objects in a bit.

Hopefully that illustrates the main difference between converting and exporting:

· Convert = Changes the data structure

· Out = Put into external storage

· Export = Convert + Out

That leaves us with Import-CSV. Let’s say you start with the following CSV file and data:

Name,Department,City

Don,IT,Las Vegas

Jeffery,IT,Syracuse

Richard,IT,London

Greg,Custodial,Denver

You can now run the following command to bring that data into the shell as objects:

PS C:\> Import-Csv .\data.csv

Name Department City

---- ---------- ----

Don IT Las Vegas

Jeffery IT Syracuse

Richard IT London

Greg Custodial Denver

As you can see, PowerShell does the work of interpreting the CSV file. At the start of this chapter, we explained that “rows” and “columns” in a spreadsheet become “objects” and “properties” when they’re made into objects, and that’s exactly what Import-CSV has done. You can then manipulate those objects as you’ve manipulated others:

PS C:\> Import-Csv .\data.csv | where { $psitem.Department -eq "IT" } |

Sort Name

Name Department City

---- ---------- ----

Don IT Las Vegas

Jeffery IT Syracuse

Richard IT London

How cool is that? Everything in PowerShell is geared to make working with objects easy. By getting the shell to convert other data structures into objects, you get to work with that stuff more easily.

Now for a quick look at HTML. The only cmdlet here is ConvertTo-HTML; for some reason, there’s no Export-HTML, so you’ll generally have to redirect the output to a file on your own. There’s also no Import or ConvertFrom option here; it’s a one-way trip to HTML. As with the CSV format, make sure you’re only converting simple property values. No nested objects. Here’s the example, and figure 7.2 shows the results.

Figure 7.2. Viewing converted-to-HTML data in Internet Explorer

PS C:\> Get-Service | Where { $_.Status -eq "Stopped" } |

ConvertTo-HTML -Property Name,Status,DisplayName |

Out-File Stopped.html

Note

The ConvertTo-HTML cmdlet has many more uses for its many different parameters. We’ll make heavy use of them toward the end of the book, in chapter 33 on creating reports.

Finally, a quick word on the CliXML format: It’s XML. It’s a simple XML that PowerShell understands natively. It’s a great way to persist objects over time, such as creating a snapshot of some objects for later examination. We’re going to use it in the next section for that purpose.

7.10. Comparing objects

The last cmdlet you’ll learn in this chapter is Compare-Object, which has an alias named Diff. You’re going to use it in conjunction with Export-CliXML and Import-CliXML to perform a cool, and incredibly useful, trick.

Do you do configuration change reporting in your environment? Many organizations do, and PowerShell can make it easy. You start by creating a baseline, or reference file, that represents the way you want things to be configured. For example:

PS C:\> Get-Process | Export-CliXML proc-baseline.xml

That takes a snapshot of the currently running processes and puts it into PowerShell’s XML format, in an external file. CliXML is better than CSV for this task, because XML can represent deeply nested data, whereas CSV can only represent a single, flat level of data. Let’s say you do this on a server, where the processes that are running should be pretty fixed. If new processes crop up over time, you’ll definitely want to know about it. So, you’ll come along in a month or so and see what’s new. The following is a one-line PowerShell command:

PS C:\> Compare-Object -ReferenceObject (Import-CliXML .\proc-baseline.xml)

-DifferenceObject (Get-Process) -Property Name

Name SideIndicator

---- -------------

calc =>

mspaint =>

notepad =>

svchost =>

A blank result set would have been good news—what the heck is going on here? MSPaint running on a server? You clearly need to have a group meeting about proper uses for servers.

Here’s what you need to do:

· Run Compare-Object.

· The first parameter is –ReferenceObject, which is your baseline. To provide the baseline data, use a parenthetical command that imports your baseline data from the XML file. The entire contents of that XML file are converted into objects, and those become the values for the parameter.

· The second parameter is –DifferenceObject, which is what you want to compare the reference to. You have the current process objects as the values for the parameter, again by using a parenthetical command.

· The properties of a process are always changing: Memory, processor, and so forth are always different. So you don’t want to compare those values, which Compare-Object would normally do. Instead, use the –Property parameter to tell it to only look at one property. That property is Name, which won’t ever change during a process’s lifetime.

· The results include a “side indicator.” It’s a little arrow, and if it points right, it means the difference set has something (in this case, the current processes) that doesn’t exist in the reference set. A left-pointing arrow means the opposite—a process existed in the baseline but doesn’t currently exist.

We’ve seen companies build scripts that are little more than dozens, or even hundreds, of those Compare-Object commands, each one comparing a different baseline to some portion of the existing configuration. They’ll even pipe the output to an HTML file, and then email the file (as an attachment, using Send-MailMessage) to someone.

7.11. Summary

Well, we covered a lot. The goal of this chapter was to introduce you to the idea of objects and to show you some of the core PowerShell cmdlets that manipulate objects. We dare say that you’ll use these commands all the time, whether you’re working with Windows, Active Directory, Exchange Server, SQL Server, SharePoint Server, VMware, Citrix, or anything else that’s manageable with PowerShell. The skills you learned in this chapter, and the ones you’ll learn in the next couple of chapters, are as fundamental to PowerShell as the mouse is to Windows itself. We covered a lot of ground, so be prepared to come back to this chapter to refresh your memory any time you need to and be sure to read any help topics we eferenced.