DevMSI: 一个 C++ MSI/Wix 延迟自定义操作 DLL 示例






4.86/5 (11投票s)
一个可用于 WiX/MSI 延迟自定义操作或独立应用程序的示例 DLL。
引言
本文的目的是研究一个可与 Wix 配合使用的 C++ 自定义操作 DLL,用于延迟自定义操作。本文使用的示例是一个 DLL,可用于为非即插即用设备创建或移除根枚举的 devnode。
配套的 CodeProject 文章 EchoInst: WDK Echo 驱动程序的 Wix 安装示例 展示了如何在 MSI 安装期间将此 DLL 集成到 WiX 项目中以安装设备驱动程序,并在 MSI 卸载期间将其移除。
DevMsi 的完整源代码可在 CodeProject 上找到此处。
背景
Windows Installer XML (WiX) 是一个工具集,它根据 XML 源代码构建 Windows 安装包。开发人员还可以通过多种方式扩展 WiX/MSI 技术,包括通过 C/C++ DLL 中的入口点。这些扩展 DLL 属于 WiX/MSI 称之为“自定义操作”的子集。
MSI 架构要求某些类型的自定义操作必须是“延迟的”。特别是,任何需要脱离用户安全上下文运行的操作都必须延迟,以获得必要的权限级别。有关此主题的更多详细信息,请阅读 MSDN 文章 延迟执行自定义操作。
WiX 3.7 在 Visual Studio 2012 中添加了向导,使得创建 C/C++ 自定义操作 DLL 成为一项简单的任务。然而,如何向延迟自定义操作 DLL 传递或接收参数并不立即显而易见。本文的目的是提供一个可行的、有用的示例,特别是对于 Windows 驱动程序开发人员。
为什么选择自定义操作类型 1 (.DLL)?为什么不选择类型 2 或 18 (.EXE)?
如果开发人员只是想在安装过程中执行某种自定义代码,那么 EXE 将是首选(且显而易见)的选择。开发人员可能更喜欢使用 C/C++ DLL 的两个原因如下:
日志记录。 自定义操作 DLL 能够在安装或卸载期间向 MSI 日志文件添加条目。否则,自定义可执行文件将不得不将其信息记录到单独的位置,这会增加一个需要收集的项目来调试安装失败。
显示。作为命令行实用程序的自定义操作将在安装过程中向用户显示一个“DOS 框”。可以通过一些巧妙的 WiX 编码或通过创建不显示任何 UI 的“Windows 程序”实用程序来抑制此显示。但是,如果开发人员必须做额外的工作只是为了避免图形上的不愉快,他可能希望研究 .DLL 方法以简化安装代码。
延迟自定义操作 DLL 的属性限制
根据 MSDN 文章获取延迟执行自定义操作的上下文信息,在延迟自定义操作期间可以检索的属性很少(三个)。唯一真正可用于向操作传递参数的属性是CustomActionData
属性。
此属性可以很容易地从 DLL 中检索,代码类似于以下内容:
LPWSTR pszCustomActionData = NULL;
hr = WcaGetProperty(L"CustomActionData", &pszCustomActionData);
ExitOnFailure(hr, "Failed to get Custom Action Data.");
// ...
LExit:
ReleaseStr(pszCustomActionData);
这种机制允许将单个字符串传递到 DLL。如果需要传递多个参数,则必须将它们嵌入到字符串中。由于 C/C++ 程序员熟悉用于将参数传递给主函数等函数的 argc/argv 机制,因此解析通过 CustomActionData
传递的参数的一种合理机制是 Windows API 函数 CommandLineToArgvW()
。
DevMsi 项目在 CustomAction.cpp 中的每个入口点都遵循此模式。它执行必要的初始化,获取 CustomActionData 属性,将此属性解析为 argc/argv 数组,然后将解析后的参数列表交给一个函数来实际完成工作。
调试延迟自定义操作 DLL
使用延迟操作自定义操作 DLL 的缺点之一是调试更困难,因为 DLL 只能通过 MSI 安装/卸载过程访问。如果 DLL 中的代码并非微不足道,那么找到另一种在 MSI 环境之外调试代码的方法是有价值的。
由于上述机制将参数提取为 argc/argv 格式,然后调用另一个函数来实际“完成工作”,因此在“完成工作”函数中向 DLL 添加第二个入口点变得轻而易举,这样外部测试应用程序就可以调用该函数进行调试或命令行测试。
“完成工作”函数的一个示例如下
#ifdef _DEVMSI_EXPORTS
# define DEVMSI_API __declspec(dllexport)
#else
# define DEVMSI_API __declspec(dllimport)
#endif
//...
HRESULT DEVMSI_API DoCreateDevnode( int argc, LPWSTR* argv );
使用这种格式,可以很容易地编写一个测试应用程序来在适当的点开始测试 DLL,如下所示:
int _tmain(int argc, _TCHAR* argv[])
{
int result = -1;
if ( argc < 2 ) {
return result;
}
LPCTSTR opName = argv[1];
argc -=2;
argv +=2;
if ( !_tcsicmp( opName, TEXT("create") ) ) {
result = SUCCEEDED( DoCreateDevnode( argc, argv ) )
? 0 : -1;
}
return result;
}
(延迟)自定义操作 DLL 入口点
除了标准的 DllMain
入口点(当进程附加/分离时必须进行一些 MSI 设置/初始化)之外,DLL 公开的各种(延迟)自定义操作还将有额外的入口点。DevMsi 在 CustomAction.def 中定义了这些入口点。它们中的每一个都遵循 CustomAction.cpp 中类似于以下内容的模式
UINT __stdcall CreateDevnode(MSIHANDLE hInstall)
{
return CustomActionArgcArgv( hInstall, DoCreateDevnode, "CreateDevnode" );
}
CustomActionArgcArgv
是一种方法,它执行上面“延迟自定义操作 DLL 的属性限制”中描述的设置,依赖于“完成工作”函数(例如 DoCreateDevnode
)对提取的参数列表执行自定义操作。第一个参数 (hInstall
) 是 MSI 框架所必需的,最后一个参数只是用于日志记录目的的名称。
记录消息和错误
如果 DLL 设计为在 MSI 环境中或从测试工具中使用,则日志输出必须能够检测正在使用哪种方法,并将输出路由到 stdout
/stderr
(在测试工具的情况下)或 MSI 日志系统。DevMsi 项目包含一个 LogResult()
方法,它根据需要执行此检测和路由。
使用此方法,在调试期间很容易查看测试工具可能生成的错误,以便开发人员可以处理问题。即使在 MSI 中部署之后,也相当容易从安装中获取日志消息,其中将包含延迟自定义操作的日志输出。
MSI 日志文件中成功日志条目的一个示例如下
MSI (s) (80:D4) [15:49:20:435]: Invoking remote custom action. DLL: C:\Windows\Installer\MSI4E61.tmp, Entrypoint: CreateDevnode
CreateDevnode: Initialized.
CreateDevnode: Custom Action Data = '"C:\Program Files\WDK Samples\Drivers\echo.inf" root\Echo'.
CreateDevnode: hwid = 'root\Echo', class = 'C:\Program Files\WDK Samples\Drivers\echo.inf'.
CreateDevnode: Converting 'C:\Program Files\WDK Samples\Drivers\echo.inf' from INF Path to Class GUID.
CreateDevnode: Class GUID {78A1C341-4539-11D3-B88D-00C04FAD5171} will be used.
CreateDevnode: SetupDiCreateDeviceInfoList() succeeded.
CreateDevnode: SetupDiCreateDeviceInfo() succeeded.
CreateDevnode: SetupDiSetDeviceRegistryProperty() succeeded.
CreateDevnode: SetupDiCallClassInstaller() succeeded.
CreateDevnode: DoCreateDevnode() Complete.
如果用户不知道从命令行创建日志文件的简单方法,以下命令可能会有所帮助:
msiexec /i myMsi.msi /l*v myInstallLogFile.log
msiexec /x myMsi.msi /l*v myUninstallLogFile.log
DevMsi 构建环境
DevMsi 在安装了 Windows 7 WDK 和 Wix 3.7 的 Visual Studio 2012 环境中构建。它假定 WIX 环境变量已设置为 Wix 3.7(或更高版本)的安装路径。该项目已针对 AMD64 平台进行构建和测试。
运行 DevMsiTest.exe
如果用户想运行 DevMsiTest 以添加或移除设备作为示例,在将 DevMsi.dll 和 DevMsiTest.exe 放在同一目录后,以下命令应该允许从控制台执行此操作:
rem The following command will create a non-PnP device root\foo in the System class:
DevMsi.exe create System root\foo
rem The following command will remove any matching non-PnP device named root\bar:
DevMsi.exe remove root\bar
rem The following command will uninstall a service named zed:
DevMsi.exe remService zed
另一篇文章将提供一个工作示例,说明如何在 Wix 安装环境中使用 DevMsi 安装和卸载 Windows WDK 中的 echo 设备。
结论
一旦理解了 Wix C/C++ 延迟自定义操作 DLL 的参数传递机制,扩展 MSI 以执行其他必要任务的过程将变得更易于实现。作者希望本文和相关代码有助于学习如何在项目中使用自定义操作。
特别是,由于非即插即用驱动程序的设备驱动程序安装在网上搜索似乎没有得到很好的解释,作者希望此示例可以帮助未来的设备驱动程序安装程序编写者避免添加和移除非即插即用设备节点所带来的一些麻烦。
历史
- 2013/04/04 - 初次提交。
- 2013/04/05 - 在文章顶部添加了源代码链接。
- 2013/04/09 - 在文章顶部添加了配套文章 EchoInst: WDK Echo 驱动程序的 Wix 安装示例 的链接。