Как лучше определить пользовательское действие в WiX? - PullRequest
13 голосов
/ 24 сентября 2008

У меня есть WiX установщик и одно пользовательское действие (плюс отмена и откат) для него, которое использует свойство из установщика. Настраиваемое действие должно произойти после того, как все файлы будут на жестком диске. Похоже, вам нужно 16 записей в файле WXS для этого; восемь в корне, вот так:

<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>

Есть ли лучший способ?

Ответы [ 3 ]

4 голосов
/ 30 сентября 2008

Пользовательские действия WiX - отличная модель для подражания. В этом случае вы только объявляете с CustomAction немедленное действие, отложенное действие и действие отката. Вы только планируете, с Custom, немедленное действие, где немедленное действие реализуется как код в собственной DLL.

Затем, в коде немедленного действия, вы вызываете MsiDoAction, чтобы запланировать откат и отложенные действия: когда они откладываются, они записываются в сценарий в точке, которую вы скорее вызываете MsiDoAction чем выполняется немедленно. Вам также нужно будет позвонить MsiSetProperty, чтобы установить данные пользовательских действий.

Загрузите исходный код WiX и изучите, например, как работает IISExtension. Действия WiX обычно анализируют пользовательскую таблицу и генерируют данные для свойства отложенного действия на основе этой таблицы.

3 голосов
/ 28 сентября 2008

Если у вас есть сложные пользовательские действия, которые должны поддерживать откат, вы можете подумать о написании расширения Wix. Расширения обычно предоставляют поддержку разработки (то есть новые теги XML, которые отображаются на записи таблицы MSI), а также автоматическое планирование пользовательских действий.

Это больше, чем просто написание настраиваемого действия, но как только ваши CA достигнут определенного уровня сложности, простота разработки, предоставляемая расширениями, может стоить того.

2 голосов
/ 16 июня 2011

Я сталкивался с той же проблемой при написании установщиков WiX. Мой подход к проблеме в основном похож на то, что предложил Майк, и у меня есть запись в блоге Реализация пользовательских действий WiX, часть 2: использование пользовательских таблиц .

Короче говоря, вы можете определить пользовательскую таблицу для ваших данных:

<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);
    }
}

Что касается реализации отложенных, откатных и фиксированных пользовательских действий, я предпочитаю использовать только одну функцию и использовать 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, если кто-то знает, как улучшить дальше, я был бы очень признателен. :)

(Вы также можете найти полный исходный код в моем сообщении в блоге, Реализация пользовательских действий Wix, часть 2: использование пользовательских таблиц .).

...