<cfhtmltopdf> broken in ColdFusion 2023

UPDATE:  Confirmed bug https://tracker.adobe.com/#/view/CF-4219706
There appears to be a bug in the cfhtmltopdf tag running on ColdFusion 2023.

The orientation attribute is ignored.

<cfhtmltopdf orientation="landscape">
<h3>Test Landscape Output</h3>
</cfhtmltopdf>
This is the output:

With SMTP Relay being increasingly phased out within Office/Exchange 365 I decided to migrate some of our application email to Microsoft Graph.

In order to use Microsoft Graph API to send email you’ll need to have an Exchange 365 account and a licensed user/inbox.

To access the Microsoft Graph API your program will need to authenticate itself using OAuth.

The first step is to register your application in Azure AD.

Open the Azure AD Portal: https://portal.azure.com/

Click App Registrations.

Azure AD Portal

Click New registration.

New registration

Enter the Name of your application. Leave everything else default.

New registration defails

Click Register.

After registering your application, write down the Application (client) ID and the Directory (tenant) ID.

App Information

We now need to create a “secret” for generating OAuth access tokens.

Click Certificates & secrets.

App Information

Click New client secret.

Certificates & Secrets

Enter a Description and select an Expires value.

New client secret

Click Add.

Write down the Value immediately because it will only be displayed once.

Client secret

Now we need to assign the app the appropriate API permissions.

Click API permissions.

API permissions

Click Add a permission.

Add a permission
Select Microsoft Graph.
Microsoft Graph
Select Application permissions.
Application permissions

Search for Mail and select Mail.Send.

Add permissions

Click Add permissions.

An administrator will need to grant the permission for the entire organization.

Grant permission

Click Grant admin consent for <YOUR ORGANIZATION>.

Once the permission has been granted you should see a green check.

Granted permission
NOTE:  This process can take some time

To limit this permissions’ mailbox access we need to create an application access policy.

This can be done using the Exchange Online PowerShell module

Set the PowerShell Execution Policy:

Set-ExecutionPolicy RemoteSigned

To install the module:

Install-Module -Name ExchangeOnlineManagement

To start using the module:

Import-Module ExchangeOnlineManagement

Connect to Exchange Online using an administrator account:

Connect-ExchangeOnline -UserPrincipalName <USERNAME>
NOTE:  Connecting using an account that is not an Administrator will result in PowerShell error messages

Create a mail-enabled security group which we can apply the application access policy:

New-DistributionGroup -Name "MyEmailApp" -Alias myemailapp -Type "Security"

Create the application access policy using the Application (client) ID:

New-ApplicationAccessPolicy -AppId <APPLICATION_CLIENT_ID> -PolicyScopeGroupId <GROUP EMAIL ADDRESS> -AccessRight RestrictAccess

Add the mailbox you want to send from to the group:

Add-DistributionGroupMember -Identity MyEmailApp -Member <SENDER_ADDRESS> -Confirm:$false

Now that the application has been registered and the appropriate permissions have been granted we can start interacting with the Microsoft Graph API

First we need to get an access_token.

httpService = new http(
     url = "https://login.microsoftonline.com/oauth2/v2.0/token",
     method = "POST"
);

httpService.addParam(
     type = "body",
     value = "client_id=<APPLICATION_CLIENT_ID>&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default&client_secret=<CLIENT_SECRET>&grant_type=client_credentials"
);

results = httpService.send().getPrefix();

The results should look like this:

results

The Filecontent key should contain a JSON encoded string:

{
     "access_token": "<ACCESS_TOKEN>",
     "expires_in": 3599,
     "token_type": "Bearer",
     "ext_expires_in": 3599
}

The expires_in value is the time in seconds until the token expires.

You can reuse the same token for subsequent Microsoft Graph API requests until it expires.

Using the the access token we can now send an email using the sendMail API endpoint:

filecontent = deserializeJSON(results.filecontent);

httpService = new http(
     url = "https://graph.microsoft.com/v1.0/users/<SENDER_EMAIL>/sendMail",
     method = "POST"
);

httpService.addParam(
     type = "header",
     name = "Authorization",
     value = "Bearer #filecontent.access_token#"
);

httpService.addParam(
     type = "header",
     name = "Content-Type",
     value = "application/json"
);

body = {
     "message": {
          "subject": "Your subject",
          "body": {
               "contentType": "Text",
               "content": "Your message content"
          },
          "toRecipients": [{
               "emailAddress": {
                    "address": "to@someone.com"
               }
          }]
     },
     "saveToSentItems": false
};

httpService.addParam(
     type = "body",
     value = serializeJSON(body)
);

results = httpService.send().getPrefix();

The results.Statuscode will show 202 Accepted if the email was sent:

results

After manually applying an update to one of the ColdFusion servers I manage I started getting this error when opening the ColdFusion Administrator console:

The administrator module is not installed.
You can install module through CLI package manager(CF_ROOT/bin/cfpm.bat) by running the command : install administrator.

The administrator module is not installed.

Executing the suggested command showed what appeared to be a successful install, but still received the same error.

I reviewed the ColdFusion update log {cfusion}\hf-updates\hf-XXXX-XXXXX-XXXXXX and found several module installs failed with this error:

An error has occurred while installing the package XXXXX. Exception : URI does not specify a valid host name: {Local Path To File}.jar

An old manual upgrade path was left in the packagesurl value within {cfusion}\lib\neo_updates.xml.

I was able to restore my pre-update snapshot backup, change neo_updates.xml back to the original value of https://www.adobe.com/go/coldfusion-packages and proceed with the manual update normally.

If the packagesurl value contains invalid/unescaped characters, you might also see this error in the update log:

Exception : Illegal character in opaque part at index X

If you’ve ever needed to use ColdFusion to manipulate Word documents, you might have tried a 3rd party library like Doc4J or Apache POI.

While these libraries are very robust I found them to be limiting in different ways. Apache POI lacks built-in mail merge capability (which to me seems very odd given the information below) and Doc4j threw an odd Jakarta error that I was not able to fix.

I also looked at paid for libraries like Apose, but the licensing costs were just too prohibitive

Finally I decided on direct OOXML manipulation.

This turned out to be much easier than I anticipated, once I learned that Office files like Word docx files are actually Zip archives!

Who knew!?!?!

So it’s possible to use the native cfzip tag to “open” the file.

<cfzip action="unzip" file="{Your Office Document File Path}" destination="{A temporary folder}" recurse="yes"/>

Once unzipped to a folder you will have a directory structure like this:
Unzipped Word document root

Within the Word subfolder there are several XML files. The document I am manipulating for the mail merge is document.xml

This file can be read into ColdFusion using xmlParse() and from there can be manipulated like any other XML object using ColdFusion’s native tags and commands.

I couldn’t find any “standard” way for altering the XML to merge data into the various template fields.

To get an idea of how Word does it I created a test Word document with a simple and complex mail merge field: Word Merge Fields.docx

Running this through a Word mail merge and then unzipping the resulting file and reviewing the document.xml for the “merged” document I found that simple merge fields (fldSimple) are completely replaced with the merged value while complex fields have a begin and end XML node delimiter that must be parsed and manipulated in specific ways.

In order to properly merge complex fields it’s necessary to determine if there is a separator XML node within the field. If so, the the node between the separator node and the end node are used as the value. If no separator node is found then the entire field is replaced with the merged value just as simple merge fields.

After updating the parsed XML it must be written back to the document.xml file:

<cffile action="write" file="{Path to document.xml}" output="#toString(CF XML object)#" charset="utf-8">

Once the document.xml file has been saved we need to rezip everything back into a Word docx file:

<cfzip action="zip" file="{Full path to resulting docx file}" source="{Folder containing unzipped contents}" recurse="yes"/>

Fully merged document:
Word Merge Fields – Merged.docx

So far I’ve only used this for mail merges, but I’m sure that is only scratching the surface.

I found that after changing the password for a MongoDB NoSQL DSN using ColdFusion Administrator you need to restart the ColdFusion Application service in Windows. Even changing to the correct password will cause a MongoDB authentication error:

Exception authenticating MongoCredential{mechanism=SCRAM-SHA-256, userName='{Your Username}', source='{Your Auth Source}', password=, mechanismProperties=} null
Command failed with error 18 (AuthenticationFailed): 'Authentication failed.' on server XXXXXXXXXXXXX:27017. The full response is {"ok": 0.0, "errmsg": "Authentication failed.", "code": 18, "codeName": "AuthenticationFailed"}

This error may be logged in the MongoDB server log:

{"t":{"$date":"2021-11-08T14:39:07.067-06:00"},"s":"I",  "c":"ACCESS",   "id":20249,   "ctx":"conn122","msg":"Authentication failed","attr":{"mechanism":"SCRAM-SHA-256","speculative":false,"principalName":"{Your Username}","authenticationDatabase":"{Your Auth Source}","remote":"XXX.XXX.XXX.XXXX:51204","extraInfo":{},"error":"AuthenticationFailed: SCRAM authentication failed, storedKey mismatch"}}
Update 11/12/23 — Since writing this I’ve found it is sometimes necessary to delete the MongoDB DSN, restart ColdFusion, add the MongoDB DSN back, then restart the ColdFusion service again.

In ColdFusion 2021 I encountered a new wrinkle when using CFHTTP. The url attribute must contain no leading or trailing spaces:

<cfhttp url="#trim(someurlvariable)#">

In my case the value of someurlvariable was coming from a database.

Another potential bug found within ColdFusion 2021 with this error:

Could not initialize class cfApplication2ecfmXXXXXXXXX

Interestingly the TYPE of error was: java.lang.NoClassDefFoundError

The class name cfApplication2ecfmXXXXXXXXX indicated to me that it was an issue with the saved class files in {CFRoot}\cfusion\wwwroot\WEB-INF\cfclasses

The file cfApplication2ecfmXXXXXXXXX.class did exist in the \cfclasses folder

The only working solution I’ve found so far is to disable the “Save class files” option using the ColdFusion administrator console (Server Settings > Cache > Uncheck Save class files)

There seems to be conflicting recommendations about this settings, however the ColdFusion administrator console states that the option should be enabled for production servers.

My own testing has revealed the primary drawback to be an initial hit on the server CPU after rebooting or resetting the ColdFusion service.

After the initial startup hit the server seems to function normally.

DateFormat() in ColdFusion 2021 is now case-sensitive.

This no longer produces the desired result:

<cfoutput>#dateFormat("11/21/2020 2:38:16 PM","MM/DD/YYYY")#</cfoutput>

Pre-ColdFusion 2021:

11/21/2020

ColdFusion 2021:

11/326/2020

Note that ColdFusion 2021 displays the day of the year

To fix:

<cfoutput>#dateFormat("11/21/2020 2:38:16 PM","mm/dd/yyyy")#</cfoutput>


UPDATE

Adobe has added a JVM flag that can adjust this behavior:

-Dcoldfusion.datemask.useDasdayofmonth which defaults to FALSE
https://helpx.adobe.com/coldfusion/cfml-reference/coldfusion-functions/functions-c-d/DateFormat.html

Since Adobe is defaulting to FALSE it's probably best to update your code accordingly.

I ran into an interesting change in ColdFusion 2018 with regard to <cfdump>

The output produced by <cfdump> suddenly lacked formatting and any ability to collapse the tree nodes.

This sample code:

<cfcomponent>
	<cffunction name="test" access="public" returntype="any" output="false">
		<cfset var test = structNew()>
		<cfset test.var1 = "Var1Value">
		<cfset test.var2 = "Var2Value">
		<cfset test.var3 = "Var3Value">
		<cfdump var="#test#"><cfabort>
	</cffunction>
</cfcomponent>

Produces this output:

CFDUMP Missing Formatting

The above is just an example, but dumping anything remotely complex such as a query or CFC resulted in near unreadable output.

As I do with any seemingly odd ColdFusion behavior I start searching for others experiencing the same behavior.

There’s not much out there.

What is available is very dated and not applicable to my problem.

With nothing much to go on I researched and tried many possibilities until I stumbled on the solution.

The problem seems to be with how ColdFusion 2018 is now using or interpreting the output attribute of <cffunction>.

I’ve always set it to FALSE by default as I typically want my functions to RETURN output rather than produce it.

In ColdFusion 2018 this appears to now prevent <cfdump> from inserting the necessary CSS and JavaScript to provide the formatted output.

Simply changing the value to TRUE (or omitting) shows the correctly formatted output:

CFDUMP Formatted

I guess just another odd quirk in ColdFusion 2018.

Update 09/15/2022

A side effect of output="true" or not setting the attribute is that calling the function inside a <cfoutput> tag will produce proceeding spaces.

Add output="false" after debugging or switch to cfscript.

Today I began migrating some of our applications to ColdFusion 2018 (from ColdFusion 2016).

Things went pretty smoothly until I ran into this error:

Could not find the ColdFusion component or interface XXX.XXX.mycfc"

Where “XXX.XXX.mycfc” is a ColdFusion component.

In my case the component definitely exists and was in the path where it was being instantiated.

After a little troubleshooting and head scratching I decided to simply try adding a mapping in Application.cfc.

Magically the component could be instantiated and the application runs as it always has.

I have no idea why a mapping is now required in the latest version of ColdFusion.

about me

An information technology professional with twenty five years experience in systems administration, computer programming, requirements gathering, customer service, and technical support.