Reputation: 22255
I literally spent a day trying to do the simplest thing. (Man, MSI/WiX is a handful!)
My goal is very simple. I need to remove the installation folder when my app is uninstalled. I create it as such (using WiX):
<Directory Id='TARGETDIR' Name='SourceDir'>
<Directory Id='ProgramFilesFolder' Name='InstallFolder'>
<Directory Id='idCompany.com' Name='$(var.CompanyName)'>
<Directory Id='INSTALLDIR' Name='$(var.ProductThis)' >
<Component Id='CompIDMyEXE1' Guid='{--GUID1--}'>
<File Id='idMyEXE1' Name='MyExe1.exe' DiskId='1' Source='MyExe1.exe' Vital='yes' KeyPath='yes' />
<ServiceControl Id="idSrvc" Name="SrvcName" Stop="both" Wait="yes" />
</Component>
<Component Id='CompIDMyEXE2' Guid='{--GUID2--}'>
<File Id='idMyEXE2' Name='MyExe2.exe' DiskId='1' Source='MyExe2.exe' Vital='yes' KeyPath='yes' />
</Component>
<!-- and so on -->
</Directory>
</Directory>
</Directory>
I first tried to use RemoveFolder WiX keyword as it was suggested here, but no matter what I did my installation folder didn't want to go. What made matters worse is that there's no way of knowing why it didn't work. Did is simply not see my tag, or what ... arghhh!
So I decided to add a custom action written in C and delete the folder from there since I would have much more control there. So I did this:
<CustomAction Id="CA_SetProperties_UninstallFinalize" Property="CA_msiOnUninstallFinalize" Value="[INSTALLDIR]" />
<CustomAction Id='CA_msiOnUninstallFinalize' BinaryKey='CADll' DllEntry='msiOnUninstallFinalize' Execute='deferred' Impersonate='no' />
<InstallExecuteSequence>
<!-- Need to run it for uninstalls only -->
<Custom Action="CA_SetProperties_UninstallFinalize" Before="InstallFinalize">
NOT REINSTALL AND NOT UPGRADINGPRODUCTCODE AND REMOVE
</Custom>
<Custom Action="CA_msiOnUninstallFinalize" After="CA_SetProperties_UninstallFinalize">
NOT REINSTALL AND NOT UPGRADINGPRODUCTCODE AND REMOVE
</Custom>
<!-- ... -->
</InstallExecuteSequence>
but when I called RemoveDirectory API from my msiOnUninstallFinalize
method on my empty installation folder it kept returning ERROR_SHARING_VIOLATION
error.
So I put a break inside my msiOnUninstallFinalize
method (by simply adding a MessageBox
call) and checked the folder itself. It turns out, the folder at that point was already empty, but when I checked to see if there's a lock on it, it turns out that MSI itself is holding it:
So what's the rub here? How the heck to do this simple task????
Upvotes: 0
Views: 266
Reputation: 22255
Really weird problem. It turns out that I created an "Uninstall" shortcut, that I was using to uninstall the app, as such:
<Shortcut Id="startmenuUninst"
Directory="ProgramMenuDir"
Target="[SystemFolder]msiexec.exe"
Arguments="/x [ProductCode]"
Name="Uninstall $(var.ProductThis)"
WorkingDirectory='INSTALLDIR'
Advertise='no'
Description="Uninstalls $(var.ProductThis)" />
The issue happens to be WorkingDirectory
attribute, or Windows working directory when msiexec
process is called. For some reason if it's set to the folder that is being removed (like INSTALLDIR
in this case) msiexec
process will have an internal lock on it and thus the folder will not be removed during uninstallation.
So the solution in my case was pretty simple. I needed to replace INSTALLDIR
with any other folder, such as: WorkingDirectory='SystemFolder'
and it deleted it automatically without the need for any custom actions, like I was showing above.
PS. By the way, if you're calling CreateProcess from C/C++ with the msiexec.exe /x {Product-Code-GUID}
command to uninstall the app, make sure to specify a working directory that is other than the folder being removed in the lpCurrentDirectory
parameter. Don't use NULL
as it may create an ambiguity with the current working directory as I explained above.
Upvotes: 1