C# wix-安装前删除旧程序文件夹
我需要安装程序删除旧的安装目录(如果存在),就在安装程序开始复制新文件之前。此文件夹包含程序使用期间生成的一些文件和子文件夹,它们不包括在安装程序中。因此,我创建了自定义操作来执行此操作 所以,一些代码。首先,自定义操作代码(没有什么特别之处): 和wix代码(定义自定义操作调用):C# wix-安装前删除旧程序文件夹,c#,wix,custom-action,C#,Wix,Custom Action,我需要安装程序删除旧的安装目录(如果存在),就在安装程序开始复制新文件之前。此文件夹包含程序使用期间生成的一些文件和子文件夹,它们不包括在安装程序中。因此,我创建了自定义操作来执行此操作 所以,一些代码。首先,自定义操作代码(没有什么特别之处): 和wix代码(定义自定义操作调用): 自找的 纽尔费 未安装且未升级ProductCode 已安装但未重新安装且未升级ProductCode 未安装且未升级ProductCode 已安装但未重新安装且未升级ProductCode 有什么问题吗?如您
自找的
纽尔费
未安装且未升级ProductCode
已安装但未重新安装且未升级ProductCode
未安装且未升级ProductCode
已安装但未重新安装且未升级ProductCode
有什么问题吗?如您所见,actionRemoveOldDatabase应该在安装程序开始复制新文件之前触发(参数已经由SetRemoveOldDatabaseParameters设置)。因此-只应删除旧文件,但这不会发生。如果我这样做,actionactionRemoveOldDatabase,安装程序将新文件复制到安装目录后,安装目录将被删除。因此-安装程序复制的所有新文件都将被删除
我不明白为什么?如何只删除旧的、已经存在的文件夹,以及为什么我的自定义操作会删除所有复制的文件
[编辑]
看来我已经知道原因了。在这种情况下,Install Dir正在使用中(可能是windows installer将其锁定),并在安装结束后释放。自定义操作将等待文件夹释放,然后将其删除。不幸的是,太晚了-文件夹已经包含新文件
您知道任何解决方法吗?的设计正是为了做到这一点。您可以使用此命令来教导MSI删除未安装的应用程序数据。优点是在回滚期间,文件将放回原位
您还可以使用RemoveFolder元素删除整个目录。通常,概念是*文件删除,并指定文件夹。这不是递归的,所以您需要对可能已经创建的任何子目录执行此操作
编写自定义操作只是重新发明轮子,增加了安装程序的脆弱性。仅当无法预先知道子目录时,才应使用它。在这种情况下,理想的情况是使用MSI中的临时行在安装时将这些行动态地发送到MSI中,并让MSI处理实际的删除。这允许回滚功能仍然工作
这是一个非常简单的版本。可以通过使其从自定义表而不是ComponentID和DirectoryID的常量字符串中驱动数据来改进它
public class RecursiveDeleteCustomAction
{
[CustomAction]
public static ActionResult RecursiveDeleteCosting(Session session)
{
// SOMECOMPONENTID is the Id attribute of a component in your install that you want to use to trigger this happening
const string ComponentID = "SOMECOMPONENTID";
// SOMEDIRECTORYID would likely be INSTALLDIR or INSTALLLOCATION depending on your MSI
const string DirectoryID = "SOMEDIRECTORYID";
var result = ActionResult.Success;
int index = 1;
try
{
string installLocation = session[DirectoryID];
session.Log("Directory to clean is {0}", installLocation);
// Author rows for root directory
// * means all files
// null means the directory itself
var fields = new object[] { "CLEANROOTFILES", ComponentID, "*", DirectoryID, 3 };
InsertRecord(session, "RemoveFile", fields);
fields = new object[] { "CLEANROOTDIRECTORY", ComponentID, "", DirectoryID, 3 };
InsertRecord(session, "RemoveFile", fields);
if( Directory.Exists(installLocation))
{
foreach (string directory in Directory.GetDirectories(installLocation, "*", SearchOption.AllDirectories))
{
session.Log("Processing Subdirectory {0}", directory);
string key = string.Format("CLEANSUBFILES{0}", index);
string key2 = string.Format("CLEANSUBDIRECTORY{0}", index);
session[key] = directory;
fields = new object[] { key, ComponentID, "*", key, 3 };
InsertRecord(session, "RemoveFile", fields);
fields = new object[] { key2, ComponentID, "", key, 3 };
InsertRecord(session, "RemoveFile", fields);
index++;
}
}
}
catch (Exception ex)
{
session.Log(ex.Message);
result = ActionResult.Failure;
}
return result;
}
private static void InsertRecord(Session session, string tableName, Object[] objects)
{
Database db = session.Database;
string sqlInsertSring = db.Tables[tableName].SqlInsertString + " TEMPORARY";
session.Log("SqlInsertString is {0}", sqlInsertSring);
View view = db.OpenView(sqlInsertSring);
view.Execute(new Record(objects));
view.Close();
}
}
我也有过类似的问题。我绕过了它,为每个版本使用了不同的install.dir,apppending dir名称和版本号,例如“install_V2.03”。好的,但是如果我有一些文件夹和子文件夹呢。我不知道在程序使用过程中创建了哪些文件和哪些子目录,我需要删除整个install dir,而不关心它的内容(因为它的内容是未知的)-我可以使用RemoveFile或RemoveFolder来执行此操作吗?您可以编写一个C#/DTF自定义操作,以获取子目录列表,并为每个子目录在MSI中创建一个临时行。然后,当标准动作触发时,它会清除所有内容。类似这样但不同的SQL表:您的意思是在卸载期间调用的自定义操作-它枚举要删除的所有剩余文件/目录并将检索到的数据传递到某处-在此之后,标准卸载操作将删除这些文件?似乎不那么容易,有没有什么例子?我应该使用什么sql表?是的,做正确的事情很复杂。更新了一些示例代码。谢谢,我会试试这个。这很奇怪-这么简单又这么复杂,我对msi很困惑:/
<CustomAction Id="actionCheckServerName" BinaryKey="actionBinary" DllEntry="CheckServerName" Execute="immediate" Return="check" />
<CustomAction Id="actionInstall" BinaryKey="actionBinary" DllEntry="Install" Execute="deferred" HideTarget="no" Impersonate ="no" Return="check"/>
<CustomAction Id="actionUninstall" BinaryKey="actionBinary" DllEntry="Uninstall" Execute="deferred" HideTarget="no" Impersonate ="no" Return="check"/>
<CustomAction Id="actionRemoveOldDatabase" BinaryKey="actionBinary" DllEntry="RemoveOldDatabase" Execute="deferred" HideTarget="no" Impersonate ="no" Return="ignore"/>
<CustomAction Id="actionGetNetworkComputers" BinaryKey="actionBinary" DllEntry="GetNetworkComputers" Execute="immediate" Return="check"/>
<CustomAction Id="SetInstallParameters" Return="check" Property="actionInstall" Value="InstallDir=[INSTALLDIR];ServerName=[SERVER_LIST];InstallMode=[SETUP_MODE];Single=[single];RemoveDatabase=[REMOVE_DATABASE]" />
<CustomAction Id="SetUninstallParameters" Return="check" Property="actionUninstsall" Value="UnInstallDir=[INSTALLDIR];ServerName=[SERVER_LIST];UnInstallMode=[INSTALL_MODE]" />
<CustomAction Id="SetRemoveOldDatabaseParameters" Return="check" Property="actionRemoveOldDatabase" Value="InstallDir=[INSTALLDIR];RemoveDatabase=[REMOVE_DATABASE]" />
<InstallExecuteSequence>
<Custom Action='AlreadyUpdated' After='FindRelatedProducts'>SELFFOUND</Custom>
<Custom Action='NoDowngrade' After='FindRelatedProducts'>NEWERFOUND</Custom>
<Custom Action="SetRemoveOldDatabaseParameters" Before="ProcessComponents"/>
<Custom Action="actionRemoveOldDatabase" After="SetRemoveOldDatabaseParameters">NOT Installed AND NOT UPGRADINGPRODUCTCODE</Custom>
<Custom Action="SetInstallParameters" Before="actionInstall"/>
<Custom Action="SetUninstallParameters" Before="RemoveFiles">Installed AND NOT REINSTALL AND NOT UPGRADINGPRODUCTCODE</Custom>
<Custom Action="actionInstall" Before="InstallFinalize">NOT Installed AND NOT UPGRADINGPRODUCTCODE</Custom>
<Custom Action="actionUninstall" After="SetUninstallParameters">Installed AND NOT REINSTALL AND NOT UPGRADINGPRODUCTCODE</Custom>
</InstallExecuteSequence>
public class RecursiveDeleteCustomAction
{
[CustomAction]
public static ActionResult RecursiveDeleteCosting(Session session)
{
// SOMECOMPONENTID is the Id attribute of a component in your install that you want to use to trigger this happening
const string ComponentID = "SOMECOMPONENTID";
// SOMEDIRECTORYID would likely be INSTALLDIR or INSTALLLOCATION depending on your MSI
const string DirectoryID = "SOMEDIRECTORYID";
var result = ActionResult.Success;
int index = 1;
try
{
string installLocation = session[DirectoryID];
session.Log("Directory to clean is {0}", installLocation);
// Author rows for root directory
// * means all files
// null means the directory itself
var fields = new object[] { "CLEANROOTFILES", ComponentID, "*", DirectoryID, 3 };
InsertRecord(session, "RemoveFile", fields);
fields = new object[] { "CLEANROOTDIRECTORY", ComponentID, "", DirectoryID, 3 };
InsertRecord(session, "RemoveFile", fields);
if( Directory.Exists(installLocation))
{
foreach (string directory in Directory.GetDirectories(installLocation, "*", SearchOption.AllDirectories))
{
session.Log("Processing Subdirectory {0}", directory);
string key = string.Format("CLEANSUBFILES{0}", index);
string key2 = string.Format("CLEANSUBDIRECTORY{0}", index);
session[key] = directory;
fields = new object[] { key, ComponentID, "*", key, 3 };
InsertRecord(session, "RemoveFile", fields);
fields = new object[] { key2, ComponentID, "", key, 3 };
InsertRecord(session, "RemoveFile", fields);
index++;
}
}
}
catch (Exception ex)
{
session.Log(ex.Message);
result = ActionResult.Failure;
}
return result;
}
private static void InsertRecord(Session session, string tableName, Object[] objects)
{
Database db = session.Database;
string sqlInsertSring = db.Tables[tableName].SqlInsertString + " TEMPORARY";
session.Log("SqlInsertString is {0}", sqlInsertSring);
View view = db.OpenView(sqlInsertSring);
view.Execute(new Record(objects));
view.Close();
}
}