Here's the basic premise:
My user clicks some gizmos and a PDF file is spit out to his desktop. Is there some way for me to send this file to the printer queue and have it print to the locally connected printer?
string filePath = "filepathisalreadysethere";
SendToPrinter(filePath); //Something like this?
He will do this process many times. For each student in a classroom he has to print a small report card. So I generate a PDF for each student, and I'd like to automate the printing process instead of having the user generated pdf, print, generate pdf, print, generate pdf, print.
Any suggestions on how to approach this? I'm running on Windows XP with Windows Forms .NET 4.
I've found this StackOverflow question where the accepted answer suggests:
Once you have created your files, you
can print them via a command line (you
can using the Command class found in
the System.Diagnostics namespace for
that)
How would I accomplish this?
Adding a new answer to this as the question of printing PDF's in .net has been around for a long time and most of the answers pre-date the Google Pdfium library, which now has a .net wrapper. For me I was researching this problem myself and kept coming up blank, trying to do hacky solutions like spawning Acrobat or other PDF readers, or running into commercial libraries that are expensive and have not very compatible licensing terms. But the Google Pdfium library and the PdfiumViewer .net wrapper are Open Source so are a great solution for a lot of developers, myself included. PdfiumViewer is licensed under the Apache 2.0 license.
You can get the NuGet package here:
https://www.nuget.org/packages/PdfiumViewer/
and you can find the source code here:
https://github.com/pvginkel/PdfiumViewer
Here is some simple code that will silently print any number of copies of a PDF file from it's filename. You can load PDF's from a stream also (which is how we normally do it), and you can easily figure that out looking at the code or examples. There is also a WinForm PDF file view so you can also render the PDF files into a view or do print preview on them. For us I simply needed a way to silently print the PDF file to a specific printer on demand.
public bool PrintPDF(
string printer,
string paperName,
string filename,
int copies)
{
try {
// Create the printer settings for our printer
var printerSettings = new PrinterSettings {
PrinterName = printer,
Copies = (short)copies,
};
// Create our page settings for the paper size selected
var pageSettings = new PageSettings(printerSettings) {
Margins = new Margins(0, 0, 0, 0),
};
foreach (PaperSize paperSize in printerSettings.PaperSizes) {
if (paperSize.PaperName == paperName) {
pageSettings.PaperSize = paperSize;
break;
}
}
// Now print the PDF document
using (var document = PdfDocument.Load(filename)) {
using (var printDocument = document.CreatePrintDocument()) {
printDocument.PrinterSettings = printerSettings;
printDocument.DefaultPageSettings = pageSettings;
printDocument.PrintController = new StandardPrintController();
printDocument.Print();
}
}
return true;
} catch {
return false;
}
}
You can tell Acrobat Reader to print the file using (as someone's already mentioned here) the 'print' verb. You will need to close Acrobat Reader programmatically after that, too:
private void SendToPrinter()
{
ProcessStartInfo info = new ProcessStartInfo();
info.Verb = "print";
info.FileName = #"c:\output.pdf";
info.CreateNoWindow = true;
info.WindowStyle = ProcessWindowStyle.Hidden;
Process p = new Process();
p.StartInfo = info;
p.Start();
p.WaitForInputIdle();
System.Threading.Thread.Sleep(3000);
if (false == p.CloseMainWindow())
p.Kill();
}
This opens Acrobat Reader and tells it to send the PDF to the default printer, and then shuts down Acrobat after three seconds.
If you are willing to ship other products with your application then you could use GhostScript (free), or a command-line PDF printer such as http://www.commandlinepdf.com/ (commercial).
Note: the sample code opens the PDF in the application current registered to print PDFs, which is the Adobe Acrobat Reader on most people's machines. However, it is possible that they use a different PDF viewer such as Foxit (http://www.foxitsoftware.com/pdf/reader/). The sample code should still work, though.
I know the tag says Windows Forms... but, if anyone is interested in a WPF application method, System.Printing works like a charm.
var file = File.ReadAllBytes(pdfFilePath);
var printQueue = LocalPrintServer.GetDefaultPrintQueue();
using (var job = printQueue.AddJob())
using (var stream = job.JobStream)
{
stream.Write(file, 0, file.Length);
}
Just remember to include System.Printing reference, if it's not already included.
Now, this method does not play well with ASP.NET or Windows Service. It should not be used with Windows Forms, as it has System.Drawing.Printing. I don't have a single issue with my PDF printing using the above code.
I should mention, however, that if your printer does not support Direct Print for PDF file format, you're out of luck with this method.
The following code snippet is an adaptation of Kendall Bennett's code for printing pdf files using the PdfiumViewer library. The main difference is that a Stream is used rather than a file.
public bool PrintPDF(
string printer,
string paperName,
int copies, Stream stream)
{
try
{
// Create the printer settings for our printer
var printerSettings = new PrinterSettings
{
PrinterName = printer,
Copies = (short)copies,
};
// Create our page settings for the paper size selected
var pageSettings = new PageSettings(printerSettings)
{
Margins = new Margins(0, 0, 0, 0),
};
foreach (PaperSize paperSize in printerSettings.PaperSizes)
{
if (paperSize.PaperName == paperName)
{
pageSettings.PaperSize = paperSize;
break;
}
}
// Now print the PDF document
using (var document = PdfiumViewer.PdfDocument.Load(stream))
{
using (var printDocument = document.CreatePrintDocument())
{
printDocument.PrinterSettings = printerSettings;
printDocument.DefaultPageSettings = pageSettings;
printDocument.PrintController = new StandardPrintController();
printDocument.Print();
}
}
return true;
}
catch (System.Exception e)
{
return false;
}
}
In my case I am generating the PDF file using a library called PdfSharp and then saving the document to a Stream like so:
PdfDocument pdf = PdfGenerator.GeneratePdf(printRequest.html, PageSize.A4);
pdf.AddPage();
MemoryStream stream = new MemoryStream();
pdf.Save(stream);
MemoryStream stream2 = new MemoryStream(stream.ToArray());
One thing that I want to point out that might be helpful to other developers is that I had to install the 32 bit version of the Pdfium native DLL in order for the printing to work even though I am running Windows 10 64 bit. I installed the following two NuGet packages using the NuGet package manager in Visual Studio:
PdfiumViewer
PdfiumViewer.Native.x86.v8-xfa
The easy way:
var pi=new ProcessStartInfo("C:\file.docx");
pi.UseShellExecute = true;
pi.Verb = "print";
var process = System.Diagnostics.Process.Start(pi);
This is a slightly modified solution. The Process will be killed when it was idle for at least 1 second. Maybe you should add a timeof of X seconds and call the function from a separate thread.
private void SendToPrinter()
{
ProcessStartInfo info = new ProcessStartInfo();
info.Verb = "print";
info.FileName = #"c:\output.pdf";
info.CreateNoWindow = true;
info.WindowStyle = ProcessWindowStyle.Hidden;
Process p = new Process();
p.StartInfo = info;
p.Start();
long ticks = -1;
while (ticks != p.TotalProcessorTime.Ticks)
{
ticks = p.TotalProcessorTime.Ticks;
Thread.Sleep(1000);
}
if (false == p.CloseMainWindow())
p.Kill();
}
System.Diagnostics.Process.Start can be used to print a document. Set UseShellExecute to True and set the Verb to "print".
You can try with GhostScript like in this post:
How to print PDF on default network printer using GhostScript (gswin32c.exe) shell command
I know Edwin answered it above but his only prints one document. I use this code to print all files from a given directory.
public void PrintAllFiles()
{
System.Diagnostics.ProcessStartInfo info = new System.Diagnostics.ProcessStartInfo();
info.Verb = "print";
System.Diagnostics.Process p = new System.Diagnostics.Process();
//Load Files in Selected Folder
string[] allFiles = System.IO.Directory.GetFiles(Directory);
foreach (string file in allFiles)
{
info.FileName = #file;
info.CreateNoWindow = true;
info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
p.StartInfo = info;
p.Start();
}
//p.Kill(); Can Create A Kill Statement Here... but I found I don't need one
MessageBox.Show("Print Complete");
}
It essentually cycles through each file in the given directory variable Directory - > for me it was #"C:\Users\Owner\Documents\SalesVaultTesting\" and prints off those files to your default printer.
this is a late answer, but you could also use the File.Copy method of the System.IO namespace top send a file to the printer:
System.IO.File.Copy(filename, printerName);
This works fine
You can use the DevExpress PdfDocumentProcessor.Print(PdfPrinterSettings) Method.
public void Print(string pdfFilePath)
{
if (!File.Exists(pdfFilePath))
throw new FileNotFoundException("No such file exists!", pdfFilePath);
// Create a Pdf Document Processor instance and load a PDF into it.
PdfDocumentProcessor documentProcessor = new PdfDocumentProcessor();
documentProcessor.LoadDocument(pdfFilePath);
if (documentProcessor != null)
{
PrinterSettings settings = new PrinterSettings();
//var paperSizes = settings.PaperSizes.Cast<PaperSize>().ToList();
//PaperSize sizeCustom = paperSizes.FirstOrDefault<PaperSize>(size => size.Kind == PaperKind.Custom); // finding paper size
settings.DefaultPageSettings.PaperSize = new PaperSize("Label", 400, 600);
// Print pdf
documentProcessor.Print(settings);
}
}
public static void PrintFileToDefaultPrinter(string FilePath)
{
try
{
var file = File.ReadAllBytes(FilePath);
var printQueue = LocalPrintServer.GetDefaultPrintQueue();
using (var job = printQueue.AddJob())
using (var stream = job.JobStream)
{
stream.Write(file, 0, file.Length);
}
}
catch (Exception)
{
throw;
}
}
I am executing an ffmpeg command with a very complex filter and with a pipe within a C# application. I am putting images into the ffmpeg input stream (pipe)for rendering this images as overlays to the final video.
I want to render images with the pipe until the pipe closes. Unfortunately, I do not know how I can recognize that the pipe of the ffmpeg process has closed. Is there any possibility to recognize this event within C#?
The process is started like:
this._ffmpegProcess = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = this._ffmpegPath,
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}
};
this._ffmpegProcess.StartInfo.Arguments = ffmpegCmd;
this._ffmpegProcess.OutputDataReceived += this.Proc_DataReceived;
this._ffmpegProcess.ErrorDataReceived += this.Proc_DataReceived;
this._ffmpegProcess.Exited += this._ffmpegProcess_Exited;
this._ffmpegProcess.Start();
this._ffmpegProcess.BeginOutputReadLine();
this._ffmpegProcess.BeginErrorReadLine();
The rendering happens within a timer:
this._renderOverlayTimer = new Timer(this.RenderOverlay);
this._renderOverlayTimer.Change(0, 30);
The timer is started right after starting the ffmpeg process:
private void RenderOverlay(object state)
{
using (var ms = new MemoryStream())
{
using (var img = GetImage(...))
{
img.Save(ms, ImageFormat.Png);
ms.WriteTo(this._ffmpegProcess.StandardInput.BaseStream);
}
}
}
The problem is that I always receive a "The pipe has ended" error at "ms.WriteTo()".
I now use a named pipe and trace the number of frames to process. The named pipe is closed right after the last frame is processed. This solution leads to a correct video without IOExceptions.
I have an application that must read it's own output that is written via
Console.WriteLine("blah blah");
I'm trying
Process p = Process.GetCurrentProcess();
StreamReader input = p.StandardOutput;
input.ReadLine();
But it doesn't work because of "InvalidOperationException" at the second line. It says something like "StandardOutput wasn't redirected, or the process has not been started yet" (translated)
How can I read my own output ? Is there another way to do that ? And to be complete how to write my own input ?
The application with the output is running already.
I want to read it's output live in the same application. There is no 2nd app. Only one.
I'm just guessing as to what your intention might be but if you want to read the output from a application you started you can redirect the output.
// Start the child process.
Process p = new Process();
// Redirect the output stream of the child process.
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = "Write500Lines.exe";
p.Start();
// Do not wait for the child process to exit before
// reading to the end of its redirected stream.
// p.WaitForExit();
// Read the output stream first and then wait.
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();
example from http://msdn.microsoft.com/en-us/library/system.diagnostics.process.standardoutput.aspx
Edit:
If you want to redirect the output of your current console application as your edit specifies you can use.
private static void Main(string[] args)
{
StringWriter writer = new StringWriter();
Console.SetOut(writer);
Console.WriteLine("hello world");
StringReader reader = new StringReader(writer.ToString());
string str = reader.ReadToEnd();
}
I'm trying to automate svnadmin dump using C# ProcessStartInfo.
The way I've done it on the command line is like so,
svnadmin dump c:\Repositories\hackyhacky > c:\backup\hackyhacky.svn_dump
Works a treat and dumps successfully, and I can verify this by restoring it into another repository like so
svnadmin load c:\Repositories\restore_test < c:\backup\hackyhacky.svn_dump
Which restores successfully - yay!
Now... I need to replicate the command line piping into another file using C#, but for some reason
var startInfo = new ProcessStartInfo(Path.Combine(SvnPath, "svnadmin"),"dump c:\Repositories\hackyhacky")
{CreateNoWindow = true, RedirectStandardOutput = true,RedirectStandardError = true,UseShellExecute = false};
process.StartInfo = startInfo;
process.Start();
StreamReader reader = process.StandardOutput;
char[] standardOutputCharBuffer = new char[4096];
byte[] standardOutputByteBuffer;
int readChars = 0;
long totalReadBytes = 0;
// read from the StandardOutput, and write directly into another file
using (StreamWriter writer = new StreamWriter(#"C:\backup\hackyhacky.svn_dump", false)) {
while (!reader.EndOfStream) {
// read some chars from the standard out
readChars = reader.Read(standardOutputCharBuffer, 0, standardOutputCharBuffer.Length);
// convert the chars into bytes
standardOutputByteBuffer = reader.CurrentEncoding.GetBytes(standardOutputCharBuffer);
// write the bytes out into the file
writer.Write(standardOutputCharBuffer.Take(readChars).ToArray());
// increment the total read
totalReadBytes += standardOutputByteBuffer.Length;
}
}
Dumps the same repo into hackyhacky.svn_dump.
But when I run my load command line now
svnadmin load c:\Repositories\restore_test < c:\backup\hackyhacky.svn_dump
I get a checksum error weird-error!
svnadmin load c:\Repositories\restore_test < c:\backup\hackyhacky.svn_dump
< Started new transaction, based on original revision 1
* adding path : Dependencies ... done.
* adding path : Dependencies/BlogML.dll ...svnadmin: Checksum mismatch, fil
e '/Dependencies/BlogML.dll':
expected: d39863a4c14cf053d01f636002842bf9
actual: d19831be151d33650b3312a288aecadd
I'm guessing this is to do with how I'm redirecting and reading the StandardOutput.
Does anyone know the right way to mimic the command line file piping behaviour in C#?
Any help is greatly appreciated.
-CV
UPDATE
I've tried using a BinaryWriter and using the standardOutputByteBuffer to write to the file, but that doesn't work either. I get a different error about incorrect header format or something.
Alright! If you can't beat em, join em....
I found a post where the author pipes to a file directly within the Process StartInfo, and claims it works.
http://weblogs.asp.net/israelio/archive/2004/08/31/223447.aspx
It didn't work for me, as described in another gentleman's post
http://webcache.googleusercontent.com/search?q=cache:http://www.deadbeef.com/index.php/redirecting_the_output_of_a_program_to_a
He writes a batch file first with the piping and then executes it...
amWriter bat = File.CreateText("foo.bat");
bat.WriteLine("#echo off");
bat.WriteLine("foo.exe -arg >" + dumpDir + "\\foo_arg.txt");
bat.Close();
Process task = new Process();
task.StartInfo.UseShellExecute = false;
task.StartInfo.FileName = "foo.bat";
task.StartInfo.Arguments = "";
task.Start();
task.WaitForExit();
In his words:
Truly horrific, but it has the
advantage of working!
To be perfectly frank, I'm a bit annoyed this has taken me as long as it has, so the batch file solution works well so I'm going to stick with it.
The first thing I'd try is writing the character array - not the byte array - to the file.
That should work as long as the output is just simple text. There's other encoding issues, though, if the output is more complex: you're writing the file as UTF-8, whereas the default for command-line output (I believe) is Windows-1252.
I've been trying to do this very thing, and just stumbled on another solution to the problem used by Hector Sosa's svnmanagerlib sourceforge project:
The key to solving this was surrounding the call to WaitForExit() with
file operations. Also needed to make sure to write the output to disk.
Here are the relevant lines:
File.AppendAllText( destinationFile, myOutput.ReadToEnd() );
svnCommand.WaitForExit(); File.AppendAllText(destinationFile,
myOutput.ReadToEnd() );
Notice that I make a call to File.AppendAllText() twice. I have found
that the output stream does not write everything during the first call
to File.AppendAllText() on some occasions.
public static bool ExecuteWritesToDiskSvnCommand(string command, string arguments, string destinationFile, out string errors)
{
bool retval = false;
string errorLines = string.Empty;
Process svnCommand = null;
ProcessStartInfo psi = new ProcessStartInfo(command);
psi.RedirectStandardOutput = true;
psi.RedirectStandardError = true;
psi.WindowStyle = ProcessWindowStyle.Hidden;
psi.UseShellExecute = false;
psi.CreateNoWindow = true;
try
{
Process.Start(psi);
psi.Arguments = arguments;
svnCommand = Process.Start(psi);
StreamReader myOutput = svnCommand.StandardOutput;
StreamReader myErrors = svnCommand.StandardError;
File.AppendAllText(destinationFile, myOutput.ReadToEnd());
svnCommand.WaitForExit();
File.AppendAllText(destinationFile, myOutput.ReadToEnd());
if (svnCommand.HasExited)
{
errorLines = myErrors.ReadToEnd();
}
// Check for errors
if (errorLines.Trim().Length == 0)
{
retval = true;
}
}
catch (Exception ex)
{
string msg = ex.Message;
errorLines += Environment.NewLine + msg;
}
finally
{
if (svnCommand != null)
{
svnCommand.Close();
}
}
errors = errorLines;
return retval;
}
I am writing the batch file and executing it through C# program.
Writing Batch file :
I will get the Path, Executable name and arguments from app.config and write them to a batch file.
Executing Batch file :
Once I write the batch file I pass the file name to below function which executes the batch file to launches an application.
Problem :
My program will write a lot of batch files which are executed immediately after each and every file is written. I find that, some times the applications are not started which means that batch files are not executed. I didn't even get any error messages or prompts for this failure of batch file execution.
Expected solution :
Any problem in executing the batch file, I should be able to log it or prompt an error.
Code that executes Batch File :
System.Diagnostics.ProcessStartInfo procinfo = new System.Diagnostics.ProcessStartInfo("cmd.exe");
procinfo.UseShellExecute = false;
procinfo.RedirectStandardError = true;
procinfo.RedirectStandardInput = true;
procinfo.RedirectStandardOutput = true;
System.Diagnostics.Process process = System.Diagnostics.Process.Start(procinfo);
System.IO.StreamReader stream = System.IO.File.OpenText(BatchPath + LatestFileName);
System.IO.StreamReader sroutput = process.StandardOutput;
System.IO.StreamWriter srinput = process.StandardInput;
while (stream.Peek() != -1)
{
srinput.WriteLine(stream.ReadLine());
}
Log.Flow_writeToLogFile("Executed .Bat file : " + LatestFileName);
stream.Close();
process.Close();
srinput.Close();
sroutput.Close();
I'm not sure where your problem lies specifically but I've had no problems with the following code:
using (FileStream file = new FileStream("xyz.cmd", FileMode.Create)) {
using (StreamWriter sw = new StreamWriter(file)) {
sw.Write("#echo ====================\n");
sw.Close();
}
}
Process p = new Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.FileName = "xyz.cmd";
//p.StartInfo.RedirectStandardOutput = true;
p.Start();
//String s = p.StandardOutput.ReadLine();
//while (s != null) {
// MessageBox.Show(s);
// s = p.StandardOutput.ReadLine();
//}
p.WaitForExit();
Obviously that's been cut down a bit for the purposes of hiding my "secret sauce" but that's code currently being used in production without issues.
I do have one question. Why don't you execute the cmd file directly rather than running cmd.exe?
Probably the first thing I'd do is to print out the BatchPath + LatestFileName value to see if you're creating any weirdly named files which would prevent cmd.exe from running them.