【问题标题】:Interrupt installation when custom action returns error自定义操作返回错误时中断安装
【发布时间】:2019-02-28 11:50:31
【问题描述】:

我有一个方法可以验证.dll 中的密码,它应该在失败时返回错误代码。它有以下原型:

#define DllExport __declspec( dllexport )
extern "C" DllExport UINT TestPassword(MSIHANDLE hInstall);

我希望当此方法返回错误代码(如ERROR_INSTALL_USEREXIT = 1602)时,整个安装过程将终止(不会执行此之后的其他自定义操作),但事实并非如此。

另外,在 Wix 中,我有以下片段:

 <CustomAction Id='TestPassword' BinaryKey='TestPassword' DllEntry='TestPassword' Execute='immediate'
              Return='check'/>

<Binary Id='TestPassword' SourceFile='DummyDll.dll'/>

【问题讨论】:

    标签: wix windows-installer custom-action


    【解决方案1】:

    快速链接:Managed Code Custom Actions(以下是 C++ 原生自定义操作)。


    更新:一些有用的链接。


    怀疑原因:

    在此处顶部列出一些建议。

    • 1) 错误的 C++ 自定义操作代码配置,经常忘记创建 CA.def 文件来定义 dll 导出。我也一直用__stdcall(微星是个老姑娘)。
    • 2) WiX 标记中自定义操作 dll 的路径错误(MSI 中的 dll 无效)。
    • 3) 忘记启用检查 WiX 标记 (*.WXS) 中的错误代码,然后 CA 没有结束设置。在您的情况下,这看起来是正确的 (Return='check')。
    • 4) 忘记在安装顺序中插入自定义操作。
    • 还有几个,暂时想不到。以后可能会添加...想到的一个是位问题 (x86/64)...
    • 文件和运行时依赖项是典型的故障转移原因。
    • 尽量静态链接。
    • 部署 DLL 应该是最小依赖项,因为它们需要在任何系统、任何语言、任何状态、任何操作系统版本等上运行...
    • 真正推荐静态链接且始终是正确选择的少数情况之一。

    注意:避免在设置中获得许可?我建议您将许可证验证放在您的应用程序中,而不是您的设置中。这里 关于这个问题的一些想法: Installer with Online Registration for Windows Application(推荐阅读)。


    技术问题:

    FileName.def:我不是 C++ 专家,但是您的项目中有 FileName.def 文件来声明导出的dll的功能?如果没有 - 添加一个(下面的步骤/程序)。确保它的格式正确(通过 Visual Studio 添加,我认为它是没有 BOM 的 UTF8)。编译并检查Dependency Walker是否所有导出都正确:

    验证 MSI 文件 DLL:您应该检查已编译的 MSI 以验证其内部是否有正确的 DLL 以及可用的正确导出。因此;验证 DLL 是否已安全进入 MSI 的二进制表:

    1. 使用 Orca (or equivalent) 打开您编译的 MSI。
    2. 二进制表,双击您的 DLL 条目的数据列。
    3. 选择“将二进制文件写入文件名”并保存到桌面(或其他地方)。
    4. 使用Dependency Walker (depends.exe) 来验证您是否拥有如上图所示的有效DLL。 常见的问题是您根本看不到导出(MyImmediateCA、MyTestFail、MyTestSuccess、etc...)。
    5. 验证文件-和产品版本以及文件属性。

    错误处理:可以设置自定义操作来抑制错误。设置了“返回属性”后,您的标记看起来正确:(Return='check')。你的sn-p:

    <CustomAction Id='TestPassword' BinaryKey='TestPassword' 
                  DllEntry='TestPassword' Execute='immediate' Return='check'/>
    

    测序:还要检查您的测序是否正常。总之,您需要指向二进制表 DLL,声明自定义操作,然后将其插入到正确的序列中。模拟 WiX 标记:

    <!--<Binary Id="CustomActions" SourceFile="$(var.TestDll.TargetPath)" />-->
    <Binary Id="CustomActions" SourceFile="C:\TestDll.dll" />
    
    <CustomAction Id="MyTestFail" BinaryKey="CustomActions" DllEntry="MyTestFail"/>
    <CustomAction Id="MyTestSuccess" BinaryKey="CustomActions" DllEntry="MyTestSuccess"/>
    
    <InstallExecuteSequence>
      <Custom Action="MyTestSuccess" After="CostFinalize" />
      <Custom Action="MyTestFail" After="MyTestSuccess" />
    </InstallExecuteSequence>
    

    C++ DLL:以及实际的 C++ DLL 本身(记住 *.def 文件)。来自MSI API Custom Action Security的底部代码段中的片段:

    Visual Studio 2017 的建议步骤:

    1. 创建新的 VC+ DLL 项目 - 动态链接库 (DLL)。
    2. 将以下代码转储到主 *.cpp 文件中(我避免使用 dllmain.cpp)。
    3. 添加 *.def 文件!
    • Right Click Source Files => Add => New Item... => Code => Module-Definition File (.def) => 任何名字都可以……(只允许一个 def 文件)
    • 添加您的导出函数名称:

    模型:

    LIBRARY
    
    EXPORTS
         MyTestFail
         MyTestSuccess
         MyImmediateCA
    

    关闭并重新打开文件以验证是否有任何格式错误。如果出现警告,请选择修复。 UTF8 without BOM 我认为是必需的。

    #include "stdafx.h"
    
    #include <windows.h>
    #include <Msiquery.h>
    #pragma comment(lib, "msi.lib")
    
    UINT __stdcall MyTestFail(MSIHANDLE hInstall)
    {
        MessageBox(NULL, L"MyTestFail", L"MyTestFail", MB_OK);    
        return ERROR_INSTALL_FAILURE;
    }
    
    UINT __stdcall MyTestSuccess(MSIHANDLE hInstall)
    {
        MessageBox(NULL, L"MyTestSuccess", L"MyTestSuccess", MB_OK);    
        return ERROR_SUCCESS;
    }
    
    // I will leave in the below snippet from the MSI API - section "Custom Action Security". Above two test methods will do though... 
    UINT __stdcall MyImmediateCA(MSIHANDLE hInstall)
    {
        MessageBox(NULL, L"Test", L"Test", MB_OK);
    
        // set up information for deferred custom action called MyDeferredCA
        const TCHAR szValue[] = TEXT("data");
        UINT uiStat = ERROR_INSTALL_FAILURE;
        if (ERROR_SUCCESS == MsiSetProperty(hInstall, TEXT("MyDeferredCA"), szValue))
        {
            uiStat = MsiDoAction(hInstall, TEXT("MyDeferredCA"));
    
            // clear CustomActionData property
            if (ERROR_SUCCESS != MsiSetProperty(hInstall, TEXT("MyDeferredCA"), TEXT("")))
                return ERROR_INSTALL_FAILURE;
        }
    
        return (uiStat == ERROR_SUCCESS) ? uiStat : ERROR_INSTALL_FAILURE;
    }
    

    Another answer on MsiGetProperty(检索属性值)。这在 C++ 中稍微复杂一点——包括缓冲区和所有内容。向下滚动查看源代码。

    最小依赖关系:为了最小化依赖关系,您应该消除 Visual C / C++ 运行时依赖关系和任何 MFC 依赖项(如果您可以帮助解决文件大小和问题,请不要使用 MFC 性能原因)。如果您使用 MFC,请将其设置为使用静态链接 - 也适用于 ATL。最后对于 C/C++ 运行时,请参见此处: Visual Studio 2010 MSVCR dependency removal? (有更好的链接,但我能找到的只是我有时间 现在 - 只是想把它放在那里,这样它就不会被遗忘)。

    发布模式 C++ 二进制文件现在不应依赖于任何 MSVC 运行时 dll:

    这是 DLL 如何在不进行此调整的情况下依赖于 MSVC 运行时 dll 的屏幕截图 - 不要介意红色图标 - 这是古老的依赖遍历器工具,它没有针对现代依赖进行更新,但完美地显示了旧式依赖:

    请注意,调试模式 DLL 可能依赖于不同于发布模式二进制文件的文件。发布模式二进制文件是重要的。显然永远不要分发调试模式的二进制文件!

    【讨论】:

    • 对 Dependency Walker 的重写,它确实有效并且仍在维护 - 见 Dependencies。我从这个SO answer 得到它并成功使用了一段时间。
    • 没错,Dependency Walker 对于检查导出等简单的事情仍然工作得相对不错,但我也使用 Dependencies 有一段时间了。不错,但有一些性能问题 - 当您遍历并排的程序集文件夹时,这是可以预料的(dll 地狱的第二次或第三次到来?):-)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多