【问题标题】:Error 2769 when Upgrading the Installer升级安装程序时出现错误 2769
【发布时间】:2015-08-04 05:54:23
【问题描述】:

我创建了一个安装程序,它将调用两个自定义操作。第一个自定义操作是WRITEFILETODISK,它将根据传递给安装程序的参数创建一个配置文件并将其存储在%ProgramData%\SomeFolder 中。第二个自定义操作是 ResidueRemove,它将清除 %ProgramData%\SomeFolder 上的任何残留物。

以下是我的 Wix 文件中自定义操作的 sn-p:

<Binary Id="SetupCA"  SourceFile="..\..\ext_library\SetupCACPP\SetupCACPP\bin\Release\SetupCACPP.dll"/>
<CustomAction Id="WRITEFILETODISK" Execute="immediate" BinaryKey="SetupCA" DllEntry="WriteFileToDisk" />
<CustomAction Id="ResidueRemove" Execute="immediate" BinaryKey="SetupCA" DllEntry="DeleteResidue" />
<InstallExecuteSequence>
  <Custom Action="WRITEFILETODISK" After="InstallFinalize">NOT Installed</Custom>
  <Custom Action="ResidueRemove" After="InstallFinalize">REMOVE~="ALL"</Custom>
  <!-- Rob Menshing Answer @ http://stackoverflow.com/a/321874/2634612 -->
</InstallExecuteSequence>

ResidueRemove 自定义操作已添加到此处:

    UINT __stdcall DeleteResidue(MSIHANDLE hInstall)
{
    HRESULT hr = S_OK;
    UINT er = ERROR_SUCCESS;
    SC_HANDLE sHandleLPA, sHandleLPAM;
    SERVICE_STATUS servStatusLPA, servStatusLPAM;
    SHFILEOPSTRUCT shFileCommFolder, shFileInstalledFolder;
    LPWSTR lpaCommFolder, lpaInstalledFolder;
    std::wstring wlpaCommFolder, wlpaInstalledFolder;
    std::string errorCode;
    hr = WcaInitialize(hInstall, "DeleteResidue");
    ExitOnFailure(hr, "Failed to initialize");

    //Stop the LPA and LPA Monitor Service. Then delete the residue.
    WcaLog(LOGMSG_STANDARD, "Doing Delete Residue"); // Fails after printing this log

    hr = WcaGetProperty(L"LPCOMMAPPFOLDER",&lpaCommFolder);
    ExitOnFailure(hr, "Failure in Common Folder"); // Getting the %ProgramData%\SomeFolder here

    hr = WcaGetProperty(L"INSTALLFOLDER",&lpaInstalledFolder);
    ExitOnFailure(hr, "Failure in getting Installed Folder"); // Getting Installed Folder here

    //Path should be double Terminated for SHFILEOPSTRUCT.pFrom
    wlpaCommFolder = std::wstring(lpaCommFolder);
    wlpaCommFolder.push_back(L'\0');

    wlpaInstalledFolder = std::wstring(lpaInstalledFolder);
    wlpaInstalledFolder.push_back(L'\0');

    try
    {
        sHandleLPAM = OpenSCManager(NULL,NULL, SC_MANAGER_ALL_ACCESS);
        if(sHandleLPAM == NULL)
        {
            WcaLog(LOGMSG_STANDARD, "OpenSCManager NULL Handle");
        }
        sHandleLPAM = OpenService(sHandleLPAM, L"SomeServiceOne",SERVICE_ALL_ACCESS);
        if(sHandleLPAM == NULL)
        {
            WcaLog(LOGMSG_STANDARD, "OpenService NULL Handle");
        }
        bool res = ControlService(sHandleLPAM, SERVICE_CONTROL_STOP,(LPSERVICE_STATUS) &servStatusLPAM);
        if(!res)
        {
            WcaLog(LOGMSG_STANDARD, "ControlService Cannot Stop the service");
        }
        // Free the service Handler
        CloseServiceHandle(sHandleLPAM);
    }
    catch(std::exception& e)
    {
        WcaLog(LOGMSG_STANDARD, e.what());
    }

    try
    {
        sHandleLPA = OpenSCManager(NULL,NULL, SC_MANAGER_ALL_ACCESS);
        if(sHandleLPA == NULL)
        {
            WcaLog(LOGMSG_STANDARD, "OpenSCManager NULL Handle");
        }
        sHandleLPA = OpenService(sHandleLPA, L"SomeServiceTwo",SERVICE_ALL_ACCESS);
        if(sHandleLPA == NULL)
        {
            WcaLog(LOGMSG_STANDARD, "OpenService NULL Handle");
        }
        bool res = ControlService(sHandleLPA, SERVICE_CONTROL_STOP,(LPSERVICE_STATUS) &servStatusLPA);
        if(!res)
        {
            WcaLog(LOGMSG_STANDARD, "ControlService Cannot Stop the service");
        }
        // Free the service Handler
        CloseServiceHandle(sHandleLPA);
    }
    catch(std::exception& e)
    {
        WcaLog(LOGMSG_STANDARD, e.what());
    }

    ZeroMemory(&shFileCommFolder, sizeof(SHFILEOPSTRUCT));
    shFileCommFolder.hwnd = NULL;
    shFileCommFolder.wFunc = FO_DELETE;
    shFileCommFolder.pFrom = wlpaCommFolder.c_str();
    shFileCommFolder.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_NOERRORUI;
    BOOL res = DirectoryExists(lpaCommFolder);
    if(res)
    {
        WcaLog(LOGMSG_STANDARD, "The directory exist");
        int result = SHFileOperation(&shFileCommFolder);
        if(!result)
            WcaLog(LOGMSG_STANDARD, "The directory should have deleted by now");
        else
        {
            errorCode = GetLastErrorStdStr();
            WcaLog(LOGMSG_STANDARD, "The directory could not be deleted %s", errorCode);
        }

    }       
    else
    {
        WcaLog(LOGMSG_STANDARD, "It Seems the Installed Folder is No more there");
    }

    ZeroMemory(&shFileInstalledFolder, sizeof(SHFILEOPSTRUCT));
    shFileInstalledFolder.hwnd = NULL;
    shFileInstalledFolder.wFunc = FO_DELETE;
    shFileInstalledFolder.pFrom = wlpaInstalledFolder.c_str();
    shFileInstalledFolder.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_NOERRORUI;
    res = DirectoryExists(lpaInstalledFolder);
    if(res)
    {
        WcaLog(LOGMSG_STANDARD, "The directory exist");
        int result = SHFileOperation(&shFileInstalledFolder);
        if(!result)
            WcaLog(LOGMSG_STANDARD, "The directory should have deleted by now");
        else
        {
            errorCode = GetLastErrorStdStr();
            WcaLog(LOGMSG_STANDARD, "The directory could not be deleted %s", errorCode);
        }
    }       
    else
    {
        WcaLog(LOGMSG_STANDARD, "It Seems the Installed Folder is No more there");
    }


LExit:
    er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
    return WcaFinalize(er);
}

日志Delete Residue 后出现错误,即:

DeleteResidue:  Doing Delete Residue
MSI (s) (64:A4) [10:54:51:643]: Leaked MSIHANDLE (11) of type 790541 for thread 980
MSI (s) (64:A4) [10:54:51:643]: Note: 1: 2769 2: ResidueRemove 3: 1 
MSI (s) (64:A4) [10:54:51:643]: Note: 1: 2205 2:  3: Error 
MSI (s) (64:A4) [10:54:51:643]: Note: 1: 2228 2:  3: Error 4: SELECT `Message` FROM `Error` WHERE `Error` = 2769 
DEBUG: Error 2769:  Custom Action ResidueRemove did not close 1 MSIHANDLEs.
The installer has encountered an unexpected error installing this package. This may indicate a problem with this package. The error code is 2769. The arguments are: ResidueRemove, 1, 
Action ended 10:54:51: ResidueRemove. Return value 3.
Action ended 10:54:51: INSTALL. Return value 3.

在(非英语)德语 Windows 中完成安装程序升级时会复制此内容。经过一些研究,我发现The InstallExecuteSequence may have been authored incorrectly. Actions that change the system must be sequenced between the InstallInitialize and InstallFinalize actions. Perform package validation and check for ICE77. 这是否意味着我的InstallExecuteSequence 是错误的?第一个 CA WriteFileToDisk 应该在安装之后和服务启动之前运行(因为可执行文件依赖于文件)。第二个 CA 应该清除残留物,卸载完成后停止服务。我的代码有什么问题?任何帮助表示赞赏。

【问题讨论】:

    标签: windows wix windows-installer wix3.8


    【解决方案1】:

    首先,您尝试使用即时模式自定义操作来更改系统状态。这是不可取的,并且违反了 Windows 安装程序的最佳做法。它可能有效,也可能无效(取决于您的环境)。 我的第一个建议是将这些操作更改为延迟模式自定义操作,因为您正在更改系统的状态。

    我看不到您的整个自定义操作代码,无法知道您在停止服务并删除名为:DeleteResidue 的函数中的文件夹到底是做什么的。我真的不知道您的自定义操作在什么情况下会失败并返回失败。但是,我在下面所说的内容可能会为您提供解决问题的提示。

    我猜您使用 Id= ResidueRemove 的自定义操作失败的原因是您试图在 InstallFinalize 之后以即时模式自定义操作停止服务或清理文件夹。当您尝试停止这些服务时,您的服务/文件夹可能不再存在于系统中。在 InstallFinalize 之前执行许多标准操作来完成清理已安装产品的任务。 这些标准操作先于您的即时模式自定义操作。当您使用 Id= ResidueRemove 执行自定义操作时,您已安装的文件将不存在。它们都将被清除。您尝试清理的文件夹也可能不再存在。

    为了更好地理解 Windows 安装程序序列和不同的自定义操作类型,我确实建议阅读以下文章:

    http://www.installsite.org/pages/en/isnews/200108/

    以下是建议的更改 - 将 CustomAction Id="WRITEFILETODISK" 更改为延迟模式自定义操作。 安排它在标准操作之后运行:“InstallFiles”但在“StartServices”标准操作之前运行

    如果您想查看 Windows 安装程序标准操作的执行顺序,请从 Windows 安装程序 SDK 安装名为“Orca”的 Windows 安装程序编辑器。检查“InstallExecuteSequence”表的内容。按“序列”列对操作进行排序。 在这里,您将看到标准操作“InstallFiles”的序列号低于标准操作:“StartFiles”。

    您现在将大致了解您可能希望在哪里对您的操作进行排序。

    -对于自定义操作="ResidueRemove",类似地,将其标记为延迟模式自定义操作。 检查“InstallExecuteSequence”表以了解您可能希望对其进行排序的位置。

    清理已安装文件和文件夹的标准操作分别是 :RemoveFiles 和 RemoveFolders。 同样,还有一个用于删除服务的名称为 :DeleteServices。

    以下是您将如何使用它:

    <!--For each deferred action where you want to access public property values, you need to use type 51 set a property custom action and sequence them in the InstallExecute sequence-->
      <!--The name of the property in the type 51 custom action should be the name of the **deferred mode** custom action where you want to access the value-->
      <!--The value of the type 51 custom action should be the value(s) you wan to access-->
      <!--In case, you need multiple property values to be accessed, seperate the values using a delimiter such as a semicolon-->
      <CustomAction Id="SetProperty_WRITEFILETODISK" Property="WRITEFILETODISK" Value="[LPCOMMAPPFOLDER];[INSTALLFOLDER]" Execute="immediate"/>
      <CustomAction Id="WRITEFILETODISK" Execute="deferred" BinaryKey="SetupCA" DllEntry="WriteFileToDisk" />
    
      <CustomAction Id="SetProperty_ResidueRemove" Property="ResidueRemove" Value="[LPCOMMAPPFOLDER];[INSTALLFOLDER]" Execute="immediate" />
      <CustomAction Id="ResidueRemove" Execute="deferred" BinaryKey="SetupCA" DllEntry="DeleteResidue" />
    
      <Property Id="LPCOMMAPPFOLDER" Secure="yes" />
      <Property Id="IPADDRESS" Secure="yes" />
      <Property Id="SSL" Secure="yes" />
    
      <CustomAction Id="SetCommonFolder" Property="CheckCommonFolder" Value="[LPCOMMAPPFOLDER]" />
      <CustomAction Id="SetIPExample" Property="CheckIP" Value="[IPADDRESS]" />
      <CustomAction Id="SetSSLExample" Property="CheckSSL" Value="[SSL]" />
      <InstallExecuteSequence>
        <Custom Action="SetCommonFolder" After="ValidateProductID" />
        <Custom Action="SetIPExample" After="ValidateProductID" />
        <Custom Action="SetSSLExample" After="ValidateProductID" />
    
        <Custom Action="SetProperty_WRITEFILETODISK" Before="WRITEFILETODISK">NOT Installed</Custom>
        <Custom Action="WRITEFILETODISK" Before="InstallServices">NOT Installed</Custom>
        <Custom Action="SetProperty_ResidueRemove" Before="ResidueRemove">REMOVE~="ALL"</Custom>
        <Custom Action="ResidueRemove" After="RemoveFiles">REMOVE~="ALL"</Custom>
        <!-- Rob Menshing Answer @ http://stackoverflow.com/a/321874/2634612 -->
      </InstallExecuteSequence>
    

    此外,在您的自定义操作代码中,您将访问属性“CustomActionData”,而不是访问属性 LPCOMMAPPFOLDER、INSTALLFOLDER。自定义操作数据的值将是您在类型 51 自定义操作中设置的值。

    另外,我认为您不需要那些 SetCommonFolder 等自定义操作。他们服务的目的是什么?我们有我用过的属性标签。

    希望这会有所帮助。

    【讨论】:

    • 我已经更新了代码。你能抢到它吗?
    • 如果我将执行更改为deferred,我不会将Properties 传递给安装程序。因此WriteFileToDisk 无法创建正确的配置文件。我也需要创建回滚操作。我应该如何处理?
    • 要访问延迟操作中的公共属性的值,您需要使用一个名为:CustomActionData 的特殊 Windows 安装程序属性。看看:firegiant.com/wix/tutorial/events-and-actions/at-a-later-stage。对于每个自定义操作,您需要一个类型 51(设置属性自定义操作)才能访问属性值。回滚自定义操作通常在尝试回滚的延迟操作之前。对于回滚操作,请记住将“执行”属性的值设置为“回滚”
    • 安装程序应该安装为msiexec /i installer.msi IP="1.1.1.1" PORT="8000" 延迟操作无法获得IPPORTINSTALLFODLER。如何设置延迟 CA 的值?
    • 您是否确保这些属性安全?否则,这些公共属性的值将不会传递给 Execute 序列。如上所述,如果要访问延迟自定义操作中的属性值,则需要使用 CustomActionData。 firegiant.com/wix/tutorial/events-and-actions/at-a-later-stage 提供了一个示例,说明如何访问延迟执行操作中的属性值。
    猜你喜欢
    • 1970-01-01
    • 2014-08-21
    • 1970-01-01
    • 2011-06-30
    • 1970-01-01
    • 1970-01-01
    • 2021-12-06
    • 2019-07-17
    • 1970-01-01
    相关资源
    最近更新 更多