I'm trying to run a local process using C# to call Powerpoint and convert a .pdf to a .ppt.
I made a standalone console app, in hopes of reproducing and isolating the problem.
Unfortunately, it works for the standalone version, but not with the integrated version.
When it doesn't work, it throws no exceptions. It just silently fails to create the .pdf file.
NEW:
I'm getting an error on the event log:
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
I don't see any sort of dialog box when running the console commands, the standalone console application, or running the integrated web project on my local machine.
The /pt command is supposed to be silent, as per the official documentation.
I can set the Identity of the ApplicationPool the project is running under to the user that I log in as, and I no longer get the above error in the event log. However, I get no other errors (that I can tell are related) from the event log.
It still doesn't work, however. Powerpoint or PDFCreator still crashes, and doesn't create the .pdf.
I also tried to run my working console app by calling it as a Process from my integrated issue, and that didn't work either.
The working console application:
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);
}
}
}
It prints
"C:\Program Files\Microsoft Office\Office15\POWERPNT.exe"
/pt "PDFCreator" "" "" dd0e03ff-f386-4e65-b89d-72c7f1ee502d.pptx
C:\Temp
The not working integrated application:
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; }
}
}
it prints
"C:\Program Files\Microsoft Office\Office15\POWERPNT.exe"
/pt "PDFCreator" "" "" dd0e03ff-f386-4e65-b89d-72c7f1ee502d.pptx
C:\Temp
I would think that one of the strings I'm using as the file path or console command would be wrong, so I had them printed to the console, but you can see that those aren't the issue.
Last try ;-)
You could use Interop, open the file and afterwards save it as pdf.
For this little example to work you need to reference the Microsoft.Office.Interop.PowerPoint assembly and the Microsoft.Office.Core namespace (found in the "Microsoft Office 14.0 Object Library" assembly which is located under the COM assemblies).
Little Example:
/// <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);
}
}
Could it be a problem with the permissions of the application pool? Maybe it does not have permissions to perform all the actions you want to - you could check that by executing your console application under the same context as the application pool is running.
Just another idea (not sure if it is a stupid one or not). As far as I remember powerpoint (at least since 2010) is able to save directly to pdf. For 2007 you can use a microsoft plugin called "Save as PDF" which can be found here 2007 Microsoft Office Add-in: Microsoft Save as PDF or XPS.
Using the provided Solution from Todd Main you could (theoretically) execute a macro at startup which could store the file directly as PDF (without printing and hopefully without dialog box).
Update:
I've tried it right now - problem is you have to store the macro in the presentation, therfore you have to use the pptm file format. Next you have to open the file once and activate the macro execution (is shown on the top of the editing screen). After that you can simply execute it from the command line like
POWERPNT.EXE /M Filename.pptm SaveAsPDF
The macro looks like this:
Sub SaveAsPDF()
Dim ap As Presentation: Set ap = ActivePresentation
ap.SaveAs ap.Path & "\" & ap.Name, ppSaveAsPDF
PowerPoint.Application.Quit
End Sub
The integrated version of the code is part of a web application
Provide access to the current working directory where this file is being created for the app pool user and verify it has Read/Write.
Check the Temp directory on the server for the app-pool process user and verify that the process has read/write access to the temp directory.
Edit Upon seeing the event log.
Asp.net should not be using any application which is a user interactive process. You need to find a different application to create these powerpoint slides that is asp.net friendly or determine what Powerpoint is trying to do when it opens a dialog.
Odds are you are seeing this problem because you've never actually opened up Powerpoint under the user that is running the application in the environment where it's failing.
This will be the user that the application pool is running under and, as I see you've mentioned to OmegaMan, that user is LocalSystem.
When LocalSystem tries to open Powerpoint, then Powerpoint will be doing that thing that all Office applications do where they complete their install the first time a new user runs them.
You are not seeing this installation box in your console app or on your local system because you're running those under your own account and you've previously opened PowerPoint.
As OmegaMan rightly says Powerpoint is meant to be an interactive process so it is probably going to cause trouble even if you're very very careful. You'll end up with instances of Powerpoint staying open after they're supposed to have closed and loads of instances building up in the
That said, if you really want to get this running then you either need to get the application pool running as a user that has opened Powerpoint interactively, or change your code to use impersonation at the point where you need to run Powerpoint and then impersonate a user that has used it interactively to do what you need.
Related
I'm currently working on a Winforms program from Visual Studio that acts as a control panel for a whole bunch of TeraTerm macros. I did a dumb and didn't add my first working version to version control, and now it's stopped working and I have no idea why/how to get back to what I had.
The function is question is
using System;
using System.IO.Ports;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using System.Diagnostics;
...
namespace Test
{
public partial class MainWindow : Form
{
...
private void ImagesButton_Click(object sender, EventArgs e)
{
RunMacro("F:\\Users\\Isaac\\Documents\\LED Sign Commands\\Macros\\StartDisplayImages.ttl");
}
....
private void RunMacro(string userArgument)
{
panel1.Enabled = false;
StatusLabel.Visible = true;
Process process = new Process();
ProcessStartInfo startInfo = new ProcessStartInfo()
{
WindowStyle = ProcessWindowStyle.Hidden,
#if DEBUG
FileName = "F:\\Users\\Isaac\\Documents\\LED Sign Commands\\teraterm\\ttpmacro.exe",
#else
FileName = "..\\teraterm\\ttpmacro.exe",
#endif
Arguments = userArgument
};
process.StartInfo = startInfo;
process.Start();
process.WaitForExit();
panel1.Enabled = true;
StatusLabel.Visible = false;
}
...
}
}
When run, I see that ttpmacro.exe does start, and if I omit the Arguments assignment then it will prompt me to select a macro; if I select StartDisplayImages.ttl, it will run as expected. If I include it as an argument as above, however, then ttpmacro still opens but immediately closes. No error comes up (and RedirectStandardOutput/Error produce nothing), it's as if ttpmacro accepts the file but won't do anything with it. I've confirmed both filepaths are valid and correct.
While I didn't add version control, I did extract the main file using ILSpy, and my original functions in the working version were:
private void ImagesButton_Click(object sender, EventArgs e)
{
RunMacro("/V ..\\Macros\\StartDisplayImages.ttl");
}
private void RunMacro(string userArgument)
{
panel1.Enabled = false;
StatusLabel.Visible = true;
Process process = new Process();
ProcessStartInfo startInfo = (process.StartInfo = new ProcessStartInfo
{
WindowStyle = ProcessWindowStyle.Hidden,
FileName = "..\\teraterm\\ttpmacro.exe",
Arguments = userArgument
});
process.Start();
process.WaitForExit();
panel1.Enabled = true;
StatusLabel.Visible = false;
}
Being from the published release, the filepaths are relative to the folder of the application. Other than that, the only difference seems to be minor syntax in how process.StartInfo is assigned, but I tried reverting that with no luck. Target framework is .NET Core 3.1. The /V flag isn't the issue; removing it simply makes the ttpmacro window visible for the fraction of a second it runs before closing. If I use a commandline execution of the same file (eg start "F:/Users/.../ttpmacro.exe" "F:/.../StartDisplayImages.ttl"), it also runs as expected.
It turned out the problem was the spaces in the full macro filepath, and surrounding it with escaped double quotes resolved the issue. TeraTerm just wasn't telling me that it wasn't finding the file. Should have been obvious, but I was sure it had been working previously when I was debugging, without requiring the quotes.
Im making an application which needs to monitor the filesystem using FileSystemWatcher, to detect how an installation affects the filesystem.
To get rid of noise i want to filter the events that are created by their creating user, and that code is working with the //BUILTIN //Administrator user, which is used by default when doing an installation. But still there are quite a bit of noise. Then i got the idea of creating a specific user that i can use for running the installation file, and filter on that specific user, and thereby getting rid of allmost all the noise.
this is my code for the process creation and start
private void executeInnoInstaller(string path, string fileName)
{
// Use ProcessStartInfo class
ProcessStartInfo installerProces = new ProcessStartInfo();
installerProces.CreateNoWindow = true;
installerProces.UseShellExecute = false;
installerProces.FileName = path + "\"" + fileName + "\"";
installerProces.WindowStyle = ProcessWindowStyle.Normal;
installerProces.UserName = "test";
System.Security.SecureString encPassword = new System.Security.SecureString();
foreach (System.Char c in "test")
{
encPassword.AppendChar(c);
}
encPassword.MakeReadOnly();
installerProces.Password = encPassword;
try
{
// Start the process with the info we specified.
// Call WaitForExit and then the using statement will close.
using (Process exeProcess = Process.Start(installerProces))
{
exeProcess.WaitForExit();
//int exitCode = exeProcess.ExitCode;
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
this code exits with a access denied.
OS=Windows
Ive already tried to run the installer.exe from the OS filehandler with SHIFT - Rightclick using the specified user, and it works.
VisualStudio is run as administrator.
Ive tried to run the build project exe file as administrator, but it does not work.
Without the user credentials, the code works and uses the //BUILTIN //Administrator account
Does anybody have any idea ?
Thank you beforehand for your time and effort.
This code works if i turn down the UAC securitylevel to the lowest.
I'm trying to build UWP app and I'm struggling with an issue:
I have noticed there is now way to launch external executable, but I can open file with its default application using LaunchFile (Like mentioned here https://learn.microsoft.com/en-us/uwp/api/Windows.System.Launcher#Windows_System_Launcher_LaunchFileAsync_Windows_Storage_IStorageFile_).
Should it work for a .bat file too? Where should i place the .bat file if it is.? Because i tried to give a path to a .bat file i wrote and I got an exception.
Thanks!!
Its not going to work. If you can get some 3rd party application to open and handle bat files by default then maybe, but otherwise nothing the OS handles like cmd or vbscript will run. Not sure what your goal is, but a 3rd party scripting app like Autohotkey does work.
From .NET 4 there is and Process and ProcessStartInfo
classes under the System.Diagnostics namespace which allow you
to Win32 applications from C#.
public static Task<bool> ExecuteBatchFile(string BatchFilePath, string BatchFileDirectory)
{
try
{
Task<bool> executeBatchFileTask = Task.Run<bool>(() =>
{
bool hasProcessExited = false;
ProcessStartInfo startInfo = new ProcessStartInfo()
{
FileName = BatchFilePath,
CreateNoWindow = false,
UseShellExecute = true,
WindowStyle = ProcessWindowStyle.Normal,
WorkingDirectory = BatchFileDirectory
};
// Start the process with the info we specified.
// Call WaitForExit and then the using-statement will close.
using (System.Diagnostics.Process exeProcess = System.Diagnostics.Process.Start(startInfo))
{
while (!exeProcess.HasExited)
{
//Do nothing
}
hasProcessExited = true;
}
return hasProcessExited;
});
return executeBatchFileTask;
}
catch (Exception ex)
{
throw;
}
}
And call this method in your code by:
bool hasBatchFileExecuted = await ExecuteBatchFile(BatchFilePath, BatchFilePathDirectory);
Create your own batch file like:
1) Create a text file in notepad and save it as run.bat under save as category and all files.
2) Write the following code to execute a C# program/However you can write your own commands also:
csc someRandomCode.cs
pause
3) Save and quit.
I'm calling a python script from a C# tool. I based my code on this post. I use ProcessStartInfo to start python and pass a .py file and some argument to it. The code runs fine when the .py, CreateAssessorMap.py, file is on the c drive but not when it is on a mapped network drive. No error is thrown but no python code is executed as far as I can see. If I manually do the same operation from the command line it runs fine.
The code below the first procStartInfo.Arguments will fail as CreateAssessorMap.py is on a network drive. The commented out line below it would work as the script is on the C drive.
private void btnPrint_Click(object sender, EventArgs e)
{
try
{
ProcessStartInfo procStartInfo = new ProcessStartInfo();
procStartInfo.FileName = "python";
procStartInfo.Arguments = string.Format("{0} {1} {2}", #"D:\Map_Generate_Scripts\CreateAssessorMap.py", this.txtSheet.Text, txtDestinationFolder.Text);
//procStartInfo.Arguments = string.Format("{0} {1} {2} ", #"C:\Projects\Map_Generate_Scripts\CreateAssessorMap.py", this.txtSheet.Text, txtDestinationFolder.Text);
procStartInfo.UserName = null;
procStartInfo.UseShellExecute = false;
procStartInfo.RedirectStandardOutput = true;
// Do not create the black window.
procStartInfo.CreateNoWindow = true;
// Now you create a process, assign its ProcessStartInfo, and start it.
using (Process process = Process.Start(procStartInfo))
{
using (System.IO.StreamReader reader = process.StandardOutput)
{
string result = reader.ReadToEnd();
Console.Write(result);
}
}
}
catch (Exception ecpt)
{
Console.WriteLine(ecpt.Message);
}
this.Parent.Hide();
}
Edit
I added some error handling and the python code is failing with the message that the .py file cannot be found.
python: can't open file 'D:\Map_Generate_Scripts\CreateAssessorMapCreateAssessorMap.py': [Errno 2] No such file or directory
I know it exists since I can run the file from the command line with the same arguments. So it seems that when I run from C# the python process can't find the d drive.
I assume that when it runs from a network drive it takes longer and your program does not wait for Python script's completion. I suggest adding proces.Wait() before reading the output stream.
As stated in the comments, the solution for Dowlers was to use the full path instead of the mapped network drive.
Change
#"D:\Map_Generate_Scripts\CreateAssessorMap.py"
To
#"\\[network path]\Map_Generate_Scripts\CreateAssessorMap.py"
Please give me a working code for achieving the time synchronization using w32tm.exe in C#.net. I already tried. Code shown below.
System.Diagnostics.Process p;
string output;
p = new System.Diagnostics.Process();
p.StartInfo = procStartInfo;
p.StartInfo.FileName = "w32tm";
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.Arguments = " /resync /computer:xxxxx977";
p.Start();
p.WaitForExit();
output = p.StandardOutput.ReadLine().ToString();
MessageBox.Show(output);
But i am getting the following error
The specified module could not be found. (0x8007007E).
and my requirement also wants to redirect the standardoutput for the success message.
you can try following C# code to enable date time sync from NTP server.
by the way i guess which is /resync command number so that i wouldn't have to launch that dirty external process
/// <summary>Synchronizes the date time to ntp server using w32time service</summary>
/// <returns><c>true</c> if [command succeed]; otherwise, <c>false</c>.</returns>
public static bool SyncDateTime()
{
try
{
ServiceController serviceController = new ServiceController("w32time");
if (serviceController.Status != ServiceControllerStatus.Running)
{
serviceController.Start();
}
Logger.TraceInformation("w32time service is running");
Process processTime = new Process();
processTime.StartInfo.FileName = "w32tm";
processTime.StartInfo.Arguments = "/resync";
processTime.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
processTime.Start();
processTime.WaitForExit();
Logger.TraceInformation("w32time service has sync local dateTime from NTP server");
return true;
}
catch (Exception exception)
{
Logger.LogError("unable to sync date time from NTP server", exception);
return false;
}
}
detailled explanation :
windows have a service, called w32time, which can sync time on your computer, first i check that the service is running, using ServiceController class, then, because i don't know which is the resync command number so that i can use ServiceController launch command method, i use a ProcessStart to launch a dos command on that services : w32tm /resync
The error is occurring when the .Net runtime JITs the method you're about to step into, because it couldn't find one of the types used by the method.
What exactly does the method that you can't step into do, and what types / methods does it use?
Refer this Link
So check whether any item you tried to load is in the folder or not.