第十三章 Windows脚本环境
现在的许多开发人员以前都是在MS-DOS环境下编程的。几乎所有人都接触过批处理文件——一种基于文本命令的文件。这种文件使你能够在一个可执行命令中组合多个指令。批处理文件的语法相当简单,很容易编写。
然而有些人认为批处理文件的语法过于简单。.bat文件的解释器对于识别某些基本的控制元素,如‘if’,是足够智能的,但是,对于提供现代的和功能强大的脚本环境,它就差得太远了。
直到最近,Windows才给出了一个较好的机制——MS-DOS批处理文件仍然作为Windows的可执行文件机制,这就是Windows脚本环境(WSH)的引进。我们在这一章将看到WSH提供的支持要比.bat文件复杂的多,因为它嵌入了脚本引擎的特征。
在这一章中我们解释:
Windows批处理文件的起源
为Shell脚本提供框架的脚本环境的轮廓
WSH对象模型
使用WSH可以做些什么
怎样通过新的自动化对象增强WSH的功能
我们将使用Jscript和VBScript写一些简单的WSH应用。如果有ActiveX兼容的脚本引擎,你可以使用任何其它的脚本语言。
Windows批处理文件
Windows脚本环境提出的想法是相当简单的,它就是作为运行时引擎工作的一个环境,用于解释以VBScrip,Jscript或其它任何脚本语言写的脚本文件,唯一的限制是每一种语言都应该有与IE ActiveX脚本引擎兼容的解析模块。
实际上,你编写了一个脚本文件,而且WSH运行时允许你象批处理或典型的Win32二进制文件一样运行这个文件,因此,有理由认为.vbs和.js文件就是一种新的可执行文件,这里.vbs文件是一种包含VBScript代码段的ASCII文件,而.js是Jscript的。
WSH能做什么
为了理解WSH的重要性,我们可以把它看作允许编程描述一系列操作的工具,例如运行外部可执行文件,或访问Windows对象如快捷方式,文件夹,甚至注册表等。使用WSH,任何重复的任务都可以存储到.vbs或.js文件,而后只需简单地双击唤醒这个文件就可以了。
使用基于WSH的脚本有许多超越批处理文件的优点:
它使用更清晰和强大的编程语言,提供多样化的控制流结构,变量,子例程和数组,这些是DOS批处理所没有的。
这些特征将随着版本的提升进一步增强,新脚本引擎的主要特征是内置了错误陷阱和恢复机制,以及运行时检查和执行代码的方法。
其次(不是不重要)WSH最强的能力是能够访问注册的COM服务器,首先,这个功能允许你建立任何存在的COM服务器实例,其次你可以设计扩展WSH运行环境能力的新COM服务器。在这一章中我们将调查这两个方面。
启动时运行脚本
在基于MS-DOS的系统中,一般有一个批处理文件在autoexec.bat之后运行,执行一些更特殊的处理。绝大多数场合,这是由基于键盘的菜单组成,使用goto 语句转移控制到要求的代码段。现在,相同的效果也可以在WSH中获得,只要编写了‘启动’中执行的代码,把它存储在‘启动’文件夹中,每次用户登录,它就都可以运行了。
WSH环境结构
Windows脚本环境是一个在Windows Shell中嵌入了ActiveX脚本引擎的集成模块。通过实现几个COM接口,你可以赋予任何Win32应用由脚本驱动的能力。这个应用应该是一个输出对象到外部世界的自动化服务器,有许多文章都举例说明了实现这一目标的方法。
在Windows Shell中,存在能够解释VBScript和Jscript源代码的程序,其基本原理与IE运行HTML内脚本是一样的。IE,IIS服务器和WSH管理器都嵌入了兼容于ActiveX脚本规格的解析模块。这三者的区别在于其对象模型的不同,换言之,从脚本的观点来看,IE和WSH的差别是对象模型,它们都围绕同一个脚本引擎而构建。
微软已经发布了一个称之为‘脚本控件’的ActiveX控件,这个控件作为可嵌入对象为应用提供脚本引擎。因此,你可以注册自己的对象使程序自动有可脚本化的能力(假设,程序输出自动化对象)。这个脚本控件可以从微软的站点下载获得。
怎样获得Windows的脚本环境
Windows脚本环境模块是Windows98操作系统的一部分。在Windows95和NT上也是可用的,但是并不包括IE4.x和活动桌面。此时你可以从微软的站点下载它们。
安装过程就是指示文件存储的路径,基本上,WSH有两个可执行文件和一些COM模块,它们总体构成了WSH的对象模型。
设置还注册两个新的文件类.vbs和.js。有一些相关的Shell动词,所以你可以打开(运行)和编辑这类文件。
WSH是什么
Windows脚本环境由两个文件wscript.exe和cscript.exe构成,它们分别在Windows和Windows/Command目录下。后一个是控制台应用,运行在DOS窗口下,而前一个是Windows程序。
WSH命令行
两种脚本环境共享相同的命令行,它们是:
wscript scriptfile [//options] [/arguments]
cscript scriptfile [//options] [/arguments]
选项在下表中列出,注意,需要使用双斜线作为前缀。相反脚本文件的变量必须使用单斜线作为前缀。下面的命令举例说明运行一个脚本文件两秒钟,一旦时间消逝,脚本终止。无论操作是否完成:
wscript myfile.js //T:2
T开关是WSH所接受的几个开关之一,这些开关的说明列表如下:
开关 |
描述 |
B |
以非交互的批处理方式运行脚本。它抑制所有消息框和任何用户要求的干预。 |
I |
以交互方式和必要时提示用户的方式运行脚本。代码终止时执行终止,对持续时间没有限制,这是默认设置。 |
logo |
启动时显示标记。这个选择仅在cscript.exe中有效,并且是默认设置。wscript.exe不支持这个选项。 |
nologo |
启动时不显示标记。这个选择仅在cscript.exe中有效,wscript.exe不支持这个选项。 |
T:nn |
脚本仅在指定的以秒表示的时间段运行。中断通过特定的ActiveX脚本引擎方法实现,可以认为是绝对线程安全的。 |
? |
显示程序的使用帮助 |
从命令行的角度看cscript 和 wscript 除了logo 和 nologo开关以外几乎是相同的。通过cscript模块运行脚本文件总是有一个DOS背景窗口出现。
Shell对脚本文件的支持
在WSH被安装之后,Shell就能识别和特殊处理.vbs和.js文件了。也就是说,你可以双击这些文件,使它们立即从探测器开始执行。这是因为在注册表中加入了‘打开’动词,我们在第11章已经看到了这种方法。在这个路径:
HKEY_CLASSES_ROOT
/.js
和这个路径下:
HKEY_CLASSES_ROOT
/.vbs
包含这个键名的默认值存储了所有Shell信息——Js文件和Vbs文件。下面这个路径:
HKEY_CLASSES_ROOT
/jsfile
/shell
/open
/command
指向点击.js文件执行的命令行,这与.vbs的一样:
Windows 9x: C:/WINDOWS/WScript.exe "%1" %*
Windows NT: C:/WINNT/System32/WScript.exe "%1" %*
其中%1表示文件名,而%* 说明程序在调用时也传递命令行变量。如果简单地在探测器下双击VBScript或Jscript文件,实际的命令行则是:
C:/WINDOWS/WScript.exe filename
反之如果使用ShellExecute()或ShellExecuteEx()编程运行,甚至经由‘运行’对话框运行VBScript或Jscript,你就可以指定附加的参数或选项。例如下面的图像显示唤醒Jscript传递变量的情形:
对于Jscript和VBScript文件其关联菜单有两个打开命令:‘打开’运行wscript.exe,而‘在MS-DOS提示中打开’(在NT的命令提示中打开)运行cscript.exe。另外,Jscript和VBScript都有客户属性页添加到属性对话框:
这个页面使你可以设置一定的秒数作为最大执行时间。对设置的任何改变都存储在一个.wsh文件中,其格式如下:
[ScriptFile]
Path=c:/myfile.js
[Options]
Timeout=2
DisplayLogo=1
BatchMode=0
在某些情况下.wsh文件是特定脚本文件的一个类似于快捷方式的文件。在处理这些文件时,cscript 和 wscript首先抽取路径,然后运行它所找到的参数。ScriptFile节定义了目标文件。而Options节则描述命令行的开关状态。
脚本引擎
WSH使用脚本文件名的扩展名来决定装入哪个解析器。逻辑上.vbs扩展是VBScript编写的脚本,而.js表示Jscript。WSH天生就支持这两种语言,因为它们是与IE一同发布的ActiveX脚本引擎所支持的。如果你手里还有另一个可用的脚本语言解析器——例如Perl——配置好后,你就可以用它来编写Windows的批处理文件了。
注册新脚本引擎
注册新引擎需要按如下步骤操作:
标识新引擎处理的文件扩展,如对Perl文件 .pl 。
在注册表中建立我们前面讨论过的类似格式的条目
添加ScriptEngine键,其‘Default’值指向另一个键
第二个键中包含实现新语言脚本解析器ActiveX模块的CLSID
对于举例的Perl而言,注册表键应该是:
HKEY_CLASSES_ROOT
/.pl
这个键的Default值指向另一个称之为plfile的键,其包含整个键的子树,形势如下:
HKEY_CLASSES_ROOT
/plfile
/defaulticon
/scriptengine
/shell
/open
/edit
这包含了所有必须的动词(打开,编辑,打印),默认的图标,以及脚本引擎键。后者的Default值包含对另一个键的引用,这个键用来标识解析器,在例子中是PScript:
HKEY_CLASSES_ROOT
/pscript
/clsid
在这个键下存储了模块的CLSID,这个模块包含了Perl的ActiveX脚本兼容解析器。
命令行变量
我们前面提到过,WSH脚本可以接收命令行变量。你可以一个接一个的指定这些变量,并保证它们由空格隔开,而包含空格的串应该用引号封装成单个参数,例如:
wscript ScriptFile.js First "Second Argument"
在脚本代码内可以通过特定的聚集对象WshArguments访问命令行的变量集。
WSH对象模型
Windows脚本环境使用了一些预定义的对象这些对象整个形成了WSH对象模型。它们允许你在Windows Shell环境下执行一定范围的活动。
主要对象是WScript、WshShell 和 WshNetwork。头一个含有WSH引擎,另外两个分别表述Windows Shell 和网络。正如我们后面将要发现的,WshShell与Shell对象存在相当的差别。WshShell 和 WshNetwork 在使用之前必须被实例化,而对于WScript,这一步就没必要了,因为它是在wscript 和 cscript中实现的对象,所以它总是在运行中的,没有必要重新实例化它。
所有其余的对象都在另外的控件中wshom.ocx实现,每当使用时都需要加载它们。
WScript对象
这个对象是WSH对象集的根对象,它提供获得调用脚本命令行变量信息的属性及方法。此外,它还建立新对象和终止存在的对象。
下表列出了WScript对象所支持的属性和方法:
属性 |
描述 |
Application |
恢复WScript对象的IDispatch指针 |
Arguments |
返回脚本的变量集,这个集合是WshArguments对象 |
FullName |
恢复脚本环境的全名 |
Interactive |
指定执行模式是‘交互的’还是‘批处理’的,这是一个读/写属性,没有说明文档 |
Name |
恢复脚本环境的名字 |
Path |
恢复脚本环境的路径 |
ScriptFullName |
恢复当前脚本文件的全名 |
ScriptName |
恢复当前脚本文件的文件名 |
Version |
返回脚本环境当前版本串 |
方法 |
描述 |
CreateObject() |
用指定的ProgID建立新对象 |
DisconnectObject() |
释放指定的对象 |
GetObject() |
用指定的ProgID恢复对象 |
Echo() |
在窗口(wscript)或DOS框(cscript)中显示信息。它由Interactive属性所影响。 |
Quit() |
停止执行脚本 |
Interactive是无文档说明的,读/写属性,它接收和返回布尔值。它的初始设置具有命令行I 开关所设置的值,但是你可以编程改变它。有趣的是,在Interactive被设置为FALSE时,WScript.Echo()不能工作。但是你仍然可以通过WshShell对象的方法显示信息。
WScript的主要方法是CreateObject(),你可以用此方法建立新对象的实例,其原型如下:
WScript.CreateObject(sProgID, [sPrefix])
sPrefix变量用于识别相关对象的事件过程名的字符串。下一节将进一步解释这个概念。
GetObject()的定义如下:
WScript.GetObject(sPathname, [sProgID], [sPrefix])
它使用指定的文件名或ProgID恢复对象。sPrefix变量的作用与CreateObject()方法同。
由于WSH输出了CreateObject()方法,显然我们可以用它来建立新对象,然而,直接使用VBScript的CreateObject()或Jscript的ActiveXObject()方法也是可以的。事实上它执行的更快,因为VBScript的CreateObject()方法依赖于它的一个副本。
WshShell对象
这个对象表示Windows Shell ,但是与我们前面各章中讨论的Shell对象有相当大的区别。它一方面缺少许多功能,而在另一方面又增加了多种方法。尽管名字很相似,它们仍然应该作为两个非常不同的控件。
WshShell只有两个属性,一是Environment,包含系统环境变量的集合,另一是SpecialFolders属性,返回Shell的特殊文件夹名集合。
方法包括了各种函数:从快捷方式、注册表和进程引导到特殊文件夹。WshShell的实例可以使用WScript建立,其方式如下:
Set s = WScript.CreateObject("WScript.Shell")
这里是WshShell全部方法的完整列表:
方法 |
描述 |
CreateShortcut() |
建立一个空的WshShortcut或WshUrlShortcut对象以分别充填和存储快捷方式或URL快捷方式。 |
ExpandEnvironmentStrings() |
展开由一对%号括起的变量。 |
Popup() |
显示消息框,你可以使用这个方法来显示消息,即使WScript.Interactive属性设置为FALSE,也可以显示。 |
RegDelete() |
删除注册表的键或值 |
RegRead() |
从注册表中读键或值 |
RegWrite() |
向注册表写键或值 |
Run() |
到处和同步化一个可执行程序 |
我们将集中讨论访问注册表的函数;同时对Run()和ExpandEnvironmentStrings()方法给出简略说明,Run()的原型为:
Run(sCommand, [iWindowType], [bWaitOnReturn])
在命令行中可以指定输出窗口的类型(最小,最大,正常,隐藏)。可用的值在下表中给出,可以看出这是SW_XXX 常量的一个子集。
常量 |
描述 |
0 |
隐藏窗口(类似于SW_HIDE) |
1 |
显示并聚焦窗口(类似于SW_SHOWNORMAL) |
2 |
最小化并聚焦窗口(类似于SW_SHOWMINIMIZED) |
3 |
最大化并聚焦窗口(类似于SW_MAXIMIZE) |
4 |
显示一个非聚焦窗口(类似于SW_SHOWNOACTIVATE) |
6 |
最小化窗口并且不聚焦这个窗口(类似于SW_MINIMIZE) |
如果bWaitOnReturn标志返回TRUE,则它阻塞调用脚本,直到导出程序被终止。包含在命令行串中的任何环境变量自动展开。
Run()方法是通过调用ShellExecuteEx()来实现的,即,我们也可以把注册到打开动词的文件名传递给ShellExecuteEx()函数。还有就是,它也默默地支持任何实现IShellExecuteHook接口的对象。在钩住程序的执行中我们将进一步解释。
ExpandEnvironmentStrings()方法接收和返回串。输入串是具有封装在%号中环境变量的文字,而输出串则是展开串。
sWinDir = ExpandEnvironmentStrings("Windows is at %WINDIR%")
如果环境串不存在,这个方法返回无定义的对象。因此,在操作之前你应该保证获得要求的结果。
快捷方式和URL快捷方式
CreateShortcut()接收文件名和返回相关文件扩展名的对象。如果扩展名是.lnk,则返回对象是WshShortcut,反之如果扩展名为.url,则返回对象是WshUrlShortcut。指定任何其它的扩展名都将获得运行时脚本错信息。在快捷方式和URL快捷方式之间的基本差别在于后者指向一个远程URL。WshUrlShortcut也比WshShortcut简单一点。
WshNetwork对象
远程打印机和网络连接是属于这个对象的范畴。通过WScript建立一个WshNetwork对象的实例:
Set n = WScript.CreateObject("WScript.Network")
下表列出了它所支持的方法:
方法 |
描述 |
AddPrinterConnection() |
运行添加新打印机连接大师 |
EnumNetworkDrives() |
允许通过返回的聚集来枚举网络驱动器(参见辅助对象) |
EnumPrinterConnections() |
允许通过返回的聚集枚举所有打印机连接(参见辅助对象) |
MapNetworkDrive() |
建立与网络驱动器的连接 |
RemoveNetworkDrive() |
删除与网络驱动器的连接 |
RemovePrinterConnection() |
删除与网络打印机的连接 |
SetDefaultPrinter() |
定义新的默认打印机。如果是网络打印机,就必须指定它的UNC名(如//server/Printer XYZ) |
这个对象还有三个属性,其作用是显然的。
ComputerName
UserDomain
UserName
辅助对象
WSH对象模型包含有六个辅助对象。首先需要注意的一点是你不能直接建立它们。它们也没有ProgID,仅由其它对象的方法或属性返回后,才可以使用它们。这些对象是:
对象 |
由…返回 |
WshArguments |
WScript.Arguments |
WshCollection |
WshNetwork.EnumNetworkDrives(), WshNetwork.EnumPrinterConnections() |
WshEnvironment |
WshShell.Environment |
WshShortcut |
WshShell.CreateShortcut() |
WshUrlShortcut |
WshShell.CreateShortcut() |
WshSpecialFolders |
WshShell.SpecialFolders |
除了WshShortcut 和 WshUrlShortcut以外,其它对象都指定为集合类型,因而它们的编程接口都是类似的,至少在语法上相同。
WshArguments对象
这个集合对象包含了运行脚本的所有命令行变量,你可以通过WScript对象的Arguments属性访问它。这个部件有集合对象通常的特征:Item和Count。Item允许你取得第i个变量的值(列表是从零开始的),Count返回变量总数。
还有一个Length属性是为了兼容性而设置的——它等价于Count。下面的VBScript代码段说明脚本怎样显示它的命令行:
For Each item In WScript.Arguments
' 显示命令行上的各个项
WScript.Echo item
Next
WshCollection对象
这个对象是一个普通的集合对象,主要由WshNetwork的方法返回。
WshEnvironment对象
这个对象的引用由WshShell.Environment属性返回,它有一个Remove()方法,Count、Length和Item使你能遍历这个集合的项。
这个集合的项是环境变量,类似于WINDIR或PATH等。除了可以传递给Item成员一个索引,更有用的是使用变量名标识串来调用Item:
Set s = WScript.CreateObject("WScript.Shell")
Set e = s.Environment
WScript.Echo e.Item("PATH")
WScript.Echo e.Remove("PATH")
WshShortcut对象
WshShortcut对象是一个允许定义和存储.lnk快捷方式到磁盘的部件。快捷方式建立有两个步骤:首先建立WshShortcut对象,然后填充它的属性并保存到磁盘。首先由WshShell.CreateShortcut()函数建立对象:
Set s = WScript.CreateObject("WScript.Shell")
Set lnk = s.CreateShortcut("mydiskc.lnk")
lnk.TargetPath = "c:/"
lnk.Save
在调用CreateShortcut()时,你就指定了最终建立的.lnk文件名。其次你填写了快捷方式的各个属性 ,然后通过调用WshShortcut.Save()保存到磁盘。如果想要在特殊文件夹上建立快捷方式,只需取得路径名并传递一个全文件名到CreateShortcut()即可。要取得特殊文件夹的物理路径,你需要使用WshSpecialFolders对象,过一会我们将解释这个对象。下面是WshShortcut的全部属性列表:
属性 |
描述 |
Arguments |
一个包含快捷方式目标变量的字符串 |
Description |
快捷方式的描述串 |
FullName |
恢复.lnk文件的全路径名 |
Hotkey |
一个包含启动快捷方式热键的表示字符串 |
IconLocation |
一个包含图标路径和索引位置的字符串。路径和索引用逗号分隔。如:c:/windows/system/shell32.dll,13 |
TargetPath |
这可以是文件夹、可执行文件,或使用ShellExecute()运行的文件 |
WindowStyle |
表示窗口的SW_XXX风格 |
WorkingDirectory |
启动可执行文件的目录 |
WshUrlShortcut对象
这个对象的工作方式与WshShortcut相同,但是仅有下面两个属性:
属性 |
描述 |
FullName |
恢复包含这个快捷方式的.url文件的全路径名。 |
TargetPath |
恢复这个快捷方式指向的URL。 |
它也支持Save()方法,存储快捷方式到磁盘。
WshSpecialFolders对象
最后这个对象是构建于SHGetSpecialFolderPath() API函数之上的一种封装。这个我们在第5章已经讨论过了。它是一个包含Item、Count和Length成员的集合,与我们前面调查过的集合对象有相同的特征。下面代码段说明怎样在Favorites文件夹上建立快捷方式:
Set s = WScript.CreateObject("WScript.Shell")
sPath = s.SpecialFolders("Favorites")
sPath = sPath & "/mydiskc.lnk"
Set lnk = s.CreateShortcut(sPath)
lnk.TargetPath = "c:/"
lnk.Save
正象代码描述的,它与前面我们的建立快捷方式到C:/ 的示例有所不同,这里引用了WshShell.SpecialFolders属性返回的WshSpecialFolders对象获得的路径。
访问注册表
Windows脚本环境给出了一种平稳访问注册表的能力,用于读写键和值。这是一个重要的特征,因为这实际授予并增进你的应用处理注册表的能力。正象我们前几章中指出的,用于访问注册表的API是笨拙的,而且是在太低的抽象层上设计的,尽管有一些轻量级API函数在这方面的改进,这个我们在第10章中已经看到了。
WshShell对象包含三个可编程修改系统注册表状态的方法,它们是:
RegDelete()
RegRead()
RegWrite()
这些方法的语法相当简单,回顾一下在第10章中遇到的SGxxx() 函数的语法,不是初始的注册表API函数。这几个基本上只要求你指定要操作的键或值以及读写缓冲。
支持的类型
Win32注册表编程接口允许存储和读出各种不同的数据类型——细节参见Win32资料。然而,WSH对象仅仅支持五种常用类型,它们是:
REG_SZ — 字符串
REG_DWORD — 32位无符号值
REG_BINARY — 二进制数据
REG_EXPAND_SZ — 包含可扩展宏的字符串,如%WINDIR%
REG_MULTI_SZ — 空终止串的数组,双空终止串数组(支持读但不支持写)
在脚本代码中所有变量都处理成VARIANTs,而上面列出的注册表方法最终使用的是要求特殊类型的底层注册表API。因此需要某些类型的转换,然而WshShell对象自动执行了这些转换。
删除注册表项
RegDelete()方法有下面的语法:
WshShell.RegDelete sKeyOrValue
根本不需要显式涉及给定的串是键还是值——只需要传递一个全注册表路径到这个函数。如果这个路径用反斜杠终止,它就被当作键,否则它就被处理成值。
在这个层面上WSH方法与Win32 注册表 API有根本不同的编程接口。Win32 API有不同的函数处理键和值。更进一步,WSH方法要求指定根节点在串内而不是象Win32API函数要求的那样作为另一个参数。
Windows9x与NT的差别
RegDelete()方法最终调用RegDeleteKey()和RegDeleteValue()函数,后者在Windows9x和NT下的工作是相同的。RegDeleteKey()函数在二者上展示了不同的行为。在Windows9x上删除键时,它的子树一并删除,而NT上则要求它是空的时候才能删除(即,没有子键)。RegDelete()方法也支持这种行为。
一个键所握有的值的个数与它是否为空这个问题无关——一个可以删除的键只是它不包含子键,即使它包含值也没关系。
读取注册表
要读取注册表,只需调用RegRead(),并传递一个引用键的全名或一个具有上面讨论的相同逻辑的值:最后的反斜杠表示键或值。没有必要指定要读出的数据类型。正确地声明存储值结果的变量是充分的。总是对象本身的体系结构关注所取得的原数据,并把它包装成VARIANT类型返回。这个函数的工作方式如下:
v = WshShell.RegRead(sKeyOrValue)
从注册表中取得系统信息
这里是一个读取Windows系统版本和注册信息的简短例程(命名为system.vbs)。
' SYSTEM.VBS
' 从注册表读取版本和注册信息
'-------------------------------------------------------------
' 建立WshShell 对象
Set s = WScript.CreateObject("WScript.Shell")
' 注册表路径常量
RP_SYSTEM = "HKLM/System/CurrentControlSet/Control/ProductOptions/"
RP_PRTYPE = "ProductType"
RP_NTVERS = "HKLM/Software/Microsoft/Windows NT/CurrentVersion/"
RP_WINVER = "HKLM/Software/Microsoft/Windows/CurrentVersion/"
' 系统名常量
WIN_NTWORK = "Windows NT Workstation"
WIN_NTSERV = "Windows NT Server"
' 读取产品类型
On Error Resume Next ' 在Win9x下因为键不存在
sProdType = ""
sProdType = s.RegRead(RP_SYSTEM & RP_PRTYPE)
' 确定 OS 版本
select case sProdType
case "WinNT"
sRegPathVer = RP_NTVERS
sBuf0 = WIN_NTWORK
sBuf1 = s.RegRead(RP_NTVERS & "CurrentVersion") + "."
sBuf2 = s.RegRead(RP_NTVERS & "CurrentBuildNumber")
sBuf3 = s.RegRead(RP_NTVERS & "CSDVersion")
case "ServerNT", "LanManNT"
sRegPathVer = RP_NTVERS
sBuf0 = "Windows NT Server"
sBuf1 = s.RegRead(RP_NTVERS & "CurrentVersion")
sBuf2 = s.RegRead(RP_NTVERS & "CurrentBuildNumber")
sBuf3 = s.RegRead(RP_NTVERS & "CSDVersion")
case ""
sRegPathVer = RP_WINVER
sBuf0 = s.RegRead(RP_WINVER & "Version")
sBuf2 = s.RegRead(RP_WINVER & "VersionNumber")
sBuf3 = "-----------------------"
end select
' 读取注册信息
sBuf4 = s.RegRead(sRegPathVer & "RegisteredOwner")
sBuf5 = s.RegRead(sRegPathVer & "RegisteredOrganization")
' 显示结果
WScript.Echo sBuf0 + " " + sBuf1 + sBuf2 + vbCrLf + _
sBuf3 + vbCrLf + vbCrLf + _
sBuf4 + vbCrLf + _
sBuf5
' 关闭
WScript.Quit
这段代码仅依靠注册表信息来确定平台类型。在Win32 代码中,几乎所有这类信息都由GetVersionEx()函数返回。然而有一个例外:这个API函数并不能区别WindowsNT的工作站和服务器版本。为此,你需要访问注册表。现在让我们看一下上面代码执行这个操作的过程。
在WindowsNT下,下面的路径含有ProductType值:
HKEY_LOCAL_MACHINE
/System
/CurrentControlSet
/Control
/ProductOptions
如果这个键不存在,则我们运行的系统可以是Windows95或Windows98。ProductType包含三个可能的串:
值 |
描述 |
WinNT |
Windows NT 工作站 |
ServerNT |
Windows NT 服务器 |
LanManNT |
作为主或备份域控制器的Windows NT 服务器 |
知道了操作系统后,你就可以很容易地在Windows和NT之间管理注册表结构的差异了。最重要的是存储在下面键下的版本和注册信息:
HKEY_LOCAL_MACHINE
/Software
/Microsoft
/Windows NT
/CurrentVersion
是在Windows NT系统下,
HKEY_LOCAL_MACHINE
/Software
/Microsoft
/Windows
/CurrentVersion
是在Windows9x系统下。其它的差别是键值的不同。两个系统的注册表都支持RegisteredOwner 和RegisteredOrganization,但是,Windows NT把安装服务包的信息存储在CSDVersion值中,而CurrentBuildNumber存储的是构建号。这个信息在Windows9x下不可用,此时有一个Version 和 VersionNumber值存储操作系统的名和它的完整版本数。
下图显示了system.vbs脚本在Windows95 和WindowsNT机器上的输出:
注意可以只取首字母来引用注册表的根节点。在上面例子中,我们使用HKLM来代替HKEY_LOCAL_MACHINE。其它的替换是:
首字母 |
等价的根节点 |
HKLM |
HKEY_LOCAL_MACHINE |
HKCR |
HKEY_CLASSES_ROOT |
HKCU |
HKEY_CURRENT_USER |
当然完全可能访问其它节点如HKEY_USERS 和 HKEY_CURRENT_CONFIG,但是它们没有首字母——只能使用全名。
写入注册表
你可以在WSH环境下使用RegWrite()方法将新内容写入注册表。下面显示了它的语法:
WshShell.RegWrite(sKeyOrValue, vValue, [iType])
这个方法自动建立出现在路径中的任何缺失键。如果传递一个键到sKeyOrValue变量,则vValue的内容作为这个键的默认值。iType指定了值的类型,是除REG_MULTI_SZ外前述的REG_XXX类型之一。默认时的这个参数是REG_SZ类型,一个串,即使你实际传输的是一个数值的vValue,也是如此。
Set s = WScript.CreateObject("WScript.Shell")
sRegPath = "HKLM/Software/Expoware Soft/"
s.RegWrite sRegPath & "Wsh/", "WSH examples"
进一步利用注册表
如果你需要进一步扩展WSH的注册表编程接口,就应该考虑编写新的COM控件来输出你所需要到功能。至少有两件事情是你所需要的,而且是当前编程接口所不能完成的。
第一你可能需要处理更多的数据类型,这是一个不同寻常的需求,除非你需要处理海量数字或字符串数组。再有就是,你可能需要你的服务器提供键和值的枚举功能。更进一步的需求是与远程注册表连接,实现变化通知机理,或实现‘保存 / 恢复’键的方法。一般,通过设计COM服务器,你可以使WSH操作注册表具有任何使用Win32 API可以实现的功能。
在添加新对象到WSH一节中我们将给出这样的COM服务器,它将提供键和值的枚举功能。
脚本化本地文件系统
脚本不能调用API函数,所以它们需要特殊的对象来提供对本地文件系统的访问。VBScript和Jscript均有一些有用的对象用以操作文件、文件夹和驱动器,它们是:
脚本对象 |
描述 |
FileSystemObject |
管理文件和文件夹操作,建立文本文件 |
Folder |
返回文件系统文件夹信息 |
Drive |
返回驱动器信息 |
File |
返回文件信息 |
Dictionary |
一个高性能集合对象 |
TextStream |
启动一个文本I/O流 |
这些对象的更新版本——微软的运行时脚本——在微软的脚本站点上是可用的。关于这些对象的更进一步信息请参考MSDN库。
由于Windows脚本环境是有些封闭的,一些事情主要是内部使用的(或是单独为PC而设计的),考虑到访问磁盘的不安全性,这些都抵消了文件系统对象的重要作用。因此,FileSystemObject对象作为WSH编程工具可能真正是有用的。
下面这个短例子显示出你可以使用FileSystemObject所作的操作。这个代码显示一个信息框,给出系统中每个驱动器的状态:
Set fs = CreateObject("Scripting.FileSystemObject")
Set dc = fs.Drives
For Each d in dc
s = s & d.DriveLetter & " - "
If d.DriveType = Remote Then
n = d.ShareName
ElseIf d.IsReady Then
n = d.VolumeName
n = n + vbCrLf + "Free: " + FormatNumber(d.FreeSpace/1024, 0) + " KB"
n = n + vbCrLf
Else
n = n + vbCrLf
End If
s = s & n & vbCrLf
Next
WScript.Echo s
访问存在的对象
Windows脚本环境是一个完全COM感知的环境。也就是说,从这个环境中你可以唤醒和使用任何正确注册的COM服务器。我们到现在为止所描述的都是分布在WSH包中的对象,而且它们是来自于WSH对象模型的。然而就我们的观点,这个对象模型的构成是有些争议的。有两个观点可以考虑:
一个自动化接口所输出的每一件东西都可以从WSH调用,而且都可以被作为对象模型的部件。
WSH对象模型只有少数接口与ActiveX脚本引擎通讯,除了WScript对象外,其它的每一件东西都可以作为相关的COM服务器,而没有必要作为对象模型的一个部件。
你可以使用WScript.CreateObject()方法访问任何存在的自动化服务器,或更有效地,使用VBScript的CreateObject()方法访问它们。例如,你可以从WSH脚本驱动我们前面章节中讨论过的Shell对象。下面这段代码说明怎样用WSH脚本显示任务条属性对话框:
Set s = WScript.CreateObject("Shell.Application")
s.TrayProperties
当然,你也可以用同样的方式操作你自己的COM服务器(参见添加新对象到WSH)。
WSH处理事件
在客户端处理事件的典型方法是定义一个过程,其名字遵循指定的惯例。这个名字有两个元素组成:一个标识激起事件对象的前缀,以及一个事件名。这个方案在象VB这样的可视环境中是普通的,其中,Button1上点击事件的处理由调用Button1_Click过程完成。
在WSH下,操作以相同的方法进行。如果服务器触发一个命名为Collapse的事件,而且表示这个服务器的对象为TreeViewNode1,则客户端处理这个事件的过程就是TreeViewNode1_Collapse。
定义事件处理器
正常情况,对象名被自动分配,并通过属性编辑器或其它工具修改。在WSH下我们不能依靠可视工具,需要一个程序的方法给对象赋予一个内部名。
在用WScript.CreateObject()调用建立新对象时,能够指定服务器的选项参数以及ProgID:
WScript.CreateObject(sProgID, [sPrefix])
sPrefix参数是一个类如MyObj_的串,它将用于任何事件过程名的前缀,这些事件是由被建立对象激起的(如前例,这个参数起到TreeViewNode1_的作用)。
建立事件处理器的步骤
如果知道服务器输出了你可能感兴趣的事件,在WScript.CreateObject()调用的时候就有必要指定第二个参数,只要把前缀赋予这个对象,你就可以用适当命名的过程处理任何事件。原型精确地由事件的语法所定义。实际,WSH环境组合可能的处理器过程名,然后试图查找它,如果成功,执行调用者端代码处理这个事件。
注意,对于相同对象的每一个可能的副本前缀必须是唯一的。另外我们推荐使用下划线字符作为终止符。
为了在Windows脚本环境中支持事件,必须使用WScript.CreateObject()来建立你自己的对象。
添加新对象到WSH
我们已经看到Windows脚本环境带有一些内置的对象,而且这些对象故意向WSH用户提供在WSH之外可用的重要功能子集。例如网络对象允许你了解远程打印机和共享磁盘,而Shell部件提供了快捷方式能力、环境变量,进程导入和注册表处理等。
这些对象作为入门是足够了,但是很快你就会发现,你还需要更多的对象。如果你需要的COM部件在机器上存在,则简单地建立实例和使用它就可以了,当然需要知道它的方法。反之,你也可以写自己的对象来扩展WSH对象模型,这就是我们将有讨论的内容。
在此我们将构造一个自动化服务器来提供对WSH较弱的方面的支持:
剪切板的支持
驱动器格式化
注册表枚举
此外我们还将重新设计和集成两个我们在前面章节中给出的例子:
怎样浏览图标
使用客户名运行程序或文件
准备ATL自动化服务器
WSH可以调用任何无论什么语言实现的自动化服务器的方法。因此,我们选择C++ 和ATL作为编程语言。首先使用ATL COM应用大师建立一个进程内DLL工程(project),命名为WshMore。
然后使用对象大师添加一个简单的ATL对象,命名为WshFun,确保其接口为IWshFun,并具有双接口属性。现在我们所要做的就是编写函数。这是小意思了。
定义编程接口
这个接口并不是一个设计得完好的模型——我们只是用来向你展示可能要做的工作。下面就是我们要加到IWshFun接口的函数:
函数 |
范围 |
描述 |
CopyText() |
剪裁板 |
拷贝文本串到剪裁板。使用CF_TEXT格式 |
PasteText() |
剪裁板 |
从剪裁板读出文本。使用CF_TEXT格式 |
AddExecuteHook() |
Shell执行 |
对IShellExecuteHook模块使用的.ini文件添加和删除条目(见第8章)以建立新的快捷键导出程序的执行。 |
BrowseForIcon() |
图标 |
显示我们在第9章建立的对话框,使你可以从给定文件中拾取图标。 |
FormatDrive() |
驱动器 |
打开系统格式化驱动器对话框。 |
FindFirstKey() |
注册表 |
给定一个基路径,枚举第一个键。 |
FindNextKey() |
注册表 |
继续枚举上面路径的键。 |
FindFirstValue() |
注册表 |
给定一个基路径,枚举第一个值。 |
FindNextValue() |
注册表 |
继续枚举上面路径的值。 |
下一节我们将开始仔细讨论这些方法的语法。挖掘它们实现的细节,并提供使用这些方法的例程。
剪裁板支持
剪裁板是一个可用于临时数据存储的系统工具,但是脚本语言通常并不提供处理它的方法。正如你所知道的,Windows的剪裁板可以以个种数格式存储数据,包括客户定义的格式。而我们所写的方法仅仅使用最简单的一种格式CF_TEXT,显示文本的格式。
添加的两个方法是CopyText() 和 PasteText(),正象名字提示的,它们的功能是拷贝文本和读取文本。由于函数的声明采用的是BSTR串,在方法体内需要进行某些必要的转换。
拷贝文本
IWshFun::CopyText()的语法是:
HRESULT CopyText([in] BSTR bText);
简单地接收要拷贝的文本,并总是返回S_OK。这个函数取BSTR串作为输入参数,建立一个包含文本数据的存储器Handle ,封包,然后把它存储到剪裁板。
STDMETHODIMP CWshFun::CopyText(BSTR bText)
{
USES_CONVERSION;
TCHAR pszText[MAXBUFSIZE] = {0};
lstrcpy(pszText, OLE2T(bText));
HANDLE hData = GlobalAlloc(GHND, MAXBUFSIZE);
LPTSTR psz = static_cast<LPTSTR>(GlobalLock(hData));
lstrcpyn(psz, pszText, MAXBUFSIZE);
GlobalUnlock(hData);
OpenClipboard(NULL);
SetClipboardData(CF_TEXT, hData);
CloseClipboard();
return S_OK;
}
我们定义了MAXBUFSIZE常量,它等于32768,即一个32K缓冲。另外还使用了ATL的OLE2T()宏来转换BSTR到LPSTR串。在VBScript或Jscript中写代码调用CopyText()有如下方式:
Dim o
Set o = WScript.CreateObject("WshMore.WshFun.1")
o.CopyText "I'm the IWshFun interface"
读出文本
PasteText()方法是从剪裁板恢复任何CF_TEXT格式内容的方法,并把它作为串返回。这个方法的声明是:
HRESULT PasteText([out, retval] BSTR* pbRetVal);
这个方法不接收任何输入参数,而是把pbRetVal值作为方法的返回值传递给脚本。下面清单说明的它的实现。
STDMETHODIMP CWshFun::PasteText(BSTR* pbRetVal)
{
USES_CONVERSION;
// 取得剪裁板的内存Handle
OpenClipboard(NULL);
HANDLE hData = GetClipboardData(CF_TEXT);
CloseClipboard();
// 抽取内容
LPTSTR psz = static_cast<LPTSTR>(GlobalLock(hData));
TCHAR pszText[MAXBUFSIZE] = {0};
lstrcpyn(pszText, psz, MAXBUFSIZE);
GlobalUnlock(hData);
// 返回 BSTR
*pbRetVal = T2BSTR(pszText);
return S_OK;
}
我们通过ATL的T2BSTR()宏建立BSTR,它取ANSI串作为输入。除非你在代码中使用类似于CComBSTR的封装类,否则在调用PasteText()时,你应该使用SysFreeString()来释放BSTR。下面是一个简单的C++ 例子:
IWshFun* pWshFun = NULL;
hr = CoCreateInstance(CLSID_WshFun, NULL, CLSCTX_INPROC_SERVER,
IID_IWshFun, reinterpret_cast<LPVOID*>(&pWshFun));
if(FAILED(hr))
return;
BSTR bstr;
pWshFun->PasteText(&bstr);
MessageBox(GetFocus(), bstr, __TEXT("PasteText"), MB_OK);
pWshFun->Release();
SysFreeString(bstr);
这里是VBScript的例子:
Dim o, s
Set o = WScript.CreateObject("WshMore.WshFun.1")
s = o.PasteText
MsgBox s
驱动器格式化
由于Windows脚本环境是Windows Shell下的脚本环境,而且与DOS的批处理风格相近,因此访问文件系统有时是必要的。我们已经介绍了FileSystemObject对象作为解决这个问题的方案。然而,在FileSystemObject所提供的众多函数集中并没有提供格式化磁盘的工具。在第10章中,我们详细地讨论了SHFormatDrive()函数。作为例子的一部分,我们将提供通过COM的方法访问这个函数的能力。方法的原型为:
HRESULT FormatDrive([in] int iDrive);
为了简单起见,我们放弃这个函数的所有增强性能。因此这个方法的源码看上去是很直接的:
extern "C" int WINAPI SHFormatDrive(long, long, long, long);
STDMETHODIMP CWshFun::FormatDrive(int iDrive)
{
int irc = SHFormatDrive(0, iDrive, 0, 0);
return (irc < 0 ? S_OK : E_FAIL);
}
这个函数返回一个布尔值来表示操作的成功或失败。也就是,这个函数实际格式化了磁盘后它返回非0值,否则返回0值(包括取消了操作)。
在第10章中说明, SHFormatDrive()包含在shell32.lib库中。但是在shellapi.h或其它的头文件中都没有给出适当的声明。因而我们必须在代码中添加声明,而且需要带有“C”前缀来保证兼容性。
下面是在Jscript代码中怎样调用这个新方法的例子:
// 格式化驱动器 A:
var o;
o = WScript.CreateObject("WshMore.WshFun.1");
o.FormatDrive(0);
使用基于0的数字表示要格式化的驱动器:0是驱动器 A,1 是 B, 2 是 C 等。
浏览图标
建立快捷方式是WSH的典型应用,一个有趣的功能是应该给出一个系统提供的对话框来选择赋予快捷方式的图标。我们已经讨论了这个对话框的必要源码——参见第9章,和第11章的部分例子。
在这个例子中,我们打算展示使这个功能可用于Windows脚本环境应用的办法,这个方法为:
HRESULT BrowseForIcon([in] BSTR bFile, [out, retval] BSTR* pbRetVal);
bFile变量表示要浏览器图标的文件,这可以在运行时通过点击对话框的浏览按钮改变。
在选择了一个图标后,这个方法返回一个带有文件名和选中图标索引的串,它们用逗号分隔:
方法的源码调用SHHelper.dll(参见第11章),一个辅助库,它收集了许多我们已经编写过的函数。这个DLL包含了SHBrowseForIcon()函数,其源码我们在第9章中开发过。
#include "shhelper.h"
STDMETHODIMP CWshFun::BrowseForIcon(BSTR bFile, BSTR* pbRetVal)
{
USES_CONVERSION;
TCHAR pszFile[MAX_PATH] = {0};
lstrcpy(pszFile, OLE2T(bFile));
HICON hIcon;
int iIndex = SHBrowseForIcon(pszFile, &hIcon);
if(iIndex >= 0)
{
TCHAR szBuf[MAX_PATH + 10] = {0};
wsprintf(szBuf, __TEXT("%s,%d"), pszFile, iIndex);
*pbRetVal = T2BSTR(szBuf);
return S_OK;
}
return S_FALSE;
}
此时需要ANSI- Unicode转换,因为SHBrowseForIcon()函数要求ANSI串。下面的代码段展示怎样从VBScript代码调用BrowseForIcon()方法。
Dim o, s
Set o = WScript.CreateObject("WshMore.WshFun.1")
s = o.BrowseForIcon("shell32.dll")
MsgBox s
注册表键枚举
我们前面提到过,WSH内置的注册表支持方法并不包含键和值的枚举功能,然而,如果你需要对注册表的某个子树做这样的操作时,这样方法可能是真正有用的。我们提供两个不同的枚举器,一是枚举键的,另一是枚举值的。
底层Win32 API函数和新Shell实用API(见第10章)有类似的方法给出。你需要指定附加的变量来标识键或值的第 n 个项。RegEnumValue() 和 SHEnumKeyEx()函数一般是由布尔变量驱动的循环,以防止在键或值清单末端中断。
就我们的实现而言,采取的是稍微不同的方法,定义两个方法,称为FindFirstXxx() 和FindNextXxx(),其原型为:
HRESULT FindFirstKey([in] long hk, [in] BSTR bRegPath,[out, retval] BSTR* pbRetVal);
HRESULT FindNextKey([out, retval] BSTR* pbRetVal);
HRESULT FindFirstValue([in] long hk, [in] BSTR bRegPath,[out, retval] BSTR* pbRetVal);
HRESULT FindNextValue([out, retval] BSTR* pbRetVal);
我们采用了与Win32相同的语法,所以注册表的路径使用根节点和路径参数分别表示。注意,WshShell方法使用全路径名,需要解析来获得根节点。这项技术使它更适合于使用如HKLM这样的首字符。
这个IWshFun接口要求传递HKEY值(即一个长值)和一个剩余的路径串。每一对函数共同工作,FindFirstXxx()完成后,FindNextXxx()函数开始连续操作,实际上,所有操作都改变枚举的索引,这个索引在调用FindFirstXxx()时设置为0,并在每次调用FindNextXxx()时增加。为了保持编程接口简单,注册表变量被隐藏,所以不需要每次都指定它们。
这两类枚举有相同的内部结构,并且是建立在GetNthKey() 和 GetNthValue()两个辅助函数之上的。
枚举键
GetNthKey()函数打开指定的键并抽取第 n 个子键,如果存在,其名字通过pbRetVal输出变量返回。SHEnumKeyEx()(在shlwapi.lib中实现)函数之所以还有用是因为它的变量比 RegEnumKeyEx()少。
DWORD CWshFun::GetNthKey(long hk, BSTR bRegPath, int iIndex, BSTR* pbRetVal)
{
USES_CONVERSION;
TCHAR szRegPath[MAX_PATH] = {0};
lstrcpy(szRegPath, OLE2T(bRegPath));
HKEY hkey;
RegOpenKeyEx(reinterpret_cast<HKEY>(hk),
szRegPath, 0, KEY_ALL_ACCESS, &hkey);
TCHAR szKey[MAX_PATH] = {0};
DWORD dwSize = MAX_PATH;
DWORD rc = SHEnumKeyEx(hkey, iIndex, szKey, &dwSize);
if(rc == ERROR_SUCCESS)
*pbRetVal = T2BSTR(szKey);
RegCloseKey(hkey);
return rc;
}
FindFirstKey()/FindNextKey()枚举的框架是一个类循环,它涉及到两个函数,并维持几个全程变量状态。
DWORD g_dwIndex = 0;
BSTR g_bRegPath;
LONG g_hk;
STDMETHODIMP CWshFun::FindFirstKey(long hk, BSTR bRegPath, BSTR* pbRetVal)
{
g_dwIndex = 0;
g_bRegPath = bRegPath;
g_hk = hk;
DWORD rc = GetNthKey(hk, bRegPath, g_dwIndex, pbRetVal);
return (rc == ERROR_SUCCESS ? S_OK : S_FALSE);
}
STDMETHODIMP CWshFun::FindNextKey(BSTR* pbRetVal)
{
g_dwIndex++;
DWORD rc = GetNthKey(g_hk, g_bRegPath, g_dwIndex, pbRetVal);
return (rc == ERROR_SUCCESS ? S_OK : S_FALSE);
}
在FindFirstKey()操作期间,使用0索引调用GetNthKey(),FindNextKey()内部则使用一个增加的值。注册表路径被保存以为FindNextKey()进一步使用。
枚举值
枚举值几乎是一个同样的过程。GetNthValue()函数依赖于SHEnumValue()函数列出的所有指定键的叶节点:
DWORD CWshFun::GetNthValue(long hk, BSTR bRegPath, int iIndex, BSTR* pbRetVal)
{
USES_CONVERSION;
TCHAR szRegPath[MAX_PATH] = {0};
lstrcpy(szRegPath, OLE2T(bRegPath));
HKEY hkey;
RegOpenKeyEx(reinterpret_cast<HKEY>(hk),
szRegPath, 0, KEY_ALL_ACCESS, &hkey);
DWORD dwType = 0;
TCHAR szKey[MAX_PATH] = {0};
DWORD dwSize = MAX_PATH;
DWORD rc = SHEnumValue(hkey, iIndex, szKey, &dwSize, &dwType, NULL, 0);
if(rc == ERROR_SUCCESS)
*pbRetVal = T2BSTR(szKey);
RegCloseKey(hkey);
return rc;
}
注意,SHEnumValue()可以返回给定值的类型,以及当前内容和它的尺寸。如果你对这些信息不感兴趣,给&dwType传递NULL。
FindFirstValue() 和 FindNextValue()函数的框架类似于与其相似的键函数:
STDMETHODIMP CWshFun::FindFirstValue(long hk, BSTR bRegPath, BSTR* pbRetVal)
{
g_dwIndex = 0;
g_bRegPath = bRegPath;
g_hk = hk;
DWORD rc = GetNthValue(hk, bRegPath, g_dwIndex, pbRetVal);
return (rc == ERROR_SUCCESS ? S_OK : S_FALSE);
}
STDMETHODIMP CWshFun::FindNextValue(BSTR* pbRetVal)
{
g_dwIndex++;
DWORD rc = GetNthValue(g_hk, g_bRegPath, g_dwIndex, pbRetVal);
return (rc == ERROR_SUCCESS ? S_OK : S_FALSE);
}
不要忘了使用SHEnumKeyEx() 和 SHEnumValue()函数需要连接shlwapi.lib库来编译这段代码。
使用枚举器
现在我们看一下在WSH应用中怎样使用这两个枚举器。这个示例是以VBScript代码编写的,说明怎样列出在
HKEY_LOCAL_MACHINE
/Software
下的键,和在键:
HKEY_LOCAL_MACHINE
/Software
/Microsoft
/Windows
/CurrentVersion
中的值。
脚本首先枚举键,然后逐步组合显示的串。每一个串都由回车换行分隔符(ASCII13+ASCII 10),这使每一个键显示在不同的行上。在枚举给定路径的值时也使用相同的逻辑。
' 根节点的常量
Const HKCR = &H80000000 ' HKEY_CLASSES_ROOT
Const HKCU = &H80000001 ' HKEY_CURRENT_USER
Const HKLM = &H80000002 ' HKEY_LOCAL_MACHINE
Const HKU = &H80000003 ' HKEY_USERS
Const HKPD = &H80000004 ' HKEY_PERFORMANCE_DATA
Dim o, s, b
Dim sValues, sKeys
Set o = CreateObject("WshMore.WshFun.1")
' 枚举键
s = o.FindFirstKey(HKLM, "Software")
if Len(s) > 0 then
b = True
while b
sKeys = sKeys + s + vbCrLf
s = o.FindNextKey
if Len(s) = 0 then
b = False
end if
wend
end if
MsgBox sKeys
' 枚举值
s = o.FindFirstValue(HKLM, "Software/Microsoft/Windows/CurrentVersion")
if Len(s) > 0 then
b = True
while b
sValues = sValues + s + vbCrLf
s = o.FindNextValue
if Len(s) = 0 then
b = False
end if
wend
end if
MsgBox sValues
为了简化我们定义了一些常量来映射注册表根节点的实际值。我们看到HKEY是一个Longs值,这里使用的常量产生于它们的精确值。这些值可以从winreg.h文件中取得,这是一个Win32 编译器include目录下的一个文件。下面的图象显示了上面代码产生的两组信息,第一个窗口显示的是键,第二个窗口显示的是值。当然这个输出依赖于实际注册表和操作系统的内容。图像采自Windows95。
钩住程序的执行
我们添加到IWshFun接口的最后一个方法提供了直接的编程方法来添加快捷键到IShellExecuteHook处理器,这是我们在第8章建立的接口。通过定义和适当地安装实现IShellExecuteHook接口的COM模块,你就有能力在由ShellExecute()和ShellExecuteEx() API函数传递的每一个命令行上设置钩子。尤其是,你可以获得由系统‘运行’对话框或由WSH的WshShell.Run()方法导出的每一个程序的控制权。
在第8章中,我们探讨了添加‘快捷键’的特征——例如,要运行notepad.exe,你可以键入n 而不是全路径。因此,要导出‘Notepad’,你只需调用:
Set s = WScript.CreateObject("WScript.Shell")
s.Run "n"
是IShellExecuteHook处理器恢复了映射并解析了特定的命令。我的处理器在称之为showhook.ini文件中查找命令,这个文件在C:根目录下。这是一个典型的.ini文件,其内容类似于:
[goldlist]
n=c:/windows/notepad.exe
这个IWshFun的方法,AddExecuteHook()只是在文件中增加一个条目或从文件中删除一个条目:
HRESULT AddExecuteHook([in] BSTR bShortcut, [in] BSTR bExeFile)
其源码是直接的,它调用WritePrivateProfileString():
const LPTSTR EXECUTEHOOK = __TEXT("c://showhook.ini");
STDMETHODIMP CWshFun::AddExecuteHook(BSTR bShortcut, BSTR bExeFile)
{
USES_CONVERSION;
TCHAR szEntry[MAX_PATH] = {0};
lstrcpy(szEntry, OLE2T(bShortcut));
TCHAR szFile[MAX_PATH] = {0};
lstrcpy(szFile, OLE2T(bExeFile));
WritePrivateProfileString(__TEXT("goldlist"), szEntry,
(lstrlen(szFile) ? szFile : NULL), EXECUTEHOOK);
return S_OK;
}
通过指定空串作为文件的名(bExeFile 变量),可以引起由bShortcut表示的条目整个被删除。下面这段代码说明怎样使用这个方法:
Dim o
Set o = CreateObject("WshMore.WshFun.1")
o.AddExecuteHook "r", "regedit.exe"
这段代码添加一个导出注册表编辑器的新条目,如果你请求运行一个称为 r 的程序:
r=regedit.exe
这个例子结束了我们关于Windows脚本环境的旅程。
关于WSH改进
WSH是一个对程序员和系统管理员提供重要帮助的系统模块,但是,它并不是完备的,特别,有两个方面的欠缺:
用户接口
代码可重用性
要实际建立有用的和威力强大的脚本,你需要设置复杂和艺术对话框的方法,以及有某种可重用机理的能力。在这一章的最后一节,我们将讨论建立这种能力的方法。在这里我们并不打算提供清晰的方案,这是因为这样的方案(和涉及的技术)超出了本书的范围。
增加用户接口支持
任何重要的开发环境都允许建立和设计对话框,没有对话框,就难于获得用户输入和难于使应用友善和可用。WSH脚本改进了MS-DOS的批处理,但是我们也确实需要替换基于旧风格的键盘菜单。
建立对话框
在WSH内没有建立通用对话框的工具,所以你必须依靠脚本语言或外部对象。VBScript提供了一个函数InputBox(),使你能交互地接收串。其用法如下:
strResult = InputBox(strMessage, strTitle, strDefault)
这个函数允许你定义希望显示的消息,对话框标题和默认值。
例如上图,由下面的调用产生:
InputBox "Enter some text", "Dialog", "Hello, world"
不幸,这个函数很不够,我们需要的是一个象通用对话框提供者一样工作的,以及可以指定界面模板的对象。更进一步,它应该允许灵活地嵌入代码,调整界面控件坐标,以及驱动这些控件。也就是说,这个对象应该有解释下面伪代码的能力:
dlg = CreateObject("Dialog.Provider");
dlg.SetDlgItemText("object1", text1);
dlg.SetDlgItemInt("object2", num1);
dlg.Show();
MessageBox(dlg.GetDlgItemText("object1"));
dlg.Close();
此外对话框的模板必须易于绘制。这个需求的潜在答案就是动态HTML,使用它你:
可以使用有吸引力的基于HTML的用户接口
不强迫学习新脚本语言来描述对话框
可以混合界面控件和代码
可以最大限度的设计对话框模版
有直接的方法标识基于对象的模版
可以在任何时候更新内容
作为这个概念的补充,我们考虑IE4.x的动态HTML对象模型给出的一个方法showModalDialog(),它接收HTML页名,并把它显示在一个模式对话框中。IE4.0的‘关于’窗口也以同样的逻辑建立。这个对话框是基于mshtml.dll输出函数ShowHTMLDialog()的——这是一个动态HTML核心库。
alert()对话框
在WSH应用中使用Jscript时可能有一点缺陷。一般的误解是Jscript有几个显示标准对话框的函数,即alert()、prompt() 和 confirm()。然而这是错误的,因为所有这些函数都是IE4.0窗口对象的方法,它们不是在Jscript运行时引擎中实现的,因此,在WSH中是不可用的。如果你需要显示某些信息,可以使用WScript.Echo() 或WshShell.Popup()方法。
即使alert()总是与Jscript相关,它在VBScript下也是一个可访问的窗口方法:
<html>
<script language="VBScript" for="window" event="onload">
window.alert "Hello, world!"
</script>
</html>
WSH文件上的拖拽
WSH文件是一个.vbs或.js文档,通常它是一个接收自身作为参数的应用。如果我们用拖拽数据在这种文件上设置其参数能行吗?为了允许在Shell的文件上执行拖拽操作,需要进行Shell扩展。这是我们在第15章套讨论的内容。
WSH中的可重用性
WSH的另一个明显的缺憾是它限制支持可重用性。这所需要的是脚本代码的可重用和部件化能力。其解决方案混合使用脚本与COM,这就是XML Scriptlets(XML 小程序)。
XML小程序是遵循XML语法的文本文件。它描述COM对象和嵌入脚本代码段(VBScript 或 JScript)。这种代码就象二进制COM代码一样被解释和发布到外部世界,也就是说,XML小程序(由<script>标记组成的)是作为COM感知的规则自动化对象出现的,这就解决了脚本代码重用的问题。
小结
WSH是桌面层脚本引擎,它可以用作自动重复的活动,并遵循DOS的批处理文件逻辑。通过现今强有力的脚本语言与COM部件的结合使用,WSH可以很理想地使你的应用更丰富和更用户友善。
我们并没有对Windows脚本环境对象的所有属性和方法进行全面详细的解释——Internet客户端SDK已经作了这方面的工作。相反,我们集中讨论了对你可能有用的技术和方法。现在,WSH有两个主要的应用领域:作为Windows NT平台的管理工具,和作为作为用户的开发平台。也就是说,系统管理人员和终端用户,以及第三方开发商都能从WSH系统提供的内在脚本对象中获得好处。如果你销售了一套相关的和集成的程序,你就应该考虑提供使用户自动收集不同程序属性的对象。
在这一章中,我们讨论了:
Windows脚本环境是什么
怎样获得它,和它怎样工作
WSH对象模型
怎样访问通用COM控件
怎样编写COM控件来扩展WSH对象模型
提示怎样改进WSH