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