1

我的产品之前的安装是在 Visual Studio 2008 中作为部署项目 (vdproj) 完成的。用户可以选择按用户(只有我)或按机器(每个人)安装。我正在 WiX 中进行新安装,如果每台机器都安装了产品,那么新安装将卸载以前的版本。但是,如果每个用户在 %ProgramFiles% 中安装了产品,那么新安装将不会卸载以前的安装,从而在同一个文件夹中造成两个安装的混乱。

我想在这种情况下检测并停止安装。没有自动卸载,只需检测并停止。如果一个用户在之前的每个用户安装,而另一个用户在每台机器上做新的,则解决方案应该处理这种情况。我认为可以使用元素,但它们在 UI 中确定目标文件夹之前运行DirectorySearchFileSearch

如果涉及 WiX 代码,请提供小示例。我会从中吸取教训。

编辑:如果我使用 AppSearch 功能添加属性DirectorySearchFileSearch然后使用Condition元素检查条件,这将在安装启动时检查条件。这不是我想要的。用户可能会更改目标文件夹,因此我需要在目标文件夹无法再更改后的某个步骤中检查目标文件夹中是否存在文件。

在我的天真中,我还尝试通过SetProperty一些自定义操作来读取上一段中提到的属性值,但这也不起作用。该值读取正常,但它是在设置开始时确定的值。

正确的解决方案可能应该包括对另一个问题的回答。根据WiX 技巧社区 wiki中给出的提示,我目前正在玩弄 JScript 。Rob Mensching 的文中提到,可能会将其设为 .dll 以避免防病毒干预。

4

2 回答 2

3

无论如何,自动卸载是不可能的。主要升级不能从 Per-User 过渡到 Per-Install,并且由于 mutex 考虑和范围考虑(每用户安装可能是不同的用户),无论如何您都不能在安装期间简单地调用卸载。

因为安装可能在另一个用户上下文中完成,用于检查元状态的 MSI API 调用(如 ComponentSearch )将不起作用。

这意味着您只剩下 AppSearch 来查找您的文件。如果找到,这样的代码片段将使用 MyFile.dll 的完整路径填充属性 FOUNDMYFILE:

<Property Id="FOUNDMYFILE">
  <DirectorySearch Id="FindMyFile" Path="[ProgramFilesFolder]MyCompany\MyProduct">
    <FileSearch Name="MyFile.dll"/>
  </DirectorySearch>
</Property>

如果找到 MyFile.dll 并且您的产品尚未安装,接下来要做的就是阻止安装。

<Condition Message="[ProductName] cannot install due to prexisting
per-user installation.">FOUNDMYFILE and Not Installed</Condition>

现在,如果您没有安装但找到了 DLL,这种情况将阻止安装。它不会阻止后续的修复和卸载。

但是,这仅适用于次要升级。对于重大升级,ProductCode 已更改,从安装程序的角度来看,即使以前的版本已安装,它也未安装。在这种情况下,您需要说类似的话。

<Condition Message="[ProductName] cannot install due to prexisting
 per-user installation.">FOUNDMYFILE and Not MAJORUPGRADEPROPERTY and
 Not Installed</Condition>
于 2012-12-03T13:53:40.843 回答
1

这就是我最终如何做到的。我正在调用排队的自定义操作RemoveExistingProducts

<Property Id='ERRORIFFILESFOUND_MESSAGE'>!(loc.OlderVersionNotRemoved)</Property>

<CustomAction Id="CA.ErrorIfFilesFound"
  BinaryKey="SetupSupport.dll" DllEntry="ErrorIfFilesFound"
  Execute="immediate" Return="check" />

<CustomAction Id='CA.ErrorIfFilesFound.SetDirectory' 
  Property='ERRORIFFILESFOUND_DIRECTORY' Value='[INSTALLDIR]' />

<InstallExecuteSequence>
  <Custom Action='CA.ErrorIfFilesFound.SetDirectory' After='RemoveExistingProducts' />
  <Custom Action='CA.ErrorIfFilesFound' After='CA.ErrorIfFilesFound.SetDirectory'>
    Not MAJORUPGRADEPROPERTY and Not Installed
  </Custom>
</InstallExecuteSequence>

在此之后,如果可能的话,旧的安装会被删除,所以如果目标文件夹中还有文件,那么就有问题了。这一刻发生得相当晚,但我不知道有一种方法可以检测是否要卸载现有文件。自定义操作中的代码仅检查目标文件夹是否存在且不为空,如果存在则调用它退出(ExitOnFailure为简洁起见,删除了错误处理):

UINT WINAPI ErrorIfFilesFound(MSIHANDLE hInstall)
{
    HRESULT hr = S_OK;
    LPWSTR pwszDirectory = NULL;
    LPWSTR pwszMessage = NULL;

    hr = ::WcaInitialize(hInstall, "ErrorIfFindFile");

    hr = ::WcaGetProperty(L"ERRORIFFILESFOUND_DIRECTORY", &pwszDirectory);

    if (PathIsDirectory(pwszDirectory) && !PathIsDirectoryEmpty(pwszDirectory))
    {
        hr = ::WcaGetProperty(L"ERRORIFFILESFOUND_MESSAGE", &pwszMessage);

        ProcessInstallMessage(hInstall,
            INSTALLMESSAGE_ERROR, pwszMessage, pwszDirectory);

        hr = -1;
    }

LExit:
    ReleaseStr(pwszMessage);
    ReleaseStr(pwszDirectory);

    UINT err = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
    return ::WcaFinalize(err);
}

void ProcessInstallMessage(MSIHANDLE hInstall,
    INSTALLMESSAGE type, TCHAR* lpszMessage, TCHAR* lpszParam)
{
    UINT rc;
    MSIHANDLE hMsg;
    UINT uiFieldNumber = lpszParam == NULL ? 0 : 1;

    hMsg = MsiCreateRecord(uiFieldNumber);

    if (hMsg != 0)
    {
        if ((rc = MsiRecordSetString(hMsg, 0, lpszMessage)) != ERROR_SUCCESS)
        {}
        else if (lpszParam != NULL &&
            (rc = MsiRecordSetString(hMsg, 1, lpszParam)) != ERROR_SUCCESS)
        {}
        else
        {
            rc = MsiProcessMessage(hInstall, type, hMsg);
        }

        rc = MsiCloseHandle(hMsg);
    }
}
于 2012-12-12T16:09:18.687 回答