您好,脚本专家!杯托在哪?

Microsoft 脚本专家

本专栏基于 Windows PowerShell 的一款预发布版本。文中的所有信息均可能会发生变更。

每当引入新技术时,人们通常都得需要一段时间才能真正将新技术与原有技术区分开来。让我们以第一辆汽车为例吧。之所以会在最初将这些装置认为是不用马拉的马车,那是因为这些装置本身几乎就是这么回事。当您审视它时,第一辆汽车与马拉的马车的区别就在于,一个使用马而另一个不使用马。除了这一点外,很难将它们区分开。

当然,现在几乎不会有人会将汽车误认为是马拉的马车。这是因为,随着时间的推移,汽车制造者们已经设法做了许多改造,这样,汽车就完全不同于马车了。例如,当今的汽车出厂时就带有 CD 播放器、空调和卫星导航系统;而马车怎么会涉及到这些东西。同样,假设您在 19 世纪 80 年代赶着一辆马车,途中需要放下一会儿您的法布奇诺咖啡;哎呦,您真是不走运。相比之下,有些休闲越野车则推出了多达 17 个杯托,而至少有一种型号的汽车为司机一个人就提供了三个杯托。一些较新款式的汽车甚至具有能够根据您的需要而加热或冷却饮料的杯托。

注 过去,人们在驾驶时竟然无法喝到三种不同的饮料,真难以想像他们是如何挣扎度日的。换作您,要是在驾驶时只能喝到一两种饮料,您同意吗?

在一项新技术得到人们的真正认可之前,要想让人们支持新兴的未知技术而不要支持那些原有的可靠技术,需要给他们一个理由。当 DVD 最先问世的时候,它们能够像录像机那样,让您在家中观看电影。从另一方面来说,DVD 比录像带更贵,而且您不能用 DVD 播放机来录下您自己的秀。不用说,DVD 并没有在世界各地引起轰动。事实上,直到 DVD 制造商开始在光盘内加入您甚至不知道会出现在电影中的删掉的场景、演员的解说词,以及其他无法从录像带上获得的东西时,DVD 才开始代替与其功能相同的 VHS。能够在家观看电影还不够;DVD 制造商们所提供的功能必须要更加先进,并超越人们已熟知的那些功能。

注 是否有人真正观看任何的附加项目则不是谈论的重点:人性就是这样,如果提供了某些东西,就会必须将其拿来,而不管对您是否有用。巧合的是,脚本专家正考虑推出这样一个 DVD 光盘:其内容主要由从 TechNet 杂志**专栏中删除的文本以及系统管理脚本的备选结尾组成。

使用 Windows PowerShell

Windows PowerShell™ 是 Microsoft 发布的一项新增命令外壳/脚本编写技术,它是证明这一点的另一个示例。当人们第一次听说 Windows PowerShell 时,他们通常会听到这样的说法:

  • “Windows PowerShell 允许您管理进程和服务。”
  • “Windows PowerShell 可以使您从事件日志中检索事件。”
  • “Windows PowerShell 能够让您使用 WMI 来管理计算机。”

当然,听起来是没什么不对的,都是一些非常有用且重要的任务。但唯一的问题在于,您早就能用 VBScript 和 Windows Script Host 来执行那些任务了。保持向后兼容性非常重要,如果 Windows PowerShell 甚至都不能做与 VBScript 相同的工作,那它的创新意义就不大(设想一下连电影都不能播放的 DVD 播放机。实际上,您根本不必设想:有一位脚本专家的家里就有一台这样的机器)。

尽管如此,但如果 Windows PowerShell 只能做与 VBScript 相同的工作,那我们为什么要一开始就关注这项新技术呢?不可否认,VBScript 没有随附任何杯托,Windows PowerShell 也是如此。那我们使用哪种脚本编写技术会有什么不同吗?

老实说,不会有任何不同。另一方面,撇开缺乏杯托这一点不谈,Windows PowerShell 确实具有 VBScript 所没有的一部分功能。如果您需要做的只是管理进程和服务,那可能就没有必要去了解 Windows PowerShell 了;有句话说得好,东西还没坏,就别急着去修它。(当谈到脚本专家时,这句谚语应该改为“你们不试着修补它吗,因为它一会儿就坏了。”)Windows PowerShell 真正有吸引力的地方在于您能用它做一些 VBScript 所不能做的事情。如,更改文件的上次编写时间。

更改上次编写时间

您没听错:更改文件的上次编写时间。出于各种原因(如版本控制的快捷形式),脚本编写者一直梦想有一天他们能够更改一个文件或一组文件的上次编写时间(或创建时间)。令人沮丧的是,Windows Management Instrumentation (WMI) 和 FileSystemObject 中居然都没有这个功能;在这两项技术中,文件日期和时间是只读的。

但有了 Windows PowerShell,如果要将文件的上次编写时间更改为当前日期和时间,则只需运行以下命令:

$a = Get-Item c:\scripts\test.txt; $a.LastWriteTime = Date

在解释这个命令是如何起作用之前,让我们先回答一个几乎人人都在想的问题:如果 WMI 不能修改文件的上次编写时间,那 Windows PowerShell 是如何做到的呢?我们认为应该先解决这个问题然后再干别的事情。原因很简单,因为它代表了对 Windows PowerShell 的常见误解。人们可能会认为 Windows PowerShell 只是使用 WMI 的一种新方式。当然,您确实能够使用 Windows PowerShell 访问 WMI 数据;在我们等待构建其他 cmdlet(本机 Windows PowerShell 命令)的同时,大多数系统管理脚本仍然主要依靠 WMI,这也是实情。但是,在本质上,Windows PowerShell 是基于 .NET Framework 构建的,Windows PowerShell cmdlet 通常处理的是 .NET 对象,而不是 WMI 对象。这是很重要的,因为通过 .NET Framework 获得的属性并非一定与通过 WMI 获得的属性相同。

例如,当我们使用 Get-Item cmdlet 绑定到文件时,我们不会得到 WMI CIM_Datafile 类的实例;相反,我们得到的是 .NET 对象。这真有什么大不了的吗?的确如此 — .NET 对象提供的用于文件管理的属性和方法集与 WMI 所提供的略有不同。但是,就像我们刚刚看到的那样,这些细微的差别是非常重要的,如只读属性与读/写属性之间的差别。

接下来的问题:我们如何知道 Get-Item cmdlet 返回了 .NET 对象?在这种情况下以及在大多数情况下,查明我们正在处理的是哪种对象的最简单途径(以及确定可供我们使用的属性和方法的最简单途径)是绑定到文件,然后将该对象传递至 Get-Member cmdlet:

Get-Item c:\scripts\test.txt | Get-Member

该命令的结果如图 1 中所示。如您所见,这确实是一个 .NET 对象(脚本专家怎么会将类似内容弄错呢!)

Figure 1 由 Get-Item 返回的对象的成员

   
TypeName: System.IO.FileInfo

Name                      MemberType     Definition
----                      ----------     ----------
AppendText                Method         System.IO.StreamWriter AppendText()
CopyTo                    Method         System.IO.FileInfo CopyTo(String destFileName),
                                         System.IO.FileInfo CopyTo(S...
Create                    Method         System.IO.FileStream Create()
CreateObjRef              Method         System.Runtime.Remoting.ObjRef CreateObjRef(
                                         Type requestedType)
CreateText                Method         System.IO.StreamWriter CreateText()

顺便说一下,这个简单的小命令说明了 Windows PowerShell 的两个更加有趣(和有用)的功能:管道操作和元信息。我们今天不会涉及技术细节,但管道操作背后的基本理念是能够使用 cmdlet 来获得某个对象然后再将该对象移交给其他 cmdlet。在这个命令中,我们使用 Get-Item cmdlet 来获得一个代表文件 C:\Scripts\Test.txt 的对象,然后通过管道将该对象(作为一个对象)移交给 Get-Member cmdlet(符号 | 在 Windows PowerShell 命令中代表管道)。

Get-Member 随后开始工作,恰巧是检索被检索对象的属性和方法。这是 Windows PowerShell 有用功能中的一个:便于您访问元信息,即关于信息的信息。通过使用 cmdlet(如 Get-Member 和 Get-Command),您能够向 Windows PowerShell 询问您需要的所有信息,其方式与编写 WMI 脚本来检索有关可用的 WMI 类、属性和方法的信息大体上相同。

注意,Get-Member 实际上比 WMI 提供的功能更进一步。虽然您能够使用 WMI 获得关于 WMI 对象的信息,但您不能使用 WMI 获得关于其他 COM 对象(如,FileSystemObject)的信息。对于 Windows PowerShell 则不然。Get-Member 适用于您能够绑定到或创建的任何对象。不记得使用 FileSystemObject 时可用的方法了吗?然后运行以下命令:

New-Object -comobject "Scripting.     FileSystemObject" | Get-Member

这真是一个非常有用,也是远没有被充分利用的功能。

很好,是吧?如果我们在使用文件 C:\Scripts\Test.txt 时滚动可用属性和方法的列表,我们将会最终看到下面这一行:

LastWriteTime             Property       System.DateTime LastWriteTime {get;set;}

有意思。显然,LastWriteTime 是一个同时支持 get(读取)和 set(写入)的属性。这意味着我们能够使用 Windows PowerShell 来更改文件上次编写时间的值吗?嗯......

Windows PowerShell 术语表

别名 代表现有 cmdlet 的可自定义名称。Windows PowerShell 提供了大量默认别名,用于将常见外壳命令映射到各自的等价 cmdlet。

Cmdlet Windows PowerShell 中的最小功能单位,与其他外壳中的内置命令相似。

命令 Windows PowerShell 中的执行单位。命令可包括多种管道操作,通过使用 ; 字符,命令还可以跨行。

法布奇诺 星巴克出售的一种混合冰咖啡饮料。

Monad/MSH/Microsoft 命令外壳 Windows PowerShell 先前的代码名称。

对象 从系统或命令检索到的结构化数据。Windows PowerShell 将数据作为 .NET 对象处理,从而您能够通过名称来处理对象属性,而不必一定对基于文本的命令输出进行分析。

参数 命令行 cmdlet 中设置的选项。Windows PowerShell 在开头使用连字符 (-) 来指定一个参数。

管道 能够将一个命令的输出发送到另一个命令的输入。英文中也称为 pipe;在命令中由 | 字符表示。

配置文件 Windows PowerShell 启动时加载的设置,能够使您配置外壳的运行方式。

脚本 保存为文件并通过文件执行的一个或多个命令。对于脚本文件,Windows PowerShell 使用的是扩展名 .ps1。

Shell 可在其中同时运行命令和 cmdlet 的命令行环境。

变量 在命令执行期间能够设置、更改或检索其值的对象或参数。

现在让我们回过头来讨论一下用于将 Test.txt 的上次编写时间设置为当前日期和时间的命令吧。该命令如下所示:

$a = Get-Item c:\scripts\test.txt; $a.LastWriteTime = Date

正如您所看到的那样,这个命令真的没有太多的内容。首先,我们声明一个名为 $a 的变量。然后我们使用 Get-Item cmdlet 连接到文件 C:\Scripts\Test.txt。反过来,我们将该对象储存到变量 $a 中。在输入一个分号(它能够使我们在一行上执行多个命令)后,我们将 LastWriteTime 属性的值设置为当前日期和时间。您知道,这几乎弥补了杯托的不足,不是吗?

展开想像的翅膀

当然,您并非只能将上次编写时间设置为当前日期和时间。例如,考虑以下命令:

$b = Get-Date "5/22/2006 8:15 PM";
$a = Get-Item c:\scripts\test.txt; $a.LastWriteTime = $b

这次我们将三个命令放在了一起。首先,我们使用 Get-Date cmdlet 来创建一个日期和时间分别为 5/22/2006、8:15 PM 的日期时间对象。为什么我们没有在第一个脚本(该脚本用于将上次编写时间设置为当前日期和时间)中使用 Get-Date 来创建日期时间对象呢?道理很简单:Date 命令将自动返回一个日期时间对象(要亲自验证这一点,将下列命令输入到 Windows PowerShell 控制台中:Date | Get-Member)。

在创建了与所需日期时间相等的日期时间对象后,接下来我们使用熟悉的 Get-Item 命令来获得代表文件 Test.txt 的对象(即我们储存在变量 $a 中的对象)。最后,我们将 LastWriteTime 属性设置为由变量 $b 代表的值:

$a.LastWriteTime = $b

不管您是否相信,您还没有完成任务的一半呢。看一看以下这个不吸引人的命令吧:

$b = Get-Date "5/22/2006 8:15 PM";
Get-ChildItem c:\scripts | ForEach-Object {$_.LastWriteTime = $b}

这个命令有什么特别有趣的地方吗?那么,让我们来看一下它的用途。第一部分非常简单:我们再次创建一个日期为 5/22/2006,时间为 8:15 PM 的日期时间对象。但是,注意在第二部分中我们使用的不是 Get-Item cmdlet,而是以下这个命令:

Get-ChildItem c:\scripts

这样做的目的是什么?嗯,无论何时将 Get-ChildItem cmdlet 应用于某个文件夹,您都会得到该文件夹中全部文件的集合。我们将获取该集合,然后将集合中的所有项目通过管道(即 | 符号)移交给 ForEach-Object cmdlet。

ForEach-Object 将会如何处理全部这些文件?嗯,它当然是更改每个文件的 LastWriteTime 属性的值了:

ForEach-Object {$_.LastWriteTime = $b}

注 尽管本月专栏并不是 Windows PowerShell 初学者指南,我们还是应该指出:$_ 是一个特殊的变量,用于代表 For Each 循环中的当前项。它等同于以下 VBScript 循环中的 objItem:

For Each objItem in colItems
    Wscript.Echo objItem.Name
Next

当 Windows PowerShell 命令运行结束时,文件夹 C:\Scripts 中的所有文件将具有相同的上次编写时间。我们想要由这些新发明的无需用马驾驶的马车之一来做这个工作**!

我们可能要整晚运行该命令的各种形式。例如,也许您只想更改文件夹 C:\Scripts 中 .txt 文件的上次编写时间?好的;您需要做的一切就是提问:

$b = Get-Date "5/22/2006 8:15 PM"; Get-ChildItem c:\scripts\*.txt | ForEach-Object {$_.LastWriteTime = $b}

激发您的兴趣

很酷吧。但是,我们应该指出的是本专栏的目的不是向您“兜售”Windows PowerShell,也不是鼓励您抛开所有的 VBScript 脚本然后将它们全部转换为 Windows PowerShell。如果这些脚本能够完成您想要和需要它们做的事情,那么您就不要去改变它;对于批处理文件、命令行工具、魔术拼写和其他任何用于管理计算机的工具也是如此。记住,如果东西还没坏......

其关键并不在于 Windows PowerShell 能够让您做曾经能够做到的事;而是在于,由于 Windows PowerShell 在某种程度上提供了对 .NET Framework 的访问,从而能够让您做先前做不到的事。修改一个文件(或一组文件)的上次访问时间就是这样一个例子。是否还有其他示例取决于您需要做的事情是什么。正是这些功能使得 Windows PowerShell 值得人们仔细研究。

对于许多人来说,这次对于 Windows PowerShell 的介绍可能是不同寻常的:毕竟,与用简单的“Hello, world”之类的示例开头不同,我们引入了一个由多部分组成的 Windows PowerShell 命令,并且抛开了管道和 cmdlet 等术语,即使每个人都了解它们是什么含义。这是有意的。毕竟,我们知道你们当中的许多人还没有尝试过 Windows PowerShell;我们担心,如果我们向您展示如何在计算机上检索服务列表,您只会耸耸肩然后接着做您要做的事。

注意,我们希望您要做的事不是驾驶。对许多人来说,开车时喝三种饮料、观看 DVD 和打手机,而不把“阅读 TechNet 杂志**”加进来是一件很困难的事。

嗨,继续往下看:我们是在对大多数人说,而不是针对所有人。

Microsoft 脚本专家为 Microsoft 工作,也就是说受雇于 Microsoft。在没有棒球等各种娱乐活动时,他们偶尔也会经营一下 TechNet 脚本中心。请访问 www.scriptingguys.com 进行查看。

© 2008 Microsoft Corporation 与 CMP Media, LLC.保留所有权利;不得对全文或部分内容进行复制.