第7-9章-7扩展命令8对象9深入理解管道
第7章 扩展命令
可扩展性是PowerShell的一个主要优势。随着微软对PowerShell的持续投入,它为Exchange Server、SharePoint Server、System Center系列、SQL Server等产品开发了越来越多的命令。
7.1 如何让一个Shell完成所有事情
PowerShell的工作原理几乎与MMC完全一致。安装一个给定产品的管理工具(安装管理工具的选项通常包含在产品的安装菜单中。如果你在Windows 7上安装类似Exchange Server的产品,该安装会仅安装管理工具)。这样做会为你提供PowerShell的相关扩展,它甚至可能会创建该产品特定的Shell管理程序。
7.2 关于产品的“管理Shell”
只有一个Windows PowerShell。并不存在Exchange PowerShell和活动目录PowerShell,只有一个Shell。
你可以在一个Shell中包含所有你想要的功能,而在开始菜单中,特定产品的快捷方式不会以任何方式限制或暗示你这些产品存在特殊版本的PowerShell.
7.3 找到并添加插件
PowerShell存在两种类型的扩展:模块和管理单元。首先讲述管理单元。
一个适合管理单元PowerShell的名字是PSSnapin,用于区别这些来自管理单元的图形MMS。PSSnapin在PowerShell v1版本的时候就已经存在。一个PSSnapin通常包含一个或多个DLL文件,同时包含配置XML文件和帮助文档。PSSnapin必须先安装和注册,然后PSSnapin才能识别它的存在。
你可以通过在PowerShell中运行Get-PSSnapin-registered
命令获取到一个可用的管理单元列表。
通过运行Add-PSSnapin
并指定管理单元名称的方式加载一个管理单元。例如:add-pssnapin sqlserverCmdletsnapin100
PSSnapin可以增加Cmdlets命令、提供PSDrive,或者两者都增加。使用Get-Command命令找出已增加的Cmdlets命令。
运行Get-PSProvider
可以查看一个管理单元是否成功加载新的PSDrive,你不能在该Cmdlet命令中指定某个管理单元,所以你必须熟悉哪些提供程序已经存在,并通过查看列表方式发现新增内容。
7.4 扩展:找到并添加模块
PowerShell v3(以及v2)提供的第二种扩展方式称为模块。模块被设计得更加独立,因此更加容易分发,但是它的工作原理类似于PSSnapins。
模块不需要复杂的注册。PowerShell会自动在一个特定的目录下查找模块,PSModulePath这个环境变量定义了PowerShell期望存放模块的路径。
PS C:\>get-content env:psmodulepath
c:\Users\Administrator\Documents\WindowsPowerShell\Modules;c:\Windows\system32\WindowsPowerShell\v1.0\Modules\
在PowerShell v3中,该路径很重要。如果你有位于其他位置的模块,你应该把模块所在的路径加入到PSModulePath这个环境变量中。需要通过系统控制面板而不是PowerShell去修改该环境变量。
为什么PSModulePath这个环境变量的路径如此重要?因为通过它,PowerShell可以自动加载位于你计算机上的所有模块。PowerShell会自动发现这些模块。它看起来好像是所有的模块都已被加载了。
如果一个模块不在被PSModulePath引用的任何一个目录下,你应该使用Import-Module
命令并指定模块的完整路径,如C:\MyPrograms\Something\MyModule。
模块还可以添加PSDrive提供程序。你必须使用与PSSnapins中相同的技巧确定有哪些新的提供程序:运行Get-PSProvider命令。
7.5 命令冲突和移除扩展
大多数的PowerShell扩展都在它们的命令名称的名词部分增加了一个短的前缀,如Get-ADUser和Invoke-SqlCmd。这些前缀看起来有些多余,但是它们可以防止命令名称的冲突。
如果你已经对冲突不厌其烦,你可以随时选择删除冲突的扩展名。你需要运行Remove-PSSnapin 或 Remove-Module
,并指定管理模块或模块命令的名称,从而卸载某个扩展。
7.8 配置脚本:在启动Shell时预加载扩展
我们给你介绍3种更好的方式。第一个涉及创建一个控制台文件。这只能记录已经加载的PSSnapins,而无法加载模块。
二是使用配置脚本。
1. 在你的文档目录创建一个名为WindowsPowerShell的新文件夹(可以在Shell中运行$profile
得到正确路径。
2. 在此文件夹使用记事本创建一个名为profile.ps1的新文件。
3. 在些文本文件中输入Add-PSSnapin和Import-Module命令,以一行一个命令的格式加载管理单元和模块。
4. 回到PowerShell中,你需要启用脚本的执行功能,这在默认情况下处于禁用状态。在该脚本中运行Set-ExecutionPolicy RemoteSigned命令。
5.关闭并重启Shell,这将会自动加载profile.ps1文件,执行里面的命令,为你加载喜欢的管理单元和模块。
7.9 从Internet获取模块
PowerShellGet很像Linux管理员喜爱的包管理器-RPM、YUM、apt-get等。微软甚至还维护一个在线源,称为PowerShell Gallery(http://powershellgallery.com)。
使用PowerShellGet非常简单,甚至有趣。
- 运行Register-PSRepository添加一个源的URL。http://PowerShellGallery.com通常是默认设置。但是也可以添加自用的“gallery”,并利用Register-PSRepository指向该地址。
- 使用Find-Module在源中查找模块。
- 找到所需的模块后,使用Install-Module下载与安装一个模块。
- 使用Update-Module确保你的模块的副本是最新的,如果不是,下载最新版本并安装。
第8章 对象:数据的另一个名称
8.3 探索对象:Get-Member
如果说对象就像内存中一个巨大的表,而PowerShell仅仅在屏幕上展示表的一部分,那么如何看到其他你需要使用的属性呢?使用另一个命令:Get-Member。别名 Gm。例如:
Get-Processs | Gm
Gm可以完整访问进程对象的属性和方法,这是由于该命令还未被过滤用于显示。Gm会查看每一个对象并构建一个包含对象属性和方法的列表。
8.4 使用对象标签,也就是所谓的“属性”
当你查看Gm的输出结果时,你会注意到一些不同种类的属性。
- 脚本属性
- 属性
- NoteProperty
- 别名属性
补充说明:通常来说,.Net Framework中的对象——也就是所有PowerShell对象的来源——只包含“属性”。PowerShell会动态添加其他内容:ScriptProperty、NoteProperty、AliasProperty等。如果你正好在MSDN文档中查看某个对象类型,你无法找到这些额外的属性。
8.5 对象行为,也就是所谓的“方法”
很多对象都支持一个或多个方法,正如我们之前提到过的,是你可以指导对象的行为。
在整本书中,我们更专注于使用PowerShell Cmdlet完成任务。Cmdlet提供了最简单、最具有管理员导向、最聚集任务的方式完成工作。而使用方法就开始进入.NET Framework编程的领域,这会更加复杂且需要更多的背景知识。实际上,我们在这一点上的哲学是:“如果无法通过Cmdlet完成,那就回头使用GUI完成。”相信我们,在你的职业生涯中都不会感受到这种哲学。
8.6 排序对象
PowerShell提供了一个简单的Cmdlet、Sort-Object,就像其名称那样,可以对对象进行排序。
Get-Process | Sort-Object -property VM,ID -Descending
Sort-Object有一个别名,也就是Sort。
8.7 选择所需的属性
另一个有用的Cmdlet是Select-Object。该Cmdlet从管道接受对象,你可以指定希望显示的属性。这使得你可以访问任意属性,减少返回列表,只返回你感兴趣的列。这对于将对象输出到HTML的ConvertTo-HTML命令来说非常有用,因此该Cmdlet通常会创建包含所有属性的表。
Select-Object命令的别名是Select。
8.8 在命令结束之前总是对象的形式
PowerShell管道在最后一个命令执行之前总是传递对象。在最后一个命令执行时,PowerShell将会查看管道中所包含的对象,并根据不同的配置文件决定哪一个属性被用于构建展示在屏幕上的最终结果。
Get-Process |
Sort-Object VM -descending |December 30, 2022 10:05 PM
Select-Object Name,ID,VM
Get-Process将进程对象放入管道。接下来运行Sort-Object,该命令将同样的进程对象放入管道。但Select-Object就有所不同了。进程对象总是拥有相同的成员。Select-Object并不能通过删除你不需要的属性减少属性列表。如果这样的话,结果就不再是进程对象。而是Select-Object创建一个名为PSObject的自定义对象,PowerShell使用这个对象将属性从进程对象中复制出来,结果是自定义对象被放入管道。
当PowerShell发现光标已经到达命令行结尾时,它必须并知道如何对文本输出结果进行排版。这是由于管道中包含的对象不再是进程对象,PowerShell不会再将默认规则和配置应用于进程对象,而是通过查询PSObject的配置和规则,这也是当前管道中包含的配置类型。由于PSObject用于自定义输出,微软并没有为PSOject提供任何规则或配置。而是PowerShell将尽最大努力进行猜测并产生表。在理论上,产生的表可以容纳上述3列信息,但表并不像正常的Get-Process输出结果那样有美观的排版,这是由于Shell缺少使得表更美观的额外的配置信息。
第9章 深入理解管道
9.1 管道:更少的输入,更强大的功能
通过学习如何使用管道,你可以更高效地完成某项工作,而无须编写脚本。
9.2 PowerShell如何传输数据给管道
当将两条命令串联在一起时,PowerShell必须搞清楚怎样将第一条命令的输出作为第二条命令的输入。例如:
PS C:\>Get-Content .\computers.txt | Get-Service
当运行Get-Content命令时,它会将文本文件中的计算机名称放入管道中。之后PowerShell再决定如何将该数据传递给Get-Service命令。但PowerShell一次只能使用单个参数接收传入数据。也就是说,PowerShell必须决定由Get-Service的哪个参数接收Get-Content的输出结果。这个决定的过程就称为管道参数绑定(Pipeline parameter binding),这也是本章主要讲解的内容。PowerShell使用两种方法将Get-Content的输出结果传入给Get-Service的某个参数。该Shell尝试使用的第一种方法称为ByValue;如果这种方法行不通,它将会尝试ByPropertyName。
9.3 方案A:使用ByValue进行管道输入
当使用ByValue这种方式实现管道参数绑定时,PowerShell会确认命令A产生的数据对象类型,然后查看命令B中哪个参数可以接受经由管道传来对象的类型。
PowerShell只允许使用一个参数接收ByValue管道返回的对象类型。也就意味着,由于-Name参数接收了来自ByValue管道返回的String类型数据,那么其他参数就无法再接收该数据。这样我们将文本文件中的计算机名称通过管道传递给Get-Service命令的希望破灭了。
在这个例子中,管道的输入可以正常工作,但是无法得到我们期望的结果。我们再看另外一个示例。在新示例中,我们能得到我们期望的结果。下面是对应的命令行。
PS C:\>Get-Process -Name note* | stop Process
这是诠释管道参数绑定一个比较恰当的示例,同时反映了PowerShell中比较重要的一个知识点:大部分情况下,使用相同名词的命令都可以使用ByValue方式相互之间进行管道传输(比如Get-Process和Stop-Process)。
9.4 方案B:使用ByPropertyName进行管道传输
通过该方法,命令B的多个参数可以被同时使用。
该Shell对该功能的实现其实非常简单:仅仅是寻找能够匹配参数名称的属性名称。就是这么简单。
9.5 数据不对齐时:自定义属性
当我们人为创建某些输入数据时,使用CSV比较适用,因为我们可以人为将属性和参数名称对齐。但是当你必须通过PowerShell处理其他对象或者他人提供的数据时,可能就会变得比较困难。
我们介绍一个之前未使用过的命令New-ADUser。该命令属于活动目录中的五个模块。New-ADUser命令包含一些参数,每个参数用来匹配一个新的活动目录帐号的信息,比如:
- -Name
- -samAccountName
- -Department
- -City
- -Title
PS C:\>Import-CSV .\NewUsers.csv |
>> Select-Object -Property *,
>> @{name='samAccountName';expression={$_.login}},
>> @{label='Name';expression={$_.login}},
>> @{n='Department';e={$_.Dept}} |
>>new-ADUser
>>
看起来,语法比较特别。下面将这部分语法拆开来看。
- 这里我们使用了Select-Object命令以及它的-Property参数。最开始,我们指定了 * 这个属性(*是指“所有存在的属性”)。在*后面,我们使用了逗号,也就意味着我们还会输入其他的一些属性列。
- 之后我们创建一个哈希表,哈希表的结构是以@{为起始,以}为结尾。哈希表中包含了一个或者多个成对的键-值(Key-Value)数据。我们使用Select-Object寻找我们指定的一些特定键。
- Select-Object需要寻找的第一个键可以是Name、N、Lable或者L,该键对应的值也就是我们想创建的属性的名称。在第一个哈希表中,我们指定了samAccountName,第二个哈希表中为Name,第三个哈希表中指定为Department。这三个属性的名称正好可以对应到New-ADUser命令的3个参数。
- Select-Object需要的第二个键可以是expression或者E.该键对应的值是一个包含在{}中的脚本块。在脚本块中,使用特定的$_占位符关联到已存在的管道对象(CSV中的每行数据)。通过$_可以读取管道对象的属性,或者说是CSV文件的一个列。也就是说,通过这种方法来指定新属性的值。
9.6 括号命令
有时候,不管我们怎么尝试,都无法处理管道的输出结果,比如Get-WMIObject。该命令的参数并不能接收来自管道的计算机名称。那么我们应该如何将其他来源的数据(比如一个文本文件,其中每行数据代表一个计算机名称)传递给该命令呢?下述命令无法执行。
PS C:\>Get-Content .\computers.txt | Get-WMIObject -Class win32_bios
此时,我们应该怎么做?答案是使用圆括号。
PS C:\> Get-WMIObject -Class win32_BIOS -computerName (Get-content .\computers.txt)
9.7 提取属性的值
下面的命令可以执行吗?
PS C:\> Get-Service -computerName (Get-ADComputer -filter * -searchBase "ou=domain controllers,dc=company,dc=pri")
很遗憾,上面的命令无法成功运行。查看Get-Service的帮助文件,你可以看到-Computer这个参数只能接收String类型的值。
请运行下面的命令。
Get-ADComputer -Filter * -SearchBase "ou=domain controllers,dc=company,dc=pri" | gm
通过Get-Member命令,我们可以看到Get-ADComputer命令的输出结果是ADComputer类型的对象,而不是String类型的对象。所以-ComputerName这个参数不知道该如何处理这部分数据。但是ADComputer类型的对象包含了一个-Name的属性。接下来我们要做的是,提取出ADComputer类型对象中的-Name属性值,然后将这些值(也就是计算机名称)传递给-ComputerName参数。
再次提醒,我们可以使用Select-Object命令解决这个问题,因为它包含一个可以接收属性名称的参数-ExpandProperty。它会获取对应的属性值,提取属性的值,然后返回这些值(作为Select-Object)的输出结果)。参考下面这个命令。
Get-ADComputer -Filter * -SearchBase "ou=domain controllers,dc=company,dc=pri" | select-object -expand name