【问题标题】:How best to define a custom action in WiX?如何最好地在 WiX 中定义自定义操作?
【发布时间】:2008-09-24 16:42:35
【问题描述】:

我有一个WiX 安装程序和一个使用安装程序属性的自定义操作(加上撤消和回滚)。在所有文件都在硬盘上之后,必须执行自定义操作。为此,您似乎需要 WXS 文件中的 16 个条目;根中的八个,如下所示:

<CustomAction Id="SetForRollbackDo" Execute="immediate" Property="RollbackDo" Value="[MYPROP]"/>
<CustomAction Id="RollbackDo" Execute="rollback" BinaryKey="MyDLL" DllEntry="UndoThing" Return="ignore"/>
<CustomAction Id="SetForDo" Execute="immediate" Property="Do" Value="[MYPROP]"/>
<CustomAction Id="Do" Execute="deferred" BinaryKey="MyDLL" DllEntry="DoThing" Return="check"/>
<CustomAction Id="SetForRollbackUndo" Execute="immediate" Property="RollbackUndo" Value="[MYPROP]"/>
<CustomAction Id="RollbackUndo" Execute="rollback" BinaryKey="MyDLL" DllEntry="DoThing" Return="ignore"/>
<CustomAction Id="SetForUndo" Execute="immediate" Property="Undo" Value="[MYPROP]"/>
<CustomAction Id="Undo" Execute="deferred" BinaryKey="MyDLL" DllEntry="UndoThing" Return="check"/>

InstallExecuteSequence 内还有八个,像这样:

<Custom Action="SetForRollbackDo" After="InstallFiles">REMOVE&lt;>"ALL"</Custom>
<Custom Action="RollbackDo" After="SetForRollbackDo">REMOVE&lt;>"ALL"</Custom>
<Custom Action="SetForDo" After="RollbackDo">REMOVE&lt;>"ALL"</Custom>
<Custom Action="Do" After="SetForDo">REMOVE&lt;>"ALL"</Custom>
<Custom Action="SetForRollbackUndo" After="InstallInitialize">REMOVE="ALL"</Custom>
<Custom Action="RollbackUndo" After="SetForRollbackUndo">REMOVE="ALL"</Custom>
<Custom Action="SetForUndo" After="RollbackUndo">REMOVE="ALL"</Custom>
<Custom Action="Undo" After="SetForUndo">REMOVE="ALL"</Custom>

有没有更好的办法?

【问题讨论】:

    标签: installation wix


    【解决方案1】:

    WiX 自定义操作是一个很好的模型。在这种情况下,您只需使用CustomAction 声明立即操作、延迟操作和回滚操作。您只需使用 Custom 安排即时操作,其中即时操作在本机 DLL 中作为代码实现。

    然后,在即时操作的代码中,您调用MsiDoAction 来安排回滚和延迟操作:由于它们被延迟,它们会在您调用MsiDoAction 时写入脚本中而不是立即执行。您还需要调用MsiSetProperty 来设置自定义操作数据。

    例如,下载 WiX 源代码并研究 IISExtension 的工作原理。 WiX 操作通常会解析自定义表并根据该表为延迟操作的属性生成数据。

    【讨论】:

      【解决方案2】:

      我在编写 WiX 安装程序时遇到了同样的问题。我解决问题的方法与 Mike 建议的差不多,我有一篇博文 Implementing WiX custom actions part 2: using custom tables

      简而言之,您可以为您的数据定义一个自定义表格:

      <CustomTable Id="LocalGroupPermissionTable">
          <Column Id="GroupName" Category="Text" PrimaryKey="yes" Type="string"/>
          <Column Id="ACL" Category="Text" PrimaryKey="no" Type="string"/>
          <Row>
              <Data Column="GroupName">GroupToCreate</Data>
              <Data Column="ACL">SeIncreaseQuotaPrivilege</Data>
          </Row>
      </CustomTable>
      

      然后编写一个即时自定义操作来安排延迟、回滚和提交自定义操作:

      extern "C" UINT __stdcall ScheduleLocalGroupCreation(MSIHANDLE hInstall)
      {
          try {
              ScheduleAction(hInstall,L"SELECT * FROM CreateLocalGroupTable", L"CA.LocalGroupCustomAction.deferred", L"create");
              ScheduleAction(hInstall,L"SELECT * FROM CreateLocalGroupTable", L"CA.LocalGroupCustomAction.rollback", L"create");
          }
          catch( CMsiException & ) {
              return ERROR_INSTALL_FAILURE;
          }
          return ERROR_SUCCESS;
      }
      

      以下代码显示了如何安排单个自定义操作。基本上你只需打开自定义表,读取你想要的属性(你可以通过调用 MsiViewGetColumnInfo() 获取任何自定义表的架构),然后将所需的属性格式化为 CustomActionData 属性(我使用/propname:value 的形式,尽管你可以使用任何你想要的东西)。

      void ScheduleAction(MSIHANDLE hInstall,
                  const wchar_t *szQueryString,
                  const wchar_t *szCustomActionName,
                  const wchar_t *szAction)
      {
          CTableView view(hInstall,szQueryString);
          PMSIHANDLE record;
      
          //For each record in the custom action table
          while( view.Fetch(record) ) {
              //get the "GroupName" property
              wchar_t recordBuf[2048] = {0};
              DWORD    dwBufSize(_countof(recordBuf));
              MsiRecordGetString(record, view.GetPropIdx(L"GroupName"), recordBuf, &dwBufSize);
      
              //Format two properties "GroupName" and "Operation" into
              //the custom action data string.
              CCustomActionDataUtil formatter;
              formatter.addProp(L"GroupName", recordBuf);
              formatter.addProp(L"Operation", szAction );
      
              //Set the "CustomActionData" property".
              MsiSetProperty(hInstall,szCustomActionName,formatter.GetCustomActionData());
      
              //Add the custom action into installation script. Each
              //MsiDoAction adds a distinct custom action into the
              //script, so if we have multiple entries in the custom
              //action table, the deferred custom action will be called
              //multiple times.
              nRet = MsiDoAction(hInstall,szCustomActionName);
          }
      }
      

      至于实现deferred、rollback和commit自定义动作,我更喜欢只用一个函数,用MsiGetMode()来区分应该做什么:

      extern "C" UINT __stdcall LocalGroupCustomAction(MSIHANDLE hInstall)
      {
          try {
              //Parse the properties from the "CustomActionData" property
              std::map<std::wstring,std::wstring> mapProps;
              {
                  wchar_t szBuf[2048]={0};
                  DWORD dwBufSize = _countof(szBuf); MsiGetProperty(hInstall,L"CustomActionData",szBuf,&dwBufSize);
                  CCustomActionDataUtil::ParseCustomActionData(szBuf,mapProps);
              }
      
              //Find the "GroupName" and "Operation" property
              std::wstring sGroupName;
              bool bCreate = false;
              std::map<std::wstring,std::wstring>::const_iterator it;
              it = mapProps.find(L"GroupName");
              if( mapProps.end() != it ) sGroupName = it->second;
              it = mapProps.find(L"Operation");
              if( mapProps.end() != it )
                  bCreate = wcscmp(it->second.c_str(),L"create") == 0 ? true : false ;
      
              //Since we know what opeartion to perform, and we know whether it is
              //running rollback, commit or deferred script by MsiGetMode, the
              //implementation is straight forward
              if( MsiGetMode(hInstall,MSIRUNMODE_SCHEDULED) ) {
                  if( bCreate )
                      CreateLocalGroup(sGroupName.c_str());
                  else
                      DeleteLocalGroup(sGroupName.c_str());
              }
              else if( MsiGetMode(hInstall,MSIRUNMODE_ROLLBACK) ) {
                  if( bCreate )
                      DeleteLocalGroup(sGroupName.c_str());
                  else
                      CreateLocalGroup(sGroupName.c_str());
              }
          }
          catch( CMsiException & ) {
              return ERROR_INSTALL_FAILURE;
          }
          return ERROR_SUCCESS;
      }
      

      通过使用上述技术,对于典型的自定义操作集,您可以将自定义操作表减少到五个条目:

      <CustomAction Id="CA.ScheduleLocalGroupCreation"
                    Return="check"
                    Execute="immediate"
                    BinaryKey="CustomActionDLL"
                    DllEntry="ScheduleLocalGroupCreation"
                    HideTarget="yes"/>
      <CustomAction Id="CA.ScheduleLocalGroupDeletion"
                    Return="check"
                    Execute="immediate"
                    BinaryKey="CustomActionDLL"
                    DllEntry="ScheduleLocalGroupDeletion"
                    HideTarget="yes"/>
      <CustomAction Id="CA.LocalGroupCustomAction.deferred"
                    Return="check"
                    Execute="deferred"
                    BinaryKey="CustomActionDLL"
                    DllEntry="LocalGroupCustomAction"
                    HideTarget="yes"/>
      <CustomAction Id="CA.LocalGroupCustomAction.commit"
                    Return="check"
                    Execute="commit"
                    BinaryKey="CustomActionDLL"
                    DllEntry="LocalGroupCustomAction"
                    HideTarget="yes"/>
      <CustomAction Id="CA.LocalGroupCustomAction.rollback"
                    Return="check"
                    Execute="rollback"
                    BinaryKey="CustomActionDLL"
                    DllEntry="LocalGroupCustomAction"
                    HideTarget="yes"/>
      

      而InstallSquence表中只有两个条目:

      <InstallExecuteSequence>
          <Custom Action="CA.ScheduleLocalGroupCreation" 
                  After="InstallFiles">
              Not Installed
          </Custom>
          <Custom Action="CA.ScheduleLocalGroupDeletion" 
                  After="InstallFiles">
              Installed
          </Custom>
      </InstallExecuteSequence>
      

      此外,只要稍加努力,大部分代码都可以编写为可重用(例如从自定义表中读取、获取属性、格式化所需属性并设置为 CustomActionData 属性),以及自定义操作中的条目表现在不是特定于应用程序的(特定于应用程序的数据写入自定义表中),我们可以将自定义操作表放在自己的文件中,然后将其包含在每个 WiX 项目中。

      对于自定义动作 DLL 文件,由于应用程序数据是从自定义表中读取的,我们可以将应用程序特定的细节保留在 DLL 实现之外,因此自定义动作表可以成为一个库,从而更容易重用。

      这是我目前编写 WiX 自定义操作的方式,如果有人知道如何进一步改进,我将不胜感激。 :)

      (您也可以在我的博文中找到完整的源代码,Implementing Wix custom actions part 2: using custom tables。)。

      【讨论】:

        【解决方案3】:

        如果您有需要支持回滚的复杂自定义操作,您可以考虑编写一个 Wix 扩展。扩展通常提供创作支持(即映射到 MSI 表条目的新 XML 标记),以及自定义操作的自动调度。

        这不仅仅是编写自定义操作,但一旦您的 CA 达到一定程度的复杂性,扩展提供的易于创作就值得了。

        【讨论】:

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