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

卸载与驱动程序文件相关的 inf 文件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.71/5 (10投票s)

2012 年 8 月 16 日

CPOL

8分钟阅读

viewsIcon

54853

downloadIcon

2338

本文介绍如何获取与驱动程序相关的 inf 文件列表并卸载这些 inf 文件。

引言

本文解释了

  • 如何识别与“.sys”文件相关的第三方“.inf”文件
  • 如何卸载/强制卸载选定的 inf 文件

应用程序的工作原理

在第三方驱动程序安装过程中,相关的 inf 文件会被重命名为 *Oemxx.inf* 并安装在“*Windows Root\Inf*”文件夹中(例如:*C:\Windows\Inf*)。Windows 还会创建一个 *.PNF* 文件并将其存储在同一位置。为了识别设备驱动程序,操作系统会遍历这些 inf 文件。

此应用程序利用上述信息来查找与驱动程序相关的 *oem\*.inf* 文件,并在列表视图中列出它们。可以通过右键单击列表视图中的条目,在记事本中打开这些文件。也可以在列表视图中右键单击列表中的文件进行卸载。

选择一个 sys 文件后,应用程序会遍历上述 inf 文件夹,并使用以下通配符进行搜索:*Oem\*.inf*。搜索到的每个 inf 文件都会被打开,并在 inf 文件中搜索以下节和键:

  • 搜索 `[SourceDisksFile]` 节
  • 在 `[SourceDisksFile]` 节下,搜索与所选 *.sys* 文件名匹配的键

例如,如果所选的 *.sys* 文件是“*scvscv.sys*”,那么我们将打开搜索到的第一个 *Oemxx.inf* 文件,并查找 `[SourceDisksFiles]` 节。在该节下,应用程序会查找名为“*scvscv.sys*”的键。如果能找到该键的值,那么我们就知道这个 inf 文件与所选驱动程序“*scvscv.sys*”相关。

然后,用户可以右键单击列表中显示的 inf 文件,在屏幕右下角的富文本框中查看其内容,或者通过右键单击列表视图中的特定条目在记事本中打开它。一旦用户确定 inf 文件可以卸载,就可以右键单击并选择“卸载”。

在某些情况下,可能无法卸载 inf 文件,因为它可能正在使用中。在这种情况下,如果用户确定 inf 文件可以卸载,则可以选择“强制卸载 inf”复选框。这将确保 inf 文件被强制卸载。UI 的屏幕截图如下所示:

只搜索 *Oemxx.inf* 文件,因为这些文件将包含在第三方驱动程序的安装中。Microsoft 建议即使卸载了 inf 文件,也不要触及驱动程序文件,因为驱动程序文件可能被其他应用程序使用。因此,此应用程序不会删除选定的 sys 文件。

完成任务的类

完成本文所述任务的全部逻辑封装在这三个类中:`CInfNDriverDelete`、`CInfNDriverDeleteData` 和 `CInfNDriverDeleteView`。每个类的职责如下所示:

  • `CInfNDriverDelete` – 此类包含分别通过 `CInfNDriverDeleteData` 和 `CInfNDriverDeleteView` 类表示的数据和视图的成员元素。在此类中会创建一个子窗口。
  • `CInfNDriverDeleteData` – 此类存储应显示在视图中的数据。
  • `CInfNDriverDeleteView` – 此类用于初始化列表视图/其他 UI 元素,并将 `CInfNDriverDeleteData` 中可用数据填充到列表视图/其他必需的 UI 元素中。

UI 元素

下面详细介绍了使用的 UI 元素以及创建它们的调用。

首先在 UI 上绘制一个静态框。然后绘制控件,以便为窗口获得合适的背景颜色。否则,分组框和复选框将具有自己的颜色,屏幕看起来会杂乱无章。我尝试了多种方式设置背景颜色,但都没有成功,因此使用了静态控件作为画布。

所有创建 UI 元素的调用都包含在源代码的以下函数中:`CInfNDriverDeleteView::Draw()`。

创建分组框

要创建分组框,应使用样式 `BW_GROUPBOX` 调用 `CreateWindowEx()`。窗口类参数应为“BUTTON”。代码片段如下所示:

// Create a groupbox to house the driver file selection button
m_hwndSelectDriverGroup = CreateWindowEx(NULL, 
                                        _TEXT("BUTTON"),
                                        _TEXT("Driver File"),
                                        WS_TABSTOP|WS_VISIBLE|WS_CHILD|BS_GROUPBOX,
                                        5,
                                        5,
                                        rect.right-10, //width
                                        rect.bottom / 4, //height
                                        m_hwndParent,
                                        NULL,
                                        (HINSTANCE) GetWindowLong(m_hwndParent, GWL_HINSTANCE),
                                        NULL);
创建列表视图

要创建列表框,应调用 `CreateWindowEx()`。样式可以是 `LVS_REPORT`。窗口类参数应为 `WC_LISTVIEW`。ID 应分配给 `HMENU` 参数。以后将使用此 ID 来获取控件事件。代码片段如下所示:

m_hwndListView = CreateWindowEx(WS_EX_CLIENTEDGE, 
                             WC_LISTVIEW,
                             L"", //caption not required
                             WS_CHILD | WS_VISIBLE | LVS_REPORT,
                             0,
                             10 + rect.bottom / 4, // start after the group box,
                             rect.right/4, // width
                             ((rect.bottom *3)/4) - 10,// height
                             m_hwndParent,
                             (HMENU)ID_INFDELETE_LIST_VIEW,
                             (HINSTANCE) GetWindowLong(m_hwndParent, GWL_HINSTANCE),
                              NULL);
创建按钮

要创建 Browse 按钮,应使用窗口类“BUTTON”调用 `CreateWindowEx()`。ID 应分配给 `HMENU` 参数。以后将使用此 ID 来获取控件事件。代码片段如下所示:

// Create a button which will launch driver file selection dialog
m_hwndSelectDriverButton = CreateWindowEx(NULL, 
                             _TEXT("BUTTON"),
                             _TEXT("Browse"),
                             WS_TABSTOP|WS_VISIBLE|WS_CHILD,
                             rect.right-180,//x,
                             30,//y
                             100,//width
                             rect.bottom/ 12, //height
                             m_hwndParent,
                             (HMENU)ID_BROWSEFILE_BUTTON,
                             (HINSTANCE) GetWindowLong(m_hwndParent, GWL_HINSTANCE),
                              NULL);
创建复选框

要创建复选框按钮,应使用窗口类“BUTTON”调用 `CreateWindowEx()`。ID 应分配给 `HMENU` 参数。以后将使用此 ID 来获取控件事件。样式标志应包含 `BS_AUTOCHECKBOX`。代码片段如下所示:

// Create checkbox
m_hwndForceInfIninstall = CreateWindowEx(NULL, 
                             _TEXT("BUTTON"),
                             _TEXT("Force Inf Uninstall"),
                             BS_AUTOCHECKBOX|WS_VISIBLE|WS_CHILD,
                             10,//x,
                             57,//y
                             120,//width
                             25, //height
                             m_hwndParent,
                             (HMENU)ID_FORCE_DRIVER_UNINSTALL_BUTTON,
                             (HINSTANCE) GetWindowLong(m_hwndParent, GWL_HINSTANCE),
                              NULL);
创建静态控件

要创建静态控件,应使用窗口类“STATIC”调用 `CreateWindowEx()`。代码片段如下所示:

// create static control for background
// all remaining controls can be written on top of it
m_hwndStaticBackground = CreateWindowEx(NULL, 
                             _TEXT("STATIC"),
                             _TEXT(""),
                             WS_VISIBLE|WS_CHILD,
                             0,//x,
                             0,//y
                             rect.right,//width
                             rect.bottom / 4 + 10, //height
                             m_hwndParent,
                             (HMENU)0,
                             (HINSTANCE) GetWindowLong(m_hwndParent, GWL_HINSTANCE),
                              NULL);
WM_NOTIFY 消息

控件将在事件发生时发送 `WM_NOTIFY` 消息。`WM_NOTIFY` 的 `lParam` 将包含指向 `NMHDR` 的指针。`NMHDR` 结构体的布局如下所示:

typedef struct tagNMHDR
{
    HWNDhwndFrom; // handle of the control sending the message
    UINT_PTR idFrom; // id of the control sending the message
    UINTcode; // notification code used to identify the event. 
              // This is used to idenfity if it is a right click 
              // or a click in the control
} NMHDR;

`NMHDR` 结构体包含控件 ID 和控件 `hwnd` 的详细信息。这就是我们之前为列表视图和富文本框分配的控件 ID 有用之处。

创建 RichEdit 控件

要创建 RichEdit 控件,应将 *RichEd32.dll* 加载到内存中。之后应调用 `CreateWindowEx`。下面是加载库并创建控件的调用:

// Load richedit library
m_hRichEdLib = LoadLibrary(_T("riched32.dll"));
 
// Create Richedit control to show selected inf file
m_hwndRichedit = CreateWindowEx(NULL,
                          RICHEDIT_CLASS,
                          _T(""),
                          WS_CHILD|WS_VISIBLE|ES_MULTILINE|ES_AUTOHSCROLL|
                          ES_AUTOVSCROLL|WS_HSCROLL|WS_VSCROLL|WS_BORDER,
                          rect.right/4 + 5,
                          10 + rect.bottom / 4, // start after the group box
                          rect.right - rect.right/4 - 4, // width
                          ((rect.bottom *3)/4) - 10, // height
                          m_hwndParent,
                          (HMENU)ID_INF_FILE_CONTENT,
                          (HINSTANCE) GetWindowLong (m_hwndParent, GWL_HINSTANCE),
                          NULL);

RichEdit 控件所需的 `#include` 为:`#include `。

注意:通过 `FreeLibrary()` 释放 RichEdit 库会导致应用程序崩溃。我没有时间进一步研究,但以后有时间时会查看。目前,我已注释掉 `FreeLibrary()` 调用。

在列表视图上创建上下文菜单

列表视图填充完毕后,右键单击它会弹出一个上下文菜单,其中包含在记事本中查看 inf 和卸载 inf 的选项。

通过控件的 `NM_RCLICK` 通知捕获了对控件的右键单击。以下调用用于创建上下文菜单:

//Create a popup menu 
POINT pt_CurPos;
GetCursorPos(&pt_CurPos);
HMENU hPopupMenu = CreatePopupMenu();
 
InsertMenu(hPopupMenu,
           0,
           MF_BYPOSITION | MF_STRING, 
           ID_POPUP_UNINSTALL,
           _T("Uninstall"));
 
InsertMenu(hPopupMenu,
           0,
           MF_BYPOSITION | MF_STRING, 
           ID_POPUP_OPEN_INF_IN_NOTEPAD, 
           _T("Open inf file in Notepad..."));
 
SetForegroundWindow(hWnd);
 
TrackPopupMenu(hPopupMenu,
           TPM_BOTTOMALIGN | TPM_LEFTALIGN, 
           pt_CurPos.x,
           pt_CurPos.y,
           0,
           hWnd,
           NULL);

编辑框子类化

当用户将 sys 文件路径复制粘贴到编辑框并按 Enter 键时,应填充 inf 文件列表。因此,编辑框被子类化了。在子类化过程中,检测到 Enter 键,并向父窗口发送用户消息,以根据用户粘贴的路径查找相关文件并更新列表视图。从编辑框过程发送到父窗口的用户消息是 `WM_SIGMA_SELECTFILE`。

要对编辑框进行子类化,必须通过调用 `SetWindowLong()` 来替换编辑框的默认 Winproc。以下是执行此操作的代码:

WNDPROC DefEditboxProc;

//subclass editbox to capture enter button
DefEditboxProc = (WNDPROC)SetWindowLong(m_hwndSelectDriverEditbox, GWL_WNDPROC, 
                                        (long)EditboxProc);

选择驱动程序文件

为了选择驱动程序文件,使用了 Win32 API `GetOpenFileName()`。这将打开“选择文件”对话框。此调用位于 *InfDriverDelete.cpp* 的 `SelectDriverFile()` 函数中。使用了 `_wsplitpath_s` 来分割并获取 *.sys* 文件名。此函数非常有用,因为它无需编写任何代码即可完成此解析。

获取 inf 文件列表

选择 sys 文件后,使用 `FindFirstFile()` 迭代 *SYSTEMROOT* 目录下的 inf 目录。搜索过程中使用的通配符过滤器是 *OEM\*.inf* 文件。从获得的 *OEM\*.inf* 文件列表中,使用 INI 文件函数 `GetPrivateProfileString` 搜索 inf 文件,以查找 `[SourceDisksFiles]` 节。在该节下,搜索与所选 sys 文件名相同的键。如果存在这样的键,则将其添加到数据数组中。最后,此数据数组将包含所选“*.sys*”文件的 *oem\*.inf* 文件列表。

使用 `GetEnvironmentVariable(_T("SYSTEMROOT"))` 获取 Windows 根目录(例如 *C:\Windows*)。

将所选 inf 文件加载到 RichEdit 控件中

所选的 inf 文件将从列表视图加载到 RichEdit 控件中。要将文件加载到 RichEdit 控件中,将使用流式传输。*InfNDriverDeleteView.cpp* 中的以下两个函数完成了此工作:`CInfNDriverDeleteView::FillRichEditFromInfFile(TCHAR* szFileName)` 和 `DWORD CALLBACK EditStreamCallback(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, PLONG pcb)`。

`CInfNDriverDeleteView::FillRichEditFromInfFile()` 打开 inf 文件,并将 `EM_STREAMIN` 消息发送到 RichEdit 句柄。`LPARAM` 将包含 `EDITSTREAM` 结构的地址。`EDITSTREAM` 将包含一个指向已打开的 Inf 文件的句柄和一个回调函数(`EditStreamCallback()`),该函数将实际读取 inf 文件并将其加载到 RichEdit 控件中。为清晰起见,下面显示了这两个函数:

DWORD CALLBACK EditStreamCallback(DWORD_PTR dwCookie, LPBYTE lpBuff,
                             LONG cb, PLONG pcb)
{
 HANDLE hFile = (HANDLE)dwCookie;
 if(ReadFile(hFile, lpBuff, cb, (DWORD *)pcb, NULL))
 {
      return 0;
 }
 return -1;
}
 
BOOL CInfNDriverDeleteView::FillRichEditFromInfFile(TCHAR* szFileName)
{
 BOOL fSuccess = FALSE;
 HANDLE hFile = CreateFile(szFileName, 
                     GENERIC_READ, 
                     FILE_SHARE_READ,
                     0, 
                     OPEN_EXISTING,
                     FILE_FLAG_SEQUENTIAL_SCAN, 
                     NULL);

 if (hFile != INVALID_HANDLE_VALUE) 
 {
      EDITSTREAM es = {0};
      es.dwCookie = (DWORD_PTR)hFile;
      es.pfnCallback = EditStreamCallback;

      if (SendMessage(m_hwndRichedit, EM_STREAMIN, SF_TEXT, (LPARAM)&es) 
           && es.dwError == 0) 
      {
           fSuccess = TRUE;
      }

      DWORD dw_LastError = GetLastError();
      CloseHandle(hFile);
 }
 return fSuccess;
}

通过发送以下消息来清除 Richedit 控件:

SendMessage(m_hwndRichedit, WM_SETTEXT, 0,(LPARAM)_T""));

卸载 Inf 文件

最后,这是此应用程序的主要任务。通过调用 *SetupApi.dll* 中的 `SetupUninstallOEMInf()` 函数来完成此操作。

SetupUninstallOEMInf(sz_UnInstallInfFilename, // inf file name
                     dw_ForceInfUninstall, // force removal or not
                     NULL);

可以通过 UI 上的“强制卸载 inf”复选框强制卸载 inf。`dw_ForceInfUninstall` 变量的值取决于此复选框的状态。

环境

本文仅在以下环境开发和测试:Lenovo T410 Laptop、VC++ 2005、Unicode、C++ 和 Windows 7 Enterprise(英语)。本文使用了我之前文章中的代码库。之前我有一个 VC++ 6.0 工作区,在此文中已升级为 VS2005 解决方案。

© . All rights reserved.