Saturday, June 16, 2012

WiX Custom Action – delete files before installation

In this implementation before installing MSI need to achieve following task:

  1. Delete the files from specific folders like .log, .ini, .dll etc. but configurable. 
  2. List of files to delete need to be configurable so that in future if new files are identified then make the change in configuration file and it will work. This list is something that needs to be provided with installer but at the same time will be only delivering MSI and not config file. 
Steps in Implementation:

  1. Create simple XML file (let say ConfigXML.xml) for maintaining list of files to delete. 
  2. Add ConfigXML.xml in MSI as an external binary file which is not part of basic installer package. 
  3. In MSI installation process add custom action before installation to retrieve file and delete those files. To implement Custom Action used C# DLL with WiX 3.5.

In the example below reading files from XML and deleting it is not included that will be taken separately. File deletion implementation left to user to implement.

XML file (ConfigXML.xml)
<?xml version="1.0" ?>
<root>
  <Node Path="c:\abc">a1.log</Node>
  <Node Path="d:\abc">a2.ini</Node>
  <Node Path="f:\abc">a3.dll</Node>
</root>

Changes in the .wxs file
<Binary Id="TestxxxXML.xml" SourceFile="d:\TestCfgXML.xml"/>
<Binary Id="myActDLL" SourceFile="d:\MyAction.CA.dll"/>

<CustomAction Id="deleteAction" BinaryKey="myActDLL" DllEntry="DeleteFiles" Impersonate="no" Execute="deferred" Return="check" />

<InstallUISequence>
  <Custom Action="deleteAction" After="ValidateProductID" >NOT Installed</Custom>
</InstallUISequence>
<InstallExecuteSequence>
  <Custom Action="deleteAction" After="ValidateProductID" >NOT Installed</Custom>
</InstallExecuteSequence>

Custom Action DLL implementation
namespace MyAction
{
    public class CustomActionClass
    {
        [CustomAction]
        public static ActionResult DeleteFiles(Session session)
        {
            try
            {
                string sPath = Path.Combine(Path.GetTempPath(), "TestXML.xml");
                var f = File.Create(sPath);
                
                using (var view = session.Database.OpenView("SELECT `Data` FROM `Binary` WHERE `Name` = `TestxxxXML.xml`", "testbinary"))
                {
                    view.Execute();
                    var rec = view.Fetch();
                    var Strm = rec.GetStream("Data");
                    if (Strm != null)
                    {
                        using (f)
                        {
                            byte[] buf = new byte[Int32.MaxValue];
                            int len;
                            while ((len = Strm.Read(buf, 0, buf.Length)) > 0)
                            {
                                file.Write(buf, 0, len);
                            }
                        }
                    }
                }
                f.Close();
                // From here code the operation to read from XML and delete those files; I would suggest you to use PLINQ for it.
            }
            catch (Exception e)
            {
                return ActionResult.Failure;
            }
            return ActionResult.Success;
        }
    }
}

2 comments:

  1. This is all good but it won't run where you're deleting from locations that require elevated permissions!

    Les

    ReplyDelete
    Replies
    1. Yes, I agree with you... I realized it later and thanks for pointing it out.
      I have already did that executing it with deferred mode and impersonate NO.

      Let me correct it :-D

      Delete