编写有用的 Windows PowerShell cmdlet






4.39/5 (9投票s)
2006年5月30日
9分钟阅读

58896

432
本文描述了我开发 Copernic Desktop Search 集成到 Windows PowerShell 中的发现。
引言
我非常喜欢 Copernic Desktop Search (CDS) 应用程序。事实上,桌面搜索应用程序的发明极大地改变了我的工作质量。但这并不是关于桌面搜索的讨论。这一切都关乎 Windows PowerShell,这是微软最新的发明,目的是让我们所有人都忠于 Windows 生态系统。
Windows PowerShell
Windows PowerShell,以前称为 Microsoft Shell (MSH),代号 Monad,现在缩写为 PoSh(我预感贝克汉姆夫人要起诉了!)它是命令提示符和 Windows 脚本宿主的完整替代品。我不会在这里详细介绍,因为它们现在已广为人知,但简而言之,它是一个面向对象的 Shell,允许推送对象而不是文本。这显然有很多好处。我们不再需要抓取屏幕文本来查找有趣的部分,我们只需使用命名属性来选择我们想要的内容。
一个强大的例子
PS> ((get-date) - [DateTime]([xml](new-object Net.WebClient).DownloadString(
"http://blogs.msdn.com/powershell/rss.aspx")).rss.channel.item[0].pubDate).Days
18
PS>
呼。这可能不是你每天都会写的东西,但我选择它只是为了演示“PowerShell”中的“Power”源自何处。上面这行代码实际上会下载 PoSh 团队博客的最新提要,并计算自上次发布以来经过的天数。在写这篇文章时,它们已经延迟了 18 天,这就是我们看到的最终输出。
我想你明白我的意思了。
Snapins 和 Cmdlets
Snapin 可以被认为是 cmdlets 的集合,发音为 command-lets,即提供特定功能的小代码片段。上面提到的 new-object
和 get-date
就是两个这样的 cmdlets。我想做的是用一个这样的 cmdlet 来扩展 PoSh,这也就引出了本文的主题。
拥抱并扩展
正如我之前所说,我喜欢 CDS。但是,我不喜欢必须使用 GUI 来执行搜索。一年前,我花了一些时间浏览 COM 对象,偶然发现了公开的 CDS API。在 CopernicDesktopSearchLib
中,提供了几个接口来使用脚本访问搜索引擎。对我们来说,有两个有趣的方法是 ICopernicDesktopSearch
接口的 ExecuteSearch
和 RetrieveXMLResults
。ExecuteSearch
启动一个异步搜索过程,结果稍后可通过 RetrieveXMLResults
方法获得。不幸的是,正如你将看到的,在 CDS 1.7 中,没有提供方法或事件来知道结果何时在 CDS 中可用。为了解决这个问题,我不得不添加一个不太理想的解决方案,即让应用程序休眠几秒钟,让结果在此期间生成。在目前处于 beta 阶段的 CDS 2.0 中,有额外的接口可以查询搜索何时完成,一旦 2.0 版本发布,我将更新本文。
入门
那么我们如何利用这些知识呢?首先,启动 Visual Studio 2005 并创建一个新的类库项目。这种项目类型将为我们构建一个 DLL,稍后我们会将其提供给 PoSh。
首先添加我们依赖的三项引用;
Copernic Desktop Search Library
安装 CDS 后,可以在“添加引用”对话框的“COM”选项卡中找到它。Visual Studio 将自动为我们创建必要的 COM 互操作。System.Management.Automation
此库包含在 System.Management.Automation.dll 文件中,您可以在 PoSh 安装文件夹中找到它。System.Configuration.Install
您可以在“添加引用”对话框的“.NET”选项卡中找到它。
System.Management.Automation
库包含所有与 PoSh 相关的接口和枚举。System.Configuration.Install
库用于安装逻辑,以便我们的 snapin 自动可用于 PoSh。这是通过添加以下代码来实现的; [RunInstaller(true)]
public class GetFromCopernicSnapIn : PSSnapIn
{
public GetFromCopernicSnapIn()
: base()
{
}
public override string Name
{
get
{
return "Get.FromCopernic";
}
}
public override string Vendor
{
get
{
return "Joakim Mцller, Envious Data Systems HB";
}
}
public override string Description
{
get
{
return "This snapin contains a cmdlet to" +
" search for items using Copernic Desktop Search.";
}
}
}
正如你所见,所有逻辑都由 PSSnapIn
基类提供,我们只需重写项目特定的属性,并用 RunInstaller
属性标记该类。
构建基础
一个典型的 cmdlet 由一个继承自 PSCmdlet
的类组成,该类用 Cmdlet
属性标记,并至少重写 BeginProcessing()
、ProcessRecord()
和 EndProcessing()
中的一个方法。在处理来自管道的输入时,PoSh 框架将根据以下协作图调用这些方法;
正如你所见,你将首先得到对 BeginProcessing()
的一次调用,然后是一次或多次对 ProcessRecord()
的调用,最后是对 EndProcessing()
的一次调用。考虑以下示例;
PS> "PoSh","rocks!" | Write-Host
PoSh
rocks!
PS>
在这里,你将一个字符串数组传递给 Write-Host
cmdlet。在这种情况下,Write-Host
将收到两次 ProcessRecord()
调用,一次是针对字符串“PoSh”,一次是针对“rocks!”。Write-Host
cmdlet 的设计是按顺序将每个字符串输出到控制台。
与此相反的是一个消耗所有输入并在 EndProcessing()
中仅发出数据的命令。Measure-Object
cmdlet 就是一个例子。在这里,所有对象信息都在 ProcessRecord()
中被静默消耗,并在 EndProcessing()
方法中发出摘要。
PS> "PoSh","rocks!" | Measure-Object
Count : 2
...
PS>
创建我们的 Cmdlet 类
我们的类定义将如下所示;
[Cmdlet(VerbsCommon.Get, "FromCopernic")]
public class GetFromCopernic : PSCmdlet
VerbsCommon
类是六个预定义标准动词枚举之一。其他枚举包括 VerbsCommunications
、VerbsData
、VerbsDiagnostics
、VerbsLifecycle
和 VerbsSecurity
。PoSh 开发人员决定采用“动词-名词”的命名方案,其中名词由您选择。在我们的例子中,我们正在从 CDS 检索内容,因此我们使用 get
动词。
添加交互性
要与 cmdlet 进行交互,您需要向类添加公共属性,并用 Parameter
属性标记它们。一个非常特殊的参数是管道值,即从另一个 cmdlet 输出并到达我们 cmdlet 的数据。
private string query;
[Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true),
ValidateNotNull()]
public string Query
{
get { return query; }
set { query = value; }
}
Parameter
属性用于将此属性提升为 cmdlet 参数。Mandatory
参数设置为 true,因为没有搜索查询我们就无法做太多事情。ValueFromPipeline
参数告诉 PoSh 我们希望在此属性中接收管道数据。
现在我们可以接收要执行的查询了。我们还需要一种方法来指定查询类型,因为 CDS 能够从各种提供程序进行搜索,包括文件和电子邮件。我们通过添加另一个名为 ResultType
的属性来实现这一点。
private string resultType = "files";
[Parameter(Mandatory = false, Position = 1),
ValidateSet(new string[] { "contacts", "emails",
"favorites", "files", "history", "music",
"pictures", "web", "videos" }, IgnoreCase = true)]
public string ResultType
{
get { return resultType; }
set { resultType = value; }
}
您注意到 ValidateSet
属性了吗?PoSh 提供了一系列内置验证器,以确保将正确的参数传递给您的 cmdlet。在这种情况下,我们希望用户从 CDS 中的选项卡(提供程序)集合中进行选择。
添加逻辑
最后,我们将通过添加几行代码来执行实际搜索来完成这项工作。我们需要获取通过 Query
属性提供给我们的查询字符串,并将其提供给 ExecuteSearch
方法。然后,我们等待几秒钟让结果生成,并使用 RetrieveXMLResults
检索结果。这些都不是 PoSh 特有的,所以我不会在这里赘述细节,您可以在源代码 zip 文件中找到它们。
由于此 cmdlet 可能是管道中的众多 cmdlet 之一,因此我们需要将结果写回管道的另一端。我们使用 WriteObject()
方法来实现这一点。由于 PoSh 非常擅长处理对象,因此我们无需特别注意,只需将生成的 XML 字符串转换为 XML 节点即可方便大家使用。这样做是为了我们以后可以直接访问输出的 XML 元素。
XmlDocument resultXml = new XmlDocument();
resultXml.LoadXml(searchResult);
XmlNodeList itemNodes = resultXml.SelectNodes("//Item");
foreach (XmlNode itemNode in itemNodes)
{
WriteObject(itemNode);
}
显示进度
由于 CDS 1.7 版本不支持任何通知、事件或状态轮询来查看搜索是否完成,因此我使用了一个简单的计时器。为了向用户指示一个耗时的过程,PoSh 提供了一个名为 WriteProgress
的命令,该命令会在控制台中显示一个图形进度条。

为了实现这一点,我添加了一个名为 WaitForResults
的方法,该方法只是获取 TimeOut
参数的值,并以小步长休眠,以便能够显示进度。
private void WaitForResults()
{
WriteVerbose("Waiting for results...");
const int numSteps = 10;
int sleepTime = timeOut / numSteps;
for (int i = 1; i <= numSteps; i++)
{
WriteProgress(new ProgressRecord(0,
String.Format(
"Searching for \"{0}\" using Copernic Desktop Search...",
query),
String.Format("{0}% done", i * numSteps)));
WriteDebug(String.Format("Sleeping for {0} ms.", sleepTime));
System.Threading.Thread.Sleep(sleepTime);
}
}
试用
构建 snapin 项目并打开 Visual Studio 2005 命令提示符。导航到调试输出文件夹,然后在 DLL 上运行 InstallUtil
命令以将其注册到 PoSh。
installutil Get.FromCopernic.dll
当然,这也可以使用 PoSh 来完成,但为了简单起见,我选择了这种方法,因为所有环境变量都已为您自动设置。
添加 Snapin
在 PoSh 中,执行以下 cmdlet;
PS> Get-PSSnapin -registered
Name : Get.FromCopernic
PSVersion : 1.0
Description : This snapin contains a cmdlet to search for items using
Copernic Desktop Search.
您会注意到,snapin 现在已准备好使用。要启用其中的 cmdlets,我们需要运行 Add-PSSnapin
cmdlet。PS> Add-PSSnapin Get.FromCopernic
激动吗?你应该这样。如果到目前为止一切顺利,您现在应该能够运行以下命令;
PS> Get-FromCopernic "Microsoft" | select Url
Url
---
C:\...
C:\...
C:\...
最有可能的是,您现在会看到一个包含单词“Microsoft”的前十个文件的路径列表。您隐式地将 System.String
“Microsoft”提供给了您刚刚构建的 Get-FromCopernic
cmdlet 的 Query
参数。恭喜您!
高级用法
我想您可能对这个 cmdlet 的更高级用法感到好奇。ResultType
属性怎么样?尝试执行以下命令;
PS> Get-FromCopernic "Microsoft" -resulttype "Foo"
Get-FromCopernic : Cannot validate argument "Foo" because it does not
belong to the set "contacts, emails, favorites,
files, history, music, pictures, web, videos".
At line:1 char:41
+ Get-FromCopernic "Microsoft" -resulttype <<<< "Foo"
哇。发生了什么?由于我们提供了一个无效的参数,该参数未包含在我们在 ValidateSet
属性中定义的集合中,因此我们会收到一个错误。该错误很友好地为我们提供了有效的 ResultType
。
那么,让我们实际使用这个 cmdlet 做些事情吧。例如,假设您想知道当前文件夹中有多少文档是通过电子邮件发送或接收的。您可以执行以下操作;
PS> Get-ChildItem *.doc | Get-FromCopernic -resulttype "emails" | Measure-Object
Count : 11
Average :
Sum :
Maximum :
Minimum :
Property :
就我而言,我有 11 个命中。我认为这非常强大!详细和调试日志记录
想要更多关于后台发生的事情的细节吗?在整个 cmdlet 中,我调用了 WriteDebug
和 WriteVerbose
方法。这些在测试期间非常有用。要启用调试日志记录,请将变量 $DebugPreference
设置为“Continue”。启用后,代码中提交的所有调试信息都将写入控制台。要再次禁用,请将变量设置为“SilentlyContinue”。详细日志记录也是如此,但通过 $VerbosePreference
变量。
总结
我希望您能从这些信息中获得一些关于如何创建自己的 snapins 和 cmdlets 的灵感。我每天都在学习新东西,以提高我管理信息的能力。我坚信,Windows 可管理性的未来将通过 PoSh 来实现。