1
votes

Exception from System.IO.Directory.Delete is not caught in customaction (msi generated with wix toolset)

Occured on .NET Framework 4.7.2 Windows 7 started as administrator.

Logfile from call of customaction:

MSI (s) (A0:D8) [08:31:39:027]: Executing op: ActionStart(Name=CacheCustomActions.DeleteOldInstallationFolders,,) MSI (s) (A0:D8) [08:31:39:027]: Executing op: CustomActionSchedule(Action=CacheCustomActions.DeleteOldInstallationFolders,ActionType=1025,Source=BinaryData,Target=DeleteOldInstallationFolders,CustomActionData=XXXDIR=C:\XXX\;) MSI (s) (A0:AC) [08:31:39:028]: Invoking remote custom action. DLL: C:\Windows\Installer\MSI7B8A.tmp, Entrypoint: DeleteOldInstallationFolders SFXCA: Extracting custom action to temporary directory: C:\Windows\Installer\MSI7B8A.tmp-\ SFXCA: Binding to CLR version v4.0.30319 Calling custom action CustomActions!CustomActions.CustomActions.DeleteOldInstallationFolders ***** Begin DeleteOldInstallationFolders ***** Deleting folder: C:\XXX\temp ***** Deleting folder: C:\XXX\temp2 CustomAction CacheCustomActions.DeleteOldInstallationFolders returned actual error code 1603 (note this may not be 100% accurate if translation happened inside sandbox)

                try
                {
                    session.LogWithTime($"Deleting directory {directory}");
                    Directory.Delete(directory, true);
                }
                catch (Exception e)
                {
                    session.LogWithTime($"Failed to delete directory {directory}");
                }

Expected: Directory is deleted or exception is thrown Actual: Custom Action exists with error code 1603.

Wxs Files look like this but I don't think that there is a problem, everything works fine on hundreds of computers, it was only one machine where the error occured:

   <CustomAction Id="PrepareArgumentsForDeferredCall.DeleteOldInstallationFolders" Property="CacheCustomActions.DeleteOldInstallationFolders" Value="XXXDIR=[XXXDIR];ISEXPERTMODEENABLED=[ISEXPERTMODEENABLED]" Execute="immediate" />
[...]
   <CustomAction Id="CacheCustomActions.DeleteOldInstallationFolders" BinaryKey="CustomActionBinary" DllEntry="DeleteOldInstallationFolders" Execute="deferred" Return="check"/>
[...]
      <Custom Action="PrepareArgumentsForDeferredCall.DeleteOldInstallationFolders" After="CostFinalize" />
[..]
      <Custom Action="CacheCustomActions.DeleteOldInstallationFolders" After="InstallInitialize">NOT REMOVE AND NOT PATCH AND NOT REINSTALL</Custom>

1
How are you using it in a WiX?Pavel Anikhouski
First question to ask is what you are removing and why? Remove operations are inherently dangerous and can cause all kinds of catastrophes. To state the obvious.Stein Åsmul
I want to cleanup some directories created by an old installer (whereas installer is maybe a bit exaggerated, an executable that copies file to other locations runs regasm, regsvr32, changes file access all done manually)Benny Bürger

1 Answers

1
votes

Disclaimer: Use caution with all remove elements (files / folders). Test on virtuals only. Obviously.


MSI: Have you tried using the RemoveFile table in MSI? This is the built-in MSI table to allow deletion of files and folders as installation operations. Empty folders only. Sample.

WiX: There is also the utility dll functions in WiX's Util namespace: RemoveFolderEx Element (Util Extension) - this is WiX's own custom action written in C++ and not MSI's built-in stuff. As far as I recall you can delete folders with files using this approach. Been a while. Sample (untested, use at your own risk - obviously).

  • Modify WiX source (top level element): <Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
  • Add reference to WixUtilExtension.dll in main WiX installation folder under Program Files (x86).
  • Use element: <util:RemoveFolderEx On="install" Property="OldWebAPP" />

Managed Code Problems: Using the above you avoid managed code entirely. Managed code suffers a number of vulnerabilities that manifest themselves eventually - like you have seen. Security lockdowns for .NET code, erronous .NET runtime version loaded, GAC dependency issues, here is an old, and overly chatty answer on the subject (possibly outdated content, certainly messy and "nuts").

Custom Action Error Checking: Note that you can also disable error checking for the custom action you refer to. That should allow the installer to go on without failing. If that is an acceptable option. Not great, but possible I guess.

Custom Action Suppression: Finally, if you condition the custom action with a property value, you can prevent the custom action from running if you pass in a custom property value via the command line:

Set in MSI property table: SUPPRESSERROR = 0. Then - when needed - on the command line set:

msiexec.exe /x {PRODUCT-GUID} SUPPRESSERROR="1"

Inside the MSI you condition the uninstall custom action with:

 REMOVE="ALL" AND SUPPRESSERROR="0"

Now the custom action will not run if SUPPRESSERROR is anything but 0, allowing you to prevent the custom action from running on machines where the uninstall is failing by pushing down a custom uninstall command line (note that the custom action can still be set to check for errors).