65.9K
CodeProject 正在变化。 阅读更多。
Home

Microsoft 修复程序检查器工具的 GUI 前端

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (3投票s)

2001年9月3日

8分钟阅读

viewsIcon

221061

downloadIcon

3946

本文演示了如何重定向子进程的输出,以及如何使用 DHTML 显示网页浏览器 UI。

 [WHotfixCheck dialog - 28K]

引言

Microsoft 最近发布了一个名为 Hfnetchk 的工具,该工具可以检查 NT 4 和 Windows 2000 系统,并生成一份报告,说明需要安装哪些 service pack 和 hotfix。虽然这是一个很棒的想法,但该工具是一个控制台模式程序,并且只打印知识库文章编号列表;它不列出 URL,也不指向有关 hotfix 的知识库文章。幸运的是,Hfnetchk 有一个开关可以生成制表符分隔的输出,这使得解析输出并以更友好的 UI 显示它变得相当简单。本文介绍了 WHotfixCheck,这是我编写的一个提供这种友好 UI 的工具,并触及了我在编写它时遇到的一些有趣的编程主题。

更新

  • 2001 年 9 月 23 日 - 增加了扫描远程计算机的功能。增加了将最后使用的设置保存和加载到注册表的功能。感谢 Uwe Keim 添加这些新功能!
  • 2001 年 10 月 11 日 - 添加了“保存结果”按钮,用于将扫描结果保存到 HTML 文件。
  • 2001 年 10 月 18 日 - 增加了对更多 Hfnetchk 开关的支持:显示缺失或已安装的 hotfix,跳过注册表检查,详细输出。使 HTML 输出看起来更漂亮。
  • 2001 年 11 月 7 日 - 改进了 Hfnetchk 的错误和消息处理。如果 Hfnetchk 输出一条消息(例如“没有可用的 hotfix”),WHotfixCheck 现在会在消息框中显示它。添加了 XP 主题支持。

使用 WHotfixCheck

要使用 WHotfixCheck,您必须首先从 Microsoft 下载并安装 Hfnetchk。浏览到 有关该工具的知识库文章,并按照该文章中的下载链接进行操作。安装完成后,运行 WHotfixCheck,然后在顶部编辑框中输入 hfnetchk.exe 的路径。如果您以前从未运行过 Hfnetchk,请将另一个编辑框留空,Hfnetchk 将下载必要的数据文件。如果您已经运行过,那么在 hfnetchk.exe 同一目录下会有一个名为 mssecure.xml 的文件;在第二个编辑框中输入该文件的路径。

在 **要显示的 Hotfix** 下拉列表中,选择您想要查看的 hotfix。默认是 *必需的 Hotfix*,它显示您尚未安装的 hotfix。选择 *已安装的 Hotfix* 来查看您已安装的 hotfix。*缺失的 Hotfix* 与 *必需的 Hotfix* 类似,但还显示已被后续 hotfix 替换的 hotfix。

如果您正在查看已安装的 hotfix,并且 WHotfixCheck 报告您已安装的 hotfix 缺失,请选中 **跳过注册表检查** 复选框。并非所有 hotfix 都会记录在注册表中,因此它们始终被报告为未安装。选中 **跳过注册表检查** 会使 Hfnetchk 忽略这些 hotfix,而不是报告它们。

选中 **详细输出** 复选框,如果您想在 hotfix 列表中看到其他详细信息。这将显示每个 hotfix 的状态(找到、未找到等)以及 Hfnetchk 如何确定状态的解释。

在 **扫描内容** 下拉列表中,有一个与要扫描的计算机相关的选项列表。Hfnetchk 可以远程扫描您拥有管理员权限的任何计算机。您可以选择仅扫描本地计算机,扫描远程计算机(由其名称或 IP 地址标识),扫描 IP 地址范围,或者扫描整个域。

提供所需文件路径后,单击 **运行** 开始扫描。如果一切顺利,您将看到一个未安装在您计算机上的可用 hotfix 列表。请参阅上图,了解列表的外观。表格的每一行都列出了计算机名称、产品名称、Microsoft 安全公告编号和知识库文章编号。后两列链接到 Microsoft 网站上相应的网页,因此您可以使用这些链接快速找到 Microsoft 对 hotfix 的描述。

运行扫描后,您可以单击 **保存结果** 按钮将结果保存到 HTML 文件。

实现细节

在本节中,我将描述代码中一些比较有趣的部分。如果您不关心程序的内部工作原理,可以安全地跳过本节。

运行 Hfnetchk 并捕获其输出

如果 WHotfixCheck 是一个控制台程序,我们可以使用 CRT 函数 _popen() 来运行 Hfnetchk 并读取其输出。然而,_popen() 在从 GUI 程序调用时不起作用,因此我们必须自己创建一个管道并将其附加到 Hfnetchk 的标准输出。

第一步是为管道句柄设置安全属性。SECURITY_ATTRIBUTES 结构包含一个标志,该标志决定我们将要启动的子进程是否可以继承我们进程的句柄。必须将此标志设置为 TRUE

LRESULT CMainDlg::OnRun(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
// ...

SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES) };
 
    sa.bInheritHandle = TRUE;

接下来,我们创建管道。我们得到两个句柄,一个用于读取端,一个用于写入端。

HANDLE hStdoutRd, hStdoutWr;
 
    if ( !CreatePipe ( &hStdoutRd, &hStdoutWr, &sa, 0 ))
        return 0;

接下来,我们设置 CreateProcess() 使用的结构。这里重要的部分是我们将子进程的标准输出设置为管道的写入端。

STARTUPINFO si = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION pi;
 
    si.dwFlags = STARTF_USESTDHANDLES;
    si.hStdOutput = hStdoutWr;

接下来,我们启动 Hfnetchk。

    if ( !CreateProcess ( NULL, szCommandLine, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS,
                          NULL, NULL, &si, &pi ))
        {
        // error handling omitted

        return 0;
        }

第五个参数是一个标志,指示子进程是否继承我们的句柄。对于 Hfnetchk 能够使用我们传递给它的管道句柄,此参数必须为 TRUE

启动 Hfnetchk 后,我们就可以开始读取其输出了。为了方便解析,我们首先将输出转储到一个临时文件。第一步是关闭我们对管道写入端的句柄。如果我们不这样做,当 Hfnetchk 完成后,写入端将不会关闭,因为当管道有打开的句柄时,管道不会关闭。

    CloseHandle ( hStdoutWr );

接下来,我们创建一个临时文件来存储 Hfnetchk 的输出。

HANDLE hTempFile;
 
    hTempFile = CreateFile ( szTempFile, GENERIC_WRITE, 0, NULL, 
                             CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
 
    if ( INVALID_HANDLE_VALUE == hTempFile )
        return 0;

现在我们可以进入一个循环并从管道读取数据。

BYTE buff[1024];
DWORD dwRead, dwWritten;
 
    while ( ReadFile ( hStdoutRd, buff, countof(buff), &dwRead, NULL ) && dwRead != 0 )
        {
        WriteFile ( hTempFile, buff, dwRead, &dwWritten, NULL );
        }

当 Hfnetchk 将所有数据写入管道并终止(从而关闭管道写入端)时,ReadFile() 将返回 FALSE

之后,只需使用 strtok() 解析行即可。每一行都包含一个 hotfix 的详细信息。我们提取计算机名称、产品名称、安全公告编号和知识库文章编号,并将它们存储在一个结构中。

struct CPatchInfo
{
    CString sComputer, sProduct, sBulletin, sKBNumber;
};

OnRun() 构建一个结构数组,并将其传递给处理 UI 的 ShowPatchList()

显示结果

WHotfixCheck 托管一个 WebBrowser 控件,并使用 IE DHTML 对象模型在浏览器中显示 HTML。起点是获取控件上的 IWebBrowser2 指针。

void CMainDlg::ShowPatchList ( CSimpleArray<CPatchInfo>& aPatchInfo )
{
CComPtr<IUnknown>       punkIE;
CComQIPtr<IWebBrowser2> pWB2;
 
    // Get an IWebBrowser2 interface to control the browser.

    AtlAxGetControl ( GetDlgItem(IDC_IE), &punkIE );
    pWB2 = punkIE;

现在,我们可以获取指向 HTML 文档的指针,然后获取 <body> 元素。一旦我们能够访问 body,我们就可以插入任何我们想要的 HTML。(实际上涉及更多代码,但为了清晰起见,这里省略了。)

CComPtr<IDispatch>        pdispDoc;
CComQIPtr<IHTMLDocument2> pDoc;
CComPtr<IHTMLElement>     peltBody;
 
    // Get a pointer to the <body> element so we can insert a table.

    pWB2->get_Document ( &pdispDoc );
    pDoc = pdispDoc;
    pDoc->get_body ( &peltBody );

现在我们可以将 HTML 插入 body。第一步是创建一个带有单个行(包含列标题)的 <table>

    // Replace the body with the beginnings of our table.

    peltBody->put_innerHTML ( CComBSTR("<table width=100% id=\"patches\" cols=3><tr>...</tr></table>"));

<table> 有一个 `id` 字段,因此我们可以使用 DHTML 对象模型修改它。下一步是获取表格上的 IHTMLTable 接口。有两种方法可以做到这一点。为了与 IE 4 兼容,WHotfixCheck 访问文档的 `all` 集合,并从该集合中获取表格。更简单的方法是使用 IHTMLDocument3::getElementById() 通过传递其 id 来获取表格的接口,但这需要 IE 5。

CComPtr<IHTMLElementCollection> pColl;
CComPtr<IDispatch>    pdispTable;
CComQIPtr<IHTMLTable> pTable;
 
    pDoc->get_all ( &pColl );
 
    pColl->item ( CComVariant("patches"), CComVariant(0), &pdispTable );
    pTable = pdispTable;

现在我们可以开始向表格添加行了。我们遍历 CPatchInfo 结构数组,对于每个结构,组装一个字符串或一个要放入表格的 HTML 片段。添加到表格包括首先添加一行,然后向该行添加单元格。以下是如何添加新行:

    for ( int i = 0; i < aPatchInfo.GetSize(); i++ )
        {
        CComPtr<IDispatch> pdispRow;
        CComQIPtr<IHTMLTableRow> pRow;
 
        // Add a new row to the end of the table.

        pTable->insertRow ( -1, &pdispRow );
        pRow = pdispRow;

传递 -1 的行索引会将新行放在表格的末尾。使用 IHTMLTableRow 接口,我们可以向行添加单元格。以下是添加第一个列(一个简单的字符串,列出计算机名称)的代码:

CComPtr<IDispatch> pdispCell;
CComQIPtr<IHTMLElement> peltCell;
 
        // Col 1 - computer name

        pRow->insertCell ( -1, &pdispCell );
        peltCell = pdispCell;
 
        peltCell->put_innerText ( CComBSTR(aPatchInfo[i].sComputer) );
 
        pdispCell.Release();
        peltCell.Release();

IHTMLElement::put_innerText() 设置元素的文本。“inner”一词表示正在更改的文本位于 HTML 标签(在本例中为 <td></td>)内部。第二列的创建方式类似,并显示产品名称。

对于第三列,我们创建一个包含 <a> 标签的 HTML 片段,这将创建一个超链接。

        // Col 3 - security bulletin name/URL

CString sText;
 
        pRow->insertCell ( -1, &pdispCell );
        peltCell = pdispCell;
 
        sText.Format ( _T("<a href=\"http://www.microsoft.com/technet/security/bulletin/%s.asp\" target=_blank>%s</a>"),
                       (LPCTSTR) aPatchInfo[i].sBulletin, (LPCTSTR) aPatchInfo[i].sBulletin );

        peltCell->put_innerHTML ( CComBSTR(sText) );
 
        pdispCell.Release();
        peltCell.Release();

这次我们使用 IHTMLElement::put_innerHTML() 将 HTML 片段插入单元格。第四列的创建方式与第三列类似,并链接到知识库文章。

正如您所见,从 C++ 访问 DHTML 对象模型有些麻烦,因为许多方法返回 IDispatch 接口,然后您必须查询该接口以获取您真正需要的接口。文档在某些方面也有所欠缺,最值得注意的是 Web Workshop 页面(包含对象模型参考)未出现在“内容”窗格中,因此您无法在帮助查看器中使用“定位”按钮。我通常会使用“定位”按钮直接跳转到接口中的方法列表。

有改进空间

ShowPatchList() 中获取 <body> 指针的代码相当丑陋,因为它必须处理第一次调用时的特殊情况,此时 WebBrowser 中还没有文档或 body。我弄了一个创建虚拟 body 的方法,因为我找不到任何看起来能够完成这项工作的 IHTMLDocument 方法。

链接

在以下知识库文章中详细了解 Hfnetchk:

WHotfixCheck 和 Hfnetchk 需要 Microsoft 的 XML 解析器。 在此处下载 3 版本

© . All rights reserved.