VS Installer Projects - Perform some code after installation

谁都会走 提交于 2020-06-16 19:18:49

问题


I have Visual Studio 2017 and just added the Microsoft Visual Studio Installer Projects extension. The project I want to add to the installer is a Windows Service application build by Topshelf.

To manually install the Windows Service, I open a CMD window as admin and run a command in the application folder (eg. bin/debug) to install the Windows Service app.

Issue

Now, I know for sure that the installer has no convenient feature to install the Service, as the only thing it does (in simple terms), is to put some files in the target directory.

So my question is

Does the installer have an option to run some code after the installation is complete? If not, what are the alternatives?


回答1:


You can add "Custom Actions" to various stages of your Installer Project. Right-click on the project in Solution Explorer and select: "View -> Custom Actions." You can then add a DLL (which you have built - see below - and installed on the target PC) to 'execute' at various points in the process.

When you have added a DLL, you then need to specify the "Entry Point" procedure (e.g. MSR202030202 in the example below) and (optionally) the "Custom Action Data" to pass to that procedure ([TARGETDIR] - the installation directory - in the sample code).

When the installer reaches the stage in which you added the DLL, it will call the stated entry point with the given data available.

Here is a sample DLL procedure that will run at the "Commit" stage of an installation. This example will compare the version of a VC-Redistributable in the target directory against any currently installed and 'spawn' execution if ours is newer. It's a fairly big chunk of code, but will hopefully give you some insights into what is required.

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

You can probably simplify down to only the ShellExecuteEx part, if you already know what program(s) you want to run.

Getting used to adding and defining such Custom Actions can be a steep learning-curve, but most things can ultimately be achieved with perseverance (and a good supply of pain-killers).




回答2:


(Update)

I found a way to do this by adding an executable app to the VS Installer Projects.

Step 1

Add an executable project (that will perform the after code, in my case) to your solution. Then, add that project in the "Application Folder" in the installer like usual (ie. Project Output). In my case, I added a simple Console App project written in C#.

Step 2

Then, in the solution window, right-click on the installer project and select View Custom Actions.

You'll have 4 events to choose from:

  • install
  • Commit
  • Rollback
  • Uninstall

The "commit" is the one that works at the end of the installation.

Step 3

Right-click on the action you want and select "Add Custom Action..." In the window "Select Item in Project" click on "Application Folder" and select the added executable.

Step 4 (Most important part)

After adding the EXE to the event, you will see it in the "Custom Actions" window. Right-click the that and select "Properties Window". You'll see in the "Properties Window" a boolean prop "Installer Class". Check it as False.


And that should do it!.

Keep in mind the EXE will not run in the target folder. It will run in "C:\WINDOWS\system32\". so "GetCurrentDirectory" won't work as expected. Also, remember that the program won't run after the installer is finished. At the end of the installation, the installer will wait for the program to finish and then let the user close the window.

Massive credits to @Adrian_Mole



来源:https://stackoverflow.com/questions/59543023/vs-installer-projects-perform-some-code-after-installation

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!