您好,脚本专家!我们最喜爱的 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** 文件属性摘要 **
如何使用脚本来检索“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** 在工具提示中显示的属性 **
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.保留所有权利;不得对全文或部分内容进行复制.