如何捕获取消长 运行 延迟的 DTF 自定义操作?
How do I trap Cancelling of a long-running deferred DTF Custom Action?
我有一个用 DTF 编写的 Deferred 自定义操作 DLL,它将一组 .RDL 文件发布到 SQL 服务器报告 Web 服务。一切正常,我可以在各种 Try Catch 块中捕获大部分错误条件。
我唯一遇到的麻烦是,如果用户在发布过程中按下了安装程序中的“取消”按钮。它会立即弹出一条消息询问我是否要取消安装,但如果我回答 Yes 然后它会抛出一条消息:
Exception of type Microsoft.Deployment.WindowsInstaller.InstallCanceledException was thrown
还有一个确定按钮。
我试过为
添加一个特殊的异常处理程序
catch (InstallCanceledException ex)
{
}
先于其他异常,但它似乎并没有捕捉到这一特定异常。
关于如何在取消长运行宁延迟自定义操作期间处理 InstallCanceledException 的任何建议?
产品团队考虑使用其中一个应用程序,但普通用户 运行 应用程序,他们不一定了解网络服务 URL 或有权将报告发布到网络服务。我放入的安装程序通常用于 运行ning SQL 脚本,我正在向安装程序添加第二个功能以发布报告。现在放弃它实际上效果很好。产品看到了我已经完成的工作并且他们喜欢它。 MSI 进度条会随着每份报告的名称更新。 MSI 提示输入 URI 和用户凭据,并且它已经知道 .RDL 文件所在的文件夹。我 运行 在他们单击下一步按钮时对 URI 进行验证,所以到我 运行执行序列中的延迟操作具有良好的 URI 和凭据。到目前为止,我什至在发布过程中断开了与 VPN 的连接,但它因正确的错误而失败。从字面上看,只有当用户按下取消时,我似乎无法捕获那个,但这也不是这项工作退出的障碍。
隐藏“取消”按钮不是一个合适的选项,因为他们可以随时取消。
public static ActionResult PublishSSRSReports(Session session)
{
session.Log("Begin PublishSSRSReports");
bool bFolderExists = false;
string sCustomActionData;
sCustomActionData = session["CustomActionData"];
string INSTALLDIR = Convert.ToString(MsiGetCustomActionDataAttribute(sCustomActionData, "/InstallDir="));
string SSRSURL = Convert.ToString(MsiGetCustomActionDataAttribute(sCustomActionData, "/SsrsUrl="));
string USERCREDENTIALS = Convert.ToString(MsiGetCustomActionDataAttribute(sCustomActionData, "/Credentials="));
string USERNAME = Convert.ToString(MsiGetCustomActionDataAttribute(sCustomActionData, "/Username="));
string PASSWORD = Convert.ToString(MsiGetCustomActionDataAttribute(sCustomActionData, "/Password="));
string ReportsFolderPath = INSTALLDIR + "SSRSReports";
DirectoryInfo directory = new DirectoryInfo(ReportsFolderPath);
FileInfo[] reports = directory.GetFiles("*.rdl"); //Getting all RDL files
ResetProgressBar(session, reports.Length);
CatalogItem[] catalogitem = null;
using (ReportingService2010 rsc = new ReportingService2010())
{
rsc.Url = SSRSURL;
if (USERCREDENTIALS == "0")
{
rsc.Credentials = System.Net.CredentialCache.DefaultCredentials; //User credential for Reporting Service
//the current logged system user
}
if (USERCREDENTIALS == "1")
{
string[] userdomain = USERNAME.Split(Convert.ToChar("\"));
rsc.Credentials = new System.Net.NetworkCredential(userdomain[1], PASSWORD, userdomain[0]);
}
catalogitem = rsc.ListChildren(@"/", false);
foreach (CatalogItem catalog in catalogitem)
{
if (catalog.Name == (DP))
{
EventLog.WriteEntry(AppDomain.CurrentDomain.FriendlyName, DP + " folder already exists");
bFolderExists = true;
}
}
if (bFolderExists == false)
{
rsc.CreateFolder(DP, @"/", null);
}
Warning[] Warnings = null;
foreach (FileInfo ReportFile in reports)
{
Byte[] definition = null;
Warning[] warnings = null;
try
{
FileStream stream = ReportFile.OpenRead();
definition = new Byte[stream.Length];
stream.Read(definition, 0, (int)stream.Length);
stream.Close();
}
catch (InstallCanceledException ex)
{
//session.Message(InstallMessage.Error, new Record { FormatString = ex.Message });
EventLog.WriteEntry(AppDomain.CurrentDomain.FriendlyName, ex.Message);
return ActionResult.UserExit;
}
catch (IOException ex)
{
session.Message(InstallMessage.Error, new Record { FormatString = ex.Message });
EventLog.WriteEntry(AppDomain.CurrentDomain.FriendlyName, ex.Message);
return ActionResult.Failure;
}
catch (Exception ex)
{
session.Message(InstallMessage.Error, new Record { FormatString = ex.Message });
EventLog.WriteEntry(AppDomain.CurrentDomain.FriendlyName, ex.Message);
return ActionResult.Failure;
}
try
{
CatalogItem report = rsc.CreateCatalogItem("Report", ReportFile.Name, @"/" + DP, true, definition, null, out Warnings);
DisplayActionData(session, ReportFile.Name);
IncrementProgressBar(session, 1);
if (report != null)
{
EventLog.WriteEntry(AppDomain.CurrentDomain.FriendlyName, ReportFile.Name + " Published Successfully ");
}
if (warnings != null)
{
foreach (Warning warning in warnings)
{
EventLog.WriteEntry(AppDomain.CurrentDomain.FriendlyName, string.Format("Report: {0} has warnings", warning.Message));
}
}
else
{
EventLog.WriteEntry(AppDomain.CurrentDomain.FriendlyName, string.Format("Report: {0} created successfully with no warnings", ReportFile.Name));
}
}
catch (InstallCanceledException ex)
{
//session.Message(InstallMessage.Error, new Record { FormatString = ex.Message });
EventLog.WriteEntry(AppDomain.CurrentDomain.FriendlyName, ex.Message);
return ActionResult.UserExit;
}
catch (SoapException ex)
{
session.Message(InstallMessage.Error, new Record { FormatString = ex.Message });
EventLog.WriteEntry(AppDomain.CurrentDomain.FriendlyName, ex.Detail.InnerXml.ToString());
return ActionResult.Failure;
}
catch (Exception ex)
{
session.Message(InstallMessage.Error, new Record { FormatString = ex.Message });
EventLog.WriteEntry(AppDomain.CurrentDomain.FriendlyName, ex.Message);
return ActionResult.Failure;
}
}
}
return ActionResult.Success;
我在 class
中也有这些
private const string SpaceForwardSlash = " /";
private const string DP = "Test";
在 DTF 源代码中,我唯一看到抛出 InstallCanceledException 的地方是 Session.Message()。这是 MsiProcessMessage Windows API 函数的包装器。在我看来,如果您使用 Session.Message() 显示来自托管自定义操作的消息框,然后单击 'Cancel' 按钮,就会出现此异常。 DTF 看到消息框 'cancel' return 代码并抛出 InstallCanceledException。也许它然后落入某个地方的捕获块(可能是不同的动作?)在那里你调用类似于
的东西
session.Message(InstallMessage.Error, new Record { FormatString = ex.Message })
显示仅包含异常的第二个消息框。
如果没有看到您的 MSI 源代码或完整的日志文件,我无法将所有内容 100% 拼凑在一起,但这也许会有所帮助。
以下是 Session.Message() 在 DTF 源代码中的定义:
public MessageResult Message(InstallMessage messageType, Record record)
{
if (record == null)
{
throw new ArgumentNullException("record");
}
int ret = RemotableNativeMethods.MsiProcessMessage((int) this.Handle, (uint) messageType, (int) record.Handle);
if (ret < 0)
{
throw new InstallerException();
}
else if (ret == (int) MessageResult.Cancel)
{
throw new InstallCanceledException();
}
return (MessageResult) ret;
}
我有一个用 DTF 编写的 Deferred 自定义操作 DLL,它将一组 .RDL 文件发布到 SQL 服务器报告 Web 服务。一切正常,我可以在各种 Try Catch 块中捕获大部分错误条件。
我唯一遇到的麻烦是,如果用户在发布过程中按下了安装程序中的“取消”按钮。它会立即弹出一条消息询问我是否要取消安装,但如果我回答 Yes 然后它会抛出一条消息:
Exception of type Microsoft.Deployment.WindowsInstaller.InstallCanceledException was thrown
还有一个确定按钮。
我试过为
添加一个特殊的异常处理程序catch (InstallCanceledException ex)
{
}
先于其他异常,但它似乎并没有捕捉到这一特定异常。
关于如何在取消长运行宁延迟自定义操作期间处理 InstallCanceledException 的任何建议?
产品团队考虑使用其中一个应用程序,但普通用户 运行 应用程序,他们不一定了解网络服务 URL 或有权将报告发布到网络服务。我放入的安装程序通常用于 运行ning SQL 脚本,我正在向安装程序添加第二个功能以发布报告。现在放弃它实际上效果很好。产品看到了我已经完成的工作并且他们喜欢它。 MSI 进度条会随着每份报告的名称更新。 MSI 提示输入 URI 和用户凭据,并且它已经知道 .RDL 文件所在的文件夹。我 运行 在他们单击下一步按钮时对 URI 进行验证,所以到我 运行执行序列中的延迟操作具有良好的 URI 和凭据。到目前为止,我什至在发布过程中断开了与 VPN 的连接,但它因正确的错误而失败。从字面上看,只有当用户按下取消时,我似乎无法捕获那个,但这也不是这项工作退出的障碍。
隐藏“取消”按钮不是一个合适的选项,因为他们可以随时取消。
public static ActionResult PublishSSRSReports(Session session)
{
session.Log("Begin PublishSSRSReports");
bool bFolderExists = false;
string sCustomActionData;
sCustomActionData = session["CustomActionData"];
string INSTALLDIR = Convert.ToString(MsiGetCustomActionDataAttribute(sCustomActionData, "/InstallDir="));
string SSRSURL = Convert.ToString(MsiGetCustomActionDataAttribute(sCustomActionData, "/SsrsUrl="));
string USERCREDENTIALS = Convert.ToString(MsiGetCustomActionDataAttribute(sCustomActionData, "/Credentials="));
string USERNAME = Convert.ToString(MsiGetCustomActionDataAttribute(sCustomActionData, "/Username="));
string PASSWORD = Convert.ToString(MsiGetCustomActionDataAttribute(sCustomActionData, "/Password="));
string ReportsFolderPath = INSTALLDIR + "SSRSReports";
DirectoryInfo directory = new DirectoryInfo(ReportsFolderPath);
FileInfo[] reports = directory.GetFiles("*.rdl"); //Getting all RDL files
ResetProgressBar(session, reports.Length);
CatalogItem[] catalogitem = null;
using (ReportingService2010 rsc = new ReportingService2010())
{
rsc.Url = SSRSURL;
if (USERCREDENTIALS == "0")
{
rsc.Credentials = System.Net.CredentialCache.DefaultCredentials; //User credential for Reporting Service
//the current logged system user
}
if (USERCREDENTIALS == "1")
{
string[] userdomain = USERNAME.Split(Convert.ToChar("\"));
rsc.Credentials = new System.Net.NetworkCredential(userdomain[1], PASSWORD, userdomain[0]);
}
catalogitem = rsc.ListChildren(@"/", false);
foreach (CatalogItem catalog in catalogitem)
{
if (catalog.Name == (DP))
{
EventLog.WriteEntry(AppDomain.CurrentDomain.FriendlyName, DP + " folder already exists");
bFolderExists = true;
}
}
if (bFolderExists == false)
{
rsc.CreateFolder(DP, @"/", null);
}
Warning[] Warnings = null;
foreach (FileInfo ReportFile in reports)
{
Byte[] definition = null;
Warning[] warnings = null;
try
{
FileStream stream = ReportFile.OpenRead();
definition = new Byte[stream.Length];
stream.Read(definition, 0, (int)stream.Length);
stream.Close();
}
catch (InstallCanceledException ex)
{
//session.Message(InstallMessage.Error, new Record { FormatString = ex.Message });
EventLog.WriteEntry(AppDomain.CurrentDomain.FriendlyName, ex.Message);
return ActionResult.UserExit;
}
catch (IOException ex)
{
session.Message(InstallMessage.Error, new Record { FormatString = ex.Message });
EventLog.WriteEntry(AppDomain.CurrentDomain.FriendlyName, ex.Message);
return ActionResult.Failure;
}
catch (Exception ex)
{
session.Message(InstallMessage.Error, new Record { FormatString = ex.Message });
EventLog.WriteEntry(AppDomain.CurrentDomain.FriendlyName, ex.Message);
return ActionResult.Failure;
}
try
{
CatalogItem report = rsc.CreateCatalogItem("Report", ReportFile.Name, @"/" + DP, true, definition, null, out Warnings);
DisplayActionData(session, ReportFile.Name);
IncrementProgressBar(session, 1);
if (report != null)
{
EventLog.WriteEntry(AppDomain.CurrentDomain.FriendlyName, ReportFile.Name + " Published Successfully ");
}
if (warnings != null)
{
foreach (Warning warning in warnings)
{
EventLog.WriteEntry(AppDomain.CurrentDomain.FriendlyName, string.Format("Report: {0} has warnings", warning.Message));
}
}
else
{
EventLog.WriteEntry(AppDomain.CurrentDomain.FriendlyName, string.Format("Report: {0} created successfully with no warnings", ReportFile.Name));
}
}
catch (InstallCanceledException ex)
{
//session.Message(InstallMessage.Error, new Record { FormatString = ex.Message });
EventLog.WriteEntry(AppDomain.CurrentDomain.FriendlyName, ex.Message);
return ActionResult.UserExit;
}
catch (SoapException ex)
{
session.Message(InstallMessage.Error, new Record { FormatString = ex.Message });
EventLog.WriteEntry(AppDomain.CurrentDomain.FriendlyName, ex.Detail.InnerXml.ToString());
return ActionResult.Failure;
}
catch (Exception ex)
{
session.Message(InstallMessage.Error, new Record { FormatString = ex.Message });
EventLog.WriteEntry(AppDomain.CurrentDomain.FriendlyName, ex.Message);
return ActionResult.Failure;
}
}
}
return ActionResult.Success;
我在 class
中也有这些private const string SpaceForwardSlash = " /";
private const string DP = "Test";
在 DTF 源代码中,我唯一看到抛出 InstallCanceledException 的地方是 Session.Message()。这是 MsiProcessMessage Windows API 函数的包装器。在我看来,如果您使用 Session.Message() 显示来自托管自定义操作的消息框,然后单击 'Cancel' 按钮,就会出现此异常。 DTF 看到消息框 'cancel' return 代码并抛出 InstallCanceledException。也许它然后落入某个地方的捕获块(可能是不同的动作?)在那里你调用类似于
的东西session.Message(InstallMessage.Error, new Record { FormatString = ex.Message })
显示仅包含异常的第二个消息框。
如果没有看到您的 MSI 源代码或完整的日志文件,我无法将所有内容 100% 拼凑在一起,但这也许会有所帮助。
以下是 Session.Message() 在 DTF 源代码中的定义:
public MessageResult Message(InstallMessage messageType, Record record)
{
if (record == null)
{
throw new ArgumentNullException("record");
}
int ret = RemotableNativeMethods.MsiProcessMessage((int) this.Handle, (uint) messageType, (int) record.Handle);
if (ret < 0)
{
throw new InstallerException();
}
else if (ret == (int) MessageResult.Cancel)
{
throw new InstallCanceledException();
}
return (MessageResult) ret;
}