I have created an installer that will call two Custom actions. First custom action is WRITEFILETODISK
that will create a config file based on the parameter passed to the installer and store it in %ProgramData%\SomeFolder
. Second custom action is ResidueRemove
that will does the job of cleaning any residue from %ProgramData%\SomeFolder
.
Below is the snippet of custom action from my Wix File:
<Binary Id="SetupCA" SourceFile="..\..\ext_library\SetupCACPP\SetupCACPP\bin\Release\SetupCACPP.dll"/>
<CustomAction Id="WRITEFILETODISK" Execute="immediate" BinaryKey="SetupCA" DllEntry="WriteFileToDisk" />
<CustomAction Id="ResidueRemove" Execute="immediate" BinaryKey="SetupCA" DllEntry="DeleteResidue" />
<InstallExecuteSequence>
<Custom Action="WRITEFILETODISK" After="InstallFinalize">NOT Installed</Custom>
<Custom Action="ResidueRemove" After="InstallFinalize">REMOVE~="ALL"</Custom>
<!-- Rob Menshing Answer @ http://stackoverflow.com/a/321874/2634612 -->
</InstallExecuteSequence>
The ResidueRemove
Custom Action is added here:
UINT __stdcall DeleteResidue(MSIHANDLE hInstall)
{
HRESULT hr = S_OK;
UINT er = ERROR_SUCCESS;
SC_HANDLE sHandleLPA, sHandleLPAM;
SERVICE_STATUS servStatusLPA, servStatusLPAM;
SHFILEOPSTRUCT shFileCommFolder, shFileInstalledFolder;
LPWSTR lpaCommFolder, lpaInstalledFolder;
std::wstring wlpaCommFolder, wlpaInstalledFolder;
std::string errorCode;
hr = WcaInitialize(hInstall, "DeleteResidue");
ExitOnFailure(hr, "Failed to initialize");
//Stop the LPA and LPA Monitor Service. Then delete the residue.
WcaLog(LOGMSG_STANDARD, "Doing Delete Residue"); // Fails after printing this log
hr = WcaGetProperty(L"LPCOMMAPPFOLDER",&lpaCommFolder);
ExitOnFailure(hr, "Failure in Common Folder"); // Getting the %ProgramData%\SomeFolder here
hr = WcaGetProperty(L"INSTALLFOLDER",&lpaInstalledFolder);
ExitOnFailure(hr, "Failure in getting Installed Folder"); // Getting Installed Folder here
//Path should be double Terminated for SHFILEOPSTRUCT.pFrom
wlpaCommFolder = std::wstring(lpaCommFolder);
wlpaCommFolder.push_back(L'\0');
wlpaInstalledFolder = std::wstring(lpaInstalledFolder);
wlpaInstalledFolder.push_back(L'\0');
try
{
sHandleLPAM = OpenSCManager(NULL,NULL, SC_MANAGER_ALL_ACCESS);
if(sHandleLPAM == NULL)
{
WcaLog(LOGMSG_STANDARD, "OpenSCManager NULL Handle");
}
sHandleLPAM = OpenService(sHandleLPAM, L"SomeServiceOne",SERVICE_ALL_ACCESS);
if(sHandleLPAM == NULL)
{
WcaLog(LOGMSG_STANDARD, "OpenService NULL Handle");
}
bool res = ControlService(sHandleLPAM, SERVICE_CONTROL_STOP,(LPSERVICE_STATUS) &servStatusLPAM);
if(!res)
{
WcaLog(LOGMSG_STANDARD, "ControlService Cannot Stop the service");
}
// Free the service Handler
CloseServiceHandle(sHandleLPAM);
}
catch(std::exception& e)
{
WcaLog(LOGMSG_STANDARD, e.what());
}
try
{
sHandleLPA = OpenSCManager(NULL,NULL, SC_MANAGER_ALL_ACCESS);
if(sHandleLPA == NULL)
{
WcaLog(LOGMSG_STANDARD, "OpenSCManager NULL Handle");
}
sHandleLPA = OpenService(sHandleLPA, L"SomeServiceTwo",SERVICE_ALL_ACCESS);
if(sHandleLPA == NULL)
{
WcaLog(LOGMSG_STANDARD, "OpenService NULL Handle");
}
bool res = ControlService(sHandleLPA, SERVICE_CONTROL_STOP,(LPSERVICE_STATUS) &servStatusLPA);
if(!res)
{
WcaLog(LOGMSG_STANDARD, "ControlService Cannot Stop the service");
}
// Free the service Handler
CloseServiceHandle(sHandleLPA);
}
catch(std::exception& e)
{
WcaLog(LOGMSG_STANDARD, e.what());
}
ZeroMemory(&shFileCommFolder, sizeof(SHFILEOPSTRUCT));
shFileCommFolder.hwnd = NULL;
shFileCommFolder.wFunc = FO_DELETE;
shFileCommFolder.pFrom = wlpaCommFolder.c_str();
shFileCommFolder.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_NOERRORUI;
BOOL res = DirectoryExists(lpaCommFolder);
if(res)
{
WcaLog(LOGMSG_STANDARD, "The directory exist");
int result = SHFileOperation(&shFileCommFolder);
if(!result)
WcaLog(LOGMSG_STANDARD, "The directory should have deleted by now");
else
{
errorCode = GetLastErrorStdStr();
WcaLog(LOGMSG_STANDARD, "The directory could not be deleted %s", errorCode);
}
}
else
{
WcaLog(LOGMSG_STANDARD, "It Seems the Installed Folder is No more there");
}
ZeroMemory(&shFileInstalledFolder, sizeof(SHFILEOPSTRUCT));
shFileInstalledFolder.hwnd = NULL;
shFileInstalledFolder.wFunc = FO_DELETE;
shFileInstalledFolder.pFrom = wlpaInstalledFolder.c_str();
shFileInstalledFolder.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_NOERRORUI;
res = DirectoryExists(lpaInstalledFolder);
if(res)
{
WcaLog(LOGMSG_STANDARD, "The directory exist");
int result = SHFileOperation(&shFileInstalledFolder);
if(!result)
WcaLog(LOGMSG_STANDARD, "The directory should have deleted by now");
else
{
errorCode = GetLastErrorStdStr();
WcaLog(LOGMSG_STANDARD, "The directory could not be deleted %s", errorCode);
}
}
else
{
WcaLog(LOGMSG_STANDARD, "It Seems the Installed Folder is No more there");
}
LExit:
er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;
return WcaFinalize(er);
}
I get error after the log Delete Residue
that is:
DeleteResidue: Doing Delete Residue
MSI (s) (64:A4) [10:54:51:643]: Leaked MSIHANDLE (11) of type 790541 for thread 980
MSI (s) (64:A4) [10:54:51:643]: Note: 1: 2769 2: ResidueRemove 3: 1
MSI (s) (64:A4) [10:54:51:643]: Note: 1: 2205 2: 3: Error
MSI (s) (64:A4) [10:54:51:643]: Note: 1: 2228 2: 3: Error 4: SELECT `Message` FROM `Error` WHERE `Error` = 2769
DEBUG: Error 2769: Custom Action ResidueRemove did not close 1 MSIHANDLEs.
The installer has encountered an unexpected error installing this package. This may indicate a problem with this package. The error code is 2769. The arguments are: ResidueRemove, 1,
Action ended 10:54:51: ResidueRemove. Return value 3.
Action ended 10:54:51: INSTALL. Return value 3.
This is replicated when the upgrade of installer is done in (Non-English) German Windows. Upon some research I found that The InstallExecuteSequence may have been authored incorrectly. Actions that change the system must be sequenced between the InstallInitialize and InstallFinalize actions. Perform package validation and check for ICE77.
Does this mean My InstallExecuteSequence
is wrong? The first CA WriteFileToDisk
should run after installation and before the service are started (because the executable depend upon the file). The second CA should remove the residue, stop services after Uninstall is complete. What's wrong here in my code? Any help is appreciated.