您好,脚本专家!我们最喜爱的 Shell 比赛

The Microsoft Scripting Guys

下载这篇文章的代码: HeyScriptingGuy2008_03.exe (150KB)

古老的 谚语说道:上天眷顾一切生命,即使是一只小小鸟也会受到她的恩泽。当然,脚本专家们并不建议大家尝试把麻雀从天上打下来。(尽管某些人希望好好“照顾”一下那些周六早上 7:00 就坐在屋顶上尖叫个不停的蠢鸟,不过,那就是另外一个故事了。[脚本编辑就像那些小小鸟,所以我们认为所谓的“照顾”撰写本专栏的脚本专家的含义是“确保他们保持健康状态”-Ed.])然而,令人欣慰的是如果您确实生病了,总会有人来关心您,并且无论您看起来多么渺小、多么不重要,始终有人在某处关注着您。[尽管有可能只是一个编辑。-Ed.]

适用于小小鸟的真理对于系统管理脚本来说同样正确。可以肯定地说,在脚本世界中,大多数注意力都集中在主流脚本技术:Windows PowerShellTM、VBScript、WMI、ADSI,甚至 FileSystemObject 上。这些技术占据了绝大多数的广告宣传也可以理解,毕竟,它们能使您完成许多又酷又有用的事情。但这并不意味着它们就是脚本编程人员们的唯一选择。远远不是。

以 Shell 对象为例。尽管 Shell 对象可能没有 Windows PowerShell 那样有名或广泛流传,但它依然非常重要并且非常有用。

很可能迟早会有人要求您用 Shell 对象来编写一个系统管理脚本。这产生了一个很明显的问题:Shell 对象究竟可以做些什么呢?事实证明,使用 Shell 对象可以实现许多相当酷的事情,其中一些(如管理磁盘配额以及在复制或移动文件时显示进度条)已在 Microsoft® Windows® 2000 脚本指南 (microsoft.com/technet/scriptcenter/guide) 中介绍过。在本月的专栏中,我们将为您展示使用 Shell 对象能实现的一些有趣的其他小事情,有些您可能根本无法想象的事情均可使用 Shell 对象来实现。

更改文件的上次修改日期

您们当中许多人可能在看到前面的标题时都会想“咳,等等。不可能使用脚本来更改文件的上次修改日期,至少用 VBScript 做不到。”对此我们只能说:您究竟是从何得出的这种想法?

噢,对了;我们脚本专家之前可能说过这样的话,是吧?那么,事实证明,我们错了。震惊吧!可使用 VBScript 来更改文件的上次修改日期。只需使用 Shell 对象即可。

注意:不要再花力气质问脚本专家们为什么会犯这样的错误了;经过这么多年的观察,答案不是显而易见的嘛。更为有趣的问题是:我们究竟如何才能确保正确地完成目标呢?

您说希望更改文件的上次修改日期,对吧?那么,请使用以下脚本:

Set objShell = _
  CreateObject("Shell.Application")

Set objFolder = _
  objShell.NameSpace("C:\Scripts")
Set objFolderItem = _
  objFolder.ParseName("Dr_Scripto.jpg")

objFolderItem.ModifyDate = _
  "01/01/2008 8:00:00 AM"

如您所看到的,这个脚本并没有什么非常复杂的地方。首先创建 Shell.Application 对象的一个实例。(顺便说一下,切勿混淆 Shell.Application 和 Wscript.Shell,后者是 Windows Script Host Shell 对象。我们现在使用的 Shell 对象是 Windows Shell 对象。)创建好 Shell 对象的实例并运行之后,我们使用 Namespace 方法绑定到文件夹 C:\Scripts,然后使用命名奇特的 ParseName 方法绑定到该文件夹中的指定文件。在本例中,我们将绑定到文件名为 Dr_Scripto.jpg 的一个 JPEG 图像:

Set objFolderItem = _
  objFolder.ParseName("Dr_Scripto.jpg")

那么在绑定到该文件之后要做什么呢?很简单;只需向 ModifyDate 属性指定一个新日期和时间即可:

objFolderItem.ModifyDate = _
  "01/01/2008 8:00:00 AM"

这就是我们所要做的一切。

没错儿,说对了。非常酷,不是吗?运行该脚本后,该文件的上次修改日期将设置为 2008 年 1 月 1 日上午 8:00。(如果需要,可在 Windows 资源管理器中打开文件夹 C:\Scripts 亲自确认。)

当然,您们中的某些人可能会想“太酷了。我可以更改文件的上次修改日期了。但我究竟为什么要更改文件的上次修改日期呢?”是这样的,人们常常将上次修改日期用作版本控制系统的排序依据;如果您的某个脚本文件有多个副本,则跟踪正式版本的方法之一是再次检查上次修改日期。通过检查上次修改日期,可确定该脚本的某个副本是否为原始脚本的未修改版本。

注意:当然,任何人都可运行刚才所展示的脚本并更改上次修改日期。但由于缺少每项操作的代码签名,某些人总能找到一些方法来破坏系统。假如您与之共享脚本的同事很有道德,此方法则比较合理。

有时,您可能决定更新并标准化所有脚本的上次修改日期。该如何做呢?好的,假设您的所有脚本都存储在文件夹 C:\Scripts 中,则只需运行下列代码段即可:

Set objShell = _
  CreateObject("Shell.Application")

Set objFolder = _
  objShell.NameSpace("C:\Scripts")
Set colItems = objFolder.Items

For Each objItem In colItems
    objItem.ModifyDate  = _
      "01/01/2008 8:00:00 AM"
Next

正如您所看到的那样,此脚本开头部分非常类似于我们向您展示的第一个脚本。但是,在本例中,绑定到 C:\Scripts 文件夹后,并不使用 ParseName 绑定到该文件夹中的某个文件。而是使用以下代码行返回该文件夹中所有文件的集合:

Set colItems = objFolder.Items

得到此集合后,建立一个 For Each 循环来遍历集合中的所有项。在 For Each 循环内,我们使用下面这行现成的代码将集合中第一个文件的 ModifyDate 属性值更改为 2008 年 1 月 1 日上午 8:00:

objItem.ModifyDate  = _
  "01/01/2008 8:00:00 AM"

然后,我们进入下一轮循环并对集合中的下一个文件重复此过程。完成所有循环后,文件夹 C:\Scripts 中的所有文件(对,隐藏文件除外)都将具有完全相同的上次修改时间。知道这有多简单了吧?现在,谁还会说无法使用 VBScript 来更改文件的上次修改日期?

哦,对了。

说到错误...

最初的时候,当脚本专家们首次开始编写系统管理脚本时,当时并无任何方法可访问附加到 NTFS 文件系统中某个文件的所有扩展文件信息。例如,如果右键单击一个 .wav 文件,然后单击属性,您将看到类似于图 1 所示的信息。

图 1 文件属性摘要

图 1** 文件属性摘要 **

如何使用脚本来检索“Bit Rate”或“Audio sample size”之类的值呢?基本不太可能。嗯,除非使用 Shell 对象。(或者 Windows Desktop Search 3.0,假设已下载并安装了该软件。)后来检索文件的所有扩展属性信息的功能被添加到了 Shell 对象中,但脚本专家们不知为何忽略了这一点。结果我们就告诉大家“无法使用脚本来确定 .wav 文件的比特率”。但是,如图 2 中的代码所示,实际上完全可以做到这一点。

Figure 2 使用脚本来获得 .wav 文件的比特率

Set objShell = CreateObject("Shell.Application")

Set objFolder = objShell.NameSpace("C:\Scripts")
Set objFolderItem = objFolder.ParseName("J0388563.wav")

For i = 0 to 33
    strHeader = objFolder.GetDetailsOf(objFolder.Items, i)
    strValue = objFolder.GetDetailsOf(objFolderItem, i)
    If strValue <> "" Then
        Wscript.Echo strHeader & vbTab & strValue
    End If
Next

此脚本的工作原理是什么?此脚本的开头部分也与我们为您展示的第一个脚本非常相似:创建好 Shell.Application 对象的一个实例并绑定到 C:\Scripts 文件夹后,使用 ParseName 方法绑定到相应的文件(在本例中为 J0388563.wav)。

此时,问题显得有些棘手。首先,创建一个 For Next 循环从 0 运行到 33;这是因为文件支持索引号从 0 到 33 的最多 31 个不同的扩展属性。(请注意:有些编号(如 27 和 28)是有效索引号,但并不包含任何属性值。)事实上,文件支持图 3 中所示的各个属性。

Figure 3 文件属性

索引 属性
0 Name
1 Size
2 Type
3 Date Modified
4 Date Created
5 Date Accessed
6 Attributes
7 Status
8 Owner
9 Author
10 Title
11 Subject
12 Category
13 Pages
14 Comments
15 Copyright
16 Artist
17 Album Title
18 Year
19 Track Number
20 Genre
21 Duration
22 Bit Rate
23 Protected
24 Camera Model
25 Date Picture Taken
26 Dimensions
29 Episode Name
30 Program Description
32 Audio Sample Size
33 Audio Sample Rate

那么在 For Next 循环内部执行什么呢?对,我们首先要做的就是执行下面这行代码:

strHeader = _
  objFolder.GetDetailsOf(objFolder.Items, i)

这行代码的含义是使用 GetDetailsOf 方法检索属性 0 的名称(请记住,循环是从 0 运行到 33)。然后将检索到的名称存在名为 strHeader 的变量中,然后使用下面这行代码检索项目 0 的属性值,并将返回信息存储在名为 strValue 的变量中:

strValue = _
  objFolder.GetDetailsOf(objFolderItem, i)

明白其工作原理了吗?项目 0 正好是 Name 属性。所以第一次循环完成后,strHeader 将等于 Name。同时,第一个文件的实际名称(即 Name 属性的值)是 J0388563.wav。因此,第一次循环完成后,strValue 将等于 J0388563.wav。经过这番解释之后,它看起来并非多么复杂了,不是吗?

您可能知道,并非所有的文件类型都支持所有扩展属性。(例如,.jpg 文件并没有“Album Title”或“Audio Sample Rate”属性。)因此,我们将使用下一行代码来检查返回值是否为空字符串:

If strValue <> "" Then

如果返回值是空字符串,则只需跳回循环开头处并对下一个扩展属性重复此过程。如果返回值不是空字符串,则我们将回显属性名称和值,如:

Wscript.Echo strHeader & vbTab & strValue

最终结果如何呢?非常类似于以下内容:

Name    j0388563.wav
Size    169 KB
Type    Wave Sound
Date Modified   1/19/2004 8:56 AM
Date Created    3/6/2006 2:02 PM
Date Accessed   12/3/2007 10:41 AM
Attributes      A
Status  Online
Owner   FABRIKAM\kenmyer
Bit Rate        90kbps
Audio Sample Size       4 bit
Audio Sample Rate       11 kHz

很好。

由于时间(和空间)有限,如果您希望得到返回最相关属性值的更为简单的方法,可以尝试图 4 中所示的脚本。

Figure 4 返回属性值的一种简便方法

Const colInfoTip = -1

Set objShell = CreateObject("Shell.Application")

Set objFolder = objShell.NameSpace("C:\Scripts")
Set objFolderItem = _
  objFolder.ParseName("01. Out On The Weekend (Album Version).wma)")

Wscript.Echo objFolder.GetDetailsOf(objFolderItem, colInfoTip)

该脚本有什么作用呢?如果在 Windows 资源管理器中并且将鼠标指针放在某个文件上,将弹出一个工具提示并向您提供一些有关此文件的有用信息(如图 5 中所示的信息)。如何使用脚本来得到该工具提示信息?没错。可以使用我们刚才向您展示的脚本:

图 5 在工具提示中显示的属性

图 5** 在工具提示中显示的属性 **

Artist: Neil Young
Album Title: Harvest
Year: 1972
Track Number: 1
Duration: 0:04:35
Type: Windows Media Audio file
Bit Rate: 256kbps
Protected: Yes
Size: 5.31 MB 

本月我们要讲的就是这些内容。最后需要注意的一点与小小鸟有关。希望跟踪它的一个理由可能是:在某些地方,麻雀进入您的屋子被认为是运气不好的象征。为什么呢?因为它意味着住在屋子里的某人快要死了。(在某些情况中,只有它落在钢琴上才会出现这种情况。但这并不是迷信,而是超自然现象。)然而有趣的是:在其他某些地方,它进入您的屋子则意味着住在屋子里的人将要结婚了。讨论一下陷于两难境地,是吧?

Scripto 博士的脚本谜题

每月一次的挑战!不仅测试您的解谜技能,还测试您的脚本编写技能。

2008 年 3 月:神秘脚本

本月的谜题是密码,即每个字母都已被其他字母替换。(我们完整保留了所有的符号和数字。)相同的字母始终代表一个替换字母。例如,所有出现字母 a 的地方都可能由字母 b 替换,所有出现字母 b 的地方都可能由字母 z 替换等等。以下是一个简单的示例:

tdsjqu dfoufs

解码如下:

script center

本例中的解码通过将每个字母用字母表中的前一个字母替换得出,所以 t 由 s 替换,d 由 c 替换,以此类推。

此谜题则更为随机。解码此脚本后,您将得到一个脚本,它能打开 Microsoft Excel® 的一个实例、添加一个新的电子表格、向该电子表格的第一列指定四个值,然后以升序方式对该列进行排序。

慢慢体会其中的乐趣吧!

谜题

kqyjs edfjkzyrlyg = 1
kqyjs edyq = 2
kqyjs edjqtstqmj = 2

jzs quwzekzd = ktzfszquwzks("zekzd.faadlkfslqy")
quwzekzd.iljludz = stcz

jzs quwmqtxuqqx = quwzekzd.mqtxuqqxj.frr
jzs quwmqtxjozzs = quwmqtxuqqx.mqtxjozzsj(1)

quwmqtxjozzs.kzddj(1, 1) = "kfs"
quwmqtxjozzs.kzddj(1, 2) = "rqg"
quwmqtxjozzs.kzddj(1, 3) = "ufs"
quwmqtxjozzs.kzddj(1, 4) = "faz"

jzs quwtfygz = quwzekzd.fkslizkzdd.zysltztqm
quwtfygz.jqts quwtfygz, edfjkzyrlyg, , , , , , edyq, , , edjqtstqmj

ANSWER:

Scripto 博士的脚本谜题

答案:神秘脚本,2008 年 3 月

以下是解码该脚本的密钥:

Actual Letter   a   b   c   d   e   f   g   h   i   j   k
Coded Letter    f   u   k   r   z   b   g   o   l   w   x

Actual Letter   l   m   n   o   p   q   r   s   t   u   v   w   x   y   z
Coded Letter    d   n   y   q   a   h   t   j   s   c   i   m   e   v   p

以下是实际解码后的脚本:

Const xlAscending = 1
Const xlNo = 2
Const xlSortRows = 2

Set objExcel = CreateObject("Excel.Application")
objExcel.Visible = True

Set objWorkbook = objExcel.Workbooks.Add
Set objWorksheet = objWorkbook.Worksheets(1)

objWorksheet.Cells(1, 1) = "Cat"
objWorksheet.Cells(1, 2) = "Dog"
objWorksheet.Cells(1, 3) = "Bat"
objWorksheet.Cells(1, 4) = "Ape"

Set objRange = objExcel.ActiveCell.EntireRow
objRange.Sort objRange, xlAscending, , , , , , xlNo, , , xlSortRows

The Microsoft Scripting Guys脚本专家为 Microsoft 工作,也就是受雇于 Microsoft。在玩、教或看棒球(以及各种其他活动)的闲暇之余,他们还负责维护 TechNet 脚本中心。要查看相关信息,请登录 www.scriptingguys.com

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