[转]使用自定义命令扩展 Windows PowerShell
Jim Truher
原文地址:http://msdn.microsoft.com/zh-cn/magazine/cc163293.aspx
代码下载位置: PowerShell2007_12.exe (154 KB)
Browse the Code Online
您可能已经发现,Windows PowerShellTM 是一款强大而灵活的工具。但是,您可能并不知道可以通过编写自己的 Cmdlet 扩展 Windows PowerShell。在本文中,我将通过编写三个可与 IsolatedStorage 交互的自定义 Cmdlet,向您展示如何创建自己的 Cmdlet。
我之所以选择 IsolatedStorage 用于下文的示例,是因为我尚未看见与 IsolatedStorage 相关的任何其他 Cmdlet,而且我认为此功能将会很有用,因为它可以提供一个保存数据的位置而不会与其他应用程序冲突。
简言之,System.IO.IsolatedStorage 命名空间可让您创建和使用独立存储。您可以读取和写入不太受信任的代码无法访问(以防止敏感信息外泄)的数据。基本上,只有当前用户或代码所在的程序集(它也可以根据域进行隔离)可以使用这些数据。
在这些示例中使用 IsolatedStorage 时,我会将一个键/值对保存为字符串。IsolatedStorage 可以存储所需的任何类型的数据,但是为了服务于本文,我坚持使用字符串类型。请记住,本文的真正目的在于 Cmdlet,IsolatedStorage 只是为我提供一个可使用的示例。还有一点需要注意,此处我只是提供一些简要介绍。当您准备好更深入地了解如何创建自己的自定义 Cmdlet 时,请务必查看 Windows PowerShell SDK。
Cmdlet 概述
Microsoft 创建 Windows PowerShell 时,其设计初衷是简化其他命令行工具的创建,这些命令行工具可提供与 Windows PowerShell 随付的工具相同的一致性和可靠性。这在很大程度上是因为外壳包含一个适用于所有 Cmdlet 的分析器 — 此模型可让开发团队确保针对用户可能执行的每一操作,会以相似的方式完成所有参数分析、错误处理等。
这样一来,Windows PowerShell Cmdlet 和其他独立外壳环境中的命令之间就存在一些非常重大的差异。比如说,Cmdlet 是 Microsoft® .NET Framework 类的实例,不是独立的可执行文件。Cmdlet 一般会输出对象而不是文本,并且不可以对其输出内容进行格式化。Cmdlet 通过对象管道而非文本流来处理其输入对象。Cmdlet 不可以分析自己的参数,也不可以为错误指定表示方式。而且,Cmdlet 是面向记录的,并且一般一次处理一个对象。
Cmdlet 具有特定的结构,必须以一种特别的方式进行属性化,并且必须从特定的基类派生。如果某个特定的 Cmdlet 支持参数,则那些参数也必须以特定方式进行属性化,并且该 Cmdlet 必须提供某些特定方法的实现。所有这些到底是什么意思呢?如果您觉得迷惑,请不要担心。接下来我将更详细地逐个说明这些要求。
Cmdlet 属性
要将 .NET 类声明为 Cmdlet,可以使用 CmdletAttribute 属性来设置该类的属性,CmdletAttribute 属性是唯一一个所有 Cmdlet 都必需的属性。指定 CmdletAttribute 属性时,必须指定一个动词和名词名称对,该对将用作 Cmdlet 的名称。此名称应描述 Cmdlet 要执行的操作以及它要使用的资源。
注意,CmdletAttribute 属性本身是 .NET 类。该类的属性与使用 Cmdlet 时的可用参数相对应。
Cmdlet 名称的名词部分可将您自己自定义的 Cmdlet 与其他 Cmdlet 区分开来,它指定了 Cmdlet 对其执行操作的资源。理想情况下,Cmdlet 名称中所用的名词必须非常详细,如果您拥有一个非常通用的术语,则应将此术语用作 Cmdlet 的后缀。例如,如果要为 SQL 数据库创建 get-server Cmdlet,则应使用“New-SQLDatabase”而不是“New-Database”。特定名词/动词名称的组合便于用户快速发现 Cmdlet,并预测其功能。记住,这些词应是人们能够认识的词,因此不要在 Cmdlet 名称中使用保留的标点(斜线、括号等)或通配符。
由于我要创建使用 Windows® IsolatedStorage 的 Cmdlet,因此我将其用作名词部分的基础。这可能有点长,但是对于 Cmdlet 名称来说,越详细越好。
在动词名称中所使用的动词具有相当严格的准则。记住,一致性在 Windows PowerShell 中非常重要,因此请确保使用恰当的动词。通过使用某个预定义的动词名称,可以提高您的自定义 Cmdlet、已包括的 Cmdlet 以及其他用户创建的 Cmdlet 之间的一致性。例如,要检索数据,应使用 Get,而不是 Retrieve 或 Acquire。Windows PowerShell 中常用的动词包括:Add、Clear、Copy、Get、Join、Lock、Move、New、Remove、Rename、Select、Set、Split 和 Unlock。从其名称即可看出每个动词的用途。在本文中,我将创建三个 Cmdlet:一个用于设置 IsolatedStorage 的数据内容,一个用于检索内容,一个用于删除 IsolatedStorage 文件。因此,我将创建以下三个 Cmdlet:Set-IsolatedStorageData、Get-IsolatedStorageData 和 Remove-IsolatedStorageFile。
Cmdlet 类定义
现在我需要创建实现 Cmdlet 的代码,从 Set-IsolatedStorageData Cmdlet 开始。首先声明 Cmdlet 类:
[Cmdlet(VerbsCommon.Set , "IsolatedStorage", SupportsShouldProcess=true)] public class SetIsolatedStorageCommand : PSCmdlet
注意,我使用的是 Pascal 大小写,并且类名称中包括 Cmdlet 的动词和名词名称。严格来讲不必这样,但这样可让我的代码可读性更强。
Cmdlet 属性表明我使用的是常用动词,本例中是 Set。如果可能的话,应在创建 Cmdlet 时始终使用常用动词。将 SupportsShouldProcess 设置为 True 表示该 Cmdlet 支持调用 ShouldProcess 方法,这使 Cmdlet 可以在执行更改系统的操作前提示用户进行确认。如果此属性不存在,或者设置为 False(默认值),则说明该 Cmdlet 不支持调用 ShouldProcess 方法。
声明 CmdletAttribute 属性时,更改 Windows PowerShell 外部资源的所有 Cmdlet 都应将 SupportsShouldProcess 属性设置为 True。这让 Cmdlet 可以在执行其操作前调用 ShouldProcess 方法。如果 ShouldProcess 调用返回 False,则将无法执行该操作。(有关 ShouldProcess 调用生成的确认请求的详细信息,请参阅位于 msdn2.microsoft.com/ bb204629 的 MSDN® 文档。)Confirm 和 WhatIf Cmdlet 参数仅可用于支持 ShouldProcess 调用的 Cmdlet。
最后一部分声明类,并将 PSCmdlet 用作基类,这意味着我将使所有扩展行为与 Windows PowerShell Cmdlet 相关联。
Windows PowerShell 支持从以下两个不同基类派生的 Cmdlet:PSCmdlet 和 Cmdlet。从 PSCmdlet 派生的 Cmdlet 允许您访问 Windows PowerShell 运行时。它允许调用其他脚本,并允许访问 Windows PowerShell 提供程序以使用会话状态。PSCmdlet 还提供对 Windows PowerShell 日志记录功能的访问权,不过此访问会导致规模变大,并且会让您依赖 Windows PowerShell 运行时。
从 Cmdlet 类派生的 Cmdlet 只提供对 Windows PowerShell 运行时的最少依赖关系。其优点是:由于具有较少的功能,这些 Cmdlet 会小一些,并且也将减小因 Windows PowerShell 随着时间推移发生变化而引起问题的可能性。此外,可以很轻松地将这些 Cmdlet 包含到没有 Windows PowerShell 运行时的其他应用程序中。
如果打算创建一个 Cmdlet 并希望它始终是 Windows PowerShell 环境的一部分,则应该使用 PSCmdlet 作为基类。但是,如果您认为代码不会只用于 Windows PowerShell,则应该使用 Cmdlet 作为基类。
就我的例子而言,我将从 PSCmdlet 派生。创建 Cmdlet 时,需要引用 System.Management.Automation.dll,但是它有些难找,因为它只存在于全局程序集缓存 (GAC) 中,不过我想好了一个帮助解决此问题的对策,马上就会与大家分享。
参数定义
接下来我需要考虑 Cmdlet 的参数。用户可以通过参数向 Cmdlet 提供输入。
Cmdlet 参数名称在整个 Cmdlet 设计过程中应保持一致。在参数名称以及如何在 Cmdlet 中使用这些名称帮助确保与可能遇到的其他 Cmdlet 保持一致的问题方面,Windows PowerShell SDK 提供了相关详细建议。
要声明 Cmdlet 的参数,必须首先定义代表这些参数的属性。要通知 Windows PowerShell 运行时某个属性是 Cmdlet 参数,只要将 ParameterAttribute 属性添加到参数定义中即可。
参数必须显式地标记为公共,那些未标记为公共的参数默认为内部参数,不会被 Windows PowerShell 运行时找到。这在您试图查明为何 Cmdlet 没有包含您认为应该包含的参数时,可能会导致一些混淆。
在我的示例中,我知道我所有的 Cmdlet 需要一个名称,用于指明 IsolatedStorage 文件的实际名称。因此以下便是参数声明 — 一个 Name 参数:
private string _name = "PowerShellIsolatedStore"; /// <summary>name of store</summary> [Parameter] public string Name { get { return _name; } set { _name = value; } }
创建参数时,应选择将创建位置参数还是命名参数。如果是位置参数,就不必提供参数名称,只提供值即可:
PS> cd c:\windows
通过将 Position=num 设置为属性的一部分,可以指定用于该参数的位置。如果不是位置参数,则可以不使用 Position 属性,而从命令行使用参数名称来提供值。
相关文档建议尽可能将常用的参数设置为位置参数。此建议的唯一问题是,如果参数很多的话,可能有点难记。当然,即使参数是位置参数,仍可从命令行使用参数名称。
Cmdlet 参数可定义为强制参数,这意味着只有在赋值后,Windows PowerShell 运行时才能调用 Cmdlet。或者,也可以将 Cmdlet 参数定义为可选参数。默认情况下,所有参数都定义为可选参数。要定义可选参数,只要省略属性声明中的 Mandatory 属性即可。由于我要将一个键和一个值存储在独立存储中,因此需要创建参数,以便收集那些值(请参见图 1)。
Figure 1 收集键和值时所需的参数
private string _key = null; [Parameter( Mandatory=true, Position=1, ValueFromPipelineByPropertyName=true )] public string Key { get { return _key; } set { _key = value; } } private string _value = null; /// <summary>the value to store</summary> [Parameter( Mandatory=true, Position=2, ValueFromPipelineByPropertyName=true )] public string Value { get { return _value; } set { _value = value; } }
Cmdlet 参数也可以有别名。要告知 Windows PowerShell 某个参数具有别名,只需将 AliasAttribute 属性添加到属性定义中即可。声明该属性的基本语法为 [Alias("alias")]。在我的示例中,我创建了一个名为 Filename 的别名,将其应用到 Name 参数,如下所示:
private string _name = "PowerShellIsolatedStore"; /// <summary>name of store</summary> [Alias("Filename")] [Parameter] public string Name { get { return _name; } set { _name = value; } }
公用参数
Windows PowerShell 保留了几个您无法使用的参数名称,即所谓的“公用参数”:WhatIf、Confirm、Verbose、Debug、ErrorAction、ErrorVariable、OutVariable 和 OutBuffer。此外,还保留了这些参数名称的下列别名:vb、db、ea、ev、ov 和 ob。
ShouldProcess 参数
只有当 Cmdlet 在其 CmdletAttribute 属性中指定 SupportsShouldProcess 关键字时,才会出现另一组参数(即 ShouldProcess 参数)。
当 Cmdlet 支持 ShouldProcess 时,就可以在运行时访问以下参数:Confirm 和 WhatIf。Confirm 指定在 Cmdlet 执行修改系统的操作前是否需要用户确认。True 表示需要确认;False 表示不需要确认。
WhatIf 指定在执行了一项操作但未实现操作效果时,Cmdlet 是否应通知用户本应显现怎样的更改。True 表示操作未发生时通知用户,False 表示该操作应该发生。
如果指定了 SupportsShouldProcess,则尝试注册 Cmdlet 时,将无法声明这些参数。不应直接在 Cmdlet 中定义这些参数,而应在使用 [Cmdlet(...)] 属性时包括 SupportsShouldProcess。
参数集
Windows PowerShell 使用参数集概念。这样您可以编写将不同参数集公开给用户的单个 Cmdlet,并根据用户指定的参数返回不同的信息。例如,如果用户指定 List 或 LogName 参数,则 Get-EventLog Cmdlet(内置于 Windows PowerShell 中)将会返回不同的信息。指定 LogName 时,Cmdlet 会返回有关给定事件日志中事件的信息。而指定 List 时,Cmdlet 返回的是有关日志文件本身(而非其包含的事件信息)的信息。在此例中,List 和 LogName 代表两个不同的参数集。
当定义了多个参数集时,如果 Windows PowerShell 没有足够的信息确定要使用的参数集,Cmdlet 可以指明。这种情况下使用的参数集称为默认参数集,它是使用 CmdletAttribute 声明的 DefaultParameterSet 关键字指定的。注意,我的示例 Cmdlet 中没有使用参数集。
方法覆盖
Cmdlet 类提供虚拟方法,如图 2 所示,这些虚拟方法可用于处理记录。其中的一个或多个方法必须由所有派生的 Cmdlet 类覆盖。
Figure 2 虚拟方法
方法 | 用途 |
---|---|
BeginProcessing | 为 Cmdlet 提供可选的一次性预处理功能。 |
ProcessRecord | 为 Cmdlet 提供逐条记录处理功能。它可以被调用任意次,也可以一次也不调用,具体视 Cmdlet 的输入而定。 |
EndProcessing | 为 Cmdlet 提供可选的一次性后处理功能。 |
StopProcessing | 当用户异步停止 Cmdlet(例如通过输入组合键 Ctrl+C)时停止处理。 |
由于我的 Cmdlet 主要负责处理文件,因此我将使用 BeginProcessing 方法实现用于打开 IsolatedStorage 文件的代码,如图 3 所示。这里有几点值得注意。首先,我是在创建模式下打开文件的,这是一种系统更改,因此我应将该代码封装在 ShouldProcess 块中。也就是说,我在实际执行操作之前有机会告诉用户我即将进行的操作。(这在用户使用 Confirm 或 WhatIf 参数时实现。)还应注意,我将捕捉异常并使用 ThrowTerminatingError 封装异常。在此示例中,如果有任何错误发生,都不可以继续操作,因为“文件打开”操作可能已经失败。捕捉所有异常通常并不是正确的做法,但是由于我将其封装在 ThrowTerminatingError 中,因此能够在发生失败时多提供一些信息。
Figure 3 使用 BeginProcessing 方法
protected override void BeginProcessing() { try { if ( ShouldProcess( Name )) { WriteVerbose("Opening Isolated Storage: " + Name); isoStore = this.GetMyStore(); fs = new IsolatedStorageFileStream( Name, FileMode.OpenOrCreate|FileMode.Append, FileAccess.Write, isoStore ); sw = new StreamWriter(fs); WriteDebug("Stream encoding: " + sw.Encoding); } } catch ( Exception e ) { this.closeStreams(); ThrowTerminatingError( new ErrorRecord( e, "OpenIsolatedStorage", ErrorCategory.NotSpecified, Name ) ); } }
在我的实现中,我将使用 ProcessRecord 为独立存储中的数据创建新条目(请参见图 4)。注意使用 try/catch 语句,以便在出错时添加更多信息。在此示例中,我使用的是 WriteError 而不是 ThrowTerminatingError,因为我不需要在发生错误写入时停止管道。
Figure 4 在 Set-IsolatedStorageData 中使用 ProcessRecord
protected override void ProcessRecord() { try { // Remember ShouldProcess may not have opened the file if(sw != null ) { WriteVerbose("Setting " + Key + " = " + Value); sw.WriteLine(Key + "=" + Value); } } catch ( Exception e ) { WriteError( new ErrorRecord( e, "SetIsolatedStorageValue", ErrorCategory.NotSpecified, Name ) ); } }
由于我从 BeginProcessing 内打开了独立存储,因此我将使用 EndProcessing 方法关闭文件。我没有在 Remove-IsolatedStorageFile 中读取或写入独立存储文件,因此我将使用 EndProcessing 删除此 Cmdlet 中的文件。Get-IsolatedStorageData 和 Set-IsolatedStorageData Cmdlet 的 EndProcessing 代码类似如下内容:
protected override void EndProcessing() { if (sw != null ) { sw.Close(); } if (fs != null ) { fs.Close(); } if (isoStore != null ) { isoStore.Close(); } }
Remove-IsolatedStorageFile 中的代码要稍微复杂些。在此 Cmdlet 中,我使用恰当的方法从 IsolatedStorage 对象中删除文件本身:
if(ShouldProcess("Remove Isolated Storage")) { WriteVerbose("Deleting Isolated Storage: " + Name); isoStore = this.GetMyStore(); isoStore.DeleteFile(Name); }
注意,我再次用到了 ShouldProcess。由于我要对系统进行更改,因此需要通知用户我要执行的操作(如果用户需要此信息的话)。
生成结果
Windows PowerShell 的全部目的就在于结果,但是在提供那些结果时需要找到一种平衡。这种平衡的意思是,在确保返回尽可能多的信息的同时,不会对性能产生太大影响(例如使用了太多内存、执行耗时太长等)。由于我要将文本保存到文件中,因此可以只返回文本本身。虽然这么做是可行的,不过我想展示一些更好的做法,因此我要提供键和值。
在我最初开始了解 IsolatedStorage 时,我发现要找到用于存储的实际文件非常麻烦,因此我想将该信息包括到我的结果中。这会使结果更有用。通过返回键、值以及数据路径,我的对象将如下所示:
public class IsolatedStorageData { public string Key; // The Key public string Value; // The Value public string FullName; // The path to the storage }
此外,我还需要确定对象的 ToString 方法应返回的字符串。默认情况下,大部分 .NET 对象只返回类型名称 — 这没有多大用处。因此,我将使 ToString 方法返回值而不是类型名称。
为了为 FullName 成员获得实际文件名的值,我需要执行一些映射,因为此信息未作为 IsolatedStorage 信息的一部分提及。这样,我要用于结果的对象的实现将如图 5 所示。
Figure 5 用于结果的对象
public class IsolatedStorageData { public string Key; // The Key public string Value; // The Value public string FullName; // The path to the storage public override string ToString() { return Value; } public IsolatedStorageData( string _key, string _value, IsolatedStorageFileStream _fs ) { Key = _key; Value = _value; FullName = _fs.GetType() . GetField("m_FullPath", BindingFlags.Instance|BindingFlags.NonPublic ) . GetValue(_fs).ToString(); } }
再次提醒注意,为了保持所有 Cmdlet 间的一致性,Windows PowerShell Cmdlet 会将对象返回到管道而不是将文本返回流中。Cmdlet 使用 WriteObject 生成结果。从 IsolatedStorage 文件中检索数据后,我将该数据转换为 IsolatedStorageData 类型的实例,并使用 WriteObject 将那些结果生成到管道中。
报告错误条件
运行代码时,有时候事情不会按预期进行,并出现某种类型的错误。Windows PowerShell 针对这类情况采取了一些相当复杂的应对行为,它所具备的错误报告功能可对所发生的情况进行精密控制。
有时,发生的错误并不是灾难性的。例如,如果想删除某个目录中的数千个文件,其中的一或两个文件未能删除并不会使其他所有文件的删除操作失效。这些都属于非致命性错误 — 也就是说,它仍然是错误,但该错误并不会导致您不得不停止正在执行的操作。您可以继续删除那些可删除的文件。
但是,有些操作是无法恢复的。假设您需要创建一个临时文件,以存储稍后要用到的一些数据。如果无法创建并使用该临时文件,那么就不必继续剩下的操作,因为需要的数据无法使用。这属于 TerminatingError。在 Windows PowerShell 中,有两种不同的 Cmdlet 方法 — WriteError 和 ThrowTerminatingError — 可帮您对此进行区分。
如果执行 Cmdlet 时发生某种异常情况,但对 Cmdlet 的整体操作而言并不致命,那么可以使用 WriteError。该方法将 ErrorRecord 的实例作为参数,允许包括除异常以外的更多信息(例如错误的原因)。
其实不应该在 Cmdlet 中引发异常。相反,ThrowTerminatingError 可让您停止管道的执行,并且提供的信息比使用异常可得到的要多得多。
您可能会注意到,在我的 Cmdlet 代码中,我在 BeginProcessing 中使用的是 ThrowTerminatingError,但接着在 ProcessRecord 方法中使用的是 WriteError。原因在于,如果我无法访问 IsolatedStorage,则通常对于 Cmdlet 操作而言,我几乎束手无策了。不过,如果在读取文件时遇到错误,我或许能够恢复并继续。
故障排除消息
Windows PowerShell 提供了许多与用户通信的方式。当想将非结果或错误一类的信息传达给用户时,可使用三种方法通知用户。您应该知道,在使用这些方法时,无法重定向即将发送的消息,但是可以通过在外壳中设置某些首选项禁止发送它们。这些方法可直接与宿主应用程序通信(在本例中是 PowerShell.exe),然后写入控制台窗口。首选项变量提供了各种行为,从不写入任何内容到询问继续前是否要写入消息等等。
我在我的示例 Cmdlet 中对这些行为的使用比您可能用到的要多一些,因为我想说明它们到底有多大帮助。如果您的代码很复杂,需要的不只是错误或结果,那么请务必使用这些可用的方法。您想做的最后一件事是从 Cmdlet 使用类似 System.Console.WriteLine 的东西。首先,这是很不正确的做法;其次,您不应该依赖宿主应用程序,因为控制台可能并不存在。
如果要向用户传递额外的带外信息,应该使用 WriteVerbose。这并不是针对开发人员的消息,而是为了让您的用户了解实际正在发生的情况。接口非常简单。以下是来自我的 Remove-IsolatedStorageFile 的示例,它使用 WriteVerbose 输出即将删除 IsolatedStorage 文件这一信息:
if(ShouldProcess("Remove Isolated Storage")) { WriteVerbose("Deleting Isolated Storage: " + Name); isoStore = this.GetMyStore(); isoStore.DeleteFile(Name); }
想与开发人员通信时,可以使用 WriteDebug,这可让他们有机会对您的 Cmdlet 中的错误行为进行故障排除。通常,您可对系统附加一个调试器,但是在实际情况下有时无法那样做。这类情况下,WriteDebug 就派上用场了。
另一种与用户通信的方法是 WriteWarning,它提供了执行 Cmdlet 的相关信息。(在我的 Cmdlet 示例中尚未使用此方法。)使用 WriteWarning 的一种很好的情形是希望随时间的推移不断更改 Cmdlet 的行为。例如,如果打算弃用某个参数并希望告知用户停止使用它而转向另一个新参数,则可以使用 WriteWarning。
Cmdlet 组
我的所有三个 Cmdlet 都与 IsolatedStorage 有关,因此所有名词都具有同一个根:IsolatedStorage。其中两个 Cmdlet 用于处理实际数据(Set-IsolatedStorageData 和 Get-IsolatedStorageData),另外一个则用于删除文件 (Remove-IsolatedStorageFile)。
由于所有这些 Cmdlet 具有共同的参数(将用到的实际文件的名称),因此我不必在所有 Cmdlet 中都实现同一参数。相反,我可以创建一个从 PSCmdlet 派生而来的 IsolatedStorageBase 基类,然后 Cmdlet 可以从 IsolatedStorageBase 派生(请参见图 6)。这是在多个 Cmdlet 之间确保一致性的好办法。
Figure 6 IsolatedStorageBase 基类
public class IsolatedStorageBase : PSCmdlet { [Parameter] public string Name ... } [Cmdlet(VerbsCommon.Set, "IsolatedStorageData",SupportsShouldProcess = true)] public class SetIsolatedStorageDataCommand: IsolatedStorageBase { ... } [Cmdlet(VerbsCommon.Get, "IsolatedStorageData")] public class GetIsolatedStorageDataCommand: IsolatedStorageBase { ... } [Cmdlet(VerbsCommon.Remove,"IsolatedStorageFile",SupportsShouldProcess = true)] public class RemoveIsolatedStorageFileCommand: IsolatedStorageBase { ... }
创建管理单元
为了使用这些新 Cmdlet,您需要将它们添加到 Windows PowerShell 环境中。Windows PowerShell 可以通过管理单元将 Cmdlet 动态添加到会话中。为避免与 MMC 管理单元产生混淆,我们将 Windows PowerShell 管理单元称为 PSSnapIn。
要创建 PSSnapIn,需要编写一些代码来执行两个任务。首先,该代码为您的管理单元提供标识,以便将其与系统上安装的其他管理单元区分开来。其次,该代码提供正确安装管理单元以及创建相应的注册表项以允许 Windows PowerShell 找到该程序集的相关信息。
System.Management.Automation 命名空间中有两种类型的 Windows PowerShell 管理单元:PSSnapIn 和 CustomPSSnapIn。如果要自动注册同一程序集中的所有 Cmdlet 和提供程序,应使用 PSSnapIn。如果要注册同一程序集中的 Cmdlet 和提供程序的子集,或者注册不同程序集中的 Cmdlet 和提供程序,则应使用 CustomPSSnapIn。我的管理单元的代码如图 7 所示。它非常简单。我只是覆盖了相应的成员,然后几乎就算完成了。
Figure 7 我的自定义 Cmdlet 的管理单元
// This class defines the properties of a snapin [RunInstaller(true)]public class IsolatedStorageCmdlets : PSSnapIn { /// <summary>Creates an instance of DemoSnapin class.</summary> public IsolatedStorageCmdlets() : base() { } ///<summary>The snap-in name that is used for registration</summary> public override string Name { get { return "IsolatedStorageCmdlets"; } } /// <summary>Gets vendor of the snap-in.</summary> public override string Vendor { get { return "James W. Truher"; } } /// <summary>Gets description of the snap-in. </summary> public override string Description { get { return "Isolated Storage Cmdlets"; } } /// <summary>The format file for the snap-in. </summary> private string[] _formats = { "IsolatedStorage.Format.ps1xml" }; public override string[] Formats { get { return _formats ; } } }
格式化
注意,在图 7 中,有一个此类管理单元的 Formats 成员值。这可用于为 Cmdlet 生成的对象创建格式化指令。在本例中,我有一个简单对象,但是我不想默认显示所有成员 — 我想确保只打印有用的信息,在本例中是键和值。
创建这些格式文件本身就可以写一篇文章,本文不对此详述。图 8 提供了用于创建表格格式的示例代码,该表格包含我所关注的两个列:IsolatedStorageData 对象的键和值。就此例而言,可以只在程序集所在的目录中创建名为 IsolatedStorage.Format.ps1xml 的文件,并在加载管理单元后,不会出现任何错误。
Figure 8 示例格式文件
<Configuration> <ViewDefinitions> <View> <Name>IsolatedStorage</Name> <ViewSelectedBy> <TypeName>IsolatedStorageSnapin.IsolatedStorageData</TypeName> </ViewSelectedBy> <TableControl> <TableHeaders> <TableColumnHeader> <Label>Key</Label> <Width>12</Width> </TableColumnHeader> <TableColumnHeader /> </TableHeaders> <TableRowEntries> <TableRowEntry> <TableColumnItems> <TableColumnItem> <PropertyName>Key</PropertyName> </TableColumnItem> <TableColumnItem> <PropertyName>Value</PropertyName> </TableColumnItem> </TableColumnItems> </TableRowEntry> </TableRowEntries> </TableControl> </View> </ViewDefinitions> </Configuration>
安装和加载 PSSnapIn
安装管理单元非常简单。只要运行 Installutil.exe 并提供程序集路径即可。运行此实用程序后,会在 HKLM\SOFTWARE\Microsoft\PowerShell\1\PowerShellSnapins\<snapinname> 下创建一些注册表项。Windows PowerShell 加载管理单元后,这些注册表项会用于加载程序集和搜索各种配置文件。还有一点值得一提,即 Installutil.exe 只是开发期间建议使用的安装方法,因为该实用程序的操作并不会正确注册它们的卸载依赖关系。就生产环境中的安装而言,应该直接配置注册表项。
加载管理单元同样很简单。您主要会用到的 Cmdlet 是 Add-PSSnapIn、Remove-PSSnapIn 和 Get-PSSnapIn。毫无疑问,add-PSSnapIn 会将一个或多个 Windows PowerShell 管理单元添加到当前会话中。Remove-PSSnapIn 会从当前会话删除管理单元。然后,Get-PSSnapIn 会检索计算机上的 Windows PowerShell 管理单元。
请注意,Remove-PSSnapIn 并不会实际卸载程序集。它只是将 Cmdlet 和提供程序从 Windows PowerShell 用于搜索 Cmdlet 和访问提供程序的列表中删除。
总结
在完成刚才介绍的所有操作后,即可开始使用我自定义的新 Cmdlet 了。当然,我是从命令行执行所有操作的。首先,我必须编译 Cmdlet 和管理单元代码。还记得我说过需要引用 System.Management.Automation.dll 来编译此代码吗?此 DLL 可以在 SDK 和 GAC 这两处找到,但是如果尚未安装 SDK,也不必担心。这个小脚本可以轻松创建管理单元程序集。需要执行的第一个操作是为 C# 编译器创建别名,创建好以后,找到 System.Management.Automation.dll 的位置,然后编译该程序集:
New-Alias csc "${V2Framework}\csc.exe" $SMADLL = [PSObject].Assembly.Location csc /target:library IsolatedStorageSnapin.cs /r:$SMADLL
既然已编译好程序集,接着我则可以使用 Installutil.exe 来注册我的新管理单元,如图 9 所示。成功完成安装后,就可以找到新的 PSSnapIn:
图 9 注册管理单元 (单击该图像获得较大视图)
PS> get-PSSnapIn -reg iso* Name : IsolatedStorageCmdlets PSVersion : 1.0 Description : Isolated Storage Cmdlets
接着,我将管理单元添加到我的会话中:
PS> add-PSSnapIn IsolatedStorageCmdlets
现在,我可以如图 10 所示的那样使用 Cmdlet 了!
图 10 使用 Cmdlet (单击该图像获得较大视图)