I am trying to print a file to a PDF by using the "Microsoft Print To PDF" driver with the below VBA that uses WINAPI calls to follow the process described here.
The code does create the output PDF file, the return value of the WritePrinter call is "1" (indicating success), and the number of bytes written exactly matches the size of the byte array that was passed...in other words, everything seems to work as it should...except that the PDF file that is created is 0 bytes in size. There are no errors and all the return values indicate that everything worked...but the output file is completely empty.
Can someone please take a look and let me know if there is an error/omission in my code that would be causing this?
Key Items to Note (i.e. things that may help you to help me):
- I can print to a PDF manually using the "Microsoft Print To PDF" driver just fine
- There are a few posts online where people are having an issue with the "Microsoft Print To PDF" driver creating 0 byte files BUT those are when using it manually (the typical accepted resolution is uninstalling/reinstalling the driver and, although the issue is NOT occurring for me manually, I've already tried the uninstall/reinstall route with no success)
- The code produces the same results (no errors, successful return values, and a blank PDF) on every computer I've tried it on (3 different brands of machine running 3 different OS builds of Windows)
- According to some sites, a common cause of the "Microsoft Print To PDF" driver printing blank files is the existence of commas or other "special characters" in the file name(s)…I am passing only alphabetic characters in the input/output file path/name
- I am aware that certain types of files can be opened in Word and then saved as a PDF...this method will not work for my needs
- What I am really trying to do is have my code read an interactive PDF that contains form controls such as calendars, dropdown boxes and the like but when it is opened in Word those controls (and their values) are stripped out when Word renders the file
- I am aware that Acrobat offers flatting functionality from within the AcroExch object model...this method will not work for my needs
- Using AcroExch requires the installation of Adobe Acrobat Pro (or at least the SDK) and, since my code will be distributed to multiple users, the installation of any third party software (including CLI PDF conversion software like PDFCreator) is out of the question
- I am aware that there are online APIs that offer this service...they will not meet my needs
- Due to the proprietary content of the PDF files, I cannot send them across the wire so everything must be done locally
- The easiest way I have found to "flatten" the PDF file is to open it and then print to a PDF using the "Microsoft Print To PDF" driver…doing so replaces all the form controls with their values
- I am aware that the Internet Explorer object offers an ExecWB method with which a print command can be executed...I'm avoiding this for 2 reasons:
- It displays a "Save As" dialog box for the output file name that my code would have to interact with via FindWindow/SendMessage or SendKeys and there's just got to be a better way of doing this silently
- IE is a quickly dying browser that my local IT department is likely to completely uninstall from our computers soon (especially with the official EOL being announced by Microsoft as 8/17/2021)
- I have tried Chrome's headless print-to-pdf fuinction but it appears that only accepts HTML files (I even tried embedding the PDF into an HTML file but it still did not work)
- I am aware that the Internet Explorer object offers an ExecWB method with which a print command can be executed...I'm avoiding this for 2 reasons:
- Changing the value of lpszDatatype from "RAW" to vbNullString makes no difference
- Passing the input file contents to the WritePrinter function as a string (using ByVal) instead of a byte array makes no difference
Hopefully it's evident that I've done my due diligence yet still need assistance.
I've seen numerous posts online written in C++/C# using this exact same technique; but, despite my best efforts, I cannot seem to get it to work in VBA.
The fact that it actually does create the output PDF file leads me to believe that it's completely possible and that I am just missing something.
Option Explicit
Type DOCINFO
lpszDocName As String
lpszOutput As String
lpszDatatype As String
End Type
Private Declare PtrSafe Function OpenPrinter Lib "winspool.drv" Alias "OpenPrinterA" (ByVal pPrinterName As String, phPrinter As Long, ByVal pDefault As Long) As Long
Private Declare PtrSafe Function StartDocPrinter Lib "winspool.drv" Alias "StartDocPrinterA" (ByVal hPrinter As Long, ByVal Level As Long, pDocInfo As DOCINFO) As Long
Private Declare PtrSafe Function StartPagePrinter Lib "winspool.drv" (ByVal hPrinter As Long) As Long
Private Declare PtrSafe Function WritePrinter Lib "winspool.drv" (ByVal hPrinter As Long, pBuf As Any, ByVal cdBuf As Long, pcWritten As Long) As Long
Private Declare PtrSafe Function EndPagePrinter Lib "winspool.drv" (ByVal hPrinter As Long) As Long
Private Declare PtrSafe Function EndDocPrinter Lib "winspool.drv" (ByVal hPrinter As Long) As Long
Private Declare PtrSafe Function ClosePrinter Lib "winspool.drv" (ByVal hPrinter As Long) As Long
Public Sub Print_File_To_PDF(strInputFile As String, strOutputPDF As String)
Dim udtDocInfo As DOCINFO
Dim lngPrinterCheck As Long, lngFileNumber As Long, lngPrinterHandle As Long, lngPrinterReturn As Long, lngBytesWritten as Long
Dim arrBytes() As Byte
'Get handle of printer
lngPrinterCheck = OpenPrinter("Microsoft Print To PDF", lngPrinterHandle, 0)
If lngPrinterCheck = 0 Then
Exit Sub
End If
'Define document info
With udtDocInfo
.lpszDocName = CreateObject("Scripting.FileSystemObject").GetBaseName(strOutputPDF)
.lpszOutput = strOutputPDF
.lpszDatatype = "RAW"
End With
'Read file into byte array
lngFileNumber = FreeFile
Open strInputFile For Binary Access Read As lngFileNumber
ReDim arrBytes(LOF(lngFileNumber))
Get lngFileNumber, , arrBytes()
Close lngFileNumber
'Print byte array to PDF file
Call StartDocPrinter(lngPrinterHandle, 1, udtDocInfo)
Call StartPagePrinter(lngPrinterHandle)
lngPrinterReturn = WritePrinter(lngPrinterHandle, arrBytes(0), UBound(arrBytes), lngBytesWritten)
Call EndPagePrinter(lngPrinterHandle)
Call EndDocPrinter(lngPrinterHandle)
Call ClosePrinter(lngPrinterHandle)
MsgBox _
"Printer Return Value: " & lngPrinterReturn & vbCrLf & _
"Bytes Written: " & lngBytesWritten
End Sub
Display More