Windows PowerShell 2.0语言之扩展类型系统


本文将简要介绍PowerShell的类型系统,以及内置的类型适配器和类型扩展如何协同工作,以实现所有代码中存在的类型。这里的扩展类型系统不仅仅是对于标准的.NET、COM和WMI对象类型的扩展,也对所有未知扩展公开。可以为活动对象添加属性和成员或者修改类型定义,这样所有该类型的对象都会自动添加新的成员,从而通过为目标对象添加自定义属性和方法来个性化定义运行环境。在学习如何修改对象和类型之前,通常会创建一些操作对象的常用函数,所以必须为函数名添加前缀或后缀以便于辨别函数操作的对象。如果将这些函数改成方法作为扩展类型,即可得到更短的类型名,从而不必记忆过多的函数名。

PowerShell可以灵活地将二进制对象转换为适当格式的文本,这样允许用户将一系列对象转换后填充到表格、列表或者其他便于在屏幕上展示的形式。文本布局系统有多种视图类型并允许用户自定义视图类型,可以将对象及其属性值转换为所需的文本格式后多次使用。本文将介绍默认视图类型,以及如何创建自定义视图并生成不同的报告类型。

1 修改对象和类型

PowerShell的对象很复杂,获取一个对象实例时不必实际操作原始对象,无论其来自.NET、COM、WMI或者其他处。PowerShell中的所有对象均来自PSObject或者其继承,所以PSObject对象是扩展类型系统的基础。它被作为对象的包装来执行,并且会保存对真实对象的引用。作用于真实对象的操作均通过PSObject对象实例起作用,在其中会被类型系统截取和操作。因而允许用户通过增加在原型系统中并不存在的属性和方法扩展类型的功能,同时为脚本用户保留一些原始对象的属性和方法。

1.1 为单对象新增成员

很多脚本语言开放在任何时候修改对象,允许脚本开发人员方便地获取对象的实例并增加属性和方法。在JavaScript等语言中增加属性如同为对象赋属性值那样简单。如果属性不存在,将会自动创建。

PowerShell加强了增加属性和方法方面的限制,但是仍然可以增加任何PSObject对象,只是需要通过使用特定的cmdlet,即Add-Member。这个cmdlet甚至可以为DirectoryInfo对象增加FileCount属性,以保存当前目录中包含的文件数,如下例所示:

PS C:\> $currentDir = (Get-Item .)
PS C:\> $currentDir | Add-Member -MemberType ScriptProperty `
>> -Name FileCount -Value { (dir $this).Count }
>>
PS C:\> $currentDir.FileCount
43

该代码段为$currentDir对象增加了一个只读且称为“脚本属性”(ScriptProperty)的新成员,它们之间的关系更类似属性与调用函数的关系。由两个脚本块定义,其中一个脚本块在主调函数取回属性值时执行;另一个会在设置新值时执行。

$this变量的引用很重要,是访问对象属性的方法。在DirectoryInfo实例中,$this引用指向DirectoryInfo对象。PowerShell中总是使用这个变量在操作方法和属性时传递当前对象实例。

Add-Member这个cmdlet用于在传递-InputObject参数时修改对象,可以传递多个对象,下例为当前目录和父目录增加相同的属性:

PS C:\PowerShell\> $dirs = (Get-Item .),(Get-Item ..)
PS C:\PowerShell\> $dirs | Add-Member -MemberType ScriptProperty `
>> -Name FileCount -Value {(dir $this).Count}
>>
PS C:\PowerShell\> $dirs | select Name,FileCount

Name                                    FileCount
C:\PowerShell\                           5
C:\                                       15

Add-Member cmdlet可以添加多种类型的成员,类型取决于右侧的-MemberType参数值。通过传递配置数据-Value和-SecondValue定义了两个参数,它们对于成员类型有不同的用处。本文主要说明下述成员类型。

(1)AliasProperty::指向同一对象的其他属性,主要用于简写属性名或重命名属性使其与他对象保持一致。此类属性类型在PowerShell中使用很广泛,如Process对象有一个称为“WS”的别名属性指向WorkingSet,以及集合的Length属性用来返回集合中元素的数目,这样与数组(array)有相似之处。

(2)NoteProperty:类似其他编程语言的公共变量,作为简单的变量存储空间,任何脚本块可以对其进行读写。

(3)ScriptProperty:最接近于.NET中执行的属性,同时提供了脚本块的getter和setter方法分别获取属性值和为属性赋值的操作。

(4)ScriptMethof:提供单个脚本块定义方法。

在PowerShell中PSObject作为公开的类型可以创建其实例,这个实例是一个没有任何方法和属性的空对象,配合Add-Member可以创建个性化的对象。PowerShell并没有提供定义类、方法和属性的内置支持,但是用户可以通过创建PSObject实例并添加所需的成员模拟这种支持。使用这种方法可以封装作用于相同数据结构对象的代码,这样的对象在从重用函数返回数据时很有用。

为了简单地创建自定义对象并快速地添加成员,引入一个抽象类。一个单独的对象只有一个方法Create()用于创建新实例,首先需要一个创建类对象的函数。命名为“Define-Class”并保存在Classs-SupportLib.ps1中,这样即可在其他脚本中重用它。

Define-Class函数接受脚本块作为参数,在脚本块中包含构造代码。其中会创建一个新的PSObject用来表现类并附加一个标签属性用于包含构造代码,然后增加Create()方法来调用构造代码,函数代码如下:

function Define-Class($constructor)
{
	$class = New-Object PSObject
	$class | Add-Member NoteProperty Constructor 	$constructor
	$class | Add-Member ScriptMethod Create {
	$instance = New-Object PSObject
	$constructorBlock = $this.Constructor
	$this = $instance
	& $constructorBlock
	return $instance
	}
return $class
}

在Create()方法定义中首先创建一个空对象,然后调用构造体。需要将$instance的值赋值给$this变量,这样构造体中的代码能够通过$this变量引用新创建的对象。

【提示】

要谨记修改$this变量可能会带来一些问题,因其已经指向Create()方法中包含的类对象。如果用户编写的代码需要访问类对象,则扩展这个方法时一定要特别留心保存$this变量的原始值并在调用对象构造体之后恢复该值。

为使用这个函数定义类,需要调用Define-Class并将其返回值存储为类对象,然后使用这个对象创建类实例。下面的代码创建名为“Person-Define.ps1”的范例脚本:

. .\Class-SupportLib.ps1

$Person = Define-Class{
	Write-Host "Creating a Person Instance"
}

$p = $Person.Create()

【提示】

PowerShell语言没有创建对象及其实例的语句,所以需要有一种方式模拟类似于C#、Visual Basic.NET或Java中new关键字的封装。最简单的模拟方式就是用静态方法作为构造体,上例中的Create()实现与Ruby中New()构造体方法相同的功能。

需要强调的是前面的代码中使用点源引用的方式将Class-SupportLib.ps1库文件包含到当前运行环境中,点源引用时两个点之间有一个空格。在构造体块中添加了一个Write-Host语句,这样会在执行任何操作时有输出信息,下面是执行代码时的输出信息:

PS C:\PowerShell> .\Person-Define.ps1
Creating a Person Instance
PS C:\PowerShell>

为了使用向类添加属性和方法的函数扩展脚本库,如果在构造体中有调用函数,则可把整个类定义封装到一个单独的脚本块中。这样做的好处是不必把对象实例作为参数传递到增加成员的函数中,因为现在已经有$this变量。

note属性如同一个公共的区域,可以创建新增Add-Filed函数。这个函数带有Name和Value参数,代码如下:

function Add-Filed($Name,$Value)
 {
     $this | Add-Member NoteProperty $Name $Value
 }

需要注意的是函数必须在类结构体中调用,因其依赖于创建它的$this属性。下例通过从不同脚本Person-NoteProperty.ps1来测试函数:

. .\Class-SupportLib.ps1

$Person = Define-Class {
Write-Host "Creating a Person instance"
Add-Field "FirstName" "LiMing"
}
$p = $Person.Create()
$p.FirstName

以下是脚本的输出:

PS C:\PowerShell> .\Person-NoteProperty.ps1
Creating a Person instance
LiMing

为在对象中添加属性和方法,创建两个函数。其中Add-Method函数的两个参数是Name和包含脚本块和方法体的Body;Add-Property函数的两个脚本块参数是Getter和Setter,分别对应于两个操作获取和设置属性值。如果熟悉Java,可能会注意到其中也有类似的Getter和Setter方法。下面是这两个函数的代码:

function Add-Property($Name, $Getter, $Setter)
{
		$this | Add-Member ScriptProperty $Name $Getter  	
$Setter
}
function Add-Method($Name, $Body)
{
		$this | Add-Member ScriptMethod $Name $Body
}

同样将这两个函数添加到Class-SupportLib.ps1脚本库文件中,便于在其他处引用。这样即可生成FullName属性,用于获取和设置之前创建的Person对象的FirstName和LastName属性。同时添加一个Greet()方法用于向控制台输出字符串。下例在新的脚本文件Person-All.ps1中测试新创建的方法和属性:

. .\Class-SupportLib.ps1

$Person = Define-Class {
		Write-Host "Creating a Person instance"
		Add-Field "FirstName" "John"
		Add-Field "LastName" "Smith"
		Add-Property "FullName" `
		{
		"$($this.FirstName) $($this.LastName)"
		} `
		{
			$value = $args[0]
			$words = $value.Split()
			$this.FirstName = $words[0]
			$this.LastName = $words[1]
}
	Add-Method "Greet" {
			Write-Host "Hello there. I am $($this.FullName)."
		}
}
$p = $Person.Create()
Write-Host "FullName:$($p.FullName)"
Write-Host "Changing LastName to Wang"
$p.LastName = "Wang"
Write-Host "FullName:$($p.FullName)"
Write-Host "Changing FullName to XiaoPang"
$p.FullName = "XiaoPang"
$p.Greet()

该代码中最重要的部分是使用$args这个特定的变量为方法和属性拆分参数,命名参数不会有效,而且总是$null,以下是调用代码时的输出:

PS C:\PowerShell> .\Person-All.ps1
Creating a Person instance
FullName:John Smith
Changing LastName to Wang
FullName:John Wang
Changing FullName to XiaoPang
Hello there. I am XiaoPang .

为了能够简单地创建自定义对象,并快速地添加成员,这里会创建一些通用函数。其中引入一个抽象类,一个单独的对象知道如何创建实例。这个对象只有一个方法Create(),用于创建新实例。首先需要一个创建类对象的函数,命名为Define-Class,并将其保存到Classs-SupportLib.ps1,这样即可在其他脚本中重用它。

Define-Class函数接受脚本块作为参数,在脚本块中包含构造代码。其中会创建一个新的PSObject,用来表现类,并附加一个标签属性用于包含构造代码。然后增加Create()方法来调用构造代码,以下是函数代码:

function Define-Class($constructor)
{
		$class = New-Object PSObject
		$class | Add-Member NoteProperty Constructor 	 	
$constructor
		$class | Add-Member ScriptMethod Create {
		$instance = New-Object PSObject
		$constructorBlock = $this.Constructor
		$this = $instance
		& $constructorBlock
		return $instance
		}
return $class
}

function Add-Field($Name, $Value)
{
		$this | Add-Member NoteProperty $Name $Value
}

function Add-Property($Name, $Getter, $Setter)
{
		$this | Add-Member ScriptProperty $Name $Getter  
$Setter
}
function Add-Method($Name, $Body)
{
		$this | Add-Member ScriptMethod $Name $Body
}

有了这个脚本库,创建自定义对象变得很方便。可以在任何脚本和脚本库中调用这个脚本库,并且用其生成自定义对象的集合。

1.2 为类的所有实例新增成员

Add-Member cmdlet虽然是个很强大的工具,但是只能作用于具有相同类型的对象,用户不能用其定义属性。PowerShell已经为所有特定类型的对象扩展方法和属性定义,这是由于在其安装目录C:\WINDOWS\system32\WindowsPowerShell\v1.0\中有一个types.ps1xml文件。打开这个文件并搜索System.Diagnostics.Process,结果如下:

<Type>
        <Name>System.Diagnostics.ProcessModule</Name>
        <Members>
            <ScriptProperty>
                <Name>Size</Name>
                <GetScriptBlock>$this.ModuleMemorySize / 1024</GetScriptBlock>
            </ScriptProperty>
            <ScriptProperty>
                <Name>Company</Name>
                <GetScriptBlock>$this.FileVersionInfo.CompanyName</GetScriptBlock>
            </ScriptProperty>
            <ScriptProperty>
                <Name>FileVersion</Name>
                <GetScriptBlock>$this.FileVersionInfo.FileVersion</GetScriptBlock>
            </ScriptProperty>
            <ScriptProperty>
                <Name>ProductVersion</Name>
                <GetScriptBlock>$this.FileVersionInfo.ProductVersion</GetScriptBlock>
            </ScriptProperty>
            <ScriptProperty>
                <Name>Description</Name>
                <GetScriptBlock>$this.FileVersionInfo.FileDescription</GetScriptBlock>
            </ScriptProperty>
            <ScriptProperty>
                <Name>Product</Name>
                <GetScriptBlock>$this.FileVersionInfo.ProductName</GetScriptBlock>
            </ScriptProperty>
        </Members>
    </Type>

上述结果说明如何配置扩展类型系统,Type.ps1xml文件包含多个与前述类似的结构单元。其中一部分是.NET的对象,还有少量WMI类。用户可以定义所有类型的成员,如同使用Add-Member cmdlet。一旦加载这个配置文件,PowerShell会处理这个文件并更新其类型数据,新创建的PSObject实例通过类型数据获取成员清单。

为了得到涉及与单个或多个文件安全相关的信息,如文件作者、作者所在的组,以及文件的访问规则或者从父目录继承的权限,通常需要使用Get-Acl cmdlet获取访问控制列表(Access Control List,ACL)并从中提取相关信息。每个文件对应的FileInfo对象都具有3个ScriptProperty成员,即Owner、Group和AccessRules。

2 扩展对象的格式

PowerShell命令作用于对象并且生成对象,在之前的所有实例中尽管是返回对象,输出均被转换为文本,这样用户不易理解返回的是对象这一概念,输出时往往是输出到屏幕或者是写入到文件中。脚本设计者引入了新的概念,称之为“视图”,不同的类型和对象会有很多与之关联的视图。在这里可以显式地引用特定的视图类型,并以表格、清单、宽列,甚至是用户自定义的格式显示。

PowerShell自带的显示视图有Format-Tables、Format-List、Format-Width和Format-Custom,每个类型均有与其关联的默认视图。如下例列出所有后缀为“ps1”的文件:

PS C:\PowerShell> dir *.ps1


    Directory: C:\PowerShell


Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---         2009-4-20     21:21        622 Class-SupportLib.ps1
-a---         2009-3-30     21:37        369 Data-Traps.ps1
-a---         2009-4-12     21:31        131 DotNetProcesses.ps1
-a---          2009-4-1      1:15       1674 Get-CertificateFiles.ps1
-a---         2009-4-20     11:15        925 Help-SearchWeb.ps1
-a---         2009-4-20     21:46        677 Person-All.ps1
-a---         2009-4-20     19:53        121 Person-Define.ps1
-a---         2009-4-20     21:12        165 Person-NoteProperty.ps1
-a---         2009-4-20      2:09        245 Search-Examples.ps1

其中的默认视图是列表形式,使用管道将文件输出传递给Format-Table:

Directory: C:\PowerShell


Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---         2009-4-20     21:21        622 Class-SupportLib.ps1
-a---         2009-3-30     21:37        369 Data-Traps.ps1
-a---         2009-4-12     21:31        131 DotNetProcesses.ps1
-a---          2009-4-1      1:15       1674 Get-CertificateFiles.ps1
-a---         2009-4-20     11:15        925 Help-SearchWeb.ps1
-a---         2009-4-20     21:46        677 Person-All.ps1
-a---         2009-4-20     19:53        121 Person-Define.ps1
-a---         2009-4-20     21:12        165 Person-NoteProperty.ps1
-a---         2009-4-20      2:09        245 Search-Examples.ps1

也可以使用管道将输出传递到Format-List:

PS C:\PowerShell> dir *.ps1 | Format-Table


    Directory: C:\PowerShell


Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---         2009-4-20     21:21        622 Class-SupportLib.ps1
-a---         2009-3-30     21:37        369 Data-Traps.ps1
-a---         2009-4-12     21:31        131 DotNetProcesses.ps1
-a---          2009-4-1      1:15       1674 Get-CertificateFiles.ps1
-a---         2009-4-20     11:15        925 Help-SearchWeb.ps1
-a---         2009-4-20     21:46        677 Person-All.ps1
-a---         2009-4-20     19:53        121 Person-Define.ps1
-a---         2009-4-20     21:12        165 Person-NoteProperty.ps1
-a---         2009-4-20      2:09        245 Search-Examples.ps1

PS C:\PowerShell> dir *.ps1 | Format-List

    Directory: C:\PowerShell

Name           : Class-SupportLib.ps1
Length         : 622
CreationTime   : 2009-4-20 19:34:57
LastWriteTime  : 2009-4-20 21:21:33
LastAccessTime : 2009-4-20 21:45:11
……

Name           : Data-Traps.ps1
Length         : 369
……
Name           : DotNetProcesses.ps1
Length         : 131
CreationTime   : 2009-4-12 21:30:28
LastWriteTime  : 2009-4-12 21:31:00
LastAccessTime : 2009-4-12 21:32:56
……

Name           : Help-SearchWeb.ps1
Length         : 925
CreationTime   : 2009-4-20 11:12:09
LastWriteTime  : 2009-4-20 11:15:12
.
Name           : Person-Define.ps1
Length         : 121
CreationTime   : 2009-4-20 19:50:09
LastWriteTime  : 2009-4-20 19:53:37
LastAccessTime : 2009-4-20 19:53:49
VersionInfo    : File:             C:\PowerShell\Person-Define.ps1
                 InternalName:
                 OriginalFilename:
                 FileVersion:
                 FileDescription:
                 Product:
                 ProductVersion:
                 Debug:            False
                 Patched:          False
                 PreRelease:       False
                 PrivateBuild:     False
                 SpecialBuild:     False
                 Language:
……

可使用管道将输出重定向到Format-Width:

PS C:\PowerShell> dir *.ps1 | Format-Wide


    Directory: C:\PowerShell



Class-SupportLib.ps1                    Data-Traps.ps1
DotNetProcesses.ps1                     Get-CertificateFiles.ps1
Help-SearchWeb.ps1                      Person-All.ps1
Person-Define.ps1                       Person-NoteProperty.ps1
Search-Examples.ps1

可以看到该视图中的输出会尽可能紧凑,但是可能会忽略重要的信息。而Format-Custom cmdlet按照默认值显示,它列出所有的属性和子属性:

PS C:\PowerShell> dir .\Class-SupportLib.ps1 | Format-Custom

class FileInfo
{
  LastWriteTime =
    class DateTime
    {
      Date =
        class DateTime
        {
          Date =
            class DateTime
            {
              Date =
                class DateTime
                {
                  Date =
                    class DateTime
                    {
                      Date = 2009-4-20 0:00:00
......
  Length = 622
  Name = Class-SupportLib.ps1
}

2.1 自定义视图

格式化系统和类型系统有很多相似之处,可以用XML配置文件来扩展,这些文件具有.format.ps1xml的后缀并包含视图定义。视图配置文件在PowerShell的安装目录中,如下例所示:

PS C:\PowerShell> dir $PSHOME\*.format.ps1xml


    Directory: C:\WINDOWS\system32\WindowsPowerShell\v1.0


Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---        2008-12-15      5:00      22258 Certificate.Format.ps1xml
-a---        2008-12-15      5:00      23932 Diagnostics.format.ps1xml
-a---        2008-12-15      5:00      73218 DotNetTypes.Format.ps1xml
-a---        2008-12-15      5:00      19761 FileSystem.Format.ps1xml
-a---        2008-12-15      5:00     256244 Help.Format.ps1xml
-a---        2008-12-15      5:00      81821 PowerShellCore.format.ps1xml
-a---        2008-12-15      5:00      13516 PowerShellTrace.format.ps1xml
-a---        2008-12-15      5:00      15040 Registry.format.ps1xml
-a---        2008-12-15      5:00      19418 WSMan.format.ps1xml

下面使用Update-FormatData cmdlet创建自定义制表符视图显示文件安全信息,并增加名称以获取规整的清单。以帮助用户在执行日常和安全相关的任务中不需要修改系统格式化文件的任何内容,而只需要加载视图配置文件即可。参照FileSystem.format.ps1xml文件来创建新的视图文件,其中仅增加与安全相关的属性。将其保存为FileSecurity.format.ps1xml的配置文件,内容如下:

<Configuration>
<ViewDefinitions>
<View>
<Name>FileSecurity</Name>
<ViewSelectedBy>
<SelectionSetName>FileSystemTypes</SelectionSetName>
</ViewSelectedBy>
<GroupBy>
<PropertyName>PSParentPath</PropertyName>
<CustomControlName>FileSystemTypes-GroupingFormat
</CustomControlName>
</GroupBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Label>Name</Label>
<Width>20</Width>
<Alignment>left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>Owner</Label>
<Width>12</Width>
<Alignment>left</Alignment>
</TableColumnHeader>
<TableColumnHeader>
<Label>AccessRules</Label>
<Alignment>left</Alignment>
</TableColumnHeader>
</TableHeaders>

该视图定义了如下重要内容。

(1)View name:在Format-Table中调用的新创建的视图名。

(2)ViewSelectedBy:定义该视图应用的类型,在FileSystem.format.ps1xml文件中定义FileSystemTypes设置,在这里只是重用这些值。

(3)GroupBy:该属性将会在归类同类型对象时使用。

(4)TableControl:定义表格,其中包含表头及表行的设置。

在视图中有3列,即Name、Owner和AccessRules。下面在使用之前定义的视图更新Shell格式化数据,为了校验定义的视图是否有效,发送一些文件给Format-Table cmdlet并指定-view参数值为FileSecurity:

PS C:\PowerShell\Chapter14> Update-FormatData –
PrependPath .\FileSecurity.format.ps1xml
PS C:\PowerShell\Chapter14>  dir person* |
 Format-Table -view FileSecurity

Name               Owner              AccessRules
----                 -----                -----------
Person-All.ps1      NULL-NOTEBOO    
BUILTIN\Administrators Allow  FullControl
                     K\Administra      
 NT AUTHORITY\SYSTEM Allow  FullControl
                     tor                
 FUHJ-NOTEBOOK\Administrator Allow  FullControl
                                        
 BUILTIN\Users Allow  ReadAndExecute, Synchronize
Person-Define.ps1    NULL-NOTEBOO 
 BUILTIN\Administrators Allow  FullControl
                     K\Administra      
NT AUTHORITY\SYSTEM Allow  FullControl
                     tor               
 NULL-NOTEBOOK\Administrator Allow  FullControl
                                        
BUILTIN\Users Allow  ReadAndExecute, Synchronize
Person-NoteProperty. NULL-NOTEBOO  
BUILTIN\Administrators Allow  FullControl
ps1                     K\Administra  
NT AUTHORITY\SYSTEM Allow  FullControl
                     tor                
NULL-NOTEBOOK\Administrator Allow  FullControl
                                       
 BUILTIN\Users Allow  ReadAndExecute, Synchronize

3 总 结

本文讲述了如何扩展PowerShell类型系统和对象格式化系统,使用类型扩展可以创建可读性很强的脚本。格式化扩展可以帮助用户创建正确的视图,用于生成日常经常用到的报告。

将所有有用的脚本添加到扩展方法中很简单,可以在任何时候使用,并且可以不用考虑其是否扩展方法。但是脚本在其他机器上运行,并在没有自定义扩展支持的情况下脚本可能会出现问题,所以在使用扩展类型系统时要小心。

 

赛迪网地址:http://news.ccidnet.com/art/32859/20100709/2111133_1.html

 

作者: 付海军

版权:本文版权归作者所有

转载:欢迎转载,为了保存作者的创作热情,请按要求【转载】,谢谢

要求:未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任

个人网站: http://txj.shell.tor.hu/


发表回复