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






4.71/5 (10投票s)
本文介绍如何获取与驱动程序相关的 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 解决方案。