Reputation: 57
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>
Upvotes: 1
Views: 209
Reputation: 42196
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).
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
WixUtilExtension.dll
in main WiX installation folder under Program Files (x86).<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 but0
, allowing you to prevent the custom action from running on machines where the uninstall is failing by pushing down acustom uninstall command line
(note that the custom action can still be set to check for errors).
Upvotes: 1