c# Print PDF with Adobe Reader and close - c#

I didnt found a good (free) and simple Solution for printing PDFs (for e.g. from a "Hot"-Folder (FileSystemWatcher) on a Server) with Acrobat and close Acrobat Reader. So i wrote my own and i hope it will help someone.
(Yes, u can use a old free Foxit Reader Version, but we had too much trouble with it, sometimes stuck in Memory without printing)
The Point was, after printing, the file must be moved to a archive dir, but Adobe did not close. So i never knowed when its done, or wait 30+ Seconds and kill (not so fine if the Server needs longer and takes to much time).

Here my Solution, i run the Process and wait until one of the subprocesses of my Adobe Process shows the recent open Window.
Thanks to mtijn for his "Process Searcher" solution https://stackoverflow.com/a/7189381/480982
var prz = Process.Start("C:\\Program Files (x86)\\Adobe\\Acrobat Reader DC\\Reader\\AcroRd32.exe", "/h /t \"" + YOURPDFFILE + "\" \"" + YOURPRINTER + "\"");
bool loop = true;
while (loop)
{
//u can use Thread.Sleep(x) too;
prz.WaitForExit(500);
ManagementObjectSearcher searcher = new ManagementObjectSearcher(
"SELECT * " +
"FROM Win32_Process " +
"WHERE ParentProcessId=" + prz.Id);
ManagementObjectCollection collection = searcher.Get();
if (collection.Count > 0)
{
foreach (var item in collection)
{
UInt32 childProcessId = (UInt32)item["ProcessId"];
if ((int)childProcessId != Process.GetCurrentProcess().Id)
{
Process childProcess = Process.GetProcessById((int)childProcessId);
//If a File is open the Title begins with "Filename - Adobe ...", but after print/closing the recent window starts with "Adobe Acr..."
if(childProcess.MainWindowTitle.StartsWith("Adobe Acrobat"))
{
loop = false;
break;
}
}
}
}
}
//"Recent" Window found, lets kill the Process
prz.Kill();
// Now here u can move or Delete the pdf file

Related

Print PDF file using Foxit Reader and C#

My program prints silently via the PDF reader Foxit Reader in a new process.
On occasion, my program attempts to print two PDFs at the same time, which causes one of them to fail to print.
Here is my code:
string filename = "file.pdf";
string fileDir1 = #"C:\Program Files (x86)\Foxit Software\Foxit Reader\FoxitReader.exe";
Process pdfProcess = new Process();
pdfProcess.StartInfo.FileName = fileDir1;
pdfProcess.StartInfo.Arguments = string.Format(#"/t {0} {1}", filename ,"pos-80");
pdfProcess.StartInfo.CreateNoWindow = true;
pdfProcess.StartInfo.WorkingDirectory = Path.GetDirectoryName(fileDir1);
pdfProcess.Start();
if (!pdfProcess.WaitForExit(2500))
{
pdfProcess.Kill();
}
Please help me resolve this issue.
Make sure you have Foxit open.
using System.Diagnostics;
List<Process> Processlist = Process.GetProcesses().ToList();
This gives you a list of currently running processes.
foreach(Process p in Processlist)
{
Console.WriteLine("Process " + p.Id + " is named '" + p.ProcessName + "'");
}
When running the above code, you should see the name of the Foxit process in your output window.
Alternatively, put a break-point on the foreach line and hover over the list to see all the names that way.
bool IsFoxitProcessRunning = false;
foreach(Process p in Processlist)
{
if(p.ProcessName == "Foxit process name here") //Replace with the name of the foxit process
{
IsFoxitProcessRunning = true;
}
}
Now, only start a new Foxit process if one isn't already running.
if(!IsFoxitProcessRunning)
{
//insert code to run next foxit process here.
}
Notes:
You may need to implement a queue to keep track of pdfs waiting to run.
You may also wish to alert IT support if a Foxit is waiting more 5 or 10 mins to run.
You could choose to make Processlist a class attribute, and periodically refresh Processlist using a Timer, by calling Processlist = Process.GetProcesses().ToList(); on the Tick event. every 30 seconds or so while a PDF is waiting to be printed.

How to queue up system process start

I've written the following code to loop around a load of data table rows and generate a PDF if it doesn't already exist. It works, but it launches the wkhtmltoPDFs all in one go so 30 processes get started and kill the server, d'oh!
What's the best way to launch one process at a time and not start the second until the previous one has run?
DB db = new DB();
DataTable dtPosters = db.GetDataTable("select * from Invoices where PDFGenerated <> 1 or PDFGenerated is Null");
foreach (DataRow DR in dtPosters.Rows)
{
//Generate Poster, If exsists (Don't!)
Directory.CreateDirectory(string.Format("D:\\Invoices\\"));
string FilePath = string.Format("D:\\Invoices\\{0}.pdf", DR["InvoiceNumber"].ToString());
if (!File.Exists(FilePath))
{
string CMD = string.Format("C:\\Services\\Automafe\\wkhtmltoPDF.exe http://invoicegen/viewinvoice.aspx?InvoiceNumber={0} {1}", DR["InvoiceNumber"].ToString(), FilePath);
System.Diagnostics.Process.Start("cmd", "/c " + CMD);
}
else
{
//Update the DB
}
}
Process.Start returns a Process object, which lets you monitor the state of the newly-created process.
A simple, blocking way would be to have each worker wait for the process to exit
var myProcess = System.Diagnostics.Process.Start("cmd", "/c " + CMD);
myProcess.WaitForExit();
A better way would be to use the Exited event handler to monitor when the process exits.
var myProcess = System.Diagnostics.Process.Start("cmd", "/c " + CMD);
myProcess.Exited += myProcessFinishedHandler;

WMI query response is writing to file

I am unable to write the response of a WMI query to file but I can print it to console.
I rewrote the query to use different WMI methods to pull the data. I changed back to the below method of ease of use.
I changed from mo["PackageName"] to mo["PackageName"].ToString() in case the response was not a writable string.
I googled - I have yet to find a similar issue and I am starting to think it is something obvious in my code that I am just overlooking.
//store log in same directory as exe is ran from
StreamWriter writeFile = new StreamWriter(filepath);
ManagementObjectSearcher mos = new ManagementObjectSearcher("SELECT PackageName FROM Win32_Product WHERE PackageName LIKE 'jre%%'");
foreach (ManagementObject mo in mos.Get())
{
if (mo["PackageName"].ToString().Contains("jre"))
{
String packageName = mo["PackageName"].ToString();
writeFile.WriteLine(host + "," + packageName);
}
}
update
see my answer below:
foreach (ManagementObject mo in mos.Get())
{
if (mo["PackageName"].ToString().Contains("jre"))
{
String packageName = mo["PackageName"].ToString();
writeFile.WriteLine(host + "," + packageName);
writeFile.Flush();
}
}
Without knowing what writeFile is or how it's defined, I can only suggest that you use the easier System.IO.File class, like so:
File.AppendAllText(pathToYourFile, host + "," + packageName);
This will automatically open, write to, and close your file for you.
I use the WMI Code Creator for WMI query code creation.
It creates C# code and it is possible to test the queries.
Not a real answer - but might help.
I forgot to flush the writer after write.
String packageName = mo["PackageName"].ToString();
writeFile.WriteLine(host + "," + packageName);
writeFile.Flush();

How to identify and close an instance of opened application in C#

I just load a PDF file in a WinForm using WebBrowser control. I have a PDF byte array and I used to write this in temporary folder of client machine when the user clicks on print preview in my application.
After preview when the user closes the form I need to delete the file. Before deleting I need to close the instance of Adobe Reader that was opened for preview.
I tried getting a list of processes using
Process[] processes = Process.GetProcessesByName("AcroRd32");
I can kill the process by this means and delete the file from temporary folder. Here comes the actual problem. Suppose the user already opened a PDF file for his own purpose then by finding the running instances and by closing them I will close his reader too.
Is there any way to find the instance of Adobe reader which is opened by my application. I need to check for Adobe reader only. Not any other.
UPDATE
I have opened adbobe reader manually which has filename or MainWindowTitle as Magazine.pdf first.Then i opened adobe reader by webbrowser control in my application.But it has MainWindowTitle as Empty.Also there shoul be two intance of adobe reader but in taskmanager it shows four instance and of them only one has MainWindowTitle as Magazine.pdf
See the below images for reference
Check the MainWindowTitle of each Acrobat process - the one you want will begin with the name of the temp file.
You might take a look at the Process's MainWindowHandle property.
I don't open the PDF using WebBrowser, but I have a similar problem to yours when I use Process.MainWindowTitle(). It seems that it only returns a value different from "" for the last opened instance of Acrobat.
The C# code I use to look for all processes of Acrobat is the following:
Process[] myProcess = Process.GetProcessesByName("AcroRd32");
for (int i = 0; i < procesoProg.Length; i++)
{
if (myProcess[i].MainWindowTitle.Contains("MyTitle"))
{
myProcess[i].CloseMainWindow();
Thread.Sleep(300);
}
}
I tried using Process.Kill() instead of Process.MainWindow(), but it closes all instances of Acrobat, not only the one I want.
If you only want to close the document you opened from your application, you can store the process when you launch the PDF, and use CloseMainWindow() when you want to close it:
private static Process myProcess = null;
public void OpenPDF()
{
myProcess = new Process();
myProcess.StartInfo.FileName = "AcroRd32.exe";
myProcess.StartInfo.Arguments = " /n /A \"pagemode=bookmarks&nameddest=" + strND + "\" \"" + strPath + "\"";
myProcess.Start();
}
public void ClosePDF()
{
// If there is
if (myProcess!= null)
{
myProcess.CloseMainWindow();
Thread.Sleep(500);
}
}
I hope this code can help you.
If you want to delete a PDF file opened in WebBrowser control you have to use this code:
WebBrowser.Navigate(YourPdfPath)
...
WebBrowser.Navigate("about:blank")
Application.DoEvents()
Dim millisecondsTimeout = 5000
While True
Try
File.Delete(YourPdfPath)
Exit While
Catch ex As IOException
If millisecondsTimeout < 0 Then Exit Sub
millisecondsTimeout -= 100
Thread.Sleep(100)
End Try
End While
UPDATE:
I found an extension method to kill a children process of a process:
<Extension>
Public Sub KillChildren(process As Process, Optional processNameToKill As String = "", Optional recursive As Boolean = False)
Static ProcessIDStack As List(Of Integer) = New List(Of Integer)
If ProcessIDStack.Contains(process.Id) Then
Exit Sub
Else
ProcessIDStack.Add(process.Id)
End If
Dim searcher = New ManagementObjectSearcher("Select * From Win32_Process Where ParentProcessID=" & process.Id) 'MLHIDE
Dim moc As ManagementObjectCollection = searcher.Get
For Each mo As ManagementObject In moc
Try
Dim ChildProcess = process.GetProcessById(Convert.ToInt32(mo("ProcessID"))) 'MLHIDE
If recursive Then
ChildProcess.KillChildren(processNameToKill, recursive)
End If
If (Not ChildProcess.HasExited) AndAlso (processNameToKill = String.Empty OrElse ChildProcess.ProcessName = processNameToKill) Then
ChildProcess.Kill()
ChildProcess.WaitForExit(1000)
End If
Catch ex As ArgumentException
' process already exited
Catch ex As InvalidOperationException
' process already exited
Catch ex As ComponentModel.Win32Exception
' process already exited
End Try
Next
ProcessIDStack.Remove(process.Id)
End Sub
...
WebBrowser.Navigate("about:blank")
Process.GetCurrentProcess.KillChildren("AcroRd32", True)
Application.DoEvents()
...
I'm searching for a better way to check if a file is not in use avoiding to sleep inside a loop (Andrew Barber correctly says that is not a good code...) but I didn't found anything else, sorry...
(Thanks to Andrew Barber)

c# : How to Monitor Print job Using winspool_drv

Recently I am making a system monitoring tool. For that I need a class to monitor print job.
Such as when a print started, is it successful or not, how many pages.
I know that I can do it using winspool.drv. But dont how. I've searched extensively but having no luck. Any code/suggestion could be very helpful.
Thanks.
Well I don't know about the winspool.drv, but you can use the WMI to get the status of the printer. Here is an example of the using Win32_Printer.
PrintDialog pd = new PrintDialog();
pd.ShowDialog();
PrintDoc.PrinterSettings = pd.PrinterSettings;
PrintDoc.PrintPage += new PrintPageEventHandler(PrintDoc_PrintPage);
PrintDoc.Print();
object status = Convert.ToUInt32(9999);
while ((uint)status != 0) // 0 being idle
{
ManagementObjectSearcher mos = new ManagementObjectSearcher("select * from Win32_Printer where Name='" + pd.PrinterSettings.PrinterName + "'");
foreach (ManagementObject service in mos.Get())
{
status = service.Properties["PrinterState"].Value;
Thread.Sleep(50);
}
}
If you don't use the PrintDialog object (to choose a printer) you can run the WMI query and it will return all the printers in the system.

Categories

Resources