Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (9.65 MB, 367 trang )
98
CHAPTER 9
The pipeline, deeper
Figure 9.1 Creating a text file
containing computer names,
with one name per line
That’s the command that produces something. The second command will be Command B, which needs to accept Command A’s output and then do its own thing.
PS C:\> CommandA | CommandB
For example, suppose you have a text file that contains one computer name on each
line, as shown in figure 9.1.
You might want to use those computer names as the input to some command, telling that command which computers you want it to run against. Consider this example:
PS C:\> Get-Content .\computers.txt | Get-Service
When Get-Content runs, it places the computer names into the pipeline. PowerShell
then has to decide how to get those to the Get-Service command. The trick with
PowerShell is that commands can only accept input on a parameter. That means
PowerShell has to figure out which parameter of Get-Service will accept the output
of Get-Content. This figuring-out process is called pipeline parameter binding, and it’s
what we’ll be covering in this chapter. PowerShell has two methods it can use to get
the output of Get-Content onto a parameter of Get-Service. The first method the
shell will try is called ByValue; if that doesn’t work, it’ll try ByPropertyName.
9.3
Plan A: pipeline input ByValue
With this pipeline parameter binding method, PowerShell looks at the type of object
produced by Command A and tries to see if any parameter of Command B can accept
that type of object from the pipeline. You can determine this for yourself: first, pipe
the output of Command A to Get-Member, to see what type of object Command A is
producing. Then, examine the full help of Command B (for example, Help
Get-Service -full) to see if any parameter accepts that type of data from the pipeline ByValue. Figure 9.2 shows what you might discover.
What you’ll find is that Get-Content produces objects of the type System.String
(or String for short). You’ll also find that Get-Service does have a parameter that
www.it-ebooks.info
Plan A: pipeline input ByValue
Figure 9.2
99
Comparing the output of Get-Content to the input parameters of Get-Service
accepts String from the pipeline ByValue. The problem is that it’s the -Name parameter, which according to the help “specifies the service names of services to be
retrieved.” That isn’t what we wanted—our text file, and therefore our String objects,
are computer names, not service names. If we ran the following,
PS C:\> Get-Content .\computers.txt | Get-Service
we’d be attempting to retrieve services named SERVER2, WIN8, and so forth, which is
probably not going to work.
PowerShell only permits one parameter to accept a given type of object from the
pipeline ByValue. This means that because the -Name parameter accepts String from
the pipeline ByValue, no other parameter can do so. That dashes our hopes for trying
to pipe computer names from our text file to Get-Service.
In this case, pipeline input is working, but it isn’t achieving the results we’d hoped
for. Let’s consider a different example, where we do get the results we want. Here’s
the command line:
PS C:\> get-process -name note* | Stop-Process
www.it-ebooks.info
100
CHAPTER 9
Figure 9.3
The pipeline, deeper
Binding the output of Get-Process to a parameter of Stop-Process
Let’s pipe the output of Command A to Get-Member and examine the full help for
Command B. Figure 9.3 shows what you’ll find.
Get-Process produces objects of the type System.Diagnostics.Process (note
that we limited the command to processes whose names start with note*; we made sure
a copy of Notepad was running so that the command would produce some output).
Stop-Process can accept those Process objects from the pipeline ByValue; it does so
on its -InputObject parameter. According to the help, that parameter “stops the processes represented by the specified process objects.” In other words, Command A will
get one or more Process objects, and Command B will stop (or kill) them.
This is a good example of pipeline parameter binding in action, and it also illustrates an important point in PowerShell: For the most part, commands sharing the
same noun (as Get-Process and Stop-Process do) can usually pipe to each other
ByValue.
Let’s cover one more example:
PS C:\> get-service -name s* | stop-process
www.it-ebooks.info
Plan A: pipeline input ByValue
Figure 9.4
101
Examining the output of Get-Service and the input parameters of Stop-Process
On the face of it, this might not seem to make any sense. But let’s see this through by
piping Command A’s output to Get-Member, and re-examining the help for Command
B. Figure 9.4 shows what you should find.
Get-Service produces objects of the type ServiceController (technically, System
.ServiceProcess.ServiceController, but you can usually take the last bit of the
TypeName as a shortcut). Unfortunately, there isn’t a single parameter of Stop-Process
that can accept a ServiceController object. That means the ByValue approach has
failed, and PowerShell will try its backup plan: ByPropertyName.
www.it-ebooks.info
102
9.4
CHAPTER 9
The pipeline, deeper
Plan B: pipeline input ByPropertyName
With this approach, you’re still looking to attach the output of Command A to parameters of Command B. But ByPropertyName is slightly different than ByValue. With this
backup method, it’s possible for multiple parameters of Command B to become
involved. Once again, pipe the output of Command A to Get-Member, and then look at
the syntax for Command B. Figure 9.5 shows what you should find: The output of Command A has one property whose name corresponds to a parameter on Command B.
A lot of folks will overthink what’s happening here, so let’s be clear on how simple
the shell is being: it’s literally looking for property names that match parameter
names. That’s it. Because the property “Name” is spelled the same as the parameter
“-Name,” the shell will try to connect the two.
Figure 9.5
Mapping properties to parameters
www.it-ebooks.info
Plan B: pipeline input ByPropertyName
103
But it can’t do so right away: first it needs to see if the -Name parameter will accept
input from the pipeline ByPropertyName. A glance at the full help, shown in
figure 9.6, is required to make this determination.
In this case, -Name does accept pipeline input ByPropertyName, so this connection
will work. Now, here’s the trick: unlike ByValue, where only one parameter would be
involved, ByPropertyName will connect every matching property and parameter (provided each parameter has been designed to accept pipeline input ByPropertyName).
Figure 9.6
Checking to see if Stop-Process’s -Name parameter accepts pipeline input ByPropertyName
www.it-ebooks.info
104
CHAPTER 9
Figure 9.7
The pipeline, deeper
Attempting to pipe Get-Service to Stop-Process
In the case of our current example, only Name and -Name match. The results? Examine
figure 9.7.
A bunch of error messages. The problem is that services’ names are usually things
like ShellHWDetection and SessionEnv, whereas the services’ executables might be
things like svchost.exe. Stop-Process only deals with those executable names. But
even though the Name property connects to the -Name parameter via the pipeline, the
values inside the Name property don’t make sense to the -Name parameter, which leads
to the errors.
Let’s look at a more successful example. Create a simple comma-separated values
(CSV) file in Notepad, using the example in figure 9.8.
Save the file as Aliases.csv. Now, back in the shell, try importing it, as shown in
figure 9.9. You should also pipe the output of Import-CSV to Get-Member, so that you
can examine the output’s members.
www.it-ebooks.info
Plan B: pipeline input ByPropertyName
Figure 9.8 Create this CSV file
in Windows Notepad.
Figure 9.9
Importing the CSV file and checking its members
www.it-ebooks.info
105
106
CHAPTER 9
Figure 9.10
The pipeline, deeper
Matching properties to parameter names
You can clearly see that the columns from the CSV file become properties, and each
data row in the CSV file becomes an object. Now, examine the help for New-Alias, as
shown in figure 9.10.
Both of the properties—Name and Value—correspond to parameter names of NewAlias. Obviously, this was done on purpose—when you create the CSV file, you can
name those columns anything you want. Now, check to see if -Name and -Value accept
pipeline input ByPropertyName, as shown in figure 9.11.
Both parameters do, meaning this trick will work. Try running the command:
PS C:\> import-csv .\aliases.csv | new-alias
The result will be three new aliases, named d, sel, and go, which point to the commands Get-ChildItem, Select-Object, and Invoke-Command, respectively. This is a
powerful technique for passing data from one command to another, and for accomplishing complex tasks in a minimum number of commands.
www.it-ebooks.info
When things don’t line up: custom properties
Figure 9.11
9.5
107
Looking for parameters that accept pipeline input ByPropertyName
When things don’t line up: custom properties
The CSV example was cool, but it’s pretty easy to make property and parameter names
line up when you’re creating the input from scratch. Things get tougher when you’re
forced to deal with objects that are created for you, or data that’s being produced by
someone else.
For this example, we’re going to introduce a new command that you might not
have access to: New-ADUser. It’s part of the ActiveDirectory module, which you’ll find
on any Windows Server 2008 R2 (or later) domain controller. You can also get that
module on a client computer by installing Microsoft’s Remote Server Administration
Tools (RSAT). But for now, don’t worry about running the command; follow along
with the example.
New-ADUser has a number of parameters, each designed to accept information
about a new Active Directory user. Here are some examples:
-Name (this is mandatory)
-samAccountName (technically not mandatory, but you have to provide it to
make the account usable)
www.it-ebooks.info
108
CHAPTER 9
The pipeline, deeper
-Department
-City
-Title
We could cover the others, but let’s work with these. All of them accept pipeline input
ByPropertyName.
For this example, we’ll again assume you’re getting a CSV file, but it’s coming from
your company’s Human Resources or Personnel department. You’ve given them your
desired file format a dozen times, but they persist in giving you something that’s close,
but not quite right, as shown in figure 9.12.
Figure 9.12 Working with
the CSV file provided by
Human Resources
As you can see in figure 9.12, the shell can import the CSV file fine, resulting in three
objects with four properties apiece. The problem is that the dept property won’t line
up with the -Department parameter of New-ADUser, the login property is meaningless, and you don’t have samAccountName or Name properties—both of which are
required if you want to be able to run this command to create new users:
PS C:\> import-csv .\newusers.csv | new-aduser
How can you fix this? Obviously, you could open the CSV file and fix it, but that’s a lot
of manual work over time, and the whole point of PowerShell is to reduce manual
labor. Why not set up the shell to fix it instead? Look at the following example:
PS C:\> import-csv .\newusers.csv |
>> select-object -property *,
>> @{name='samAccountName';expression={$_.login}},
>> @{label='Name';expression={$_.login}},
>> @{n='Department';e={$_.Dept}}
>>
login
: DonJ
www.it-ebooks.info