[IIS]有了 Log(即 Log Parser)就万事大吉了

转载:http://hi.baidu.com/totaobao/blog/item/c922a3649e3750f1f636542c.html

我们的专栏的“常客”已经发现了这样一个事实:Log Parser 确实是一种非常酷的小实用工具,它解决了一些非常棘手的脚本启动问题。我们仍然不知道分析 SQL Server 日志有什么用,但是我们知道 Log Parser 的最新版本(2.2 版)能做以下事情:

搜索事件日志,快速找到并报告所关注的事件。

搜索文件系统,快速找到并报告所关注的文件和文件夹。

搜索 Active Directory,快速找到并报告所关注的对象。

搜索各种各样的日志文件。例如,Log Parser 能够搜索使用 W3C 扩展日志文件格式的日志。使用此格式的日志包括:个人防火墙日志;Microsoft Internet Security and Acceleration (ISA) Server 日志;Windows Media Services 日志;Exchange 跟踪日志;简单邮件传输协议 (SMTP) 日志文件。

快速而轻松地搜索 Internet 信息服务 (IIS) 日志、Netmon 捕获文件、注册表等多种文件。

这些功能还占不到它的全部功能的一半呢。Log Parser 使用一种真正的 SQL 查询语言,这意味着您可以执行聚合查询。想知道您的事件日志中的各种事件 ID 类型的数量(三十七个事件 100、四十三个事件 101、十四个事件 102)吗?没问题,Log Parser 能够胜任这项工作。当然,它还可以胜任很多很多其他工作。您可以使用一个 SQL 命令(例如“ORDER BY”)来以您需要的任何方式对返回数据进行排序,也可以使用“TOP”来仅返回若干最“如何如何”的记录(例如,您的硬盘上的 10 个最大的文件)。您可以从命令提示符下运行 Log Parser 2.2,也可以将其作为一个可编写脚本的 COM 对象 (MSUtil.LogQuery) 进行调用。我们想看邻家女孩如何做这项工作。

好的,很好。只是,我们是要看她将返回的数据显示为图形。

好吧,让我们暂且忘了那个邻家女孩。(嗨,难道就没有那个女孩做不了的事情吗?)

顺便说一句,我们说 Log Parser 能够快速查找东西,这不是 Microsoft 在夸大其词。(Microsoft 也不喜欢虚夸其产品或任何东西。)例如,以一台符合以下规格的测试计算机为例:

2.39 GHz 处理器

512 MB RAM

仅有 1.5 GB 可用空间的 14 GB 硬盘(和好几万个文件)

假定我们要求 Log Parser 查找存储在硬盘上的所有的 Microsoft PowerPoint® 文件(.ppt 文件)。您猜 Log Parser 将花费多长时间来查获这些文件?一个小时?两个小时?六或七天?只需十四秒。这可不是 PowerPoint 文件中内置的一些秘密 Microsoft 欺骗代码虚报的。尝试搜索 .mp3 文件。四秒。(该计算机上只有少量 .mp3 文件。)尝试搜索 .jpg 文件。十一秒。相当快。

换句话说,这不是盲目夸大:这实在是一个令人难以置信的有用工具(并且是免费的!),它能够处理内置在 Windows 中的脚本技术没有很好地处理的许多问题。(再说,我们脚本专家往往将这些盲目夸大的说法留下来描述我们自己的工具,例如 Scriptomatic 2.0。)

最重要的是,Log Parser 的功能非常多。您想将其用作命令行工具,就可以将其用作命令行工具。如果您喜欢通过脚本使用 Log Parser,那也很好;Log Parser 的所有功能都通过一个自动化对象公开。因为这是一个脚本专栏(是的,有时候连我们自己都很难相信这一点),所以我们打算重点介绍通过脚本使用 Log Parser。但是,如果您喜欢将其用作一个命令行工具,则可以参考 Log Parser 提供的一个非常详尽的帮助文件,其中介绍了如何进行操作。(嗨,我们脚本专家并不能把所有的事情都包下来。)

我们建议您下载 Log Parser 2.2 并试用一下(我们还要对 Log Parser 的创建者 Gabriele Giuseppini 说声对不起,因为这么长时间以来我们一直忽略了这个工具)。为帮助您在获取该下载后快速入门,我们认为我们应当花一点时间来介绍如何编写一些简单而非常有 用的 Log Parser 脚本。同时为了使这些任务尽可能地具有通用性,我们打算将重点放在文件系统(每个系统管理员都必须处理的某种东西)上。

使 Log Parser 2.2 成为可编写脚本的程序

您必须首先注册 Log Parser DLL,然后才能开始编写 Log Parser 脚本。在您下载并解压缩 Log Parser 实用工具后,在 Log Parser 所在的文件夹中打开一个命令窗口,然后从命令提示符下键入以下内容:

regsvr32 LogParser.dll

这就是您要做的全部工作;现在您已做好了准备,可以编写 Log Parser 脚本了。

好吧,去打电话告诉您妈妈这个好消息;我们在这里等着。

您的第一个 Log Parser 脚本

Log Parser 脚本是什么样子呢?对了,今天一定是您的幸运日,因为我们正巧有一个 Log Parser 可以展示给您。下面是一个 Log Parser 脚本,它返回在文件夹 C:\Scripts 中找到的所有文件的名称和大小(纯粹是为了演示,它按照文件名的字母顺序对返回的数据进行排序):

Set objLogParser = CreateObject("MSUtil.LogQuery")
Set objInputFormat = _
      CreateObject("MSUtil.LogQuery.FileSystemInputFormat")
objInputFormat.recurse = 0
Set objOutputFormat = _
      CreateObject("MSUtil.LogQuery.NativeOutputFormat")
objOutputFormat.rtp = -1
strQuery = "Select Name, Size FROM 'C:\Scripts\*.*' orDER BY Name ASC"
objLogParser.ExecuteBatch strQuery, objInputFormat, objOutputFormat

别担心,我们将向您解释各行代码是如何工作的。虽然此刻它看起来可能有点神秘,但相信我们:在本专栏结束时,您将能够编写出成千上万个这样的脚本。如果这不能打动那个邻家女孩,那么她就根本不值得您留恋。

大多数情况下,Log Parser 脚本包括五个步骤。Log Parser 脚本必须:

创建 Log Parser 对象的一个实例

创建并配置 InputFormat 对象

创建并配置 OutputFormat 对象

创建一个 Log Parser 查询

执行查询并返回数据

让我们再稍微详细一些看看各个步骤吧。

步骤 1:创建 Log Parser 对象的一个实例

是的,我们知道,“创建……的一个实例”听起来总是很难。实际上并不是那么回事,这里的实例只是一行代码:

Set objLogParser = CreateObject("MSUtil.LogQuery")

就这么简单;运行该行代码,您就会获得一个可帮助您实现各种愿望的 Log Parser 实例。

或许不是“这种”愿望,而是许多别的愿望。

步骤 2:创建并配置“InputFormat”对象

正如上文所提到的,Log Parser 可以从各种位置获取数据:事件日志、注册表、日志文件、Netmon 捕获文件等等。InputFormat 对象只是指定您要从何处获取数据。要从文件系统获取数据,您需要使用以下代码:

Set objInputFormat = _
      CreateObject("MSUtil.LogQuery.FileSystemInputFormat")

这将创建一个 InputFormat 对象,在此例中是一个用来处理文件系统的对象。您需要做的全部工作就是创建该对象;一旦 Log Parser 获得了该对象的一个副本,它就知道如何处理它了。在此示例中,Log Parser 现在已知道如何访问文件系统。

顺便说一下,对于文件系统的处理,InputFormat 有三个可选属性供您配置:

属性 说明

recurse

最大的子目录递归级别。0 禁用子目录递归;-1 启用无限制递归。

preserveLastAccTime

保留被访问文件的上次访问时间。枚举文件和目录会导致它们的上次访问时间被更新。如果将此参数设置为“ON”,则将使文件系统输入格式恢复被访问文件的上次访问时间。

useLocalTime

为时间戳字段使用本地时间。设置为“ON”时,“CreationTime”、“LastAccessTime”和“LastWriteTime”字段的值是以本地时间表示的。设置为“OFF”时,这些字段的值是以通用协调时间 (UTC) 表示的。

默认情况下,Log Parser 从起始点开始递归所有的子文件夹;如果您在 C:\ 中开始您的搜索,则 Log Parser 将搜索驱动器 C 上的每个文件夹。但是如果您确实想只搜索根目录 (C:\),那该怎么办呢?在这种情况下,只需要将“recurse”属性设置为 0 即可:

Set objInputFormat = _
      CreateObject("MSUtil.LogQuery.FileSystemInputFormat")
objInputFormat.recurse = 0

但是如果您只想搜索两层深,那又该怎么办呢?请按以下方法进行:

Set objInputFormat = _
      CreateObject("MSUtil.LogQuery.FileSystemInputFormat")
objInputFormat.recurse = 2

要保证您能够对每个子文件进行递归搜索,只需将 recurse 属性设置为 -1 即可。

步骤 3:创建并配置“OutputFormat”对象

如果 InputFormat 对象表示输入格式,那么 OutputFormat 对象一定是表示 — 您猜对了,OutputFormat 对象表示由 Log Parser 返回的数据的输出格式。Log Parser 有很多用于输出数据的选项吗?当然啦,请看以下内容:

输出格式 说明

图表

创建包含输出记录字段值的图表的图像文件。

CSV

将输出记录写成逗号分隔值文本。

Datagrid

将输出记录显示在图形用户界面中。

IIS

将输出记录写成 Microsoft IIS 日志文件格式。

NAT

将输出记录写成可读的表列格式。

SQL

将输出记录上载到 SQL 数据库中的表中。

SysLog

将消息发送到 Syslog 服务器;创建包含 Syslog 消息的文本文件;将 Syslog 消息发送到用户。

TPL

根据用户定义的模板的格式写输出记录。

TSV

将输出记录写成制表符分隔值或空格分隔值文本。

W3C

将输出记录写成 W3C 扩展日志文件格式。

XML

将输出记录写成 XML 文档节点。

现在,我们打算重点介绍 NAT(本机)输出,它在命令窗口中将您的数据显示为良好的表格格式。如何将 OutputFormat 对象设置为 NAT?只需执行以下操作即可:

Set objOutputFormat = _
      CreateObject("MSUtil.LogQuery.NativeOutputFormat")

对吧,我们说过这很容易。当然,Log Parser 还提供了许多用于配置该输出的选项。下面是 NAT 可用的一些更有趣的选项:

选项 说明

rtp

当写入到 STDOUT 时,NAT 输出格式批量显示输出记录,批的行数等于为此参数指定的值。显示一批行后,NAT 输出格式将提示用户按任意键显示下一批行。

如果为此参数指定 -1,则将禁用批。

headers

此参数启用或禁用显示在每批输出行前的列标头。

ralign

将此参数设置为“ON”时,NAT 输出格式将值靠各列的右侧对齐。

将此参数设置为“OFF”时,值将靠各列的左侧对齐。

要使用这些选项中的某一个,只需要将该选项和所需的值指定为 OutputFormat 对象的一个属性即可。例如,要右对齐您的输出,请使用以下代码:

Set objOutputFormat = _
      CreateObject("MSUtil.LogQuery.NativeOutputFormat")
objOutputFormat.ralign = "ON"

步骤 4:创建 Log Parser 查询

下面就要开始进行查询了。让我们以一个非常简单的查询开始,该查询列出文件夹 C:\Scripts 中的所有文件(及其大小),然后按字母顺序对它们进行排序。

strQuery = "Select Name, Size FROM 'C:\Scripts\*.*' orDER BY Name ASC"

只要您对 SQL 稍有了解,上面的查询就丝毫难不住您。正如在大多数 SQL 查询中一样,我们从请求特定的属性开始;在该例中是 Name 和 Size 属性。(如果我们要得到所有的属性,我们应当使用 Select * FROM。)然后我们需要告诉 Log Parser 到哪里去查找此信息。在典型的 SQL 查询中,您应当传递数据库表的名称。因为我们现在要处理文件系统,所以我们传递我们将从其开始进行搜索的文件夹的名称。因此,我们使用 ‘C:\Scripts\*.*’。

顺便说一下,如果您想获取 C:\Scripts 中的所有文件的列表,请确保您在代码中包括了 *.* 通配符;如果没有,您将收到一条错误信息,指出 Log Parser“Cannot find any file or folder matching ‘C:\Scripts’”(找不到与“C:\Scripts”匹配的任何文件或文件夹)。如果您只关心某些文件,比方说,.vbs 文件,那么该怎么办呢?请使用与下面的内容类似的查询:

strQuery = "Select Name, Size FROM 'C:\Scripts\*.vbs' " & _
      "ORDER BY Name ASC"

如果您需要搜索驱动器 C 上的一个特定文件,则只需添加一个 Where 从句即可:

strQuery = "Select Name, Size FROM 'C:\*.*' Where " & _
      "NAME = 'inventory.vbs' orDER BY Name ASC"

好了,有些人可能有点迷惑了:我们怎么知道我们可以在查询中包括 Name 和 Size 属性?那非常容易,只是我刚才没有告诉您。我们查看了 Log Parser 帮助文件并检查了可用于文件系统对象的字段。那些字段显示在下表中。

字段 说明

Path

文件或目录的完整路径。

Name

文件或目录的名称。

Size

文件的大小(以字节为单位)。

Attributes

文件或目录的属性。文件属性包括:

•      Archive
•      Compressed
•      Directory
•      Encrypted
•      Hidden
•      Offline
•      ReadOnly
•      System
•      Temporary

CreationTime

文件或目录的创建日期和时间(本地或 UTC 时间,取决于 useLocalTime 参数的值)。CreationTime 看起来与以下内容类似:6/10/2004 1:29:50 PM。

LastAccessTime

文件或目录的上次访问日期和时间(本地或 UTC 时间,取决于 useLocalTime 参数的值)。LastAccessTime 看起来与以下内容类似:6/10/2004 1:29:50 PM。

LastWriteTime

文件或目录的上次修改日期和时间(本地或 UTC 时间,取决于 useLocalTime 参数的值)。LastWriteTime 看起来与以下内容类似:6/10/2004 1:29:50 PM。

FileVersion

文件的版本。通常只有可执行文件和 .dll 文件有此字段。

ProductVersion

文件随其发布的产品的版本。

InternalName

文件的内部名称。

ProductName

与文件一起分发的产品的名称。

CompanyName

创作文件的供应商公司的名称。

LegalCopyright

适用于文件的版权声明。

LegalTrademarks

适用于文件的商标和注册商标。

PrivateBuild

文件的专用版本信息。

SpecialBuild

特殊的文件版本注释。

Comments

与该文件有关的注释。

FileDescription

文件的说明。

OriginalFilename

文件的原始名称。

步骤 5:执行查询并返回数据

此时,您所要做的就是执行查询并在 Log Parser 开始返回数据时进行监视。这只需要一行代码:

objLogParser.ExecuteBatch strQuery, objInputFormat, objOutputFormat

正如您所看到的,您调用了 ExecuteBatch 方法并向其传递了三个参数:持有您的 SQL 查询的变量 (strQuery);对您的 InputFormat 对象的对象引用 (objInputFormat);对您的输出格式的对象引用 (objOutputFormat)。那接下来将会发生什么呢?您应当得到类似以下内容的输出:

注意:此脚本的运行时间为一秒。

总的说来,一切都非常好。还有,您可能注意到我们的列表中的前两个条目是点 (.) 和双点 (..)。这是怎么回事呢?如果您记得 MS-DOS 过去的好时光,那么您就会知道单个点是当前目录的简写符号,而两个点是父目录的简写。很好,但是这两者都不是实际的文件,因此我们可能不希望它们打乱我们 的输出。(顺便说一下,这不是这两种简写方法的过错;如果此文件夹有任何子文件夹,则那些子文件夹的名称同样也将显示在我们的输出中。)

那么,我们是否能够从我们的输出中去除这些文件夹和子文件夹而只显示文件名呢?当然可以。我们知道,文件夹有一个属性,即 Directory,文件系统可以从该属性区分文件和文件夹。我们所需要做的全部工作就是更改我们的查询以清除具有 Directory 属性的任何东西。换句话说,我们使用以下查询:

strQuery = "Select Name, Size FROM 'C:\Scripts\*.*' Where NOT " & _
      "Attributes LIKE '%D%' orDER BY Name"

虽然有一点笨拙(请注意,这是 SQL),但是“Where NOT Attributes”只是表示“没有启用该属性的地方”。换句话说,如果启用了 Directory 属性,则这个东东就是一个文件夹,我们不需要它。

顺便说一句,我们使用 LIKE 运算符来测试所讨论的属性是否以字母 D 开头。如果以字母 D 开头,则这是一个目录。要查找隐藏文件,我们可以使用 LIKE ‘%H%’;要检查加密文件,我们可以使用 LIKE ‘%E%’;依此类推。

注意:这未必是检查文件和文件夹属性的通用方式,但这是 Log Parser 检查属性的方式。同样,LIKE 运算符的使用与典型 SQL 查询中的 LIKE 使用方式也稍有不同。在标准 SQL 中,LIKE ‘%E%’ 返回属性名中的某处为字母 e 的所有属性。但是在 Log Parser 中,这只返回加密的文件;也就是说,只返回其属性以字母 e开头 的文件。

我们修改后的脚本类似于以下内容:

Set objLogParser = CreateObject("MSUtil.LogQuery")
Set objInputFormat = CreateObject("MSUtil.LogQuery.FileSystemInputFormat")
objInputFormat.Recurse = 0
Set objOutputFormat = CreateObject("MSUtil.LogQuery.NativeOutputFormat")
objOutputFormat.rtp = -1
strQuery = "Select Name, Size FROM 'C:\Scripts\*.*' Where NOT " & _
      "Attributes LIKE '%D%' orDER BY Name"
objLogParser.ExecuteBatch strQuery, objInputFormat, objOutputFormat

下面是新的输出:

这就好多了,但仍然不够完美。例如,让我们看看文件的排序方式:

Copy of kix_wmi.kix
New Text Document.txt
New_user.sxls
Process.ppt
Script1.vbs
bookmarkdoc.doc
computers.txt

怎么样?如果这是按字母顺序排序的,那么 bookmarkdoc.doc 和 computers.txt 跑到 Script1.vbs 的后面来做什么呢?

实际上,Log Parser 是按照在文件名中找到的字符的 ASCII 值排序的。在 ASCII 系统中,所有的大写字母都排在小写字母之前。因此,名为 ZZZZZZZZ.doc 的文件排在名为 aaaaaaaa.doc 的文件之前。好惨哪,但这是事实。

那么,是不是仅仅为了在 Log Parser 中让一切都按字母顺序排序,我们就必须检查和重命名我们所有的文件?是的,很糟糕。开心点儿!

不,我们只是想吓唬吓唬您;这很容易纠正了。实际上,Log Parser 有许多函数,可供您用来在检索数据时对数据进行操纵。例如,假如我们什么都不使用,只是将文件名中的字母变为小写,那么我们的文件集合将会是按照字母顺序排序的。那么,为什么不让 Log Parser 将所有字母都视为小写呢?请看下面这个查询:

strQuery = "Select TO_LOWERCASE (Name) AS NewName, Size FROM " & _
      "'C:\Scripts\*.*' Where NOT Attributes LIKE '%D%' orDER BY NewName"

此处我们所做的就是使用“TO_LOWERCASE”函数将所有的文件名都转换为小写。(注意,这种转换仅供 Log Parser 使用,实际的文件名不会更改。)该语法看起来也有点古怪(除非您已做了某些 SQL 编码),但我们所说的基本上是:不选择 Name 属性,而使用 TO_LOWERCASE 函数将 Name 属性转换为全小写字母。因为这实质上是创建了一个新属性(不再是 Name,现在它是 converted-to-lowercase-Name),我们使用 AS 运算符为此属性分配一个新名称。(我们作了一个聪明的选择,使用了 NewName,不过您可以使用您想使用的任何名称。)最终结果是,Log Parser 将获取所有的文件名,将它们转换为小写,然后再进行排序。

下面是改进后的新脚本:

Set objLogParser = CreateObject("MSUtil.LogQuery")
Set objInputFormat = CreateObject("MSUtil.LogQuery.FileSystemInputFormat")
objInputFormat.Recurse = 0
Set objOutputFormat = CreateObject("MSUtil.LogQuery.NativeOutputFormat")
objOutputFormat.rtp = -1
strQuery = "Select TO_LOWERCASE (Name) AS NewName, Size FROM " & _
      "'C:\Scripts\*.*' Where NOT Attributes LIKE '%D%' orDER BY NewName"
objLogParser.ExecuteBatch strQuery, objInputFormat, objOutputFormat

下面是我们的输出:

您说得对,这也只不过是如此而已。但是,按照字母顺序对文件进行排序而不区分大小写,已足以使任何人感到惊奇。

检索文件集合

ExecuteBatch 方法执行一个脚本然后在命令窗口中将输出显示/保存为 HTML 文件、SQL 数据库或某种其他形式。如果您只是想查看或保存输出,那么这就非常棒了。但是,如果您有别的想法该怎么办?比方说,如果您想查找您的计算机上最旧的 10 个文件以便对它们进行存档,那该怎么办?如果您想查找大于 5 MB 的所有文件以便对它们进行压缩,又该怎么办?在这些情况下,您实际上不想将返回数据保存为文本文件或显示为图表,而是希望将数据保留在内存中以便能够对其 进行某些实际处理。

我们不妨问一问:“使用 Log Parser 能完成此工作吗?”此刻,您心中已有了答案。那么让我们来看一个 Log Parser 脚本,该脚本不是直接显示信息而是获取数据并将其保留在内存中的一个记录集中:

Set objLogParser = CreateObject("MSUtil.LogQuery")
Set objInputFormat = _
      CreateObject("MSUtil.LogQuery.FileSystemInputFormat")
objInputFormat.recurse = 0
strQuery = "Select TO_LOWERCASE (Name) AS NewName, Size FROM " & _
      "'C:\Scripts\*.*' Where NOT Attributes LIKE '%D%' orDER BY NewName"
Set objRecordSet = objLogParser.Execute(strQuery, objInputFormat)
Do While Not objRecordSet.AtEnd
     Set objRecord = objRecordSet.GetRecord
     Wscript.Echo "Name: " & objRecord.GetValue("NewName")
     Wscript.Echo "File size: " & objRecord.GetValue("Size")
     objRecordSet.MoveNext
Loop

您看,这个脚本不赖吧。此脚本的前半部分中实际上只有两处与直接将数据显示到命令窗口的脚本不同。首先,您可能已注意到我们没有创建 OutputFormat 对象。这是因为我们不需要使用 OutputFormat 对象;我们不输出数据,而是要将数据保留在内存中。

其次,我们没有使用 ExecuteBatch 方法,而是调用了 Execute 方法并传递了两个参数:我们使用的查询 (strQuery) 和 InputFormat 对象 (objInputFormat)。该操作是由下面这行代码完成的:

Set objRecordSet = objLogParser.Execute(strQuery, objInputFormat)

在该行代码执行后,我们将获得一个在 C:\Scripts 中找到的所有文件的列表。不同之处在于:那些文件不会回显到命令窗口。这些数据将保留在内存中一个名为 objRecordSet 的记录集中。如果我们想访问该数据,我们需要做的就是遍历该记录集。这就是我们在脚本的后半部分所做的工作:

Do While Not objRecordSet.AtEnd
     Set objRecord = objRecordSet.GetRecord
     Wscript.Echo "Name: " & objRecord.GetValue("NewName")
     Wscript.Echo "File size: " & objRecord.GetValue("Size")
     objRecordSet.MoveNext
     Loop

此处,我们创建了一个 Do While 循环,它一直循环到我们到达记录集 (objRecordSet.AtEnd) 的末尾才停止。在该循环内部,我们使用 GetRecord 方法来获取记录集中的第一条记录。我们使用 GetValue 方法来获取并回显 NewName 字段的值,然后对 Size 字段执行同样的操作。就是这么回事。我们使用 MoveNext 方法移动到下一条记录,然后重复该过程。当我们完成所有操作后,我们会得到类似以下内容的输出:

很酷吧,是不是?现在,毫无疑问,仅仅回显值并不是记录集功能最激动人心的用法,但是它向您说明了您可以如何获取数据然后按照您的想法对其进行处理。

您可以看出,使用 Log Parser 来检索有关文件和文件夹的信息非常方便。但是要记住,Log Parser 并非仅仅是一个奇特的 dir 命令;它还能够从事件日志、Active Directory 和各种各样的日志文件等信息源获取类似的信息。说到 Log Parser 的功能,我们其实连它的皮毛还没有看完呢!大概在下个月,我们将看一下能否将一些示例 Log Parser 脚本(和查询)发布到脚本中心。同时,Log Parser 下载也包括了许多示例,它们应当可以帮助您编写自己的脚本。

现在,对不起,我们想去看看隔壁邻家究竟是谁。很难说,对吗?

我们的专栏的“常客”已经发现了这样一个事实:Log Parser 确实是一种非常酷的小实用工具,它解决了一些非常棘手的脚本启动问题。我们仍然不知道分析 SQL Server 日志有什么用,但是我们知道 Log Parser 的最新版本(2.2 版)能做以下事情:

这些功能还占不到它的全部功能的一半呢。Log Parser 使用一种真正的 SQL 查询语言,这意味着您可以执行聚合查询。想知道您的事件日志中的各种事件 ID 类型的数量(三十七个事件 100、四十三个事件 101、十四个事件 102)吗?没问题,Log Parser 能够胜任这项工作。当然,它还可以胜任很多很多其他工作。您可以使用一个 SQL 命令(例如“ORDER BY”)来以您需要的任何方式对返回数据进行排序,也可以使用“TOP”来仅返回若干最“如何如何”的记录(例如,您的硬盘上的 10 个最大的文件)。您可以从命令提示符下运行 Log Parser 2.2,也可以将其作为一个可编写脚本的 COM 对象 (MSUtil.LogQuery) 进行调用。我们想看邻家女孩如何做这项工作。

好的,很好。只是,我们是要看她将返回的数据显示为图形。

好吧,让我们暂且忘了那个邻家女孩。(嗨,难道就没有那个女孩做不了的事情吗?)

顺便说一句,我们说 Log Parser 能够快速查找东西,这不是 Microsoft 在夸大其词。(Microsoft 也不喜欢虚夸其产品或任何东西。)例如,以一台符合以下规格的测试计算机为例:

2.39 GHz 处理器

512 MB RAM

仅有 1.5 GB 可用空间的 14 GB 硬盘(和好几万个文件)

假定我们要求 Log Parser 查找存储在硬盘上的所有的 Microsoft PowerPoint® 文件(.ppt 文件)。您猜 Log Parser 将花费多长时间来查获这些文件?一个小时?两个小时?六或七天?只需十四秒。这可不是 PowerPoint 文件中内置的一些秘密 Microsoft 欺骗代码虚报的。尝试搜索 .mp3 文件。四秒。(该计算机上只有少量 .mp3 文件。)尝试搜索 .jpg 文件。十一秒。相当快。

换句话说,这不是盲目夸大:这实在是一个令人难以置信的有用工具(并且是免费的!),它能够处理内置在 Windows 中的脚本技术没有很好地处理的许多问题。(再说,我们脚本专家往往将这些盲目夸大的说法留下来描述我们自己的工具,例如 Scriptomatic 2.0。)

最重要的是,Log Parser 的功能非常多。您想将其用作命令行工具,就可以将其用作命令行工具。如果您喜欢通过脚本使用 Log Parser,那也很好;Log Parser 的所有功能都通过一个自动化对象公开。因为这是一个脚本专栏(是的,有时候连我们自己都很难相信这一点),所以我们打算重点介绍通过脚本使用 Log Parser。但是,如果您喜欢将其用作一个命令行工具,则可以参考 Log Parser 提供的一个非常详尽的帮助文件,其中介绍了如何进行操作。(嗨,我们脚本专家并不能把所有的事情都包下来。)

我们建议您下载 Log Parser 2.2 并试用一下(我们还要对 Log Parser 的创建者 Gabriele Giuseppini 说声对不起,因为这么长时间以来我们一直忽略了这个工具)。为帮助您在获取该下载后快速入门,我们认为我们应当花一点时间来介绍如何编写一些简单而非常有 用的 Log Parser 脚本。同时为了使这些任务尽可能地具有通用性,我们打算将重点放在文件系统(每个系统管理员都必须处理的某种东西)上。

使 Log Parser 2.2 成为可编写脚本的程序

您必须首先注册 Log Parser DLL,然后才能开始编写 Log Parser 脚本。在您下载并解压缩 Log Parser 实用工具后,在 Log Parser 所在的文件夹中打开一个命令窗口,然后从命令提示符下键入以下内容:

regsvr32 LogParser.dll

这就是您要做的全部工作;现在您已做好了准备,可以编写 Log Parser 脚本了。

好吧,去打电话告诉您妈妈这个好消息;我们在这里等着。

您的第一个 Log Parser 脚本

Log Parser 脚本是什么样子呢?对了,今天一定是您的幸运日,因为我们正巧有一个 Log Parser 可以展示给您。下面是一个 Log Parser 脚本,它返回在文件夹 C:\Scripts 中找到的所有文件的名称和大小(纯粹是为了演示,它按照文件名的字母顺序对返回的数据进行排序):

Set objLogParser = CreateObject("MSUtil.LogQuery")
Set objInputFormat = _
      CreateObject("MSUtil.LogQuery.FileSystemInputFormat")
objInputFormat.recurse = 0
Set objOutputFormat = _
      CreateObject("MSUtil.LogQuery.NativeOutputFormat")
objOutputFormat.rtp = -1
strQuery = "Select Name, Size FROM 'C:\Scripts\*.*' orDER BY Name ASC"
objLogParser.ExecuteBatch strQuery, objInputFormat, objOutputFormat

别担心,我们将向您解释各行代码是如何工作的。虽然此刻它看起来可能有点神秘,但相信我们:在本专栏结束时,您将能够编写出成千上万个这样的脚本。如果这不能打动那个邻家女孩,那么她就根本不值得您留恋。

大多数情况下,Log Parser 脚本包括五个步骤。Log Parser 脚本必须:

创建 Log Parser 对象的一个实例

创建并配置 InputFormat 对象

创建并配置 OutputFormat 对象

创建一个 Log Parser 查询

执行查询并返回数据

让我们再稍微详细一些看看各个步骤吧。

步骤 1:创建 Log Parser 对象的一个实例

是的,我们知道,“创建……的一个实例”听起来总是很难。实际上并不是那么回事,这里的实例只是一行代码:

Set objLogParser = CreateObject("MSUtil.LogQuery")

就这么简单;运行该行代码,您就会获得一个可帮助您实现各种愿望的 Log Parser 实例。

或许不是“这种”愿望,而是许多别的愿望。

步骤 2:创建并配置“InputFormat”对象

正如上文所提到的,Log Parser 可以从各种位置获取数据:事件日志、注册表、日志文件、Netmon 捕获文件等等。InputFormat 对象只是指定您要从何处获取数据。要从文件系统获取数据,您需要使用以下代码:

Set objInputFormat = _
      CreateObject("MSUtil.LogQuery.FileSystemInputFormat")

这将创建一个 InputFormat 对象,在此例中是一个用来处理文件系统的对象。您需要做的全部工作就是创建该对象;一旦 Log Parser 获得了该对象的一个副本,它就知道如何处理它了。在此示例中,Log Parser 现在已知道如何访问文件系统。

顺便说一下,对于文件系统的处理,InputFormat 有三个可选属性供您配置:

属性 说明

recurse

最大的子目录递归级别。0 禁用子目录递归;-1 启用无限制递归。

preserveLastAccTime

保留被访问文件的上次访问时间。枚举文件和目录会导致它们的上次访问时间被更新。如果将此参数设置为“ON”,则将使文件系统输入格式恢复被访问文件的上次访问时间。

useLocalTime

为时间戳字段使用本地时间。设置为“ON”时,“CreationTime”、“LastAccessTime”和“LastWriteTime”字段的值是以本地时间表示的。设置为“OFF”时,这些字段的值是以通用协调时间 (UTC) 表示的。

默认情况下,Log Parser 从起始点开始递归所有的子文件夹;如果您在 C:\ 中开始您的搜索,则 Log Parser 将搜索驱动器 C 上的每个文件夹。但是如果您确实想只搜索根目录 (C:\),那该怎么办呢?在这种情况下,只需要将“recurse”属性设置为 0 即可:

Set objInputFormat = _
      CreateObject("MSUtil.LogQuery.FileSystemInputFormat")
objInputFormat.recurse = 0

但是如果您只想搜索两层深,那又该怎么办呢?请按以下方法进行:

Set objInputFormat = _
      CreateObject("MSUtil.LogQuery.FileSystemInputFormat")
objInputFormat.recurse = 2

要保证您能够对每个子文件进行递归搜索,只需将 recurse 属性设置为 -1 即可。

步骤 3:创建并配置“OutputFormat”对象

如果 InputFormat 对象表示输入格式,那么 OutputFormat 对象一定是表示 — 您猜对了,OutputFormat 对象表示由 Log Parser 返回的数据的输出格式。Log Parser 有很多用于输出数据的选项吗?当然啦,请看以下内容:

输出格式 说明

图表

创建包含输出记录字段值的图表的图像文件。

CSV

将输出记录写成逗号分隔值文本。

Datagrid

将输出记录显示在图形用户界面中。

IIS

将输出记录写成 Microsoft IIS 日志文件格式。

NAT

将输出记录写成可读的表列格式。

SQL

将输出记录上载到 SQL 数据库中的表中。

SysLog

将消息发送到 Syslog 服务器;创建包含 Syslog 消息的文本文件;将 Syslog 消息发送到用户。

TPL

根据用户定义的模板的格式写输出记录。

TSV

将输出记录写成制表符分隔值或空格分隔值文本。

W3C

将输出记录写成 W3C 扩展日志文件格式。

XML

将输出记录写成 XML 文档节点。

现在,我们打算重点介绍 NAT(本机)输出,它在命令窗口中将您的数据显示为良好的表格格式。如何将 OutputFormat 对象设置为 NAT?只需执行以下操作即可:

Set objOutputFormat = _
      CreateObject("MSUtil.LogQuery.NativeOutputFormat")

对吧,我们说过这很容易。当然,Log Parser 还提供了许多用于配置该输出的选项。下面是 NAT 可用的一些更有趣的选项:

选项 说明

rtp

当写入到 STDOUT 时,NAT 输出格式批量显示输出记录,批的行数等于为此参数指定的值。显示一批行后,NAT 输出格式将提示用户按任意键显示下一批行。

如果为此参数指定 -1,则将禁用批。

headers

此参数启用或禁用显示在每批输出行前的列标头。

ralign

将此参数设置为“ON”时,NAT 输出格式将值靠各列的右侧对齐。

将此参数设置为“OFF”时,值将靠各列的左侧对齐。

要使用这些选项中的某一个,只需要将该选项和所需的值指定为 OutputFormat 对象的一个属性即可。例如,要右对齐您的输出,请使用以下代码:

Set objOutputFormat = _
      CreateObject("MSUtil.LogQuery.NativeOutputFormat")
objOutputFormat.ralign = "ON"

步骤 4:创建 Log Parser 查询

下面就要开始进行查询了。让我们以一个非常简单的查询开始,该查询列出文件夹 C:\Scripts 中的所有文件(及其大小),然后按字母顺序对它们进行排序。

strQuery = "Select Name, Size FROM 'C:\Scripts\*.*' orDER BY Name ASC"

只要您对 SQL 稍有了解,上面的查询就丝毫难不住您。正如在大多数 SQL 查询中一样,我们从请求特定的属性开始;在该例中是 Name 和 Size 属性。(如果我们要得到所有的属性,我们应当使用 Select * FROM。)然后我们需要告诉 Log Parser 到哪里去查找此信息。在典型的 SQL 查询中,您应当传递数据库表的名称。因为我们现在要处理文件系统,所以我们传递我们将从其开始进行搜索的文件夹的名称。因此,我们使用 ‘C:\Scripts\*.*’。

顺便说一下,如果您想获取 C:\Scripts 中的所有文件的列表,请确保您在代码中包括了 *.* 通配符;如果没有,您将收到一条错误信息,指出 Log Parser“Cannot find any file or folder matching ‘C:\Scripts’”(找不到与“C:\Scripts”匹配的任何文件或文件夹)。如果您只关心某些文件,比方说,.vbs 文件,那么该怎么办呢?请使用与下面的内容类似的查询:

strQuery = "Select Name, Size FROM 'C:\Scripts\*.vbs' " & _
      "ORDER BY Name ASC"

如果您需要搜索驱动器 C 上的一个特定文件,则只需添加一个 Where 从句即可:

strQuery = "Select Name, Size FROM 'C:\*.*' Where " & _
      "NAME = 'inventory.vbs' orDER BY Name ASC"

好了,有些人可能有点迷惑了:我们怎么知道我们可以在查询中包括 Name 和 Size 属性?那非常容易,只是我刚才没有告诉您。我们查看了 Log Parser 帮助文件并检查了可用于文件系统对象的字段。那些字段显示在下表中。

字段 说明

Path

文件或目录的完整路径。

Name

文件或目录的名称。

Size

文件的大小(以字节为单位)。

Attributes

文件或目录的属性。文件属性包括:

•      Archive
•      Compressed
•      Directory
•      Encrypted
•      Hidden
•      Offline
•      ReadOnly
•      System
•      Temporary

CreationTime

文件或目录的创建日期和时间(本地或 UTC 时间,取决于 useLocalTime 参数的值)。CreationTime 看起来与以下内容类似:6/10/2004 1:29:50 PM。

LastAccessTime

文件或目录的上次访问日期和时间(本地或 UTC 时间,取决于 useLocalTime 参数的值)。LastAccessTime 看起来与以下内容类似:6/10/2004 1:29:50 PM。

LastWriteTime

文件或目录的上次修改日期和时间(本地或 UTC 时间,取决于 useLocalTime 参数的值)。LastWriteTime 看起来与以下内容类似:6/10/2004 1:29:50 PM。

FileVersion

文件的版本。通常只有可执行文件和 .dll 文件有此字段。

ProductVersion

文件随其发布的产品的版本。

InternalName

文件的内部名称。

ProductName

与文件一起分发的产品的名称。

CompanyName

创作文件的供应商公司的名称。

LegalCopyright

适用于文件的版权声明。

LegalTrademarks

适用于文件的商标和注册商标。

PrivateBuild

文件的专用版本信息。

SpecialBuild

特殊的文件版本注释。

Comments

与该文件有关的注释。

FileDescription

文件的说明。

OriginalFilename

文件的原始名称。

步骤 5:执行查询并返回数据

此时,您所要做的就是执行查询并在 Log Parser 开始返回数据时进行监视。这只需要一行代码:

objLogParser.ExecuteBatch strQuery, objInputFormat, objOutputFormat

正如您所看到的,您调用了 ExecuteBatch 方法并向其传递了三个参数:持有您的 SQL 查询的变量 (strQuery);对您的 InputFormat 对象的对象引用 (objInputFormat);对您的输出格式的对象引用 (objOutputFormat)。那接下来将会发生什么呢?您应当得到类似以下内容的输出:

注意:此脚本的运行时间为一秒。

总的说来,一切都非常好。还有,您可能注意到我们的列表中的前两个条目是点 (.) 和双点 (..)。这是怎么回事呢?如果您记得 MS-DOS 过去的好时光,那么您就会知道单个点是当前目录的简写符号,而两个点是父目录的简写。很好,但是这两者都不是实际的文件,因此我们可能不希望它们打乱我们 的输出。(顺便说一下,这不是这两种简写方法的过错;如果此文件夹有任何子文件夹,则那些子文件夹的名称同样也将显示在我们的输出中。)

那么,我们是否能够从我们的输出中去除这些文件夹和子文件夹而只显示文件名呢?当然可以。我们知道,文件夹有一个属性,即 Directory,文件系统可以从该属性区分文件和文件夹。我们所需要做的全部工作就是更改我们的查询以清除具有 Directory 属性的任何东西。换句话说,我们使用以下查询:

strQuery = "Select Name, Size FROM 'C:\Scripts\*.*' Where NOT " & _
      "Attributes LIKE '%D%' orDER BY Name"

虽然有一点笨拙(请注意,这是 SQL),但是“Where NOT Attributes”只是表示“没有启用该属性的地方”。换句话说,如果启用了 Directory 属性,则这个东东就是一个文件夹,我们不需要它。

顺便说一句,我们使用 LIKE 运算符来测试所讨论的属性是否以字母 D 开头。如果以字母 D 开头,则这是一个目录。要查找隐藏文件,我们可以使用 LIKE ‘%H%’;要检查加密文件,我们可以使用 LIKE ‘%E%’;依此类推。

注意:这未必是检查文件和文件夹属性的通用方式,但这是 Log Parser 检查属性的方式。同样,LIKE 运算符的使用与典型 SQL 查询中的 LIKE 使用方式也稍有不同。在标准 SQL 中,LIKE ‘%E%’ 返回属性名中的某处为字母 e 的所有属性。但是在 Log Parser 中,这只返回加密的文件;也就是说,只返回其属性以字母 e开头 的文件。

我们修改后的脚本类似于以下内容:

Set objLogParser = CreateObject("MSUtil.LogQuery")
Set objInputFormat = CreateObject("MSUtil.LogQuery.FileSystemInputFormat")
objInputFormat.Recurse = 0
Set objOutputFormat = CreateObject("MSUtil.LogQuery.NativeOutputFormat")
objOutputFormat.rtp = -1
strQuery = "Select Name, Size FROM 'C:\Scripts\*.*' Where NOT " & _
      "Attributes LIKE '%D%' orDER BY Name"
objLogParser.ExecuteBatch strQuery, objInputFormat, objOutputFormat

下面是新的输出:

这就好多了,但仍然不够完美。例如,让我们看看文件的排序方式:

Copy of kix_wmi.kix
New Text Document.txt
New_user.sxls
Process.ppt
Script1.vbs
bookmarkdoc.doc
computers.txt

怎么样?如果这是按字母顺序排序的,那么 bookmarkdoc.doc 和 computers.txt 跑到 Script1.vbs 的后面来做什么呢?

实际上,Log Parser 是按照在文件名中找到的字符的 ASCII 值排序的。在 ASCII 系统中,所有的大写字母都排在小写字母之前。因此,名为 ZZZZZZZZ.doc 的文件排在名为 aaaaaaaa.doc 的文件之前。好惨哪,但这是事实。

那么,是不是仅仅为了在 Log Parser 中让一切都按字母顺序排序,我们就必须检查和重命名我们所有的文件?是的,很糟糕。开心点儿!

不,我们只是想吓唬吓唬您;这很容易纠正了。实际上,Log Parser 有许多函数,可供您用来在检索数据时对数据进行操纵。例如,假如我们什么都不使用,只是将文件名中的字母变为小写,那么我们的文件集合将会是按照字母顺序排序的。那么,为什么不让 Log Parser 将所有字母都视为小写呢?请看下面这个查询:

strQuery = "Select TO_LOWERCASE (Name) AS NewName, Size FROM " & _
      "'C:\Scripts\*.*' Where NOT Attributes LIKE '%D%' orDER BY NewName"

此处我们所做的就是使用“TO_LOWERCASE”函数将所有的文件名都转换为小写。(注意,这种转换仅供 Log Parser 使用,实际的文件名不会更改。)该语法看起来也有点古怪(除非您已做了某些 SQL 编码),但我们所说的基本上是:不选择 Name 属性,而使用 TO_LOWERCASE 函数将 Name 属性转换为全小写字母。因为这实质上是创建了一个新属性(不再是 Name,现在它是 converted-to-lowercase-Name),我们使用 AS 运算符为此属性分配一个新名称。(我们作了一个聪明的选择,使用了 NewName,不过您可以使用您想使用的任何名称。)最终结果是,Log Parser 将获取所有的文件名,将它们转换为小写,然后再进行排序。

下面是改进后的新脚本:

Set objLogParser = CreateObject("MSUtil.LogQuery")
Set objInputFormat = CreateObject("MSUtil.LogQuery.FileSystemInputFormat")
objInputFormat.Recurse = 0
Set objOutputFormat = CreateObject("MSUtil.LogQuery.NativeOutputFormat")
objOutputFormat.rtp = -1
strQuery = "Select TO_LOWERCASE (Name) AS NewName, Size FROM " & _
      "'C:\Scripts\*.*' Where NOT Attributes LIKE '%D%' orDER BY NewName"
objLogParser.ExecuteBatch strQuery, objInputFormat, objOutputFormat

下面是我们的输出:

您说得对,这也只不过是如此而已。但是,按照字母顺序对文件进行排序而不区分大小写,已足以使任何人感到惊奇。

检索文件集合

ExecuteBatch 方法执行一个脚本然后在命令窗口中将输出显示/保存为 HTML 文件、SQL 数据库或某种其他形式。如果您只是想查看或保存输出,那么这就非常棒了。但是,如果您有别的想法该怎么办?比方说,如果您想查找您的计算机上最旧的 10 个文件以便对它们进行存档,那该怎么办?如果您想查找大于 5 MB 的所有文件以便对它们进行压缩,又该怎么办?在这些情况下,您实际上不想将返回数据保存为文本文件或显示为图表,而是希望将数据保留在内存中以便能够对其 进行某些实际处理。

我们不妨问一问:“使用 Log Parser 能完成此工作吗?”此刻,您心中已有了答案。那么让我们来看一个 Log Parser 脚本,该脚本不是直接显示信息而是获取数据并将其保留在内存中的一个记录集中:

Set objLogParser = CreateObject("MSUtil.LogQuery")
Set objInputFormat = _
      CreateObject("MSUtil.LogQuery.FileSystemInputFormat")
objInputFormat.recurse = 0
strQuery = "Select TO_LOWERCASE (Name) AS NewName, Size FROM " & _
      "'C:\Scripts\*.*' Where NOT Attributes LIKE '%D%' orDER BY NewName"
Set objRecordSet = objLogParser.Execute(strQuery, objInputFormat)
Do While Not objRecordSet.AtEnd
     Set objRecord = objRecordSet.GetRecord
     Wscript.Echo "Name: " & objRecord.GetValue("NewName")
     Wscript.Echo "File size: " & objRecord.GetValue("Size")
     objRecordSet.MoveNext
Loop

您看,这个脚本不赖吧。此脚本的前半部分中实际上只有两处与直接将数据显示到命令窗口的脚本不同。首先,您可能已注意到我们没有创建 OutputFormat 对象。这是因为我们不需要使用 OutputFormat 对象;我们不输出数据,而是要将数据保留在内存中。

其次,我们没有使用 ExecuteBatch 方法,而是调用了 Execute 方法并传递了两个参数:我们使用的查询 (strQuery) 和 InputFormat 对象 (objInputFormat)。该操作是由下面这行代码完成的:

Set objRecordSet = objLogParser.Execute(strQuery, objInputFormat)

在该行代码执行后,我们将获得一个在 C:\Scripts 中找到的所有文件的列表。不同之处在于:那些文件不会回显到命令窗口。这些数据将保留在内存中一个名为 objRecordSet 的记录集中。如果我们想访问该数据,我们需要做的就是遍历该记录集。这就是我们在脚本的后半部分所做的工作:

Do While Not objRecordSet.AtEnd
     Set objRecord = objRecordSet.GetRecord
     Wscript.Echo "Name: " & objRecord.GetValue("NewName")
     Wscript.Echo "File size: " & objRecord.GetValue("Size")
     objRecordSet.MoveNext
     Loop

此处,我们创建了一个 Do While 循环,它一直循环到我们到达记录集 (objRecordSet.AtEnd) 的末尾才停止。在该循环内部,我们使用 GetRecord 方法来获取记录集中的第一条记录。我们使用 GetValue 方法来获取并回显 NewName 字段的值,然后对 Size 字段执行同样的操作。就是这么回事。我们使用 MoveNext 方法移动到下一条记录,然后重复该过程。当我们完成所有操作后,我们会得到类似以下内容的输出:

很酷吧,是不是?现在,毫无疑问,仅仅回显值并不是记录集功能最激动人心的用法,但是它向您说明了您可以如何获取数据然后按照您的想法对其进行处理。

您可以看出,使用 Log Parser 来检索有关文件和文件夹的信息非常方便。但是要记住,Log Parser 并非仅仅是一个奇特的 dir 命令;它还能够从事件日志、Active Directory 和各种各样的日志文件等信息源获取类似的信息。说到 Log Parser 的功能,我们其实连它的皮毛还没有看完呢!大概在下个月,我们将看一下能否将一些示例 Log Parser 脚本(和查询)发布到脚本中心。同时,Log Parser 下载也包括了许多示例,它们应当可以帮助您编写自己的脚本。

现在,对不起,我们想去看看隔壁邻家究竟是谁。很难说,对吗?

赞(0) 打赏
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏