c00000fd
c00000fd

Reputation: 22255

How to remove installation folder when app is uninstalled? Installation folder happens to be locked by msiexec.exe process

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:

enter image description here

So what's the rub here? How the heck to do this simple task????

Upvotes: 0

Views: 266

Answers (1)

c00000fd
c00000fd

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

Related Questions