In order to convert a file to PDF with the demo software, you should either have one of the freeware PDF printers or Adobe Acrobat. If you don't have any of them, the file will be sent to your default printer.
Introduction
Nowadays, PDF is the standard file format used in many reporting tools. The application that I developed with VB.NET for my job needed to convert a lot of Microsoft Project and Excel files to PDF files, and place them to custom specified directories without any user interaction. Almost everybody at the office has some free PDF printer instead of Adobe Acrobat - either PDF995 or FreePDF. They are both very nice converters, but my problem was they always pop (naturally) the Save As dialog. I read the documentation of both printers, searched a while on the Internet, and actually found a lot of examples for how to automate the printing, but none of them in VB.NET. So, I thought, VB.NET is (already) a nice OOP language, why not use it to make a simple-to-use class for easily converting files into PDF.
Background
If you have Adobe Acrobat, you will also have a PDF printer. Acrobat has also provided, in the Acrobat SDK, very handy and easy to understand examples for how to silently convert any file to PDF with VB.NET. In their examples, they also show how to find the default application for every known file extension. For this example, this is not needed, because most of the file extensions in Windows have already an associated application, so all we need is to start a process with the verb print
. First, we need to know what a verb is. According to Microsoft, a verb is:
"File associations use verbs as shorthand for actions that are invoked on items in the Shell
namespace, including files and folders. Verbs are closely related to the Shell shortcut menus, where each menu item is associated with a verb. IContextMenu
and ShellExecute
support canonical names for verbs; canonical verb names remain constant regardless of platform or language, which makes it possible for developers to invoke known canonical verbs without knowing the details about a Shell
namespace item. For example, ShellExecute
can invoke the Print
verb on a Microsoft Word document, which requests that the installed handler print the document, without knowing whether Word, Microsoft WordPad, or some other application does the printing."
and also:
"A file association generally has a preferred action that is taken when the user double-clicks a file of this type. This preferred action is linked to a verb referred to as the primary verb. The primary verb is specified by the default value of the shell key, or the open key if the shell key has no default value. The most common primary verb is open
. However, in media files, the most common primary verb is play
. The primary verb is also referred to as the default verb.
...
print
: The application prints the contents of the file and exits, displaying as little as necessary to complete the task.".
More detailed information about verbs and file associations can be found here.
Using the Code
Now that we know what a verb is, we will use it to print our file. One possibility to print an existing file is to start a process, giving the file name and the verb “PRINT
” to the StartInfo
structure. What Windows does is find the associated application, open the file with the associated application, send it to the default printer, and close the application.
Before starting to explain the details about PDF995 and FreePDF XP, we will discuss, in short, the start of a process.
The .NET Framework has the System.Diagnostics
namespace that provides, among other things, the Process
class. With it, the programmer's life is very easy. In order to start a process, make its window invisible, and wait until the process is ready (this is possible when printing files), we write:
Dim procStartInfo As ProcessStartInfo = Nothing
procStartInfo = New ProcessStartInfo
procStartInfo.FileName = "c:\file.xls"
procStartInfo.Verb = "Print"
procStartInfo.CreateNoWindow = True
procStartInfo.WindowStyle = ProcessWindowStyle.Hidden
Dim procPrint As Process = Process.Start(procStartInfo)
If Not procPrint Is Nothing Then
procPrint.WaitForExit()
End If
More information about the Process
class can be found here: .NET Framework Process class. Now, let's see how the above mentioned PDF printers work.
The PDF995 printer uses an INI file to store its settings. The INI file resides in the res subdirectory of the printer's installation path. Further read of the developer's FAQ says, that in order to bypass the “Save As” dialog and to place the PDF file to a custom directory, all we need to do is override the following INI file options:
OutputFile
– will cause the file to be saved under this name into the given folder. AutoLaunch
– setting this to zero will prevent opening the PDF reader application every time after creating a PDF file.
The FreePDF XP printer, on the other hand, uses the Windows registry to store its settings, and also, first converts the file to a PostScript, and then the PS file to a PDF file. To put the PDF file into the user specified directory, we need to temporarily set the psDir
Registry value to the desired directory string. Then, the administrator manual says that in order to convert a PDF file from the command line with the freepdf.exe application, we need the following options:
- “/3 delps, end” - Meaning to convert the file, delete the PS file, and exit the FreePDF application.
- The desired PDF file name and the PS file name from which we create the PDF. We will convert only one file at a time, and FreePDF PS files have always the same name format: current username000001.ps. If we have queued more than one file, we have had 000002, 000003, and so on, but since we always convert one file and wait for the printer to finish, this should be working correctly for us.
Summarizing the actions we need to undertake gives us the following:
PDF995 stores its settings in an INI file, so we need to:
- Retrieve the printer installation path through the Registry
- Save the original INI file from the installation path /res directory
- Create a customized INI
- Convert to PDF
- Restore the original INI file
FreePDF XP stores its settings in the registry (full path: HKEY_LOCAL_MACHINE\SOFTWARE\shbox\FreePdfXP) and uses a PS intermediate file, so we need to:
- Save the original Registry values
- Write customized values
- Create PS
- Convert PS to PDF
- Restore Registry values
Now we know how to design our base printer class. We need, by all means, a function to save the original printer settings, a function to restore the original printer settings, a variable to keep the full path of the desired file to print, and a print
function. Since both printers have a different settings approach, it makes sense to declare this function as abstract
, and since the creating of the print process is always the same, it makes sense to override the print
function. This is only partial code for clarity. The full source code can be found in the zip file.
Imports System.IO
Public MustInherit Class CPrinter
Protected _strFileToPrint As String = ""
Protected MustOverride Sub pushSettings()
Protected MustOverride Sub popSettings()
Public Overridable Sub PrintFile()
Dim procStartInfo As ProcessStartInfo = Nothing
If System.IO.File.Exists(_strFileToPrint) Then
procStartInfo = New ProcessStartInfo
procStartInfo.FileName = _strFileToPrint
procStartInfo.Verb = "Print"
procStartInfo.CreateNoWindow = True
procStartInfo.WindowStyle = ProcessWindowStyle.Hidden
Dim procPrint As Process = Process.Start(procStartInfo)
System.Threading.Thread.Sleep(2500)
If Not procPrint Is Nothing Then
procPrint.WaitForExit()
End If
End If
End Sub
End Class
Now that we have an abstract
base class, we need to inherit it. Currently, the classes will place the PDF file into the same directory as the original file.
The PDF995 Printer Class
Imports Microsoft.Win32
Imports System.IO
Public Class CPrinterPDF995 : Inherits CPrinter
Private Const strOriginalIniFile As String = "pdf995.ini"
Private Const strOriginalIniSave As String = "pdf995.ini_ORIGINAL"
Private Const strRegPath As String = _
"Software\Microsoft\Windows\CurrentVersion\Uninstall\Pdf995"
Protected Overrides Sub pushSettings()
Dim strIniPath As String = RetrieveIniPath()
FileSystem.Rename(strIniPath + strOriginalIniFile, _
strIniPath + strOriginalIniSave)
Me.createCustomIni()
End Sub
Protected Overrides Sub popSettings()
Dim strIniPath As String = RetrieveIniPath()
File.Delete(strIniPath + strOriginalIniFile)
FileSystem.Rename(strIniPath + strOriginalIniSave, _
strIniPath + strOriginalIniFile)
End Sub
Private Sub createIni()
Dim strFileToPrint As String = MyBase.strFileToPrint
Dim strIniPath As String = RetrieveIniPath()
Dim fi As System.IO.FileInfo = New System.IO.FileInfo(strFileToPrint)
Dim strPDFOutputDirectory As String = fi.DirectoryName + "\"
Dim oWriter As StreamWriter = Nothing
fi = Nothing
oWriter = New StreamWriter(strIniPath + "pdf995.ini", False, _
System.Text.Encoding.Unicode)
oWriter.WriteLine("[Parameters]")
oWriter.WriteLine("Install = 0")
oWriter.WriteLine("Quiet = 1")
oWriter.WriteLine("AutoLaunch = 0")
oWriter.WriteLine("Document Name = " + strFileToPrint)
oWriter.WriteLine("User File = " + strFileToPrint + ".pdf")
oWriter.WriteLine("Output File = " + strFileToPrint + ".pdf")
oWriter.WriteLine("Initial Dir = " + strPDFOutputDirectory)
oWriter.WriteLine("Use GPL Ghostcript = 1")
oWriter.Close()
oWriter = Nothing
System.Threading.Thread.Sleep(1000)
End Sub
Public Overrides Sub printFile()
Me.pushSettings()
MyBase.PrintFile()
Me.popSettings()
End Sub
End Class
The FreePDF Printer Class
Imports Microsoft.Win32
Imports System.IO
Public Class CPrinterXFreePDF : Inherits CPrinter
Private strFreePDFExe As String = ""
Private strPSTempDir As String = ""
Private Const strRegPath As String = "SOFTWARE\shbox\FreePdfXP"
Public Overrides Sub printFile()
Dim fi As System.IO.FileInfo = New System.IO.FileInfo(fileToPrint)
Dim strFileToPrintDir As String = fi.DirectoryName + "\"
fi = Nothing
Me.pushSettings()
Dim strCmd As String = strFreePDFExe + " /3 delps,end ""eBook"" """ + _
fileToPrint + ".pdf"" """ + _
strFileToPrintDir + Environment.UserName + _
"000001.ps"""
MyBase.PrintFile()
Shell(strCmd, AppWinStyle.Hide, True)
Me.popSettings()
End Sub
Protected Overrides Sub pushSettings()
Dim regKey As RegistryKey = Nothing
Dim fi As System.IO.FileInfo = New System.IO.FileInfo(MyBase.strFileToPrint)
Dim strPSPath As String = fi.DirectoryName + "\"
fi = Nothing
regKey = Registry.LocalMachine.OpenSubKey(strRegPath, True)
strFreePDFExe = regKey.GetValue("fpDir") + "freepdf.exe"
strPSTempDir = regKey.GetValue("psDir")
If Not String.IsNullOrEmpty(strPSPath) Then
regKey.SetValue("psDir", strPSPath)
End If
regKey.Close()
regKey = Nothing
End Sub
Protected Overrides Sub popSettings()
Dim regKey As RegistryKey = Nothing
regKey = Registry.LocalMachine.OpenSubKey(strRegPath, True)
regKey.SetValue("psDir", strPSTempDir)
regKey.Close()
regKey = Nothing
End Sub
You can always instantiate an object from one of the classes and convert a file like this:
Dim oPrinter As CPrinterPDF995 = New CPrinterPDF995
oPrinter.fileToPrint = "c:\temp\report.xls"
oPrinter.printFile()
oPrinter = Nothing
I hope the commented code is self-explanatory and will help you save some time at home or at work. If you have any trouble understanding it, ask, and I'll be glad if I can help. Also, do not forget to set the desired printer as the default printer system-wide, from the Control Panel or from within the code.
Happy converting! :-)
History
- 05-Oct-2008: v1.00 - Initial version
- 13-Oct-2008: Added C# source code