How to concatenate .pdf files with VBA? - vba

I am trying to concatenate .pdf files with VBA. Nothing fancy, literally sticking the pages one after each other. I did numerous web searches but was unable to find any solutions that are even close to working. Has anyone done this before? Thanks!

If a GPL library is a valid option for you, you could use ghostscript as proposed in this SO question. You can do this by calling the ShellExecute function from Windows API or by using the class WScript.Shell if you are creating a vbscript file.
If a commercial library is an option, I recommend Amyuni PDF Creator ActiveX or Amyuni PDF Converter, both have an Append function that will do the work. The code for Amyuni PDF Converter for example would look like this:
Set PDFDoc = CreateObject("CDintfEx.Document.4.5")
PDFdoc.SetLicenseKey "your company", "your license code"
PDFDoc.Open "test_append1.pdf"
PDFDoc.Append "test_append2.pdf"
PDFDoc.Save "result_append.pdf"
Set PDFdoc = Nothing
Usual disclaimer applies for the latest suggestion

I found this topic while having the same task, with a customer using amyuni. Thanks to yms for a good approach. I found Acces crashing on "Set PDFdoc = Nothing". This one works fine for me:
Public Sub fctPDO_Concatenate_pdf_with_Amyuni_Document_6_0()
' PDO: Usage of .append: Crashes on destruction of pdfdoc-Object. pdf-file is created properly. But usind .append , MS Access crashes - without it's okay.
' Solution: Build second pdfdoc2 - object , and concatenate using .AppendEx(Object) .
On Error Resume Next
Dim PDFdoc As Object
Dim PDFdoc2 As Object
Const strLibraryVersion As String = "CDintfEx.Document.6.0"
' PDO: Examples
'Set PDFdoc = CreateObject("CDintfEx.Document.6.0") ' PDO: See Object catalog
'Set PDFdoc = CreateObject("CDintfEx.Document") ' PDO: Not sufficient w/o version
'Set PDFdoc = CreateObject("CDintfEx.Document.4.5") ' PDO: Older version
Set PDFdoc = CreateObject(strLibraryVersion)
Set PDFdoc2 = CreateObject(strLibraryVersion)
'PDO: Open first file
PDFdoc.Open "D:\PDO_test\Beratungsprotokoll_2018.pdf"
'PDFdoc.Append "D:\PDO_test\GV_0093Z0_Einzelantrag.pdf" ' PDO: Crashes on set PDFdoc = nothing
' PDO: Open and append second file (as Object, not as file)
PDFdoc2.Open "D:\PDO_test\GV_0093Z0_Einzelantrag.pdf"
PDFdoc.AppendEx PDFdoc2
' PDO: Open and append third file (as Object, not as file). Re-use of second Object possible
PDFdoc2.Open "D:\PDO_test\result_append_sammel.pdf"
PDFdoc.AppendEx PDFdoc2
'PDO: Save with a new name
PDFdoc.Save "D:\PDO_test\result_append_sammelsammel.pdf"
'PDFdoc.Close => Not existing.
Set PDFdoc = Nothing '=> Access crashed, with PDFdoc.Append
Set PDFdoc2 = Nothing
Debug.Print "Done: " & Now() & " Error: " & Err.Number
End Sub
If you prefer Ghostscript you can use a single line:
C:\PROGRA~2\gs\gs9.19\bin\gswin32c.exe -q -dSAFER -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sOwnerPassword=pass2 -sUserPassword=pass1 -dCompatibilityLevel=2.0 -sOutputFile="D:\PDO_test\Beratungsprotokoll_2018_Sammel.pdf" "D:\PDO_test\Beratungsprotokoll_2018.pdf" "D:\PDO_test\GV_0093Z0_Einzelantrag.pdf"
This concatenates the two latter files into the (new) first one and applies a password (see security details before applying). The short path can be obtained with a FileScripting Object "fso" using
fso.GetFolder(strFolder).ShortPath

I run sedja-console and add my pdf's as parameters. Quite easy to implement. Do not forget to check before starting Sedja-console if the readonly flag of the possible previous created destination pdf isn't set to yes, as there is no feedback of this external process.

Related

Add a path to a code VB.net / visual basic

how do I add a path to a code where "HERE_HAS_TO_BE_A_PATH" is. When I do, Im getting an error message. The goal is to be able to specific the path where is the final text file saved.
Thanks!
Here is a code:
Dim newFile As IO.StreamWriter = IO.File.CreateText("HERE_HAS_TO_BE_A_PATH")
Dim fix As String
fix = My.Computer.FileSystem.ReadAllText("C:\test.txt")
fix = Replace(fix, ",", ".")
My.Computer.FileSystem.WriteAllText("C:\test.txt", fix, False)
Dim query = From data In IO.File.ReadAllLines("C:\test.txt")
Let name As String = data.Split(" ")(0)
Let x As Decimal = data.Split(" ")(1)
Let y As Decimal = data.Split(" ")(2)
Let z As Decimal = data.Split(" ")(3)
Select name & " " & x & "," & y & "," & z
For i As Integer = 0 To query.Count - 1
newFile.WriteLine(query(i))
Next
newFile.Close()
1) Use a literal string:
The easiest way is replacing "HERE_HAS_TO_BE_A_PATH" with the literal path to desired output target, so overwriting it with "C:\output.txt":
Dim newFile As IO.StreamWriter = IO.File.CreateText("C:\output.txt")
2) Check permissions and read/write file references are correct:
There's a few reasons why you might be having difficulties, if you're trying to read and write into the root C:\ directory you might be having permissions issues.
Also, go line by line to make sure that the input and output files are correct every time you are using one or the other.
3) Make sure the implicit path is correct for non-fully qualified paths:
Next, when you test run the program, it's not actually in the same folder as the project folder, in case you're using a relative path, it's in a subfolder "\bin\debug", so for a project named [ProjectName], it compiles into this folder by default:
C:\path\to\[ProjectName]\bin\Debug\Program.exe
In other words, if you are trying to type in a path name as a string to save the file to and you don't specify the full path name starting from the C:\ drive, like "output.txt" instead of "C:\output.txt", it's saving it here:
C:\path\to\[ProjectName]\bin\Debug\output.txt
To find out exactly what paths it's defaulting to, in .Net Framework you can check against these:
Application.ExecutablePath
Application.StartupPath
4) Get user input via SaveFileDialogue
In addition to a literal string ("C:\output.txt") if you want the user to provide input, since it looks like you're using .Net Framework (as opposed to .Net Core, etc.), the easiest way to set a file name to use in your program is using the built-in SaveFileDialogue object in System.Windows.Forms (like you see whenever you try to save a file with most programs), you can do so really quickly like so:
Dim SFD As New SaveFileDialog
SFD.Filter = "Text Files|*.txt"
SFD.ShowDialog()
' For reuse, storing file path to string
Dim myFilePath As String = SFD.FileName
Dim newFile As IO.StreamWriter = IO.File.CreateText(myFilePath) ' path var
' Do the rest of your code here
newFile.Close()
5) Get user input via console
In case you ever want to get a path in .Net Core, i.e. with a console, the Main process by default accepts a String array called args(), here's a different version that lets the user add a path as the first parameter when running the program, or if one is not provided it asks the user for input:
Console.WriteLine("Hello World!")
Dim myFilePath = ""
If args.Length > 0 Then
myFilePath = args(0)
End If
If myFilePath = "" Then
Console.WriteLine("No file name provided, please input file name:")
While (myFilePath = "")
Console.Write("File and Path: ")
myFilePath = Console.ReadLine()
End While
End If
Dim newFile As IO.StreamWriter = IO.File.CreateText(myFilePath) ' path var
' Do the rest of your code here
newFile.Close()
6) Best practices: Close & Dispose vs. Using Blocks
In order to keep the code as similar to yours as possible, I tried to change only the pieces that needed changing. Vikyath Rao and Mary respectively pointed out a simplified way to declare it as well as a common best practice.
For more information, check out these helpful explanations:
Can any one explain why StreamWriter is an Unmanaged Resource. and
Should I call Close() or Dispose() for stream objects?
In summary, although streams are managed and should garbage collect automatically, due to working with the file system unmanaged resources get involved, which is the primary reason why it's a good idea to manually dispose of the object. Your ".close()" does this. Overrides for both the StreamReader and StreamWriter classes call the ".dispose()" method, however it is still common practice to use a Using .. End Using block to avoid "running with scissors" as Enigmativity puts it in his post, in other words it makes sure that you don't go off somewhere else in the program and forget to dispose of the open filestream.
Within your program, you could simply replace the "Dim newFile As IO.StreamWriter = IO.File.CreateText("C:\output.txt")" and "newFile.close()" lines with the opening and closing statements for the Using block while using the simplified syntax, like so:
'Dim newFile As IO.StreamWriter = IO.File.CreateText(myFilePath) ' old
Using newFile As New IO.StreamWriter(myFilePath) ' new
Dim fix As String = "Text from somewhere!"
newFile.WriteLine(fix)
' other similar operations here
End Using ' new -- ensures disposal
'newFile.Close() ' old
You can write that in this way. The stream writer automatically creates the file.
Dim newFile As New StreamWriter(HERE_HAS_TO_BE_A_PATH)
PS: I cannot mention all these in the comment section as I have reputations less than 50, so I wrote my answer. Please feel free to tell me if its wrong
regards,
vikyath

MS Word runs on background and requests documents to be saved even though it is already saved

I have a procedure that creates a PDF file according to an ms word template and its data is retrieved from a database.
It works fine, creates a PDF file perfectly , no run time errors. The problem is that whenever I shut off the computer, ms word prevents the shutdown and if I press cancel ms word shows a message;
The code goes like this;
Dim wordApp As Word.Application
Dim templateBookmarks As Word.Bookmarks
Dim templateName As String
Dim template As Word.Document
'Some other variables for computations
wordApp = CreateObject("Word.Application")
sourceTable = New DataTable
'Some other procs to fill the data table
templateName = "Foo Template.docx"
template = wordApp.Documents.Add(templatePath & templateName)
templateBookmarks = template.Bookmarks
templateBookmarks.Item("sample bookmark").Range.Text = "foo"
'Then fills the table in the template like...
template.Tables(1).Cell(1, 1).Range.Text = dataSource.Rows(0).Item(0)
'Then saves the document as a pdf
Dim saveName As String = "sample file"
template.SaveAs2(savePath & saveName, Word.WdSaveFormat.wdFormatPDF)
I have tried to force garbage collection for the word COM resources, as well as changing the template from an actual document i.e. docx to a word template .dotx. I also tried the method Quit() but it only shows the ms word message much earlier. This is the first time I needed to use interop so pardon if I don't have much idea about it.
The files I needed are saved, the only problem is the ms word message and unsaved and unnecessary files e.g. Document1,Document2,Document3 that seems to be created aside from the required PDF
Use the Document.Close method which closes the specified document after saving files using the PDF file format. It allows specifying the SaveChanges parameter which specifies the save action for the document. Can be one of the following WdSaveOptions constants: wdDoNotSaveChanges, wdPromptToSaveChanges, or wdSaveChanges.
On Error GoTo errorHandler
ActiveDocument.Close SaveChanges:=wdDoNotSaveChanges
errorHandler:
If Err = 4198 Then MsgBox "Document was not closed"

Printing a document from VB

I have an app which monitors a network location for new documents.
They can be word documents, PDF files, spreadsheets etc.
When a document is detected, it is copied to a local folder within c:\Temp.
What I need is for the document, once copied, to be sent to a specified (not default) printer.
Has anyone got any ideas on how I can achieve this?
Thanks!!
You may need to create a variety of functions to print your document. Like using LPR to print PDF or PostScript, Microsoft.Office.Interop for Word and Excel documents, and so on...
If it were me, I would use System.IO.Path.GetExtension(filename) to determine the file type and then call whichever function was most appropriate...or log that the file was not printable if the format was not handled.
Microsoft.Office.Interop
Using Microsoft.Office.Interop.Excel, you can call the PrintOutEx method on a Workbook or Worksheet and specify which printer to use.
See:
https://msdn.microsoft.com/en-us/library/microsoft.office.interop.excel.worksheets.printoutex.aspx
and
https://msdn.microsoft.com/en-us/library/microsoft.office.interop.excel._workbook.printoutex.aspx
With Microsoft.Office.Interop.Word you can set the ActivePrinter property of the Application and then use the PrintOut method on the Document.
See:
https://msdn.microsoft.com/en-us/library/microsoft.office.interop.word._application.activeprinter.aspx
and https://msdn.microsoft.com/en-us/library/microsoft.office.interop.word._document.printout.aspx
LPR
I wrote a tool once that prints PDF and PostScript files. The code was something like:
'Set the IP address of the printer to use.
If printer1 Then
printserver = printer1Address
ElseIf printer2 Then
printserver = printer2Address
ElseIf printer3 Then
printserver = printer3Address
End If
'Use LPR to print the file.
Dim lprProcess As New Process()
With lprProcess.StartInfo
.UseShellExecute = False
.FileName = "CMD"
.Arguments = "/c LPR -s " & printserver & " -P P3 " & myNewFileName
End With
lprProcess.Start()
LPR is not a included by default in Windows 7 and above, but it is a cake-walk to turn on. See steps 1-3 on http://campus.mst.edu/cis/desktop/documentation/pc/win7_x64/lpr_printer/install.htm

Using Spire.PDF to merge pdf files Errors

I am using the free licenced version of Spire PDF. My program has in the region of 166,ooo pdf files which represent individual pages. I need to merge between 1 and 20 of these with the same names into one pdf.
I have a routine the builds a string of filenames to be added to an array which is passed to the following sub as PDFFiles. The OutputFile is the string with the name of the output file with it's path.
Private Sub MergePDFs(ByVal PDFFiles As String, ByVal OutPutFile As String)
Dim files As [String]() = New [String]() {"E:\Ballads of the 20th Century\1st bari #1.pdf", "E:\Ballads of the 20th Century\1st bari #2.pdf"}
Dim i As Integer
'open pdf documents
Dim docs As PdfDocument() = New PdfDocument(files.Length - 1) {}
For i = 0 To files.Length - 1
docs(i) = New PdfDocument(files(i))
Next
'append document
docs(0).AppendPage(docs(1))
'import PDF pages
i = 0
While i < docs(2).Pages.Count
docs(0).InsertPage(docs(2), i)
i = i + 2
End While
End Sub
I have the Solution Explorer I have the Spire.Pdf.dll as a file. In References I have Spire.Pdf and Spire.Licence.
At runtime I get An unhandled exception of type 'System.ArgumentException' occurred in Spire.Pdf.dll
Additional information: File doesn't exist.
The PDFFiles is not used in this example for clarity. The two files listed are taken directly from the program output for testing purposes.
There has to be a simple explanation for this error, but I haven't found one yet.
Please can you help solve it.
Thanks
Graham
I found the answer to this myself.
The actual problem was the way Spire.pdf parses a string into a pdf document.
There must be no spaces in the filename, then it works fine.
Graham

Fill a PDF Form from MS Access

I found a post on here on how to fill a fillable PDF from from MS Access.
It is a Visual Basic code for filling out a fillable PDF. I have been using Excel to perform this function and would like to migrate my database to Access and still keep the same functionality. I have figured out how to add my VB code to the button but when I click it gives me and error. Any help that can be giving would be greatly appreciated.
I have Adobe Acrobat X Pro and MS Access 2010. My PDF files was created in word and then converted to a PDF file, I know all my field names because I created them. The PDF document is saved as c:\CX.pdf, it has 9 pages, some examples of field names on the document are: “Plant Name”, “Station Location”, “Installer or Owner”. My MS Access Data Base Fields are named the same.
Option Compare Database
Private Sub Command105_Click()
Dim FileNm, gApp, avDoc, pdDoc, jso
FileNm = "c:\CX.pdf" 'File location
Set gApp = CreateObject("AcroExch.app")
Set avDoc = CreateObject("AcroExch.AVDoc")
If avDoc.Open(FileNm, "") Then
Set pdDoc = avDoc.GetPDDoc()
Set jso = pdDoc.GetJSObject
jso.getField("CX[0].Page1[0].Plant_Name[0]").Value = "Plant_Name"
jso.getField("CX[0].Page1[0].Station_Location[0]").Value = "Station_Location"
jso.getField("CX[0].Page1[0].Installer_or_Owner[0]").Value = "Installer_or_Owner"
pdDoc.Save PDSaveIncremental, FileNm 'Save changes to the PDF document
pdDoc.Close
End If
'Close the PDF; the True parameter prevents the Save As dialog from showing
avDoc.Close (True)
'Some cleaning
Set gApp = Nothing
Set avDoc = Nothing
Set pdDoc = Nothing
Set jso = Nothing
End Sub
It was giving me the error "Object not found", now it's not giving me error but it's still not writing to the PDF.
This is a bit old but it helped me in the long run. I did figure out what was wrong. "Object not found" means that the code cannot find the PDF field and therefore you can not assign anything to the object that "getFeild()" returns. The most likely culprit is that the "path" to the Field is incorrect. Try just putting the field name and you may need to export the data to a FTF file and read the file in notepad to find the field name.
the field names will look like
T/(FeildName)v/(FeildValue)
Once the object is actually being returned you'll be able to assign it a value.