我正在尝试使用 C# 运行本地进程来调用 Powerpoint 并将.pdf转换为.ppt。
我制作了一个独立的控制台应用程序,希望重现和隔离问题。不幸的是,它适用于独立版本,但不适用于集成版本。
当它不起作用时,它不会引发任何异常。它只是静默地无法创建.pdf文件。
新增功能:
我在事件日志上收到错误:
Microsoft PowerPoint
PowerPoint can't do this because a dialog box is open. Please close the dialog box to continue.
P1: 400205
P2: 15.0.4420.1017
在本地计算机上运行控制台命令、独立控制台应用程序或运行集成 Web 项目时,看不到任何类型的对话框。
根据官方文档,/pt 命令应该是静默的。
我可以将运行项目ApplicationPool
的Identity
设置为我登录的用户,并且我不再在事件日志中收到上述错误。但是,我没有从事件日志中收到其他错误(我可以说是相关的)。
但是,它仍然不起作用。Powerpoint 或 PDFCreator 仍然崩溃,并且不会创建.pdf。
我还尝试通过将其调用为集成问题的Process
来运行我的工作控制台应用程序,但这也没有用。
工作控制台应用程序:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System;
using System.Diagnostics;
using System.Text;
namespace ConsoleApplication3
{
class Program
{
static void Main(string[] args)
{
var psi = new ProcessStartInfo();
psi.FileName = ""C:\Program Files\Microsoft Office\Office15\POWERPNT.exe"";
psi.Arguments = "/pt "PDFCreator" "" "" dd0e03ff-f386-4e65-b89d-72c7f1ee502d.pptx";
psi.WorkingDirectory = "C:\Temp";
psi.CreateNoWindow = true;
psi.ErrorDialog = true;
psi.UseShellExecute = false;
psi.WindowStyle = ProcessWindowStyle.Hidden;
psi.RedirectStandardOutput = true;
psi.RedirectStandardInput = false;
psi.RedirectStandardError = true;
try
{
using (Process exeProcess = Process.Start(psi))
{
exeProcess.PriorityClass = ProcessPriorityClass.High;
var outString = new StringBuilder(100);
exeProcess.OutputDataReceived += (s, e) => outString.AppendLine(e.Data);
exeProcess.BeginOutputReadLine();
var errString = exeProcess.StandardError.ReadToEnd();
if (!string.IsNullOrEmpty(errString))
{
Console.WriteLine("errors reported 1");
}
}
}
catch (Exception ex)
{
ex.ToString();
Console.WriteLine("Errors reported 2 ");
Console.WriteLine(ex.ToString());
}
Console.WriteLine(psi.FileName);
Console.WriteLine(psi.Arguments);
Console.WriteLine(psi.WorkingDirectory);
}
}
}
它打印
"C:Program FilesMicrosoft OfficeOffice15POWERPNT.exe"
/pt "PDFCreator" "" "" dd0e03ff-f386-4e65-b89d-72c7f1ee502d.pptx
C:Temp
无法正常工作的集成应用程序:
using System;
using System.Diagnostics;
using System.Text;
namespace CT.Services.Helper
{
public static class ExecutableRunner
{
public static ExecutableResult RunExeNamed(string exeFilename, string commandLineArgs)
{
return RunExeNamed(exeFilename, commandLineArgs, null);
}
public static ExecutableResult RunExeNamed(string exeFilename, string commandLineArgs, string workingDirectory)
{
var result = new ExecutableResult { WasSuccessful = true };
var psi = new ProcessStartInfo();
psi.FileName = """+exeFilename+""";
psi.Arguments = commandLineArgs;
if(!string.IsNullOrEmpty(workingDirectory))
{
psi.WorkingDirectory = workingDirectory;
}
psi.CreateNoWindow = false;
psi.ErrorDialog = true;
psi.UseShellExecute = false;
psi.WindowStyle = ProcessWindowStyle.Hidden;
psi.RedirectStandardOutput = true;
psi.RedirectStandardInput = false;
psi.RedirectStandardError = true;
try
{
// Start the process with the info we specified.
// Call WaitForExit and then the using statement will close.
using (Process exeProcess = Process.Start(psi))
{
exeProcess.PriorityClass = ProcessPriorityClass.High;
var outString = new StringBuilder(100);
// use ansynchronous reading for at least one of the streams
// to avoid deadlock
exeProcess.OutputDataReceived += (s, e) => outString.AppendLine(e.Data);
exeProcess.BeginOutputReadLine();
// now read the StandardError stream to the end
// this will cause our main thread to wait for the
// stream to close (which is when ffmpeg quits)
var errString = exeProcess.StandardError.ReadToEnd();
if (!string.IsNullOrEmpty(errString))
{
result.WasSuccessful = false;
result.ErrorMessage = errString;
}
}
}
catch (Exception ex)
{
result.WasSuccessful = false;
result.ErrorMessage = ex.ToString();
}
Debug.WriteLine(psi.FileName);
Debug.WriteLine(psi.Arguments);
Debug.WriteLine(psi.WorkingDirectory);
return result;
}
}
public class ExecutableResult
{
public bool WasSuccessful { get; set; }
public string ErrorMessage { get; set; }
}
}
它打印
"C:Program FilesMicrosoft OfficeOffice15POWERPNT.exe"
/pt "PDFCreator" "" "" dd0e03ff-f386-4e65-b89d-72c7f1ee502d.pptx
C:Temp
我认为我用作文件路径或控制台命令的字符串之一会是错误的,所以我将它们打印到控制台,但您可以看到这些不是问题。
最后一次尝试 ;-)您可以使用互操作工具,打开文件,然后将其另存为 pdf。
要使这个小示例正常工作,您需要引用 Microsoft.Office.Interop.PowerPoint 程序集和 Microsoft.Office.Core 命名空间(位于位于 COM 程序集下的"Microsoft Office 14.0 对象库"程序集中找到)。
小例子:
/// <summary>
/// Converts the specified source file and saves it as pdf with the
/// specified destination filename.
/// </summary>
/// <param name="sourceFilename">The filename of the file to be converted into a pdf.</param>
/// <param name="destinationFilename">The filename of the pdf.</param>
/// <exception cref="System.IO.FileNotFoundException">Is thrown if the specified source file does not exist.</exception>
/// <exception cref="System.Exception">Is thrown if something went wrong during the convertation process.</exception>
public static void SaveAsPDF(string sourceFilename, string destinationFilename)
{
if (!File.Exists(sourceFilename))
{
throw new FileNotFoundException(string.Format("The specified file {0} does not exist.", sourceFilename), sourceFilename);
}
try
{
Microsoft.Office.Interop.PowerPoint.Application app = new Microsoft.Office.Interop.PowerPoint.Application();
app.Presentations.Open(sourceFilename).SaveAs(destinationFilename, Microsoft.Office.Interop.PowerPoint.PpSaveAsFileType.ppSaveAsPDF);
app.Quit();
}
catch (Exception e)
{
throw new Exception(string.Format("Unable to convert {0} to {1}", sourceFilename, destinationFilename), e);
}
}
可能是应用程序池的权限有问题吗?也许它没有执行您想要的所有操作的权限 - 您可以通过在应用程序池正在运行的同一上下文中执行控制台应用程序来检查这一点。
只是另一个想法(不确定它是否是一个愚蠢的想法)。据我所知,Powerpoint(至少自2010年以来)能够直接保存到pdf。对于 2007,您可以使用名为"另存为 PDF"的 Microsoft 插件,该插件可在此处找到 2007 Microsoft Office 加载项:Microsoft另存为 PDF 或 XPS。
使用Todd Main提供的解决方案,您可以(理论上)在启动时执行一个宏,该宏可以直接将文件存储为PDF(无需打印,希望没有对话框)。
更新:
我现在试过了 - 问题是您必须将宏存储在演示文稿中,因此您必须使用 pptm 文件格式。接下来,您必须打开文件一次并激活宏执行(显示在编辑屏幕的顶部)。之后,您可以简单地从命令行执行它,例如
POWERPNT.EXE /M Filename.pptm SaveAsPDF
宏如下所示:
Sub SaveAsPDF()
Dim ap As Presentation: Set ap = ActivePresentation
ap.SaveAs ap.Path & "" & ap.Name, ppSaveAsPDF
PowerPoint.Application.Quit
End Sub
代码的集成版本是 Web 应用程序的一部分
- 提供对正在为应用程序池用户创建此文件的当前工作目录的访问权限,并验证它是否具有读/写。
- 检查服务器上应用程序池进程用户的临时目录,并验证该进程是否具有对临时目录的读/写访问权限。
看到事件日志后编辑。
Asp.net 不应使用任何作为用户交互过程的应用程序。您需要找到一个不同的应用程序来创建这些 asp.net 友好的Powerpoint幻灯片,或者确定Powerpoint在打开对话框时尝试执行的操作。
看到此问题,因为您从未在失败的环境中运行应用程序的用户下实际打开 Powerpoint。
这将是运行应用程序池的用户,正如我看到您向 OmegaMan 提到的,该用户是 LocalSystem。
当LocalSystem尝试打开Powerpoint时,Powerpoint将执行所有Office应用程序在新用户第一次运行它们时完成安装时执行的操作。
你不会在控制台应用或本地系统中看到此安装框,因为你是在自己的帐户下运行这些安装框,并且以前打开过 PowerPoint。
正如OmegaMan正确指出的那样,Powerpoint是一个交互式过程,因此即使您非常非常小心,它也可能会造成麻烦。您最终会得到 Powerpoint 实例在它们应该关闭后保持打开状态,并且大量实例在
也就是说,如果您确实想要运行它,那么您需要以交互方式打开 Powerpoint 的用户身份运行应用程序池,或者将代码更改为在需要运行 Powerpoint 时使用模拟,然后模拟以交互方式使用它来执行所需操作的用户。