如果目标文件夹中存在先前每个用户,则停止每台计算机WiX安装

时间:2012-12-03 12:51:29

标签: wix

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

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

如果涉及WiX代码,请提供小例子。我会从中学习。

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

在我天真的时候,我也尝试在某些自定义操作中使用SetProperty读取前一段中提到的属性值,但这也不起作用。该值可以正常读取,但它是在安装开始时确定的值。

正确的解决方案应该包括给another question的答案。我现在正在使用JScript,根据WiX tricks community wiki给出的提示。在Rob Mensching的blog post中提到,可能会成为避免反病毒干扰的.dll。

2 个答案:

答案 0 :(得分:2)

无论如何都无法自动卸载。主要升级无法从每用户转换为每安装,并且由于互斥注意事项和范围考虑因素(每用户安装可能是不同的用户),您无法在安装期间直接调用卸载。

因为安装可以在另一个用户上下文中完成,所以检查元状态的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,此条件将阻止安装。它不会阻止后续的修复和卸载。

但是,这仅适用于次要升级。对于Major Upgrades,ProductCode已经更改,从安装程序的角度来看,即使以前的版本也没有安装。在这种情况下,你需要说出类似的内容。

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

答案 1 :(得分:0)

这就是我最终如何做到的。我正在调用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);
    }
}