BlogFileMaker

Combine PDFs with FileMaker, Fast and Free

By September 13, 2016 19 Comments

The scenario addressed in this blog post is one where you have multiple PDF documents that you would like to combine into a single PDF document. This is not to be confused with the script step to append to an existing PDF.

This post examines how to take two existing PDFs and combine them instead of printing a layout or report and appending it to an existing file.
This example takes multiple PDFs stored in container fields, combines them into a single document and then stores the resulting file in another container field.

Illustration showing combining multiple PDFs into one

Lessons Learned

I believe this is a good example, in part because it combines several techniques, each of which has value. Some of the techniques used in this example:

  • Perform Script on Server
  • Using server-side plugins
  • Pass multiple script parameters
  • Performing command line scripts on a server
  • Integrating with a 3rd party server-side program
  • Encode/decode Base64 to create text files on the fly
  • Multi-use safe script

By performing this script on the server, there is no need for third party plugins to be installed on the client, which also makes it compatible across the platform and supports FileMaker Go and WebDirect. It also does not matter if you have external storage enabled for container fields, since FileMaker takes care of that part for you.

Another benefit is the savings in networks latency if this action were to be performed on the client. Normally, you would need to download all PDF files to the client via container fields, export them somewhere where they can be manipulated and then combined and finally re-uploaded the result to another container field. Performing the script on the server avoids hair-pinning and doubling the amount of network traffic required.

Now let’s get to building this thing! Can we build it?

Yes, we can!

Prerequisites

  • First, this technique uses the Perform Script on Server script step (a.k.a. PSOS) so we already assume that your environment includes FileMaker Server.
  • Next, we will be using the now open source Goya BaseElements (thanks Goya!) plugin installed on the server. This means that once we are done, clients won’t need to have the plugin installed, but for development, we will also have it installed on FileMaker Pro. Also recommended is the use of FileMaker Pro Advanced for development, but this is not a requirement.
  • Finally, we will be integrating with a freely available and popular server-side program for manipulating PDFs, PDFtk server. This version provides a command line interface which is fine for our server-side implementation. PDFtk is also capable of doing much more than combining PDFs, but for this example, that will be our end result.

PDFtk also supports handling different size pages of PDFs and combines them as a single document.

Also of note, this example uses FileMaker Server installed on Windows Server. All the pieces involved will also work on OS X, but the shell scripting would be different.

PSOS Setup

For the script that runs on the server, exporting files to the Documents folder that resides in the FileMaker Server Data folder is the safe place to put things and avoid potential permissions issues, so we will use that. To avoid multi-user conflicts that will potentially use the same file names to when performing the scripts, we can create a unique folder on the server that represents the user.

Once that is done, we can work in that directory until done and then clean up after ourselves by removing the folder at the end of the script. The client’s persistent ID can be used as an identifier for each users device that is connected to the server.

Passing Parameters

Since the script running on the server needs to know about the current context we are working in, we need to pass it a couple of script parameters. We pass in the Persistent ID of the computer or device where the script is running, and the current ID of the parent record we are on.

Note: there are several ways to pass multiple script parameters to a subscript. At Soliant, we have several custom functions to aid in making this easier, but you may substitute whatever method you prefer.
The objective is the same, to hand off multiple parameters to our script running on the server. Once we have those, we can unpack them in our server-side script and use them.

In our example, we use a custom function to handle quoting and to make it easier to write. It ends up looking like this:

Perform Script On Server [ Wait for Completion ; "server cat ( persistentID ; appID )" ; Parameter: #t ( Get ( PersistentID ) ) & #t ( CON__Contracts::ID ) ]

“#t” is a custom function that prepares the parameter. We also have a custom function in the script that we are calling to automatically create variables defined in the script name that look like parameters. Personally I like this syntax because it follows syntax used in other languages as well, such as javascript functions. Again, you can use any method you like but feel free to examine the custom functions used in the sample file.

Working Directory in a Multi-User Environment

FileMaker Server can write to the Documents directory in the FileMaker Server folder on the server. By creating a separate folder for each user, using their Persistent ID, each user’s folder will only contain their documents.

To create a folder in the Documents folder, we can use some standard functions to calculate the correct path:

Right ( Get ( DocumentsPath ) ; Length ( Get ( DocumentsPath ) ) - 1 ) & $_persistentID

Remember that we passed in the Persistent ID by using “Get ( PersistentID )” from the client. We use it here to get the proper path that we want to create in the Documents folder on the server. If the preceding line is set to a variable named “$this.docspath” then we can set another variable, named “$cmd” to the command line used to create that folder on the server.

"cmd.exe /c mkdir " & Quote ( $this.docspath )

Now we can use the function “BE_ExecuteSystemCommand” from the BaseElements plugin to run this command on the server like so:

BE_ExecuteSystemCommand ( $cmd ; "-1" )

The “-1” parameter tells the script to “wait forever” for the system script to complete. Once we have our working directory, we similarly create a sub folder called “output” that will eventually hold the combined PDF.

CAT BAT

Eventually we will output a batch file (or .bat file) to concatenate (cat) all PDFs in our working directory into a single file. That command line looks like the following and references the default installed location of PDFtk server like this:

"C:Program Files (x86)PDFtk Serverbinpdftk.exe" *.pdf cat output output/combined.pdf

This command line will run the application specified, telling it to match anything ending with “.pdf” and concatenate it to a file named “combined.pdf” in a relative folder called “output” located in our working directory.

Since this file is generic enough, we can create batch file, named “cat.bat” and store it in a container field> We will show a technique to create simple text files like this in container fields on the fly.

Base64 Encode and Decode

It is possible to populate a container field with a text file that contains any text we want using the Base64 functions. This technique requires FileMaker (Pro or Server) 14 or higher. With the “cat.bat” content set in a text field “cat_bat”, we can use the Set Field script step to set the container field like so:

Base64Decode ( Base64Encode ( Resources::cat_bat ) ; "cat.bat" )

Since the Export Field Contents script step is not compatible with server-side scripts, we can again lean on the Base Elements plugin by using the BE_ExportFieldContents function to get our file to the server.

With all this in place, we are ready to loop through all the PDFs we want to combine and save them to the server by using the BE_ExportFieldContents function.

Once that is done, it is time to call our cat.bat script using the following command:

"cmd.exe /c cd " & Quote ( $this.docspath ) & " & " & Quote ( "cat.bat" )

When we execute the batch file, again with the BE_ExecuteSystemCommand function, all the PDFs we exported to our working directory are combined. I find it best to add a slight pause at this point to make sure the script completes, even though we specify (with “-1”) to wait to completion for the command to run.

Import the File

Once again, the Base Elements plugin allows us to overcome a server side scripting limitation by importing a file into a container field with the function, BE_ImportFile. Our complete file will be in the “/output/combined.pdf” folder relative to our working folder.

Clean Up Time

We wouldn’t want to arbitrarily leave lots of PDF files littering our server, especially since this is not consider temporary space that regularly gets emptied. Fortunately this is fairly easy to do with BE_ExecuteSystemCommand. This command looks like this:

"cmd.exe /c rmdir /s /q " & Quote ( $this.docspath )

This command tells the system to remove the specified directory, along with all files and sub-directories container within.

Using PSOS

Now we need a script that will run on the client, sets up the variables to pass as parameters, and call the Perform Script on Server script step. You should enable the option to “wait for completion” when using this script step, so the resulting PDF will be available in our client when the server is done combining and saving it in a container field.

In the client’s user interface, the button to run the script calls the “parent” script, which in turn runs the PSOS script step.

Run the "Perform Script on Server" script step

Run the “Perform Script on Server” script step.

Expand image

Ensure Interactive Content

This optional last step is to ensure that the resulting PDF is available to be displayed interactively. Normally, you would be required to have inserted the file in an acceptable way for PDFs to use this feature, but if we use our Base64 functions to encode and decode, the file gets saved in the field correctly. Use the Set Filed script step to set the container field like this:

Base64Decode ( Base64Encode ( CON__Contracts::pdf_combined_r ) ; GetAsText (CON__Contracts::pdf_combined_r ))

By using the GetAsText function, we tell the resulting container field content what the file name is, dynamically based on whatever it was to begin with.

In Summary

We hope this example has demonstrated several takeaway techniques that can be used independently. Taken as a whole, it also provides a free and easy way to address a common request. Even if you do not have the specific need to combine PDFs, the techniques listed at the top of this post can be cherry picked as needed to solve your own project requirements as needed.

Download the Sample File

After installing both PDFtk and the BaseElements plugin on the server, you can get the sample file available here:

Special thanks to my colleague, Mislav Kos for reviewing and contributing feedback on this post.

For another technique that uses Applescript, check out Makah’s post.

References

Mike Duncan

Mike Duncan

Mike is an AWS Certified Solutions Architect as well as a certified FileMaker Developer. In addition to his work, Mike also enjoys pursuing his art, freelance writing, traveling, and spending time with his family.

19 Comments

  • […] Source: Combine PDFs with FileMaker – Soliant Consulting […]

  • Avatar Gianandrea Gattinoni says:

    It’s great!
    Just one point: why you did not used the baseelemts function “BE_CreateFolder” to create the folder?
    Gianandrea

  • Avatar Taco says:

    Hi,

    Does anyone has a osx vision of the example?

    Thanks,
    Taco

    • Avatar Mike Duncan says:

      No, but the BaseElements plugin and PDFtk server will also work on OS X. You would just need OS X command line to substitute the Windows command line steps.

  • Avatar Siva says:

    Hi,
    I tried the sample file for macOS.
    BE_ExecuteSystemCommand ( “pdftk -version” ) which returns ?
    BE_ExecuteSystemCommand ( “/bin/bash -c \”pdftk -version\””; -1 ) also returns ?

    Command “pdftk -version” works fine on Terminal.
    BE_ExecuteSystemCommand ( “ls” ) lists the root directory. So Baseelements seems to be working fine with shell. Something not working with the combination of BE and PDFtk.

    Can someone point me the right direction?

    Thanks,
    Siva

  • Avatar Siva says:

    Figured the solution from a forum post.
    Full path needs to be included in the command.
    BE_ExecuteSystemCommand ( “/usr/local/bin/pdftk -version” )

    Thank you Vincent_L

    • Avatar Mike Duncan says:

      Right on. That is basically what I would have said to try.

      • Avatar Gobinath says:

        Can you please send me the sample database for mac or send the scripts steps to perform script on server?

        • Avatar Mike Duncan says:

          The example shown includes working with PSOS (perform script on server) and previous comments show how others have gotten it working on OS X.

          • Avatar Gobinath says:

            It was not working for me. I can able to create a folder and export the pdf but merging the pdf is not executed in mac. Can you send the command lines?

  • Avatar Nicolae Todor says:

    It is a wonderful presentation but some details would make students life better.
    To run a script on the server we need full access to database but run with full privileges does not work.
    If our database have different accounts with different privileges, from an account with limited privileges we have to use re-login to an account with full access before calling the server script and after that another re-login to the original account.

    • Avatar Mike Duncan says:

      PSOS will work with all privilege sets. It could be that what the script is doing requires more access that a particular privilege set but you should be able to adjust that one set to allow what needs to be done.

  • Avatar DS says:

    Thank you very much for posting the sample file. I am having some difficulty in getting this to work on my Mac OSX Server. The error was that PDFtk Server was not installed, but I did successfully run the installer.
    I can see that another user (Siva) has figured it out but I am not sure where to make the changes in the script. Would someone be able to share the code so I can test a script to combine multiple PDF’s?

    • Avatar Mike Duncan says:

      I do not have a completed copy for OS X, but if you look for lines in the script that use that base elements function, you should be able to tell where to change.

      • Avatar Zhongguo says:

        My OS X merging:
        “do shell script \”python ‘/System/Library/Automator/Combine PDF Pages.action/Contents/Resources/join.py’ -o ” & $ExpFinalPath & ” ” & $AllFilePath & “\””
        $AllFilePath looks like ‘folder/file1’ ‘folder/file2’

  • Hi Mike,

    What an amazing app & service to the developer community. Thank you for sharing your work.

    I was doing some testing and found that it completely messes up the sequence of PDFs when combining them. The export and numbering is perfect, but when running the command to combine it makes a mess. Same result on a WIN 2012 and 2016 Server, latest pdfkit and BE plugin, latest FMS 17.

    I also have one particular PDF (also created in FileMaker like all others) which makes the workflow crash with error -1.

    Would you be willing to help? Happy to pay you for your efforts.

    Many thanks,
    Michael

    • Avatar Mike Duncan says:

      Hi Michael,
      It probably has something to do with how PDFs are named and the default sort order when combining pdfs. You could test by naming the files before combining so they sort correctly. Do you generate all PDFs from FileMaker? If so, you might also be able to use the append PDF script step which is now supported on FM Server, and run it as a server side script. For PDF files NOT created by FM, this technique may still be needed.

  • Avatar Carlos Esteban says:

    Hi, thanks for the help.
    I tried to do it work on Mac server but is imposible for me. Can sameone help me?

    Thanks.

Leave a Reply