WiX:在卸载时正确删除非空的临时文件和文件夹

时间:2014-08-21 18:35:16

标签: c# .net wix windows-installer

在开始之前,请注意,我read almost所有相关问题都在此处,并且没有找到解决方案。因此,问题的目标与以前的问题相同:

  

如何正确删除应用程序使用WiX工具时正在创建的临时文件夹和misc文件?

到目前为止,我想出了以下方法:

  1. 使用 CustomAction (例如,在C#中编写),它将删除所有文件和文件夹(这很有用,但这种解决方法在我看来无效,因为没有支持从MSI方面回滚。)

  2. 使用 WixUtilExtension 中的 util:RemoveFolderEx 标记。我无法完成这项工作。

  3. 使用 CustomAction (再次在C#中编写)将填充MSI数据库的表格(目录删除文件)在UnInstall发生之前。这将强制MSI以自己的方式正确地卸载所有枚举的文件和文件夹(理论上它应该这样做)通过 RemoveFiles 默认操作。

  4. 我专注于第三种方式并在此向您提供一些帮助。

    关于应用程序布局的几点说明 我构建的MSI与 ProgramFiles 快捷方式注册表以及所有这些功能相得益彰。但是在安装过程中,我将一些配置文件放入C:\ProgramData\MyApp\文件夹(它们也被删除,没有任何问题)。

    但是当应用程序工作时,它会在C:\ProgramData\MyApp\中生成其他文件和目录,用于在新版本可用时更新应用程序。假设用户在更新过程中关闭应用程序并想要卸载应用程序。以下是我们目前在C:\ProgramData\MyApp文件夹中的内容:

      

    C:\ ProgramData \ MyApp的\
      C:\ ProgramData \ MyApp的\ TEMP \
      C:\ ProgramData \ MyApp的\ TEMP \ tool.exe
      C:\ ProgramData \ MyApp的\ TEMP \ somelib.dll
      C:\ ProgramData \ MyApp的\温度\< UniqueFolderNameBasedOnGUID> \ someliba.dll
      C:\ ProgramData \ MyApp \ Temp \< UniqueFolderNameBasedOnGUID< \ somelibb.dll

    在卸载过程结束时,我想看到没有C:\ProgramData\MyApp\文件夹。如果没有创建临时目录/文件,我可以看到这个。

    请注意,我不知道放在C:\ProgramData\MyApp\Temp\文件夹中的文件夹和文件的名称,因为最终文件夹名称是使用GUID自动生成的。

    让我专注于项目最重要的部分,并向您展示我到目前为止完成任务所做的工作(请记住,我选择了第三种方式:通过 CustomAction ) :

    Main MyApp.wxs file

    <Product Id=...>
      ...
      <!-- Defines a DLL contains the RemoveUpdatesAction function -->
      <Binary Id="RemoveUpdatesAction.CA.dll" src="RemoveUpdatesAction.CA.dll" />
      <CustomAction Id="RemoveUpdatesAction" 
                    Return="check" 
                    Execute="immediate" 
                    BinaryKey="RemoveUpdatesAction.CA.dll" 
                    DllEntry="RemoveUpdatesAction" />
      ...
      <InstallExecuteSequence>
        <!-- Perform custom CleanUp only on 'UnInstall' action - see condition -->
        <Custom Action='RemoveUpdatesAction' Before='RemoveFiles'>
            (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL")    
        </Custom>
      </InstallExecuteSequence>
      ...
    </Product>
    

    稍后在文件中,我为 ProgramData 目录定义 Folder Layout

    <Fragment>
      <Directory Id="TARGETDIR" Name="SourceDir">
        ....
        <!-- ...\Program Files\MyApp\ -->
        <Directory Id="ProgramFilesFolder">
          <Directory Id="APPINSTALLFOLDER" Name="MyApp" />
        </Directory>
    
        ....
    
        <!-- This is it! ...\ProgramData\MyApp\ -->
        <Directory Id="CommonAppDataFolder">
          <Directory Id="SETTINGSINSTALLFOLDER" Name="MyApp" />
        </Directory>
        ....
      </Directory>
    </Fragment>
    
    ... a lot of different stuff here ...
    
    <Fragment>
      <Component Id="AddConfigurationFilesFolder" ...>
      ...
      <!-- This component just serves as a bound point - see below -->
      ...
    </Fragment>
    

    到目前为止一切顺利。

    现在, RemoveUpdatesAction.cs file 包含自定义操作:

    public class CustomActions
    {
        [CustomAction]
        public static ActionResult RemoveUpdatesAction(Session session)
        {
            try
            {
                // Provide unique IDs
                int indexFile = 1;
                int indexDir = 1;
    
                // Begin work
                session.Log("Begin RemoveUpdatesAction");
    
                // Bind to the component that for sure will be uninstalled during UnInstall action
                // You can see this component mentioned above
                const string componentId = "AddConfigurationFilesFolder";
    
                // Get '..\{ProgramData}\MyApp' folder
                // This property (SETTINGSINSTALLFOLDER) is mentioned too
                string appDataFolder = session["SETTINGSINSTALLFOLDER"];
    
                // Populate RemoveFile table in MSI database with all files 
                // created in '..\{ProgramData}\MyApp\*.*' folder - pls see notes at the beginning
                if (!Directory.Exists(appDataFolder))
                {
                  session.Log("End RemoveUpdatesAction");
                  return ActionResult.Success;
                }
    
                foreach (var directory in Directory.GetDirectories(appDataFolder, "*", SearchOption.AllDirectories))
                {
                    session.Log("Processing Subdirectory {0}", directory);
                    foreach (var file in Directory.EnumerateFiles(directory))
                    {
                        session.Log("Processing file {0}", file);
    
                        string keyFile = string.Format("CLEANFILE_{0}", indexFile);
    
                        // Set values for columns in RemoveFile table:
                        // {1}: FileKey => just unique ID for the row
                        // {2}: Component_ => reference to a component existed in Component table
                        //      In our case it is already mentioned 'AddConfigurationFilesFolder' 
                        // {3}: FileName => localizable name of the file to be removed (with ext.)
                        // {4}: DirProperty => reference to a full dir path
                        // {5}: InstallMode => 3 means remove on Install/Remove stage
                        var fieldsForFiles = new object[] { keyFile, componentId, Path.GetFileName(file), directory, 3 };
    
                        // The following files will be processed:
                        // 1. '..\ProgramData\MyApp\Temp\tool.exe'
                        // 2. '..\ProgramData\MyApp\Temp\somelib.dll'
                        // 3. '..\ProgramData\MyApp\Temp\<UniqueFolderNameBasedOnGUID>\someliba.dll'
                        // 4. '..\ProgramData\MyApp\Temp\<UniqueFolderNameBasedOnGUID>\somelibb.dll'
                        InsertRecord(session, "RemoveFile", fieldsForFiles);
    
                        indexFile++;
                    }
    
                    string keyDir = string.Format("CLEANDIR_{0}", indexDir);
    
                    // Empty quotes mean we we want to delete the folder itself
                    var fieldsForDir = new object[] { keyDir, componentId, "", directory, 3 };
    
                    // The following paths will be processed:
                    // 1. '..\ProgramData\MyApp\Temp\'
                    // 2. '..\ProgramData\MyApp\Temp\<UniqueFolderNameBasedOnGUID>\'
                    InsertRecord(session, "RemoveFile", fieldsForDir);
    
                    indexDir++;
                }
    
                session.Log("End RemoveUpdatesAction");
                return ActionResult.Success;
            }
            catch (Exception exception)
            {
                session.Log("RemoveUpdatesAction EXCEPTION:" + exception.Message);
                return ActionResult.Failure;
            }
        }
    
        // Took completely from another SO question, but is accoring to MSDN docs
        private static void InsertRecord(Session session, string tableName, Object[] objects)
        {
            Database db = session.Database;
            string sqlInsertSring = db.Tables[tableName].SqlInsertString + " TEMPORARY";
            View view = db.OpenView(sqlInsertSring);
            view.Execute(new Record(objects));
            view.Close();
        }
    }
    

    注意:我从here获取的条件((NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL"))。

    这几乎就是我所做的全部,在安装/卸载循环之后,我在msi日志文件和RemoveFile表中看到(通过枚举记录)确实插入了所有这些条目。 ..\ProgramData\MyApp\Temp\...中的文件和文件夹仍然无论我做什么

    任何人都可以澄清我做错了什么吗?

    我认为问题可能在于如何在Custom Action类中定义 DirProperty 。我在那里放了一个目录路径,但我知道(使用Orca)在目录表中没有我通过Directory.GetDirectories发现的临时文件夹的记录。

    所以 RemoveFile 表正在填充对目录表有无效引用的记录(是吗?)。我尝试手动将这些发现的文件夹添加到目录表中但失败了 - 每次我尝试引用目录表时都会出现异常。在MSI中填充目录表的正确方法是什么?根本填充目录表是否有意义?

    例如,如果我需要放置以下路径:

    1. ..\ProgramData\MyApp\Temp\
    2. ..\ProgramData\MyApp\Temp\<UniqueFolderNameBasedOnGUID>\
    3. 我该怎么做?

      无论如何,请提出建议 - 任何建议,提示或意见将不胜感激!

      非常感谢!

      MSI卸载日志

      MSI (s) (B4:28) [05:28:39:427]: Doing action: RemoveUpdatesAction
      ....
      MSI (s) (B4:4C) [05:28:39:450]: Invoking remote custom action. DLL: C:\WINDOWS\Installer\MSI7643.tmp,    Entrypoint:     RemoveUpdatesAction
      ....  
      Action start 5:28:39: RemoveUpdatesAction.  
      SFXCA: Extracting custom action to temporary directory: C:\WINDOWS\Installer\MSI7643.tmp-\  
      SFXCA: Binding to CLR version v4.0.30319  
      Calling custom action RemoveUpdatesAction!RemoveUpdatesAction.CustomActions.RemoveUpdatesAction  
      Begin RemoveUpdatesAction  
      Processing Subdirectory C:\ProgramData\MyApp\Temp  
      Processing file C:\ProgramData\MyApp\Temp\somelib.dll  
      Processing file C:\ProgramData\MyApp\Temp\tool.exe  
      Processing Subdirectory C:\ProgramData\MyApp\Temp\48574917-4351-4d4c-a36c-381f3ceb2e56  
      Processing file C:\ProgramData\MyApp\Temp\48574917-4351-4d4c-a36c-381f3ceb2e56\someliba.dll  
      Processing file C:\ProgramData\MyApp\Temp\48574917-4351-4d4c-a36c-381f3ceb2e56\somelibb.dll  
      End RemoveUpdatesAction  
      MSI (s) (B4:28) [05:28:39:602]: Doing action: RemoveFiles  
      MSI (s) (B4:28) [05:28:39:602]: Note: 1: 2205 2:  3: ActionText   
      Action ended 5:28:39: RemoveUpdatesAction. Return value 1.  
      Action start 5:28:39: RemoveFiles.  
      MSI (s) (B4:28) [05:28:39:607]: Note: 1: 2727 2: C:\ProgramData\MyApp\Temp   
      MSI (s) (B4:28) [05:28:39:607]: Note: 1: 2727 2: C:\ProgramData\MyApp\Temp\48574917-4351-4d4c-a36c-381f3ceb2e56   
      MSI (s) (B4:28) [05:28:39:607]: Counted 2 foreign folders to be removed.  
      MSI (s) (B4:28) [05:28:39:607]: Removing foreign folder: C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Compass Mobile\  
      MSI (s) (B4:28) [05:28:39:607]: Removing foreign folder: C:\ProgramData\MyApp\  
      MSI (s) (B4:28) [05:28:39:607]: Doing action: RemoveFolders  
      MSI (s) (B4:28) [05:28:39:607]: Note: 1: 2205 2:  3: ActionText   
      Action ended 5:28:39: RemoveFiles. Return value 1.  
      Action start 5:28:39: RemoveFolders.  
      

      正如您所看到的,我有 2727 错误代码。 That means对于以下文件夹,目录表中没有记录:

        

      C:\ ProgramData \ MyApp的\温度
        C:\ ProgramData \ MyApp的\ TEMP \ 48574917-4351-4d4c-a36c-381f3ceb2e56

      那么,也许我的建议是正确的?

      所提到的标签,MSI表等

      引用

        

      Creating WiX Custom Actions in C# and Passing Parameters
        MSI DB's RemoveFile table description
        MSI DB's Directory table description
        RemoveFiles Action
        WiX CustomAction Element

1 个答案:

答案 0 :(得分:0)

据我所知,你没有正确填充RemoveFile表。如果你记录了那个确切的SQL插入字符串,看看它到底有什么用,这会有所帮助。我认为你得到错误2727因为你插入的第四件事应该是一个引用MSI文件中的目录表的目录属性。它不是字面上的目录名 - 它应该是MSI文件目录表中的一个键 - 据我所知,它是一个实际目录,而不是Directory表中的值。