There isn’t currently a public API that handles attachments, so the requirement will often be dismissed, and instead addressed via process/training. However, sometimes the requirement is of such high priority that it must move forward somehow. For such cases, Geoff Ross and I decided to reverse engineer and figure out how to make this work, even if unconventional. So, buckle up, let’s show what attachments are doing behind-the-scenes and how they could be added to a custom page or task in a pinch. (Please see note at the end about recommendations surrounding public/private API calls.)
Adding File attachments to Work Items
First, we need to look at what exactly is happening out-of-box when we add an attachment to Work Items or a Configuration Item. An easy way to see what is going on is to use the Web Developer tools in your browser (F12 in Chrome). By going to the Networking tab, we can see every call that goes in or out on the page we are currently viewing.
Let’s clear the mess that’s in there already, add an attachment, and see what happens!
It seems that when we add an attachment (in this case, to an Incident), a call is made to the “/FileAttachment/UploadAttachment” endpoint. We can see a couple parameters here. The second one is straightforward—that’s the class name for the Incident module, but what is the first parameter? Plugging that GUID into a PowerShell cmdlet, we can see that it represents the Incident we currently have open:
One other thing to note is that this call happens before we hit Save, and that it returned some data. Let’s take a closer look at that data. It appears to be a JSON string, which is a little hard to read as-is. Doing a quick Google search for a JSON parser (such as http://jsonparseronline.com/) and inputting that text, we get something easier to digest:
This looks to be a bunch of file metadata, such as when it was added, the person who added it, the name/extension of the file, and more. This looks like the type of metadata we would see when opening a form. By that I mean, when we load a form we don’t load the file attachments too, as that would be an expensive operation. We simply load information about the attachment and we can download/open the whole attachment ad hoc if we have a need. Let’s compare the data returned from that call with the ViewModel of the form itself. Without saving, we can now see the following using the Developer Tool’s Console tab:
Sure enough, the data in the ViewModel matches up with the results of the “/FileAttachment/UploadAttachment” call.
Let’s pause for a minute and recap what we’ve discovered so far. For one, we know what API call is being used to upload an attachment, and that is occurring before we save anything. We also know that the call returns data that goes right into an array in the ViewModel, like other relationships. In theory, we should be able to take this information and use it in a custom task or page, so let’s try that (spoiler alert: we can’t quite yet).
This blog post won’t be getting fully into the details of custom task creation, as there are several examples out there already. In short, any custom task that updates a Work Item or Configuration Item needs to use the “/GetProjectionByCriteria” API call to get the projection, changes need to be made locally to that projection, then that is followed by the /Commit call. First, the custom task would need to add a control for a File Upload (plenty of options out there via a quick Google Search). We know that this control would need to send the attachment via the “/FileAttachment/UploadAttachment” API call, and the results of that call would need to go into the FileAttachments array/relationship in the ViewModel. What we will notice is that when we do this and call the “/Commit” call, the file doesn’t attach.
Looking at the Cireson API documentation for the “/Commit” call, we see the following:
A-ha! An optional parameter I have admittedly ignored to-date but seems super relevant here. It seems we need to figure out where that file went when we uploaded it via the earlier call. Digging around in the CiresonPortal directory on our server, we were able to find it:
There is an “\App_Data\TempFiles\” directory in the root of the CiresonPortal website that holds these files. The path to the file is dynamic, and we recognize the second part of the path to be our Work Item GUID from earlier. The first part of the path turns out to be the GUID of the user, as can be seen in the earlier screenshots. This makes sense, as the same attachment (by name) could be attached to multiple Work Items, and multiple people could add that same attachment. Pretty cool!
Okay, so back to our custom task. We now need to send in this fileAttachmentPath parameter, and path to the directory we just found. The first part is easy, minus those tricky escape characters:
But the file isn’t at that level alone, so we also need to pass in the user’s GUID. In the custom task, this GUID can be found using “session.user.Id,” or if you’re writing typescript (i.e. for an Angular App), you can find it like this:
In full, the URL for the API call looks like this:
/api/V3/Projection/Commit?fileAttachmentPath=C:\\inetpub\\CiresonPortal\\App_Data\\TempFiles\\’ + window[‘session’].user.Id
Using that updated “/Commit” call—params and all—we see that the file attached this time. Woohoo!
Side note: the “/FileAttachment/UploadAttachment” call illustrated in this blog post is not an official public API call and is shown here primarily as a learning exercise. The public API calls found in the Cireson API documentation are supported/tested upon new releases of the Portal and are deprecated for at least a version if/when a replacement call is created; however, internal/private calls could change on any release and are not supported for customization. This call or other private API calls should be used with caution, as they could easily change between Portal versions.
Transform your SCSM experience. Check out the Cireson My Active Work Items App.