This issue is caused by the fact that the record users add attachments to in the portal is not in fact the record being integrated by Unifi. It's a temporary record which ServiceNow will then move the attachments from to the requested item/target record of a record producer at a later time.
An issue arises here though as a Unifi create message can be triggered before ServiceNow moves the attachments. If this happens then Unifi will not send the attachments.
To get around this issue we can trigger a new ServiceNow event to be fired when Unifi sends a create message. This will then give time for the attachment to be added to the request. We will use a script action to then force update any attachments on the requested item, thus triggering Unifi as if the attachment was just added.
Follow these steps:
Create a new event, say, unifi.send.portal.attachments.on.create
Create a new Script action, which runs on the above new event, with the following code:
Modify the create message to trigger the event, i.e. in the Source to stage script of the CreateRequest message, add the following code below the autogenerated scripts:
An issue with inbound attachments being saved using this mechanism has been seen in ServiceNow instances upgrading to Quebec and later which are using base64 encoded attachments. An example of this error is shown below:
InternalError: The choice of Java method com.glide.ui.SysAttachment.write matching JavaScript argument types (object,org.mozilla.javascript.ConsString,string,null) is ambiguous; candidate methods are:
class java.lang.String write(com.glide.script.GlideRecord,java.lang.String,java.lang.String,java.io.InputStream)
class java.lang.String write(com.glide.script.GlideRecord,java.lang.String,java.lang.String,java.io.File)
class java.lang.String write(com.glide.script.GlideRecord,java.lang.String,java.lang.String,java.lang.String)
class java.lang.String write(com.glide.script.GlideRecord,java.lang.String,java.lang.String,byte[])
Unifi comes packaged with a utility for helping save attachments:
AttachmentHandler.saveAttachment(record, filename, content_type, data)
There has been a change to the GlideSysAttachment
API being used by this method. Now, passing the base64 decoded string as a byte array to the global GlideSysAttachment.write()
method via the Unifi AttachmentHandler appears to only work when Unifi trace mode is on. Internal investigation has revealed that there is some interaction between ServiceNow and the Unifi scoped app which is causing the byte array to be converted to null when Unifi trace mode is off. We suspect this is because ServiceNow routes the variable through some coercion in the Rhino environment when passing it through Unifi and that forces it to work. Without the debug mode turned on, the byte array is being passed through and, for some reason that isn't clear, it is treated differently.
To work around this issue it is recommended to modify the Extract attachments script on the message which is being used to process attachments. By adding a new saveAttachments()
function that makes use of ServiceNow's writeBase64()
method, the data does not need to be decoded like it was in the past and attachments are processed properly regardless of Unifi settings.
Decoding a base64 string should be done with binary aware methods to ensure that binary files are decoded correctly. We have seen problems using methods like GlideStringUtil.base64Decode() with binary files where the attachment is created but it is not usable. This is likely the problem if plain text files are working but images are not.
Extract attachments script
This example is for a JSON payload, but the same concept applies to SOAP.
If you want to send attachments with special characters in their file names, you will need to make sure that you encode the file name first, e.g., using encodeURI()
. Here's an example of a standard outbound Unifi attachment stream script with the correct escaping in place.