Windows Scripting Technology

Windows Scripting Technology

坚果 Jimbowhy 前后端脚本编程轻松篇集合文章:

Windows Script Host & Windows Script File

接上一文小而巧 editplus 脚本编程继续, Windows 脚本文件 .wsf 和 .wsh 是含有可扩展标记语言 (XML) 代码的文本文档, (.wsh)主要用来设置和自定义脚本属性. 通过 Include 语句复用 js 和 vbs 脚本文件, 可同时使用多种脚本文件类型, 这样编写脚本时就可以按文件来进行功能分类了. 支持类型库, 在下面的示例中的ActiveX控件 "MyComponent" 可以用 Microsoft Visual Basic 5.0 开发, 即 Visual Studio 97 包含的 Visual Basic. 通过 reference 来引用 progID 指定的类库后, 脚本就可以使用库定义好的常数了, 类库信息可以在这些文件中找到 .tlb, .olb, .dll .

<job id="IncludeExample">
    <reference progid="MyComponent.MyClass">
    <script language="JScript" src="some.js"/>
    <script language="VBScript">
        Set MyVar = CreateObject("MyComponent.MyClass")
        'call function from script file ...
    <script>
</job>

WSF中有两个重要的元素 <object> 和 <reference>, 在理解这两个元素之前, 可以简单理解为前者是实例化一个类, 后者是应用一个类型库以使用库里面的一些常量定义等. 要深入理解, 就要先弄清 progID 和 CLSID. 先来说说ID, 及 Identifier 唯一标识. 重复是一个普遍的现象, 书的名字会有重复的, 人的也会有重名的 progID, 自然的重复就就常态. 但是计算机的编码也是0和1的重复, 但是这种重复现象在编程过程就必须避免, 否则就会导致逻辑胡乱, 程序更无法正常运行. 据说当初微软设计com规范的时候. 有两种选择来保证用户的设计的com组件可以全球唯一. 第一种是采用和Internet地址一样的管理方式, 成立一个管理机构,用户如果想开发一个COM组件的时候需要向该机构提出申请, 并交一定的费用. 我第一次看到这种说法时觉得好笑,可是在当时那个时代,这种事确实可能会发生. 但最后实施时是用一种算法,每次都能产生一个全球唯一的COM组件标识符. 这种算法用 Globally Unique Identifiers 即GUID来标识COM组件, GUID是一个128位长的数字, 一般用16进制分成五段表示, 开头这段是随机数, 接着是两段时间戳, 最后两段是网卡MAC相关数, 就是文中看到的这些 CLSID. 算法的核心思想是结合机器的网卡, 当地时间, 一个随即数来生成GUID. 从理论上讲, 如果一台机器每秒产生10000000个GUID, 则可以在高概率保证3240年不重复. 所以 GUID 是在目前这个计算机速度前提下成立的, 如果以后的机器速度够快, 那同样的时间内生成的 GUID 就会更多, 重复的概率也就越大, 到一定条件下可能就失去作用了. 从唯一标识这个角度看, 微软COM技术中的 GUID, UUID, CLSID, IID 是一回事,只不过各自代表的意义不同:

UUID    代表COM
CLSID   代表COM组件中的类 
IID     代表COM组件中的接口

来看看他们在C语言下的结构定义就更清晰了:

typedef struct _GUID { 
 DWORD Data1;   // 随机数 
 WORD Data2;    // 和时间相关 
 WORD Data3;    // 和时间相关 
 BYTE Data4[8]; // 和网卡MAC相关 
} GUID;

typedef GUID CLSID;  // 组件ID 
typedef GUID IID;    // 接口ID 
#define REFCLSID const CLSID &

每一个COM组件都需要指定一个 CLSID 保证重复是“不可能”的, 但是这样的一组数码不方便开发者使用,因此, 另一种字符串名称方式也就是 ProgID 被用上了, 通过注册表 HKEY_CLASSES_ROOT 来查找系统已经注册的 ProgID 组件类型, 都按 Library.Class 这种方式命名, 这个名字就可用 WSF 的 <object> 元素的 ProgID 中, 如 Word.Application, 在执行 new ActiveXObject 就是从这里获取信息的. 以下导出的注册表信息供参考:

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\Scripting.Dictionary]
@="Scripting.Dictionary"
[HKEY_CLASSES_ROOT\Scripting.Dictionary\CLSID]
@="{EE09B103-97E0-11CF-978F-00A02463E06F}"

[HKEY_CLASSES_ROOT\Scripting.FileSystemObject]
@="FileSystem Object"
[HKEY_CLASSES_ROOT\Scripting.FileSystemObject\CLSID]
@="{0D43FE01-F093-11CF-8940-00A0C9054228}"

[HKEY_CLASSES_ROOT\Word.Application]
@="Microsoft Word Application"
[HKEY_CLASSES_ROOT\Word.Application\CLSID]
@="{000209FF-0000-0000-C000-000000000046}"
[HKEY_CLASSES_ROOT\Word.Application\CurVer]
@="Word.Application.12"

[HKEY_CLASSES_ROOT\Word.Document]
@="Microsoft Office Word Document"
[HKEY_CLASSES_ROOT\Word.Document\CLSID]
@="{F4754C9B-64F5-4B40-8AF4-679732AC0607}"
[HKEY_CLASSES_ROOT\Word.Document\CurVer]
@="Word.Document.12"

再细看每个类型中包含一个 CLSID 子键, 里面的值就是加载组件的依据, 系统会通过这个值来查询注册表的 HKEY_CLASSES_ROOT\CLSID, 这里包含了类型库的详细信息, 如程序文件的位置, 来看看这里的注册表内容:

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\CLSID\{88D969EA-F192-11D4-A65F-0040963251E5}]
@="XML HTTP 5.0"
[HKEY_CLASSES_ROOT\CLSID\{88D969EA-F192-11D4-A65F-0040963251E5}\InProcServer32]
@="C:\\Program Files\\Common Files\\Microsoft Shared\\OFFICE11\\msxml5.dll"
"ThreadingModel"="Apartment"
[HKEY_CLASSES_ROOT\CLSID\{88D969EA-F192-11D4-A65F-0040963251E5}\ProgID]
@="Msxml2.XMLHTTP.5.0"
[HKEY_CLASSES_ROOT\CLSID\{88D969EA-F192-11D4-A65F-0040963251E5}\TypeLib]
@="{F5078F18-C551-11D3-89B9-0000F81FE221}"
[HKEY_CLASSES_ROOT\CLSID\{88D969EA-F192-11D4-A65F-0040963251E5}\Version]
@="5.0"


[HKEY_CLASSES_ROOT\CLSID\{88d96a0a-f192-11d4-a65f-0040963251e5}]
@="XML HTTP 6.0"
[HKEY_CLASSES_ROOT\CLSID\{88d96a0a-f192-11d4-a65f-0040963251e5}\InProcServer32]
@="%SystemRoot%\System32\msxml6.dll"
"ThreadingModel"="Apartment"
[HKEY_CLASSES_ROOT\CLSID\{88d96a0a-f192-11d4-a65f-0040963251e5}\ProgID]
@="Msxml2.XMLHTTP.6.0"
[HKEY_CLASSES_ROOT\CLSID\{88d96a0a-f192-11d4-a65f-0040963251e5}\TypeLib]
@="{F5078F18-C551-11D3-89B9-0000F81FE221}"
[HKEY_CLASSES_ROOT\CLSID\{88d96a0a-f192-11d4-a65f-0040963251e5}\Version]
@="6.0"

这里留意 TypeLib, 上面罗列的 Msxml2.XMLHTTP.5.0 和 Msxml2.XMLHTTP.6.0 都归属同一个库, 跟着这个库的 GUID 又可以在 注册表的这个位置 HKEY_CLASSES_ROOT\TypeLib 找到类型库信息

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\TypeLib\{F5078F18-C551-11D3-89B9-0000F81FE221}]

[HKEY_CLASSES_ROOT\TypeLib\{F5078F18-C551-11D3-89B9-0000F81FE221}\3.0]
@="Microsoft XML, v3.0"
[HKEY_CLASSES_ROOT\TypeLib\{F5078F18-C551-11D3-89B9-0000F81FE221}\3.0\0\win32]
@="%SystemRoot%\System32\msxml3.dll"
[HKEY_CLASSES_ROOT\TypeLib\{F5078F18-C551-11D3-89B9-0000F81FE221}\3.0\FLAGS]
@="0"

[HKEY_CLASSES_ROOT\TypeLib\{F5078F18-C551-11D3-89B9-0000F81FE221}\4.0]
@="Microsoft XML, v4.0"
[HKEY_CLASSES_ROOT\TypeLib\{F5078F18-C551-11D3-89B9-0000F81FE221}\4.0\0\win32]
@="C:\\windows\\SysWow64\\msxml4.dll"
[HKEY_CLASSES_ROOT\TypeLib\{F5078F18-C551-11D3-89B9-0000F81FE221}\4.0\FLAGS]
@="0"
[HKEY_CLASSES_ROOT\TypeLib\{F5078F18-C551-11D3-89B9-0000F81FE221}\4.0\HELPDIR]
@="C:\\windows\\system32"

[HKEY_CLASSES_ROOT\TypeLib\{F5078F18-C551-11D3-89B9-0000F81FE221}\5.0]
@="Microsoft XML, v5.0"
[HKEY_CLASSES_ROOT\TypeLib\{F5078F18-C551-11D3-89B9-0000F81FE221}\5.0\0\win32]
@="C:\\Program Files\\Common Files\\Microsoft Shared\\OFFICE11\\msxml5.dll"
[HKEY_CLASSES_ROOT\TypeLib\{F5078F18-C551-11D3-89B9-0000F81FE221}\5.0\FLAGS]
@="0"
[HKEY_CLASSES_ROOT\TypeLib\{F5078F18-C551-11D3-89B9-0000F81FE221}\5.0\HELPDIR]
@="C:\\Program Files\\Common Files\\Microsoft Shared\\OFFICE11\\"

[HKEY_CLASSES_ROOT\TypeLib\{F5078F18-C551-11D3-89B9-0000F81FE221}\6.0]
@="Microsoft XML, v6.0"
[HKEY_CLASSES_ROOT\TypeLib\{F5078F18-C551-11D3-89B9-0000F81FE221}\6.0\0\win32]
@=%SystemRoot%\System32\msxml6.dll
[HKEY_CLASSES_ROOT\TypeLib\{F5078F18-C551-11D3-89B9-0000F81FE221}\6.0\FLAGS]
@="0"

在WSF中使用 <reference> 元素是就是使用这个 TypeLib 的 GUID. 所以, 可以在WSF中这样定义:

<reference id="Microsoft Visual Basic 5.0 Extensibility" guid="{EF404E00-EDA6-101A-8DAF-00DD010F7EBB}" version="5.0"/>
<reference id="Microsoft WinHTTP Services, version 5.1" guid="{662901FC-6951-4854-9EB2-D9A2570F2B2E}" version="5.0"/>
<reference id="Microsoft Script Control 1.0" guid="{0E59F1D2-1FBE-11D0-8FF2-00A0D10038BC}" version="1.0"/>

<object id="word" progid="Word.Application" events="true"/>
<object id="fso" progid="Scripting.FileSystemObject" events="true" reference="true"/>
<object id="ie" progid="InternetExplorer.Application" events="true" reference="false"/>
<object id="encoder" progid="Scripting.Encoder" events="true" reference="false"/>
<obect id="fso" progid="Scripting.FileSystemObject"/>

但是不能这样定义, 因为 Word.Application 是类对象而不是类型库:

<reference object="Word.Application" />

请注意, VBScript 强制引用 Visual Basic For Applications, 可以通过以下语句测试任一个常数如 vbAbort, 在 VBScript 脚本中是可以读取的, 但是在 JScript 就不能, 很不统一的逻辑问题, 应该算是八阿哥. 并且也不能在脚本块中进行引用, 因为 VBScript 默认就是引用的, 所以, 添加以下VBA引用后, 脚本就不能正常工作了, 错误提示也不是找不到类库, 而是不能加载类库. 因为, 有引用的类库和默认的有冲突, 这一点很像 Visual Basic 5.0 工程中不能移除 VBA 引用, 也不能添加其它版本 VBA 库一样。

MsgBox "VB Const vbAbort=" & vbAbort

<reference id="Visual Basic For Applications 5.0" guid="{000204EF-0000-0000-C000-000000000046}" version="5.0"/>
<reference id="Visual Basic For Applications 6.0" guid="{EA544A21-C82D-11D1-A3E4-00A0C90AEA82}" version="6.0"/>

为了方便 JScript 使用VB常数, 我将常用的 MsgBox 常数整理到 vbconst.js, 只要引入它就可以使用了. 处理这些常数我是使用正则表达来处理的, 在 editplus 的替换功能中, 只需要以下两条正则式子即可:

Find What: ^(\w+)\t(\d*)
Replace: var \1 = \2; //

然后就可以得到整理好的 JScript 代码, 分毫不差快速有效率:

    // vbconst.js
    function test(){
        return WScript.Name
    }
    
    // MsgBox Arguments
    // Constant Value   Description
    var vbOKOnly = 0; //    OK button only (default)
    var vbOKCancel = 1; //  OK and Cancel buttons
    var vbAbortRetryIgnore = 2; //  Abort, Retry, and Ignore buttons
    var vbYesNoCancel = 3; //   Yes, No, and Cancel buttons
    var vbYesNo = 4; // Yes and No buttons
    var vbRetryCancel = 5; //   Retry and Cancel buttons
    var vbCritical = 16; // Critical message
    var vbQuestion = 32; // Warning query
    var vbExclamation = 48; //  Warning message
    var vbInformation = 64; //  Information message
    var vbDefaultButton1 = 0; //    First button is default (default)
    var vbDefaultButton2 = 256; //  Second button is default
    var vbDefaultButton3 = 512; //  Third button is default
    var vbDefaultButton4 = 768; //  Fourth button is default
    var vbApplicationModal = 0; //  Application modal message box (default)
    var vbSystemModal = 4096; //    System modal message box
    var vbMsgBoxHelpButton = 16384; //  Adds Help button to the message box
    var VbMsgBoxSetForeground = 65536; //   Specifies the message box window as the foreground window
    var vbMsgBoxRight = 524288; //  Text is right aligned
    var vbMsgBoxRtlReading = 1048576; //    Specifies text should appear as right-to-left reading on Hebrew and Arabic systems
        
    // MsgBox Return Values
    // Constant Value   Description
    var vbOK = 1; //    OK button pressed
    var vbCancel = 2; //    Cancel button pressed
    var vbAbort = 3; // Abort button pressed
    var vbRetry = 4; // Retry button pressed
    var vbIgnore = 5; //    Ignore button pressed
    var vbYes = 6; //   Yes button pressed
    var vbNo = 7; //    No button pressed

到这里, 应该掌握像 PrimalScript 这样的程序是如何生成对象和引用列表的了. 也应该理解开发COM组件时会用到的类似 CLSIDFromProgID(), CLSIDFromProgIDEx() 这样的方法是如何由 ProgID 得到 CLSID, 就是通过注册表API查询呗.

PAUSE! 写到这里, 我不得不给 Sublime 打 CALL啦, 一个 Unregistered 的软件, 用起来竟然可以这么流畅, Ctrl+R 快速定位代码可行定位可以方法定位, 更别说那提示同样敏捷. 它的 Package Control 提供强大的扩展性, 可以加装 SFTP, 可以加装 github 等等, 太多太赞了。按下 Ctrl+Shift+P, 输入 Package Control, 回车安装它. 完后再来一次, 输入 Install Package, 回车安装它. 现在的 Sublime 以可以开始暴力圈粉模式了, 同样按下 Ctrl+Shift+P, 输入 Install Package, 对就是上一步的重复, 只不过这次不是安装 Install Package, 而是其他工具的 Package, 这次要等 Install Package 更新可用工具包列表, 住意状态栏, 完了就可以输入想要的工具了, sftp 自然不在话下, Markdown Preview 也是可以有的. 不同的工具出行的菜单位置不同, SFTP 是在文件菜单, Markdown Preview 则在 tools=>build system, 打开MD文件, 选择 build system 里的 Markdown, 然后按下 Ctrl+B 编译 md 文件, 就会生成一个 html 页面. 也可以换个方式, 依次按下 Ctrl+Shift+P => Markdown, 看看是不是出现 Preview in Browser, 对没错选他, 立刻就在浏览器看到渲染后的 md 文档.

I love you

回到正题, 从OOP的观点来看, 注册表的相关信息是对应的, 包括注册表的一个 Interface 子键, 完整思维导图是这个样的:

[HKEY_CLASSES_ROOT\Interface\{IID}\]
[HKEY_CLASSES_ROOT\TypeLib]
[HKEY_CLASSES_ROOT\CLSID]
[HKEY_CLASSES_ROOT\(ProgID)]

注册表的(ProgID)子键下设关联CLSID, 这是一对一关联, CLSID子键下会有多个子键与TypeLib关联, 因为库是包含多个类的, 这就一致的. Interface 对应接口, ID也按IID这样表示, 类实现接口与否没有强制.

为了查询方便, 我将editplus配置为命令行辅助工具, 按以下要求给 editplus 添加一个用户工具, 这样就可以在 editplus 中输入命令行而不必到系统中打开 cmd.exe 来执行命令了:

Text=Execute Line
Command=$(CurLineText)
ACTiON=Capture output
Save=None

配置完后, 只需要在 editplus 输入类似以下的命令, 然后光标停在准备执行的行, 按下快捷键, editplus 就会捕捉命令输出的内容, 这样可以避免多开程序窗口, 也免去了程序切换的麻烦:

cmd.exe /c dir c:\
reg.exe query /?

接下来, 通过以下注册表命令来查询需要的信息:

reg.exe query HKCR\Microsoft.XMLHTTP /s
reg.exe query HKCR\MSXML2.XMLHTTP /ve
reg.exe query HKCR\MSXML2.XMLHTTP /s
reg.exe query HKCR\Msxml2.ServerXMLHTTP.6.0 /s
reg.exe query HKCR\Word.Application /s
reg.exe query HKCR\CLSID\{88d96a0b-f192-11d4-a65f-0040963251e5} /s
reg.exe query HKCR\TypeLib\{0E59F1D2-1FBE-11D0-8FF2-00A0D10038BC} /s

还有一个自动定位注册表位置的小技巧, 注册表关闭时会将最后的位置存储到 LastKey 中, 下次打开时就还原到这个位置, 因此, 利用这个巧点可以实现注册表的自动定位:

cmd /c reg.exe add HKCU\Software\Microsoft\Windows\CurrentVersion\Applets\Regedit /f /v LastKey /t REG_SZ /d HKEY_CLASSES_ROOT\Word.Application && regedit.exe
cmd /c reg.exe add HKCU\Software\Microsoft\Windows\CurrentVersion\Applets\Regedit /f /v LastKey /t REG_SZ /d HKEY_CLASSES_ROOT\scriptletfile && regedit.exe

注册表维护

脚本能否运行和注册表有很大的关系, Windows 会在注册表将脚本解析引擎与特定的脚本文件类型关联起来, 如果软件操作不当, 损坏了相关信息, 则可能导致脚本不能正常运行, 以下命令就可以用来查看脚本文件的关联信息:

reg.exe query HKCR\.vbs /s
reg.exe query HKCR\.js /s
reg.exe query HKCR\.wsf /s
reg.exe query HKCR\.wsc /s

本机就是安装 ASE Admin Script Editor 出现 js 脚本文件关联问题, 导致脚本不能运行, 注册表显示 js 文件关联类型是 EditPlus.js, 正确的应该是 JSFile, :

reg.exe query HKCR\EditPlus.js /s
reg.exe query HKCR\vbsfile /s

HKEY_CLASSES_ROOT\.js
    (默认)            REG_SZ    EditPlus.js
    Content Type    REG_SZ    application/x-javascript
    PerceivedType   REG_SZ    Text

修复命令行参考如下, 将正确文件类型关联上即可:

reg.exe add HKEY_CLASSES_ROOT\.js /ve /t REG_SZ /f /d JSFile
reg.exe add HKEY_CLASSES_ROOT\.vbs /ve /t REG_SZ /f /d VBSFile
reg.exe add HKEY_CLASSES_ROOT\.vbs /ve /t REG_SZ /f /d WSFFile
reg.exe add HKEY_CLASSES_ROOT\.vbs /ve /t REG_SZ /f /d scriptletfile

这里是正确的注册表信息, 损坏的系统可以参考进行修复:

HKEY_CLASSES_ROOT\.js
    (默认)    REG_SZ    JSFile
HKEY_CLASSES_ROOT\.js\PersistentHandler
    (默认)    REG_SZ    {5e941d80-bf96-11cd-b579-08002b30bfeb}
HKEY_CLASSES_ROOT\.wsc
    (默认)    REG_SZ    scriptletfile
    Content Type    REG_SZ    text/scriptlet
HKEY_CLASSES_ROOT\.wsf
    (默认)    REG_SZ    WSFFile
HKEY_CLASSES_ROOT\.vbs
    (默认)    REG_SZ    VBSFile
HKEY_CLASSES_ROOT\.vbs\PersistentHandler
    (默认)    REG_SZ    {5e941d80-bf96-11cd-b579-08002b30bfeb}


HKEY_CLASSES_ROOT\JSFile
    FriendlyTypeName    REG_EXPAND_SZ    @%SystemRoot%\System32\wshext.dll,-4804
    (默认)    REG_SZ    JavaScript File
HKEY_CLASSES_ROOT\JSFile\DefaultIcon
    (默认)    REG_SZ    C:\Windows\System32\WScript.exe,3
HKEY_CLASSES_ROOT\JSFile\ScriptEngine
    (默认)    REG_SZ    JScript
HKEY_CLASSES_ROOT\JSFile\ScriptHostEncode
    (默认)    REG_SZ    {85131630-480C-11D2-B1F9-00C04F86C324}
HKEY_CLASSES_ROOT\JSFile\Shell
    (默认)    REG_SZ    Open
HKEY_CLASSES_ROOT\JSFile\Shell\Edit\Command
    (默认)    REG_SZ    C:\Windows\System32\Notepad.exe %1
HKEY_CLASSES_ROOT\JSFile\Shell\Open\Command
    (默认)    REG_SZ    C:\Windows\System32\WScript.exe "%1" %*
HKEY_CLASSES_ROOT\JSFile\Shell\Open2
    (默认)    REG_SZ    Open &with Command Prompt
    MUIVerb    REG_SZ    @C:\Windows\System32\wshext.dll,-4511
HKEY_CLASSES_ROOT\JSFile\Shell\Open2\Command
    (默认)    REG_SZ    C:\Windows\System32\CScript.exe "%1" %*
HKEY_CLASSES_ROOT\JSFile\Shell\Print\Command
    (默认)    REG_SZ    C:\Windows\System32\Notepad.exe /p %1
HKEY_CLASSES_ROOT\JSFile\ShellEx\ContextMenuHandlers\OpenGLShExt
    (默认)    REG_SZ    {E97DEC16-A50D-49bb-AE24-CF682282E08D}
HKEY_CLASSES_ROOT\JSFile\ShellEx\DropHandler
    (默认)    REG_SZ    {60254CA5-953B-11CF-8C96-00AA00B8708C}
HKEY_CLASSES_ROOT\JSFile\ShellEx\PropertySheetHandlers\WSHProps
    (默认)    REG_SZ    {60254CA5-953B-11CF-8C96-00AA00B8708C}


HKEY_CLASSES_ROOT\scriptletfile
    (默认)    REG_SZ    Windows Script Component
HKEY_CLASSES_ROOT\scriptletfile\AutoRegister
    (默认)    REG_SZ    C:\Windows\system32\scrobj.dll
HKEY_CLASSES_ROOT\scriptletfile\CLSID
    (默认)    REG_SZ    {06290BD2-48AA-11D2-8432-006008C3FBFC}
HKEY_CLASSES_ROOT\scriptletfile\DefaultIcon
    (默认)    REG_SZ    C:\Windows\system32\scrobj.dll,0
HKEY_CLASSES_ROOT\scriptletfile\ScriptHostEncode
    (默认)    REG_SZ    {06290BD4-48AA-11D2-8432-006008C3FBFC}
HKEY_CLASSES_ROOT\scriptletfile\Shell\Generate Typelib
    (默认)    REG_SZ    &Generate Type Library
HKEY_CLASSES_ROOT\scriptletfile\Shell\Generate Typelib\command
    (默认)    REG_SZ    "C:\Windows\system32\RUNDLL32.EXE" "C:\Windows\system32\scrobj.dll",GenerateTypeLib "%1"
HKEY_CLASSES_ROOT\scriptletfile\Shell\Open
    (默认)    REG_SZ    &Open
HKEY_CLASSES_ROOT\scriptletfile\Shell\Open\command
    (默认)    REG_SZ    "C:\Windows\system32\NOTEPAD.EXE" "%1"
HKEY_CLASSES_ROOT\scriptletfile\Shell\Register
    (默认)    REG_SZ    &Register
HKEY_CLASSES_ROOT\scriptletfile\Shell\Register\command
    (默认)    REG_SZ    "C:\Windows\system32\REGSVR32.EXE" /i:"%1" "C:\Windows\system32\scrobj.dll"
HKEY_CLASSES_ROOT\scriptletfile\Shell\Unregister
    (默认)    REG_SZ    &Unregister
HKEY_CLASSES_ROOT\scriptletfile\Shell\Unregister\command
    (默认)    REG_SZ    "C:\Windows\system32\REGSVR32.EXE" /u /n /i:"%1" "C:\Windows\system32\scrobj.dll"


HKEY_CLASSES_ROOT\WSFFile
    (默认)    REG_EXPAND_SZ    Windows Script File
    FriendlyTypeName    REG_EXPAND_SZ    @%SystemRoot%\System32\wshext.dll,-4801
HKEY_CLASSES_ROOT\WSFFile\DefaultIcon
    (默认)    REG_EXPAND_SZ    %SystemRoot%\System32\WScript.exe,2
HKEY_CLASSES_ROOT\WSFFile\Shell
    (默认)    REG_SZ    Open
HKEY_CLASSES_ROOT\WSFFile\Shell\Edit\Command
    (默认)    REG_EXPAND_SZ    "%SystemRoot%\System32\Notepad.exe" %1
HKEY_CLASSES_ROOT\WSFFile\Shell\Open\Command
    (默认)    REG_EXPAND_SZ    "%SystemRoot%\System32\WScript.exe" "%1" %*
HKEY_CLASSES_ROOT\WSFFile\Shell\Open2
    (默认)    REG_EXPAND_SZ    Open &with Command Prompt
    MUIVerb    REG_EXPAND_SZ    @%SystemRoot%\System32\wshext.dll,-4511
HKEY_CLASSES_ROOT\WSFFile\Shell\Open2\Command
    (默认)    REG_EXPAND_SZ    "%SystemRoot%\System32\CScript.exe" "%1" %*
HKEY_CLASSES_ROOT\WSFFile\Shell\Print\Command
    (默认)    REG_EXPAND_SZ    "%SystemRoot%\System32\Notepad.exe" /p %1
HKEY_CLASSES_ROOT\WSFFile\ShellEx\ContextMenuHandlers\OpenGLShExt
    (默认)    REG_SZ    {E97DEC16-A50D-49bb-AE24-CF682282E08D}
HKEY_CLASSES_ROOT\WSFFile\ShellEx\DropHandler
    (默认)    REG_SZ    {60254CA5-953B-11CF-8C96-00AA00B8708C}
HKEY_CLASSES_ROOT\WSFFile\ShellEx\PropertySheetHandlers\WSHProps
    (默认)    REG_SZ    {60254CA5-953B-11CF-8C96-00AA00B8708C}


HKEY_CLASSES_ROOT\VBSFile
    FriendlyTypeName    REG_EXPAND_SZ    @%SystemRoot%\System32\wshext.dll,-4802
    (默认)    REG_SZ    VBScript Script File
HKEY_CLASSES_ROOT\VBSFile\DefaultIcon
    (默认)    REG_EXPAND_SZ    %SystemRoot%\System32\WScript.exe,2
HKEY_CLASSES_ROOT\VBSFile\ScriptEngine
    (默认)    REG_SZ    VBScript
HKEY_CLASSES_ROOT\VBSFile\ScriptHostEncode
    (默认)    REG_SZ    {85131631-480C-11D2-B1F9-00C04F86C324}
HKEY_CLASSES_ROOT\VBSFile\Shell
    (默认)    REG_SZ    Open
HKEY_CLASSES_ROOT\VBSFile\Shell\Edit\Command
    (默认)    REG_EXPAND_SZ    "%SystemRoot%\System32\Notepad.exe" %1
HKEY_CLASSES_ROOT\VBSFile\Shell\Open\Command
    (默认)    REG_EXPAND_SZ    "%SystemRoot%\System32\WScript.exe" "%1" %*
HKEY_CLASSES_ROOT\VBSFile\Shell\Open2
    (默认)    REG_EXPAND_SZ    Open &with Command Prompt
    MUIVerb    REG_EXPAND_SZ    @%SystemRoot%\System32\wshext.dll,-4511
HKEY_CLASSES_ROOT\VBSFile\Shell\Open2\Command
    (默认)    REG_EXPAND_SZ    "%SystemRoot%\System32\CScript.exe" "%1" %*
HKEY_CLASSES_ROOT\VBSFile\Shell\Print\Command
    (默认)    REG_EXPAND_SZ    "%SystemRoot%\System32\Notepad.exe" /p %1
HKEY_CLASSES_ROOT\VBSFile\ShellEx\ContextMenuHandlers\OpenGLShExt
    (默认)    REG_SZ    {E97DEC16-A50D-49bb-AE24-CF682282E08D}
HKEY_CLASSES_ROOT\VBSFile\ShellEx\DropHandler
    (默认)    REG_SZ    {60254CA5-953B-11CF-8C96-00AA00B8708C}
HKEY_CLASSES_ROOT\VBSFile\ShellEx\PropertySheetHandlers\WSHProps
    (默认)    REG_SZ    {60254CA5-953B-11CF-8C96-00AA00B8708C}

检索可引用类型库的方法

除了以上这种查注册表的方法,尽管这个不太优雅, 但确实是最根本的方法, 以下提到的所有工具都是依据注册表查询实现的.

第一个工具就是 Visual Studio 97 自带的 Visual Basic 5.0, 新建一个项目, 在 Project 工程菜单下找到引用 Reference, 在引用对话框里罗列了当前系统已注册的类型库, 勾选需要的目标, 然后保存工程, 再用记事本打开工程文档 vbp, 里面会相关的类型库信息, 把GUID复制过来就可以使用. 注意, Microsoft Office 12.0 Object Library 和 Microsoft Word 12.0 Object Library 是两个不同的库, 后者才是 Word 类型库, 可以用它来自动化DOC问档处理, 同样 Excel, Access 等也是, VBA 是办公自动化的根基:

Visual Basic project reference
Reference=*\G{EF404E00-EDA6-101A-8DAF-00DD010F7EBB}#5.0#0#..\..\VB5EXT.OLB#Microsoft Visual Basic Extensibility
Reference=*\G{2DF8D04C-5BFA-101B-BDE5-00AA0044DE52}#2.4#0#..\..\..\..\Common Files\Microsoft Shared\OFFICE12\MSO.DLL#Microsoft Office 12.0 Object Library
Reference=*\G{00020905-0000-0000-C000-000000000046}#8.4#0#..\..\..\..\Microsoft Office\Office12\MSWORD.OLB#Microsoft Word 12.0 Object Library
Reference=*\G{00025E01-0000-0000-C000-000000000046}#4.0#0#..\..\..\..\Common Files\Microsoft Shared\DAO\DAO350.DLL#Microsoft DAO 3.5 Object Library

第二个工具是 Visual C++ 5.0 自带的 OLE/COM Object Viewer, 打开 View 视图下的 Expert Mode 专家模式, 可以翻看各种COM组件, 完全理解罗列的内容需要微软的COM基础.

OLE/COM object viewer

第三个是 PrimalScript, 这也是一个专业的通用语言IDE, 看它支持的语言列表就觉得厂家花了大工夫的, 用它写脚本真的可以省力气, 如果安装了SDK, 用它开发也是不错的选择. 集成了FTP这样实用的工具, 也支持源代码管理工具 Microsoft SourceSafe 方便团队合作.

PrimalScript.png

PrimalScript_languages.png

在右侧 Workspace Browser 中弹出右键菜单, 可以建立新工程, 也可以在已有的脚本工程中设置属性, 还可给job添加 object, reference 等:

Select Object
PrimalScript_set_reference.png
Browse and select type library

Visual Basic 和 PrimalScript 都有对象浏览器, 可以用来查询对象的结构.

object browser

使用对象浏览器的一个好处是可以查看类一些详细的信息, 如 Scripting.FileSystemObject 的几个常数定义:

+--CompareMethod
|   +--BinaryCompare=0
|   +--TextCompare=1
|   +--DatabaseCompare=2
+--IOMode
|   +--ForReading=1
|   +--ForWriting=2
|   +--ForAppending=8
+--Tristate
    +--TristateTrue
    +--TristateFalse=0
    +--TristateUseDefault=-2
    +--TristateMixed=-2

这些常数只需要在 wsf 中加入引用即可以访问:

    <object id="fso" progid="Scripting.FileSystemObject" events="true"/>

玩 Visual Studio 97 时发现自带一个 MIDI 合成小玩具 Music Producer:

Music Composer

WSF 的一些小技巧

通过右键设置 wsf 脚本文件的属性,系统会自动生成 wsh 配置文件,内容大概如下:

[ScriptFile]
Path=E:\coding\coding.wsf
[Options]
Timeout=10
DisplayLogo=0

为了交叉调用, 需要安装 MSScriptControl, 下面的例子演示在 JScript 中调用 VBScript 的 InputBox:

// this require MSScriptControl
function(){
    var scriptCtrl = new ActiveXObject("MSScriptControl.ScriptControl");
    scriptCtrl.Language = "VBScript";
    this.prompt = function(msg) {
        return scriptCtrl.Eval(
            'InputBox(unEscape("' + escape(msg) + '"), "Input")'
        );
    };
}

wsf 可以有多个 job 定义, 单个 job 可以不使用 package, 执行脚本时第一个 job 会被执行,可以在命令行执行指定 job 的代码:

CScript //Job:IncludeExample //Job:MoreExample MyScripts.wsf

wsf 元素介绍

<?job?>元素 用于指定错误处理属性的 XML 处理指令。 
<?XML?>元素 表示文件应作为 XML 进行分析。 
<description> 元素 对用户运行 ShowUsage() 或运行带有 /? 命令行时出现的描述。 
<example> 元素 使脚本具有自我说明性。 
<job> 元素 在 Windows 脚本文件 (*.wsf) 中标记作业的开始和结束。 
<named> 元素 对脚本的特定命名参数进行标记。 
<object> 元素,可用在 Windows 脚本组件文件中,并定义可由脚本引用的对象。 
<package> 元素 将多个作业定义放在 Windows 脚本宿主控制文件 (.wsf) 中。 
<reference> 元素 用于包括对外部类型库引用的 XML 元素。 
<resource> 元素 用于隔离不应硬编码到脚本中的文本数据或数值数据的 XML 元素。 
<runtime> 元素 将某个脚本的一系列运行时参数组合起来。 
<script> 元素 包含用于定义 Windows 脚本组件行为的脚本的 XML 元素。

最重要的事, WSF是一个XML文档, 而脚本语言中的逻辑运算符 < 或 > 等会引起XML标记的冲突, 因此一定记得使用 CDATA 将脚本包裹起来, 不然调试器给的信息不沾边, 半天找不出原因, 我试过两会了:-(

<![CDATA[
    // scripting here ...
]]>

完整的wsf例子:

    <?XML version="1.0" standalone="yes" ?>
    <package>
    <job id="ScriptMe" args="arg1 arg2" prompt="no">
        <?job error="false" debug="false" timeout="30" ?>
        <runtime>
            <description>Let us study Windows Script Host           </description>
            <unnamed helpstring="the first one" name="UnamedArg" required="10"/>
            <named helpstring="some help" name="arg1" required="true" type="boolean"/>
            <named helpstring="some help about a2" name="arg2" required="true" type="string"/>
            <named helpstring="some text about it" name="arg3" required="false" type="simple"/>
            <example>Run it as you want to and test it as you like</example>
            <usage>This is a scripting demo         </usage>
        </runtime>
        <reference id="Microsoft Visual Basic 5.0 Extensibility" guid="{EF404E00-EDA6-101A-8DAF-00DD010F7EBB}" version="5.0"/>
        <reference id="Microsoft WinHTTP Services, version 5.1" guid="{662901FC-6951-4854-9EB2-D9A2570F2B2E}" version="5.0"/>
        <reference id="Microsoft Script Control 1.0" guid="{0E59F1D2-1FBE-11D0-8FF2-00A0D10038BC}" version="1.0"/>
        <object id="word" progid="Word.Application" events="true"/>
        <object id="fso" progid="Scripting.FileSystemObject" events="true" reference="true"/>
        <object id="encoder" progid="Scripting.Encoder" events="true" reference="true"/>
        <script language="JScript" src="vbconst.js"/>
        <script id="vba" language="VBScript">
        <![CDATA[
            MsgBox "VB Const vbAbort=" & vbAbort
            word.Quit

            ' define function for jscript
            Function ShowBox(msg)
                showBox = InputBox(msg, "Tip")
            End Function

            Function messageBox (msg, style, title)
                MsgBox msg, style, title
            End Function
        ]]>
        </script>
        <script id="js" language="JScript">
        <![CDATA[
            // demo procedures programming
            function write(msg){
                WScript.echo(msg);
            }
            var global="GLOBAL";
            messageBox ("Test MsgBox in JScript", vbYesNoCancel|vbExclamation, "Tip");
            write("Test FileSystemObject const ForAppending:"+ForAppending);
            write("Test for script include:"+test());
            var ret = showBox("Test VBScript in JScript");
            write("Test VBScript in JScript & showBox return: "+ (!ret? "User Cancel":ret) );
        ]]>
        </script>
        <script id="vba" language="VBScript">
        <![CDATA[
            'call function from script file ...
            write("Test acess variable global="+global)
            write("Test JScript in VBScript:"+WScript.version)
        ]]>
        </script>
        <script id="oop" language="JScript">
        <![CDATA[
            // demo object-oriented programming
            function Base(){
                var p = "I'm yours"
                this.action = function(){
                    write("public function access private member: "+p);
                }
            }
            var a = new Base();
            a.action();
        ]]>
        </script>
    </job>
    <job id="WordInScripting">
        <?job error="false" debug="true" ?>
        <script language="JScript">
        <![CDATA[
            var Word, Doc, Uncorrected, Corrected;
            var wdDialogToolsSpellingAndGrammar = 828;
            var wdDoNotSaveChanges = 0;
            Uncorrected = "Helllo world!";
            Word = new ActiveXObject("Word.Application");
            Doc = Word.Documents.Add();
            Word.Selection.Text = Uncorrected;
            Word.Dialogs(wdDialogToolsSpellingAndGrammar).Show();
            if (Word.Selection.Text.length != 1) 
                Corrected = Word.Selection.Text;
            else
                Corrected = Uncorrected;
            Doc.Close(wdDoNotSaveChanges);
            Word.Quit();
            //write("not calling between jobs");
        ]]>
        </script>
    </job>
    </package>

脚本还可以编写脚本组件, Windows Script Components, WSC文件也是XML格式, 通过各种元素来实现COM规范组件, 简单说就是用脚本编写 COM 组件, JScript 也可以像 Visual Basic 一样写 COM 组件. 这个似乎很厉害呢, 看参考文档去吧, 这里不深入了. 使用 PrimalScript 的 WCF Wizard 可以快速建立一个脚本组件模板. 提示一点, MSDN 内容虽然丰富, 但是没有对应的基础, 很容易看迷糊的, 这时就需要好好想想去补习基础了.

比如说 Visual Studio 97 中 ActiveX SDK 就提供了一个 WebPost API, WebPost 这个货就是用来做 HTTP 文件上传的. 如果弄懂了 HTTP 协议, 上传文件是个轻松活, 可是一头扎进 MSDN 就不容易弄懂了. 因为每个API都是人写的, 每个人思维模式都不一样, 另外整理MSDN文档的人也没有义务把清晰的思路整理出来给你, 他们做的只是技术文档, 这个文档能用来说明什么是什么就够了, 所以参考MSDN也是需要一个方法论来指导的. 而且 WebPost 本身是建立在基础架构之上的, 没有这些架构的基础, 理解起来就是件麻烦. 作为一个有经验的 Web 开发者, 我看到 WebPost 参考文档时也是觉得头大的, 所以我不信你不会:

+--WebPost API Functions
|   +--WpBindToSite
|   +--WpDeleteSite
|   +--WpListSites 
|   +--WpPost
|   +--WpPostFile
+--WebPost API Structures
|   +--WPSITEINFO
+--WebPost SPI Functions
|   WppBindToSite 
|   WpBindToSite
|   WppListSites
+--WebPost SPI Interface Functions
    +--AddWizardPages
    +--Commit
    +--DeleteFile
    +--FindClose
    +--FindFirstFile
    +--FindNextFile
    +--GetError
    +--GetParam
    +--GetSiteInfo
    +--NetWorkConnect
    +--PostFiles
    +--ServerLogin
    +--ServerLogout
    +--SetParam

另外, 我也相信 WebPost 就是一个挖出来的坑, 并不是那么好用的东西, 我用 JScript 脚本可以做的事就不必搞什么 SDK 了, 如果再费时点, 那可以选择 C 语言自己写一个嘛.

参考文档

强烈推荐Microsoft Windows脚本技术, 英文版 script56.chm 内容更新更齐全, 特别是关于正则表达式的内容, 这部分内容很丰富, 值得另取一文讲解. 还有Advanced Windows Script Host Developers Guide 这本书深入讲解了Windows脚本编程技术, 脚本与应用程序的交互, 配合 Visual Basic 开发的 ActiveX 组件来扩展脚本功能. 书中举例 JScript 实现 VBScript 的 InputBox 功能, 通过 Internet Explorer 组件加载页面来模拟, 又可以通过开发 ActiveX 来实现, 使用 wsf 集合 VBScript 和 JScript 来实现, 甚至更直接地使用 WScript.Shell 的 Environment 属性实现通过环境变量来传递数据.

还有一本资料《Advanced Development with Microsoft Windows Script Host 2.0》, 看标题应该是深层次的数, 可惜找不到资源. 此外,SAPIEN 即 PrimalScript 发行公司初版的以本书是《WSH and VBScript Core 》。

微软官方早期的 MSDN 也可以作为参考, 在 Microsoft Visual 97 下载链接中可以找到.

Admin Script Editor Enterpise 3.1
PrimalScript 2014_7.0.32 x86
epp500_0651_smart.exe
《Microsoft Windows Script Host 2.0 Developer's Guide》 scripthost20dev.chm
《Advanced Windows Script Host Developers Guide (2003)》
Microsoft Windows脚本技术
Microsoft Windows Script Technologies script56.chm
Microsoft Visual 97 我喜欢的
Microsoft Visual 97 加密分享: 9km8

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容