Automated PDF Conversion using the PDF995 and FreePDF_XP Freeware Printers






4.67/5 (9 votes)
An object oriented class automating the creation of PDF files from any file using freeware tools.
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
andShellExecute
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 aShell
namespace item. For example,ShellExecute
can invoke the
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 isplay
. The primary verb is also referred to as the default verb....
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:
'Define properties for the print process
Dim procStartInfo As ProcessStartInfo = Nothing
procStartInfo = New ProcessStartInfo
procStartInfo.FileName = "c:\file.xls" ' e.g. an excel file
procStartInfo.Verb = "Print" ' print please
'Make process invisible
procStartInfo.CreateNoWindow = True
procStartInfo.WindowStyle = ProcessWindowStyle.Hidden
'start print process for the file with/from the associated application
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
'we want an abstract base class
Public MustInherit Class CPrinter
Protected _strFileToPrint As String = ""
'save original settings
Protected MustOverride Sub pushSettings()
'restore original settings
Protected MustOverride Sub popSettings()
'start printing process (adopted from Adobe PDF SDK examples)
Public Overridable Sub PrintFile()
'Define properties for the print process
Dim procStartInfo As ProcessStartInfo = Nothing
If System.IO.File.Exists(_strFileToPrint) Then
procStartInfo = New ProcessStartInfo
procStartInfo.FileName = _strFileToPrint
procStartInfo.Verb = "Print"
'Make process invisible
procStartInfo.CreateNoWindow = True
procStartInfo.WindowStyle = ProcessWindowStyle.Hidden
'start print process for the file with/from the associated application
Dim procPrint As Process = Process.Start(procStartInfo)
'give the system some time
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
'the INI that we want to change in oder to "silently" use the PDF printer
Private Const strOriginalIniFile As String = "pdf995.ini"
'we want to recover the original INI file after every print
Private Const strOriginalIniSave As String = "pdf995.ini_ORIGINAL"
'used to locate PDF995 installation directory
Private Const strRegPath As String = _
"Software\Microsoft\Windows\CurrentVersion\Uninstall\Pdf995"
Protected Overrides Sub pushSettings()
Dim strIniPath As String = RetrieveIniPath()
'save original INI file first
FileSystem.Rename(strIniPath + strOriginalIniFile, _
strIniPath + strOriginalIniSave)
Me.createCustomIni()
End Sub
Protected Overrides Sub popSettings()
Dim strIniPath As String = RetrieveIniPath()
'restore original INI file
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
'write INI file, create PDF from "strFileToPrint" and save it to "directory"
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) ' give Pdf995 some time
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
'freepdf.exe full path
Private strFreePDFExe As String = ""
'PS temp directory, needed for silent ps to pdf creation
Private strPSTempDir As String = ""
'FreePDF path in registry
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"""
'create the PS file
MyBase.PrintFile()
'create PDF from PS file, can also be started with System.Diagnostics.Process
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)
'fpDir = freepdf.exe directory
strFreePDFExe = regKey.GetValue("fpDir") + "freepdf.exe"
'psDir = location for temp PS files, save it
strPSTempDir = regKey.GetValue("psDir")
'set currently new PS files location
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)
'restore original psDir
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