Printing an external PDF document in VB.net - vb.net

I know this question has been asked before, but my situation is a bit wonky.
Basically, I'm trying to print a PDF file that I've generated using a previous Windows Form. I can find the file no problem, and I used the following code which I found off MSDN's help forums:
Dim p As New System.Diagnostics.ProcessStartInfo()
p.Verb = "print"
p.WindowStyle = ProcessWindowStyle.Hidden
p.FileName = "C:\534679.pdf" 'This is the file name
p.UseShellExecute = True
System.Diagnostics.Process.Start(p)
So far so good, but everytime I press the button to run this code, it keeps asking me to save it as a PDF file instead, as shown below:
I've also tried adding a PrintDialog to the Windows Form, getting it to pop up, and I can select the printer I want to use from there, but even after selecting the printer it still asks me to print to PDF Document instead.
What am I doing wrong?

To print massive PDF documents with VB.Net you can use LVBPrint and run it via command line:
http://www.lvbprint.de/html/gsbatchprint1.html
For Example:
C:\temp\gsbatchprint64\gsbatchprintc.exe -P \\server\printer -N A3 -O Port -F C:\temp\gsbatchprint64\Test*.pdf -I Tray3
I use the following function in my application:
' print a pdf with lvbrint
Private Function UseLvbPrint(ByVal oPrinter As tb_Printer, fileName As String, portrait As Boolean, sTray As String) As String
Dim lvbArguments As String
Dim lvbProcessInfo As ProcessStartInfo
Dim lvbProcess As Process
Try
Dim sPrinterName As String
If portrait Then
lvbArguments = String.Format(" -P ""{0}"" -O Port -N A4 -F ""{1}"" -I ""{2}"" ", sPrinterName, fileName, sTray)
Else
lvbArguments = String.Format(" -P ""{0}"" -O Land -N A4 -F ""{1}"" -I ""{2}"" ", sPrinterName, fileName, sTray)
End If
lvbProcessInfo = New ProcessStartInfo()
lvbProcessInfo.WindowStyle = ProcessWindowStyle.Hidden
' location of gsbatchprintc.exe
lvbProcessInfo.FileName = LvbLocation
lvbProcessInfo.Arguments = lvbArguments
lvbProcessInfo.UseShellExecute = False
lvbProcessInfo.RedirectStandardOutput = True
lvbProcessInfo.RedirectStandardError = True
lvbProcessInfo.CreateNoWindow = False
lvbProcess = Process.Start(lvbProcessInfo)
'
' Read in all the text from the process with the StreamReader.
'
Using reader As StreamReader = lvbProcess.StandardOutput
Dim result As String = reader.ReadToEnd()
WriteLog(result)
End Using
Using readerErr As StreamReader = lvbProcess.StandardError
Dim resultErr As String = readerErr.ReadToEnd()
If resultErr.Trim() > "" Then
WriteLog(resultErr)
lvbProcess.Close()
Return resultErr
End If
End Using
If lvbProcess.HasExited = False Then
lvbProcess.WaitForExit(3000)
End If
lvbProcess.Close()
Return ""
Catch ex As Exception
Return ex.Message
End Try
End Function
I discourage on using AcrRd32.exe as it doesn't work with massive printings.

First, to be able to select a Printer, you'll have to use a PrintDialog and PrintDocument to send graphics to print to the selected printer.
Imports System.Drawing.Printing
Private WithEvents p_Document As PrintDocument = Nothing
Private Sub SelectPrinterThenPrint()
Dim PrintersDialog As New PrintDialog()
If PrintersDialog.ShowDialog(Me) = System.Windows.Forms.DialogResult.OK Then
Try
p_Document = New PrintDocument()
PrintersDialog.Document = p_Document
AddHandler p_Document.PrintPage, AddressOf HandleOnPrintPage
Catch CurrentException As Exception
End Try
End If
End Sub
Private Sub HandleOnPrintPage(ByVal sender As Object, ByVal e As PrintPageEventArgs) Handles p_Document.PrintPage
Dim MorePagesPending As Boolean = False
'e.Graphics.Draw...(....)
'e.Graphics.DrawString(....)
' Draw everything...
If MorePagesPending Then
e.HasMorePages = True
Else
e.HasMorePages = False
End If
End Sub
That's what I'm doing since I usually have custom objects to print.
But to print PDF Files, you must understand that PDF means absolutely nothing to dotNet. Unlike common images like Bitmaps (.bmp) or Ping images (.png) the dotNet doesn't seem to have any inbuilt parser/decoder for reading, displaying and printing PDF files.
So you must use a third party application, thrid party library or your own custom PDF parser/layout generator in order to be able to send pages to print to your printer.
That's why you can't launch a hidden (not visible) process of Acrobat Reader with the command "print". You won't be able to select a printer but will direct to the default one instead !
You can however launch the Acrobat Reader process just to open the file, and do the printing manipulations (select a printer) inside Acrobat Reader (you're outside dotNet coding now)
A workaround for your may aslo to select another default printer by opening Acrobat Reader, and print one blank page on an actual working printer. This should deselect your FoxIt thing in favour of an actual printer..

This code will help you to print in a specific printer.
The sample print a file using a ProcessStartInfo and a specific printer you can change the printer to use in the process.
If the print process is not finished after 10 seconds we kill the print process.
'Declare a printerSettings
Dim defaultPrinterSetting As System.Drawing.Printing.PrinterSettings = Nothing
Private Sub cmdPrint_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdPrint.Click
Try
dim fileName As String = "C:\534679.pdf"
'Get de the default printer in the system
defaultPrinterSetting = DocumentPrinter.GetDefaultPrinterSetting
'uncomment if you want to change the default printer before print
'DocumentPrinter.ChangePrinterSettings(defaultPrinterSetting)
'print your file
If PrintFile(fileName, defaultPrinterSetting) then
msgbox ("your print file success message")
else
msgbox ("your print file failed message")
end if
Catch ex As Exception
mssbox(ex.Message.toString)
End Try
End Sub
Public NotInheritable Class DocumentPrinter
Shared Sub New()
End Sub
Public Shared Function PrintFile(ByVal fileName As String, printerSetting As System.Drawing.Printing.PrinterSettings) As Boolean
Dim printProcess As System.Diagnostics.Process = Nothing
Dim printed As Boolean = False
Try
If PrinterSetting IsNot Nothing Then
Dim startInfo As New ProcessStartInfo()
startInfo.Verb = "Print"
startInfo.Arguments = defaultPrinterSetting.PrinterName ' <----printer to use----
startInfo.FileName = fileName
startInfo.UseShellExecute = True
startInfo.CreateNoWindow = True
startInfo.WindowStyle = ProcessWindowStyle.Hidden
Using print As System.Diagnostics.Process = Process.Start(startInfo)
'Close the application after X milliseconds with WaitForExit(X)
print.WaitForExit(10000)
If print.HasExited = False Then
If print.CloseMainWindow() Then
printed = True
Else
printed = True
End If
Else
printed = True
End If
print.Close()
End Using
Else
Throw New Exception("Printers not found in the system...")
End If
Catch ex As Exception
Throw
End Try
Return printed
End Function
''' <summary>
''' Change the default printer using a print dialog Box
''' </summary>
''' <param name="defaultPrinterSetting"></param>
''' <remarks></remarks>
Public Shared Sub ChangePrinterSettings(ByRef defaultPrinterSetting As System.Drawing.Printing.PrinterSettings)
Dim printDialogBox As New PrintDialog
If printDialogBox.ShowDialog = Windows.Forms.DialogResult.OK Then
If printDialogBox.PrinterSettings.IsValid Then
defaultPrinterSetting = printDialogBox.PrinterSettings
End If
End If
End Sub
''' <summary>
''' Get the default printer settings in the system
''' </summary>
''' <returns></returns>
''' <remarks></remarks>
Public Shared Function GetDefaultPrinterSetting() As System.Drawing.Printing.PrinterSettings
Dim defaultPrinterSetting As System.Drawing.Printing.PrinterSettings = Nothing
For Each printer As String In System.Drawing.Printing.PrinterSettings.InstalledPrinters
defaultPrinterSetting = New System.Drawing.Printing.PrinterSettings
defaultPrinterSetting.PrinterName = printer
If defaultPrinterSetting.IsDefaultPrinter Then
Return defaultPrinterSetting
End If
Next
Return defaultPrinterSetting
End Function
End Class

I used this code to print my PDF files on VB NET:
Dim PrintPDF As New ProcessStartInfo
PrintPDF.UseShellExecute = True
PrintPDF.Verb = "print"
PrintPDF.WindowStyle = ProcessWindowStyle.Hidden
PrintPDF.FileName = dirName & fileName 'fileName is a string parameter
Process.Start(PrintPDF)
When you do this, process remains open with a adobe reader window that users have to close manually. I wanted to avoid user's interaction, just want them to get their documents. So, I added a few code lines to kill process:
Private Sub killProcess(ByVal processName As String)
Dim procesos As Process()
procesos = Process.GetProcessesByName(processName) 'I used "AcroRd32" as parameter
If procesos.Length > 0 Then
For i = procesos.Length - 1 To 0 Step -1
procesos(i).Kill()
Next
End If
End Sub
If you put the kill process method right after the print method you won't get your document printed (I guess this is because process is killed before it is sent to printer). So, between these 2 methods, I added this line:
Threading.Thread.Sleep(10000) ' 10000 is the milisecs after the next code line is executed
And with this my code worked as I wanted. Hope it helps you!

Related

I cannot figure out ProcessStartInfo and to be able to use Process.Start.StandardOutput.ReadToEnd to display what is happening in Command line

I am trying to display what is going on in Command Line Interface without opening up command line. I know Process class has that ability but I am having a hard time with StandardOutput.ReadOnly. I have the process set to one button and then after I click, I want it to show a status that command line is in fact connecting. Thoughts?
Private Sub EstablishConnection_Click(sender As Object, e As EventArgs) Handles EstablishConnection.Click
' One file parameter to the executable
Dim sourceName1 As String = strXFileName
Dim sourceName2 As String = strYFileName
' New ProcessStartInfo created
Dim p As New ProcessStartInfo
' Specify the location of the binary
p.FileName = "C:\Software\John\Doe"
' Use these arguments for the process
p.Arguments = $"-Application -Connect -Example -E"" {stXFileName} "" -S"" {strYFileName} """
' Use a hidden window
p.WindowStyle = ProcessWindowStyle.Hidden
p.UseShellExecute = False
p.RedirectStandardOutput = True
p.RedirectStandardError = True
p.RedirectStandardInput = True
' Start the process
Process.Start(p)
'Dim output As String = p.StandardOutputEncoding
'Open the Status Screen form once connection is established
StatusScreen.Show()
Me.Hide()
End Sub

Send files to printer without printer dialoge in VB.net using InvokeVerbEx("Print")

I'd like to send several types of files to the printer wihtout the printer dialog;
I've found a vbs script which works and translate it to vb.net like so;
Private Sub SendToPrinter2(MyPj As PrintJob, bgw As BackgroundWorker)
Try
Dim MyFolder As Shell32.Folder2
Dim MyFile As FolderItems2
MyFolder = GetShell32Folder(MyPj.Path)
MyFile = MyFolder.ParseName(MyPj.FileName)
MyFile.InvokeVerbEx("Print")
Catch ex As Exception
bgw.ReportProgress(0, ex.Message)
End Try
End Sub
Private Function GetShell32Folder(folderPath As String) As Shell32.Folder2
Dim shellAppType As Type = Type.GetTypeFromProgID("Shell.Application")
Dim shell As [Object] = Activator.CreateInstance(shellAppType)
Return DirectCast(shellAppType.InvokeMember("NameSpace", System.Reflection.BindingFlags.InvokeMethod, Nothing, shell, New Object() {folderPath}), Shell32.Folder2)
End Function
It goes wrong with this error message;
Unable to cast COM object of type 'System.__ComObject' to interface type 'Shell32.FolderItems2'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{C94F0AD0-F363-11D2-A327-00C04F8EEC7F}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).
On this line; MyFile = MyFolder.ParseName(MyPj.FileName)
The MyPj.Filename contains the name of the file in the folder (MyPj.Path).
I also tried this example; https://msdn.microsoft.com/en-us/library/windows/desktop/bb774057%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396 without succes.
Before I've used this to send files to the printer, but this presents a dialoge with a 'okey' button;
Private Sub SendToPrinter(MyPj As PrintJob)
Dim info As New ProcessStartInfo()
info.Verb = "print"
info.FileName = MyPj.PathFileName
info.CreateNoWindow = True
info.WindowStyle = ProcessWindowStyle.Hidden
Dim p As New Process()
p.StartInfo = info
p.Start()
p.WaitForInputIdle()
System.Threading.Thread.Sleep(3000)
If False = p.CloseMainWindow() Then
p.Kill()
End If
End Sub

Why does File.Open() not open the file?

I am implementing a Save File button in my VB.NET Windows Forms application.
I am attempting to encapsulate the normally expected behaviour of Save buttons in Windows applications. I.E: If a file was already selected then open the current file it, write to it and save it; else if there is no current file, or Save As was used, then show a SaveFileDialog, then open, write and save just the same.
I currently have coded the function below but I keep getting an exception:
Cannot access a closed file
The file is created just fine, but is empty (It should contain "Test string"). I can't understand how the file is closed unless some kind of garbage collection is doing away with it somehow??
The current code:
Function SaveFile(ByVal Type As ProfileType, ByVal suggestedFileName As String, ByVal saveAs As Boolean, ByVal writeData As String) As Boolean
Dim FileStream As Stream = Nothing
Dim FolderPath As String = Nothing
Dim CancelSave As Boolean = False
Dim SaveFileDialog As SaveFileDialog = New SaveFileDialog()
Try
If Type = ProfileType.Product Then 'Select the initial directory path
FolderPath = ProductPath
Else
FolderPath = ProfilePath
End If
If (FileName = String.Empty Or saveAs = True) Then 'If a file is not already selected launch a dialog to allow the user to select one
With SaveFileDialog
.Title = "Save"
.AddExtension = True
.CheckPathExists = True
.CreatePrompt = False
.DefaultExt = "xml"
.Filter = "Xml Files (*.xml)|*.xml"
.FilterIndex = 0
.FileName = suggestedFileName
.InitialDirectory = FolderPath
If .ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
FullyQualfiedPathName = New String(SaveFileDialog.FileName) 'Save the path and name of the file
FileName = Path.GetFileName(FullyQualfiedPathName)
Else
CancelSave = True
End If
.Dispose()
End With
End If
If (FileName <> String.Empty) Then 'Write the string to the file if the filewas correctly selected
FileStream = File.Open(FullyQualfiedPathName, FileMode.OpenOrCreate, FileAccess.ReadWrite) 'Open the file
Using FileStreamWriter As New StreamWriter(FileStream) 'Create the stream writer
FileStreamWriter.Write(writeData) 'Write the data
FileStream.Close() 'Clse the file
End Using
ElseIf (CancelSave <> True) Then 'Only throw an exception if the user *didn't* cancel the SavefileDialog
Throw New Exception("File stream was nothing", New IOException())
End If
Catch ex As Exception
MessageBox.Show(ex.Message & Environment.NewLine & FullyQualfiedPathName)
End Try
Return True
End Function
One problem I see is that you should be putting your File.Open in a Using block:
Using fs = File.Open(fullyQualfiedPathName, FileMode.OpenOrCreate, FileAccess.ReadWrite)
Using writer As New StreamWriter(fs) 'Create the stream writer
writer.Write(writeData) 'Write the data
'fs.Close() <--- you do not need this line becuase the "Using" block will take care of this for you.
End Using
End Using
I'm not sure if this will resolve your issue because I can't run your code, but the Using block will automatically take care of closing and cleaning up disposable instances like FileStream and StreamWriter, even if an exception is thrown.
By the way, you should use proper naming conventions (lower camel case) for local variables.

How do I kill a process that doesn't have any windows in VB.net?

I am new to VB.net and I am trying to write a forms app that works with Autodesk Inventor.
Unfortunately, every time I close Inventor, the "Inventor.exe" process still stays. I didn't realize this while debugging and I only realized this when I checked the task manager after the whole system started to lag.
Killing every process with the same name is fairly simple, but the issue is that the end user might have separate documents open in another Inventor window. So I need to write a function that only kills the Inventor processes that don't have a window open.
Public Class Form1
Dim _invApp As Inventor.Application
Dim _started As Boolean = False
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Try
_invApp = Marshal.GetActiveObject("Inventor.Application")
Catch ex As Exception
Try
Dim invAppType As Type = _
GetTypeFromProgID("Inventor.Application")
_invApp = CreateInstance(invAppType)
_invApp.Visible = True
'Note: if you shut down the Inventor session that was started
'this(way) there is still an Inventor.exe running. We will use
'this Boolean to test whether or not the Inventor App will
'need to be shut down.
_started = True
Catch ex2 As Exception
MsgBox(ex2.ToString())
MsgBox("Unable to get or start Inventor")
End Try
End Try
End Sub
Another section where I start a process, which is a specific 3D model file.
Public Sub SaveAs(oTemplate As String)
'define the active document
Dim oPartDoc As PartDocument = _invApp.ActiveDocument
'create a file dialog box
Dim oFileDlg As Inventor.FileDialog = Nothing
Dim oInitialPath As String = System.IO.Path.GetFullPath("TemplatesResources\" & oTemplate)
_invApp.CreateFileDialog(oFileDlg)
'check file type and set dialog filter
If oPartDoc.DocumentType = kPartDocumentObject Then
oFileDlg.Filter = "Autodesk Inventor Part Files (*.ipt)|*.ipt"
ElseIf oPartDoc.DocumentType = kAssemblyDocumentObject Then
oFileDlg.Filter = "Autodesk Inventor Assembly Files (*.iam)|*.iam"
ElseIf oPartDoc.DocumentType = kDrawingDocumentObject Then
oFileDlg.Filter = "Autodesk Inventor Drawing Files (*.idw)|*.idw"
End If
If oPartDoc.DocumentType = kAssemblyDocumentObject Then
oFileDlg.Filter = "Autodesk Inventor Assembly Files (*.iam)|*.iam"
End If
'set the directory to open the dialog at
oFileDlg.InitialDirectory = "C:\Vault WorkSpace\Draft"
'set the file name string to use in the input box
oFileDlg.FileName = "######-AAAA-AAA-##"
'work with an error created by the user backing out of the save
oFileDlg.CancelError = True
On Error Resume Next
'specify the file dialog as a save dialog (rather than a open dialog)
oFileDlg.ShowSave()
'catch an empty string in the imput
If Err.Number <> 0 Then
MessageBox.Show("Any changes made from here will affect the original template file!", "WARNING", MessageBoxButtons.OK, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button1, MessageBoxOptions.ServiceNotification)
ElseIf oFileDlg.FileName <> "" Then
Dim MyFile As String = oFileDlg.FileName
'save the file
oPartDoc.SaveAs(MyFile, False)
'open the drawing document
System.Diagnostics.Process.Start(oInitialPath & ".idw")
Dim oFinalPath As String = oPartDoc.FullFileName
MessageBox.Show(oFinalPath, "", MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1, MessageBoxOptions.ServiceNotification)
MessageBox.Show("Loaded", "", MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1, MessageBoxOptions.ServiceNotification)
Dim oDrawingDoc As DrawingDocument = _invApp.Documents.ItemByName(oInitialPath & ".idw")
'oDrawingDoc.SaveAs()
End If
End Sub
Any help is appreciated.
Thanks
At the end on your form, probably FormClosed, add the following:
Private Sub Form1_FormClosed(sender As Object, e As FormClosedEventArgs) Handles Me.FormClosed
If (_invApp Is Nothing) Then Return ' if empty, then no action needed
_invApp.Quit()
Marshal.ReleaseComObject(_invApp)
_invApp = Nothing
System.GC.WaitForPendingFinalizers()
System.GC.Collect()
End Sub
This should make .NET release and properly dispose the COM Object for Inventor.
And consider declare the Inventor variable and assign Nothing/null, it's safer (avoid mistakes)
Dim _invApp As Inventor.Application = Nothing

Visual Basic (Visual Studio 2005) redirect inputstream to process

I've been searching around the net for roughly three hours now w/o getting forward. I don't know VB very well, but I need to create a wrapper program for any executable that logs the arguments, all input, output and err information:
My wrapper is called: e.g. someApp.exe arg1 arg2
Logs to someApp.log: arg1 arg2
Calls original executable: _someApp.exe arg1 arg2
Must log and forward any console input to _someApp process inputstream
Must log any output and error stream from _someApp process
Okay, I'm stuck at point 4 now:
Dim p As New ProcessStartInfo
p.FileName = execute
p.Arguments = Command()
p.UseShellExecute = False
p.CreateNoWindow = True
p.RedirectStandardInput = True
p.RedirectStandardError = True
p.RedirectStandardOutput = True
Dim process As System.Diagnostics.Process
process = Diagnostics.Process.Start(p)
process.WaitForExit()
After _someApp ends I am able to read out and err stream to log it, but I still need to provide my own wrappers input to the process and I want to read out and err stream as it happens.
Thanks for info/examples
Okay here the solution...
Variables needed:
Private process As System.Diagnostics.Process
Private threadOut As Thread
Private streamOut As System.IO.StreamReader
Private threadErr As Thread
Private streamErr As System.IO.StreamReader
Private threadIn As Thread
Private streamIn As System.IO.StreamWriter
Subs needed:
Private Sub ThreadTaskOut()
Dim line
While Not process.HasExited
line = streamOut.ReadToEnd
If line <> Nothing And line <> "" Then
log("Out: " & line)
Console.Out.Write(line)
End If
End While
End Sub
Private Sub ThreadTaskErr()
Dim line
While Not process.HasExited
line = streamErr.ReadToEnd
If line <> Nothing And line <> "" Then
log("Err: " & line)
Console.Error.Write(line)
End If
End While
End Sub
Private Sub ThreadTaskIn()
Dim line
While Not process.HasExited
line = Console.In.ReadLine
If line <> Nothing And line <> "" Then
log("In: " & line)
streamIn.WriteLine(line)
End If
End While
End Sub
Inside main:
' create process information
Dim p As New ProcessStartInfo
p.FileName = execute
p.Arguments = Command()
p.UseShellExecute = False
p.CreateNoWindow = True
p.RedirectStandardInput = True
p.RedirectStandardError = True
p.RedirectStandardOutput = True
' log process start
log("Execute: " & execute & " " & Command())
' start process
process = Diagnostics.Process.Start(p)
' start thread for output stream
streamOut = process.StandardOutput
threadOut = New Thread(AddressOf ThreadTaskOut)
threadOut.IsBackground = True
threadOut.Start()
' start thread for error stream
streamErr = process.StandardError
threadErr = New Thread(AddressOf ThreadTaskErr)
threadErr.IsBackground = True
threadErr.Start()
' start thread for input stream
streamIn = process.StandardInput
threadIn = New Thread(AddressOf ThreadTaskIn)
threadIn.IsBackground = True
threadIn.Start()
' wait for the process to finish
process.WaitForExit()
log is another sub to log to file, execute is the variable holding the _someApp.exe frommy initial post. I still don't know if the console to inputstream thread dows it right, because my wrapped app had no input as it seems. May someone spotts an error...
For my purposes, hmm it works like I need it
Greetz,
GHad
Code inside main
How about for writing to the app:
Dim sw as IO.StreamWriter = process.StandardInput
sw.WriteLine("Boo")
and for reading from the standard output:
Dim sr As IO.StreamReader = process.StandardOutput
Do
aString = sr.ReadLine()
Loop Until (sr.EndOfStream)