Downloading Dynamics CRM attachments with JavaScript

I ran into a requirement this week where I needed to give Dynamics CRM users an easy way to download PDF attachments from a set of links in an iframe. I thought it would be easy enough to just grab the corresponding annotation id and pass it to the CRM download.aspx page, but that didn't work. A slightly more complex solution that did work was the following:

  1. Retrieve the file name and document body data for the annotation that has the attachment you want to download.
  2. Convert the document body (a Base64-encoded representation of the attachment) to a Uint8Array.
  3. Initiate a file download using the download.js library.

Here's the code that does all this:

//method to retrieve PDF attachment data and execute callbacks as necessary
function retrieveAttachment(objectid){  
    var select = "$select=DocumentBody,FileName";
    var filter = "$filter=ObjectId/Id eq (guid'"+objectid+"')&IsDocument eq true and MimeType eq 'application/pdf'";
    var orderby = "$orderby=CreatedOn desc&$top=1";

    var query = select+"&"+filter+"&"+orderby

    //this was originally written for CRM 2015, but the approach should work for CRM 2016 or it can be converted to use the Web API
    SDK.REST.retrieveMultipleRecords("Annotation", query, retrieveAttachmentSuccess, retrieveAttachmentError, retrieveAttachmentComplete);
}

//empty function because SDK.REST.retrieveMultipleRecords expects something
function retrieveAttachmentComplete(){}

//function to convert the Base64-encoded attachment to a Uint8Array - from http://stackoverflow.com/a/21797381
function base64ToArrayBuffer(base64) {  
    var binaryString =  window.atob(base64);
    var len = binaryString.length;
    var bytes = new Uint8Array( len );
    for (var i = 0; i < len; i++)        {
        bytes[i] = binaryString.charCodeAt(i);
    }
    return bytes.buffer;
}

//look at the returned data and initiate the download
function retrieveAttachmentSuccess(returnData){  
    if (returnData != null && returnData.length==1) {
        if(returnData[0].DocumentBody){
            //obviously you need to have included the download.js library for this next line to work
            download(base64ToArrayBuffer(returnData[0].DocumentBody), returnData[0].FileName, "application/pdf");
        }
        else{
            alert("Cannot render document from missing body.");
        }
    }
    else{
        alert("Cannot find single matching attachment.");
    }
}

//handle errors
function retrieveAttachmentError(err){  
    alert("There was an error retrieving the attachment: " + err);
}

Note this code requires the window.atob function, so it will not work in Internet Explorer prior to version 10. You can check for compatibility across all major browsers here. Happy attachment downloading!

comments powered by Disqus