【问题标题】:Failed to get MSI property in UPGRADINGPRODUCTCODE, WIX_UPGRADE_DETECTED无法在 UPGRADINGPRODUCTCODE、WIX_UPGRADE_DETECTED 中获取 MSI 属性
【发布时间】:2019-05-24 14:10:00
【问题描述】:

我想在升级过程中跳过一些自定义操作uninstallDriver,为此我尝试检索属性 WIX_UPGRADE_DETECTEDUPGRADINGPRODUCTCODE,但两者都没有设置。

示例代码:

UninstallDriver(MSIHANDLE hInstall)  
{
   char szBuff[1024]; DWORD dwValue = 0;

   OutputDebugStringA("UninstallDriver");
   MsiGetPropertyA(hInstall, "UPGRADINGPRODUCTCODE", szBuff, &dwValue);
   OutputDebugStringA("UPGRADINGPRODUCTCODE");OutputDebugStringA(szBuff);

   MsiGetPropertyA(hInstall, "WIX_UPGRADE_DETECTED", szBuff, &dwValue);
   OutputDebugStringA("WIX_UPGRADE_DETECTED");OutputDebugStringA(szBuff);

   result = UninstallDriver();

   return result;
}

而我的 customAction 是,

<Custom Action='UninstallMYDriverAction'
 After='InstallInitialize'>
        REMOVE~="ALL" OR REINSTALL</Custom>

【问题讨论】:

  • 一个问题:您使用的是第一个版本吗?如果是这样,那么您需要修补已发布的版本,以防止它运行其嵌入式卸载序列。这又是另一回事了,它涉及使用次要升级来修补已安装的产品(不会卸载现有产品,而是将其升级到位)而不是重大升级(即卸载旧版本并重新安装新版本)。 Somwhat similar issue.

标签: wix windows-installer custom-action


【解决方案1】:

自定义操作条件:您应该能够直接设置自定义操作而不通过自定义操作?微星 条件因难以做到恰到好处而臭名昭著,因为 有这么多安装模式要处理:install, repairself-repairmodifyminor upgrade patchinguninstallmajor upgrade initiated uninstalls等……有 还有几个。

在您的情况下,添加到现有条件可能就足够了:

AND (NOT UPGRADINGPRODUCTCODE)

如果检测到升级,这将使条件无效。

我实际上会尝试:(REMOVE~="ALL") AND (NOT UPGRADINGPRODUCTCODE) (as seen in this answer)。


重要提示:您可以通过此处描述的方法非常有效地调试 MSI 条件: How to execute conditional custom action on install and modify only? (见底部)。您使用 VBScript 和一些 MSI API 调用 确定哪些条件为真,然后您可以更改 排序以检查不同时间的条件。他们可能 改变。

注意:这只是一个自怨自艾的可怜人的 MSI 调试器,但它可以工作。


属性字符串大小:我不是 C++ 专家,但这里来自 the MSI SDK:您需要先用空字符串调用 MsiGetProperty,然后再调用为 null 终止 添加 1。像往常一样,C++ 的“线路噪音”很高。很多管道和手续几乎没有效果 - 但让我们处理它:-)

以下 C++ 示例来自 the MSI SDK

UINT __stdcall MyCustomAction(MSIHANDLE hInstall)
{
    TCHAR* szValueBuf = NULL;
    DWORD cchValueBuf = 0;
    UINT uiStat =  MsiGetProperty(hInstall, TEXT("MyProperty"), TEXT(""), &cchValueBuf);
    //cchValueBuf now contains the size of the property's string, without null termination
    if (ERROR_MORE_DATA == uiStat)
    {
        ++cchValueBuf; // add 1 for null termination
        szValueBuf = new TCHAR[cchValueBuf];
        if (szValueBuf)
        {
            uiStat = MsiGetProperty(hInstall, TEXT("MyProperty"), szValueBuf, &cchValueBuf);
        }
    }
    if (ERROR_SUCCESS != uiStat)
    {
        if (szValueBuf != NULL) 
           delete[] szValueBuf;
        return ERROR_INSTALL_FAILURE;
    }   

// custom action uses MyProperty
// ...

delete[] szValueBuf;

return ERROR_SUCCESS;

}

VBScript:出于测试目的,可以轻松使用 VBScript(因此您可以确定是否有编码问题):

MsgBox Session.Property("PROPERTYNAME")

一些进一步的链接(仅供参考和方便检索,我认为您不需要):

【讨论】:

  • 谢谢 Stein,我检查了编码问题,没有编码问题。目前还有一个查询我的两个安装程序都是 version2..0.0.0 但不同的版本,BUILD1,BUILD2,在这种情况下,UPGRADINGPRODUCTCODE 是否已设置?或者仅在版本号(2.1.0.0)之一更改的情况下
  • 如果我不想运行我的 customAction,任何类型的升级(1.0.0.0 到 1.0.0.0/1.0.0.0-1.1.0.0/1.0.0.0.-2.0.0.0 等)然后我该如何设置条件。
  • 基本上我只想在卸载/修复案例中执行我的 CA
  • UPGRADINGPRODUCTCODE 仅在正在卸载的旧设置的重大升级中设置。如果没有发生重大升级,则未设置。我现在没有时间测试确切的情况,但请参阅上面提供的链接以获取大量信息。尝试 VBScript 方法来验证我在上面的答案(顶部提示部分)中提到的条件。
  • UPGRADINGPRODUCTCODE 仅当重大升级意味着?只有第一个版本被改变? 1.0.0.0 到 2.0.0.0 不适合其他 1.0.0.0 到 1.1.0.0?这可能是新手/愚蠢的问题,但请原谅我。
【解决方案2】:

忽略调试字符串,更容易看出缓冲区处理不正确。我建议还输出来自MsiGetPropertyA() 的返回值和dwValue 中的值来确认,但这是我认为正在发生的事情(cmets 指的是dwValue):

char szBuff[1024]; DWORD dwValue = 0;
MsiGetPropertyA(hInstall, "UPGRADINGPRODUCTCODE", szBuff, &dwValue); // passes 0, updated to ?x?
MsiGetPropertyA(hInstall, "WIX_UPGRADE_DETECTED", szBuff, &dwValue); // passes ?x?, updated to ?y?

当请求缓冲区长度为零的UPGRADINGPRODUCTCODE 属性时,提取将永远不会成功,因为它必须始终至少接受一个空字符。因此,这将返回 ERROR_MORE_DATA 并将 dwValue 设置为不包括空字符 (?x?) 的长度。

然后它将请求WIX_UPGRADE_DETECTED 的值,声称缓冲区长度为(?x?)。如果新长度 (?y?) 小于旧长度 (?x?),您将在缓冲区中获取其内容;否则它也只会查询这个新属性的长度。

由于WIX_UPGRADE_DETECTED 包含一个或多个GUID 的列表,而UPGRADINGPRODUCTCODE 仅包含一个,并且此代码从不增加dwValue 以解释空值,因此只有在后者为?y? 时才可能成功为 0(空),?x? 为非空。但请注意,第二次调用传递了一个未经验证的值作为缓冲区的长度,这种模式是等待发生的缓冲区溢出。

所以修复你的缓冲区处理。我喜欢使用的模式(如下)类似于 Stein 描述的模式,但如果我知道缓冲区的良好默认大小,则避免第二次调用。就您而言,听起来您对 1024 元素缓冲区很满意,但请考虑您是否需要处理超过 1024 / len(GUID) 相关的升级代码。

(我猜你没问题。但至少要考虑清楚。即使 GUID 是 ASCII 所以内容无关紧要,请这些天请构建 UNICODE...)

WCHAR szBuf[1024];
DWORD cchBuf = 1024; // or _countof(szBuf);
DWORD dwErr = MsiGetPropertyW(hInstall, L"UPGRADINGPRODUCTCODE", szBuf, &cchBuf);
if (dwErr != ERROR_MORE_DATA) {
    // exercise: increment cchBuf for null, adjust buffer, call MsiGetPropertyW again
}
if (dwErr != ERROR_SUCCESS) {
    // per https://docs.microsoft.com/en-us/windows/desktop/msi/custom-action-return-values
    return ERROR_INSTALL_FAILURE;
}

// reset buffer length for next call, in case second property is longer than first
cchBuf = 1024;
dwErr = MsiGetPropertyW(hInstall, L"WIX_UPGRADE_DETECTED", szBuf, &cchBuf);
// : : :

【讨论】:

  • 很好的解释。我只是想知道为什么要直接调用函数的 Unicode 版本?您不应该依赖MsiGetProperty 调用并让编译器和链接器完成其余的工作吗?例如,如果突然有新版本的 Unicode 的另一个功能? (ze trutz izt out zhere!)。 C++ 是令人惊讶的晦涩“管道”和样板代码景观,不是吗?我也不知道应该改用L"Text here" 还是ATL macrosL"" 是语言本身的一部分吗?很混乱。无论如何,另一个问题。感谢您对字符串缓冲区的详细解释。
  • @SteinÅsmul 肯定有专门的问题。就我自己而言,我通常只是在 VS 项目设置中指定 Unicode,并在我的代码中使用 TCHAR 和不带后缀的函数名称。在这里,我主要使用显式 WCHAR 和 ...W 函数来与原始代码进行对比。地平线上没有新的过渡,所以我会说明确是有效的但不寻常的。 L 字符串文字前缀是语言的一部分。在字符串文字中,L"" 和 TEXT("") 很好,而 CA2CW("") 或类似的东西是浪费工作;参见例如stackoverflow.com/q/13087219.
猜你喜欢
  • 2022-12-14
  • 1970-01-01
  • 2018-01-25
  • 2016-10-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-02
  • 1970-01-01
相关资源
最近更新 更多