Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / VB

Automated PDF Conversion using the PDF995 and FreePDF_XP Freeware Printers

4.67/5 (10 votes)
16 Oct 2008BSD7 min read 1   3.1K  
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 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:

VB.NET
'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:

  1. OutputFile – will cause the file to be saved under this name into the given folder.
  2. AutoLaunch – setting this to zero will prevent opening the PDF reader application every time after creating a PDF file.

anyfiletopdf1.gif

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:

  1. “/3 delps, end” - Meaning to convert the file, delete the PS file, and exit the FreePDF application.
  2. 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.

anyfiletopdf2.gif

Summarizing the actions we need to undertake gives us the following:

PDF995 stores its settings in an INI file, so we need to:

  1. Retrieve the printer installation path through the Registry
  2. Save the original INI file from the installation path /res directory
  3. Create a customized INI
  4. Convert to PDF
  5. 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:

  1. Save the original Registry values
  2. Write customized values
  3. Create PS
  4. Convert PS to PDF
  5. 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.

VB.NET
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

VB.NET
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

VB.NET
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:

VB.NET
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.

anyfiletopdf3.gif

Happy converting! :-)

History

  • 05-Oct-2008: v1.00 - Initial version
  • 13-Oct-2008: Added C# source code

License

This article, along with any associated source code and files, is licensed under The BSD License