Проекты установщика VS - выполнить некоторый код после установки

У меня есть Visual Studio 2017, и я только что добавил расширение Проекты установщика Microsoft Visual Studio. Проект, который я хочу добавить в установщик, представляет собой приложение службы Windows, созданное Topshelf.

Чтобы вручную установить службу Windows, я открываю окно CMD от имени администратора и запускаю команду в папке приложения (например, bin/debug), чтобы установить приложение службы Windows.

Проблема

Теперь я точно знаю, что у установщика нет удобной функции для установки Сервиса, поскольку единственное, что он делает (просто говоря), — это помещает некоторые файлы в целевой каталог.

Итак, мой вопрос

Есть ли у установщика возможность запустить какой-либо код после завершения установки? Если нет, то какие есть альтернативы?


person OZ_CM    schedule 31.12.2019    source источник
comment
Поделитесь, пожалуйста, как вы собрали установщик   -  person Pavel Anikhouski    schedule 31.12.2019
comment
Вы можете создавать библиотеки DLL, которые могут вызываться установщиком в определенные моменты установки, в том числе после установки. К этим библиотекам DLL предъявляются очень специфические требования, и они не могут вызывать многие обычные API-интерфейсы Windows.   -  person Adrian Mole    schedule 31.12.2019
comment
@PavelAnikhouski Аниховский, я не создавал установщик, это расширение.   -  person OZ_CM    schedule 31.12.2019
comment
Хорошо, я нашел способ сделать это! Я опубликую ответ достаточно скоро!   -  person OZ_CM    schedule 31.12.2019


Ответы (2)


Вы можете добавить «Пользовательские действия» на различные этапы вашего проекта установщика. Щелкните правой кнопкой мыши проект в обозревателе решений и выберите «Просмотр -> Пользовательские действия». Затем вы можете добавить DLL (которую вы построили — см. ниже — и установили на целевой ПК) для «выполнения» на различных этапах процесса.

Когда вы добавили DLL, вам нужно указать процедуру «Точка входа» (например, MSR202030202 в приведенном ниже примере) и (необязательно) «Данные настраиваемого действия» для передачи этой процедуре ([TARGETDIR] — каталог установки — в образец кода).

Когда установщик достигнет этапа, на котором вы добавили DLL, он вызовет указанную точку входа с доступными данными.

Ниже приведен пример процедуры DLL, которая будет выполняться на этапе «Зафиксировать» установки. В этом примере будет сравниваться версия VC-Redistributable в целевом каталоге с любой текущей установленной и «порождающей» версией, если наша версия новее. Это довольно большой кусок кода, но мы надеемся, что он даст вам некоторое представление о том, что требуется.

extern "C" uint32_t __declspec(dllexport) __stdcall MSR202030202(MSIHANDLE hInst)
{
    // First (default) action: Get the location of the installation (the destination folder) into "dstPath" variable...
    DWORD dstSize = MAX_PATH; wchar_t dstPath[MAX_PATH]; MsiGetProperty(hInst, L"CustomActionData", dstPath, &dstSize);
    CString regPath = L"SOFTWARE\\Microsoft\\VisualStudio\\14.0\\VC\\RunTimes\\";
    #if defined(_M_X64)
    wchar_t vcrFile[] = L"vc_redist.x64.exe";   regPath += L"x64";
    #elif defined(_M_IX86)
    wchar_t vcrFile[] = L"vc_redist.x86.exe";   regPath += L"x86";
    #endif
    CString vcrPath = CString(dstPath) + vcrFile;
    // Get the version numbers of the VC Runtime Redistributable currently installed (from the registry) ...
    WORD rVer[4] = { 0, 0, 0, 0 };
    HKEY hKey;  DWORD gotSize, gotType, gotData;
    if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, regPath, 0, KEY_READ, &hKey) == ERROR_SUCCESS) {
        gotSize = sizeof(DWORD);
        if (RegQueryValueEx(hKey, L"Major", nullptr, &gotType, reinterpret_cast<BYTE *>(&gotData), &gotSize) == 0) {
            if ((gotType == REG_DWORD) && (gotSize == sizeof(DWORD))) rVer[0] = LOWORD(gotData);
        }
        gotSize = sizeof(DWORD);
        if (RegQueryValueEx(hKey, L"Minor", nullptr, &gotType, reinterpret_cast<BYTE *>(&gotData), &gotSize) == 0) {
            if ((gotType == REG_DWORD) && (gotSize == sizeof(DWORD))) rVer[1] = LOWORD(gotData);
        }
        gotSize = sizeof(DWORD);
        if (RegQueryValueEx(hKey, L"Bld", nullptr, &gotType, reinterpret_cast<BYTE *>(&gotData), &gotSize) == 0) {
            if ((gotType == REG_DWORD) && (gotSize == sizeof(DWORD))) rVer[2] = LOWORD(gotData);
        }
        gotSize = sizeof(DWORD);
        if (RegQueryValueEx(hKey, L"Rbld", nullptr, &gotType, reinterpret_cast<BYTE *>(&gotData), &gotSize) == 0) {
            if ((gotType == REG_DWORD) && (gotSize == sizeof(DWORD))) rVer[3] = LOWORD(gotData);
        }
        RegCloseKey(hKey);
    }
    // Get the version numbers of the VC Redistributable provided with this installation (from the file) ...
    WORD fVer[4] = { 0, 0, 0, 0 };
    if (_waccess(vcrPath, 0) == 0) {
        DWORD vHand, vSize = GetFileVersionInfoSize(vcrPath.GetString(), &vHand);
        VS_FIXEDFILEINFO *vInfo = nullptr;  unsigned vSiz2 = 0u;
        if (vSize > 0) {
            BYTE* vBuff = new BYTE[vSize]; vHand = 0;
            BOOL vStat = GetFileVersionInfo(vcrPath.GetString(), vHand, vSize, vBuff);
            if (vStat) vStat = VerQueryValue(vBuff, L"\\", reinterpret_cast<void **>(&vInfo), &vSiz2);
            if (vStat && (vInfo != nullptr)) {
                DWORD msdw = vInfo->dwFileVersionMS, lsdw = vInfo->dwFileVersionLS;
                fVer[0] = HIWORD(msdw);
                fVer[1] = LOWORD(msdw);
                fVer[2] = HIWORD(lsdw);
                fVer[3] = LOWORD(lsdw);
            }
            delete[] vBuff;
        }
    }
    // Compare the two sets of version numbers to see if we need to run the installer ...
    bool hasVCR = false;
    if (rVer[0] > fVer[0]) hasVCR = true;
    else if (rVer[0] == fVer[0]) {
        if (rVer[1] > fVer[1]) hasVCR = true;
        else if (rVer[1] == fVer[1]) {
            if (rVer[2] >= fVer[2]) hasVCR = true;
            // Don't bother checking the last ('rebuild') version!
        }
    }
    if (!hasVCR) { // Set up a ShellExecute command to run the installer when this program exits ...
        CString params = L"/q /norestart";
        SHELLEXECUTEINFO shexInfo;  memset(&shexInfo, 0, sizeof(SHELLEXECUTEINFO));
        shexInfo.cbSize = sizeof(SHELLEXECUTEINFO);
        shexInfo.fMask = SEE_MASK_DEFAULT;
        shexInfo.lpFile = vcrPath.GetString();
        shexInfo.lpParameters = params.GetString();
        ShellExecuteEx(&shexInfo);
    }
    return ERROR_SUCCESS;
}

Вы, вероятно, можете упростить только часть ShellExecuteEx, если вы уже знаете, какие программы вы хотите запустить.

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

person Adrian Mole    schedule 31.12.2019
comment
Примечание: вероятно вы можете написать код DLL на языках, отличных от C++ или C, но "подписи" точек входа должны соответствовать соглашению о вызовах WINAPI! Файл .msi, который создает ваш проект установщика, сам по себе не является исполняемым файлом — скорее, это «своего рода» база данных SQL, которая передается в виде данных в программу MSI (системный установщик). - person Adrian Mole; 31.12.2019
comment
Я как бы надеялся написать какой-нибудь простой код на C#. Поскольку единственное, что мне нужно сделать, это запустить команду CMD и запустить службу. Но я попробую добавить простой исполняемый файл консоли, чтобы посмотреть, работает ли он. - person OZ_CM; 31.12.2019
comment
@OZ_CM Возможно: я действительно не знаю C#, но я уверен, что запуск другой программы (или команды) с эквивалентом ShellExecute тривиален. Проблема будет заключаться в экспорте вашего кода в формат, понятный MSI. - person Adrian Mole; 31.12.2019
comment
Может ли он запускать пакетные файлы - person OZ_CM; 31.12.2019
comment
Я не знаю, как MSI может напрямую запускать пакетные файлы, но это может сделать довольно простая C DLL - или просто выполнить необходимые команды, которые должен иметь пакетный файл (например, запуск оказание услуг). Также обратите внимание, что установщик MSI работает в режиме с повышенными правами (администратора). - person Adrian Mole; 31.12.2019

(Обновлять)

Я нашел способ сделать это, добавив исполняемое приложение в проекты установщика VS.

Шаг 1

Добавьте исполняемый проект (который в моем случае будет выполнять последующий код) в свое решение. Затем добавьте этот проект в «Папку приложения» в программе установки, как обычно (например, Вывод проекта). В моем случае я добавил простой проект консольного приложения, написанный на C#.

Шаг 2

Затем в окне решения щелкните правой кнопкой мыши проект установщика и выберите Просмотреть дополнительные действия.

У вас будет 4 события на выбор:

  • установить
  • Совершить
  • Откат
  • Удалить

«Коммит» — это тот, который работает в конце установки.

Шаг 3

Щелкните правой кнопкой мыши нужное действие и выберите «Добавить пользовательское действие...». В окне «Выбрать элемент в проекте» щелкните «Папка приложения» и выберите добавленный исполняемый файл.

Шаг 4 (самая важная часть)

После добавления EXE к событию вы увидите его в окне «Пользовательские действия». Щелкните правой кнопкой мыши это и выберите «Окно свойств». Вы увидите в «Окне свойств» логическое значение «Класс установщика». Отметьте его как False.


И это должно сделать это!.

Имейте в виду, что EXE не будет запускаться в целевой папке. Он будет работать в "C:\WINDOWS\system32\". поэтому «GetCurrentDirectory» не будет работать должным образом. Также помните, что программа не запустится после завершения установки. В конце установки установщик дождется завершения работы программы, а затем позволит пользователю закрыть окно.

Огромное спасибо @Adrian_Mole

person OZ_CM    schedule 31.12.2019