【问题标题】:Stop per machine WiX installation if previous per user exist in target folder如果目标文件夹中存在以前的每个用户,则停止每台机器的 WiX 安装
【发布时间】:2012-12-13 00:09:18
【问题描述】:

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

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

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

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

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

正确的解决方案可能应该包括给another question 的答案。根据WiX tricks community wiki 中给出的提示,我目前正在玩弄 JScript。 Rob Mensching 的blog post 中提到,可能会将其设为 .dll 以避免防病毒干预。

【问题讨论】:

    标签: wix


    【解决方案1】:

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

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

    这意味着您只剩下 AppSearch 来查找您的文件。如果找到,像这样的 sn-p 将使用 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>
    

    【讨论】:

    • 感谢您的回答。它很有用,但它不是我的解决方案。我已经更新了这个问题,并将继续努力。
    • 您可以通过将 CA 创作为控制事件来保持 CA 的简单性,该事件在选择目标后在“下一步”按钮上运行,并在找到 DLL 时设置属性。然后,如果表达式的计算结果为真,则可以有另一个带有执行 SpawnDialog(模态)条件的控制事件。您还希望在 UI 中对 CA 和执行序列进行类似的调度以及类型 19 错误自定义操作,以在静默安装期间执行相同的保护。抱歉,在回答狭窄问题时需要涵盖很多内容。
    【解决方案2】:

    这就是我最后的做法。我正在调用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);
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多