Second Life of a Hungarian SharePoint Geek

April 7, 2014

How to Use JavaScript to Delete Short-Term Locks from Documents Opened from SharePoint?

After I find a way to delete the short-term lock using PowerShell, I decided to check how Word clears the lock it put on the file, and try to simulate the same network traffic using JavaScript. See again this post regarding the details of the background communication when opening a document from a SharePoint library, but if you are really hardcore, you will find this documentation to be useful too.

As usual I utilized Fiddler to capture the network traffic between Word and SharePoint, and found the following steps:

First, Word calls _vti_bin/_vti_aut/author.dll of FrontPage Server Extensions (FSE) via HTTP POST and sends a package similar to this one (assuming the URL of your document is http://yoursite.company.com/subsite/library/yourdocument.docx):

method=getDocsMetaInfo%3a14%2e0%2e0%2e6009&url%5flist=%5bhttp%3a%2f%2fyoursite%2ecompany%2ecom%2fsubsite%2flibrary%2fyourdocument%2edocx%3bhttp%3a%2f%2fyoursite%2ecompany%2ecom%2fsubsite%2flibrary%5d&listHiddenDocs=false&listLinkInfo=false

The decoded version of the content:

method=getDocsMetaInfo:14.0.0.6009&url_list=[http://yoursite.company.com/subsite/library/yourdocument.docx;http://yoursite.company.com/subsite/library%5D&listHiddenDocs=false&listLinkInfo=false

Next to the getDocsMetaInfo method you can see the encoded version number of FSE (14.0.0.6009), as well the the url_list parameter that contains both the document and library URLs. (I  found later that it’s enough to include only the document URL, since we need only the metadata of the document, but not of the library).

The response of the server should be similar to this one (assumed that the file is already locked by the user):

Content-type: application/x-vermeer-rpc

<html><head><title>vermeer RPC packet</title></head>
<body>
<p>method=getDocsMetaInfo:14.0.0.6009
<p>document_list=
<ul>
<ul>
<li>document_name=library/yourdocument.docx
<li>meta_info=
<ul>
<li>vti_etag
<li>SW|"{A4E045C9-2160-47B6-A4E5-06B4A51DA0B4},1"
<li>vti_parserversion
<li>SR|14.0.0.6117
<li>vti_folderitemcount
<li>IR|0
<li>vti_timecreated
<li>TR|23 Aug 2012 11:00:23 -0000
<li>_Category
<li>SW|
<li>vti_canmaybeedit
<li>BX|true
<li>vti_author
<li>SR|domain\user1
<li>_dlc_DocIdItemGuid
<li>SW|a4e045c9-2160-47b6-a4e5-06b4a51da0b4
<li>vti_sourcecontrollockexpiresvalue
<li>TR|28 Mar 2014 09:32:06 -0000
<li>vti_approvallevel
<li>SR|
<li>vti_categories
<li>VW|
<li>vti_level
<li>IR|1
<li>vti_foldersubfolderitemcount
<li>IR|0
<li>vti_modifiedby
<li>SR|SHAREPOINT\system
<li>vti_assignedto
<li>SR|
<li>Keywords
<li>SW|
<li>_Status
<li>SW|
<li>vti_sourcecontrollockid
<li>SW|{3F9C6295-354F-449A-B38B-C7BA192E3EA2}

<li>vti_filesize
<li>IR|487936
<li>ContentTypeId
<li>SW|0x0101006727D7B3E7C4C24DB7D5B98B2F8901BB
<li>_dlc_DocId
<li>SW|SITE-351-479
<li>vti_title
<li>SR|Division X
<li>_Author
<li>SW|John
<li>vti_timelastmodified
<li>TR|23 Aug 2012 11:00:23 -0000
<li>vti_sourcecontrolmultiuserchkoutby
<li>VR|DOMAIN\\User2
<li>vti_candeleteversion
<li>BR|true
<li>_dlc_DocIdUrl
<li>SW|
http://yoursite.company.com/subsite/_layouts/DocIdRedir.aspx?ID=SITE-351-479, SITE-351-479
<li>_Comments
<li>SW|
<li>vti_sourcecontrolcheckedoutby
<li>SR|DOMAIN\User2

<li>vti_sourcecontroltimecheckedout
<li>TR|28 Mar 2014 08:32:06 -0000
<li>vti_sourcecontrollocktype
<li>IR|0
<li>vti_sourcecontrolversion
<li>SR|V1.0
<li>vti_sourcecontrolcookie
<li>SR|fp_internal
<li>Subject
<li>SW|
<li>vti_sourcecontrollockexpires
<li>TR|28 Mar 2014 09:32:06 -0000

<li>vti_rtag
<li>SW|rt:A4E045C9-2160-47B6-A4E5-06B4A51DA0B4@00000000001
</ul>
</ul>
</ul>
<p>urldirs=
<ul>
<ul>
<li>url=library
<li>meta_info=
<ul>
<li>vti_isexecutable
<li>BR|false
<li>vti_listenableminorversions
<li>BR|false
<li>vti_listenablemoderation
<li>BR|false
<li>vti_rss_ChannelDescription
<li>SW|RSS-Feed for the List ‘library’.
<li>vti_rtag
<li>SW|rt:69EB8F13-1D5E-4368-9897-4474C14FDA2E@00000000000
<li>vti_etag
<li>SW|"{69EB8F13-1D5E-4368-9897-4474C14FDA2E},0"
<li>vti_hassubdirs
<li>BR|false
<li>vti_rss_ItemLimit
<li>IW|25
<li>vti_folderitemcount
<li>IR|14
<li>vti_timecreated
<li>TR|07 Feb 2008 12:22:51 -0000
<li>vti_listname
<li>SR|{A434F6B1-ACDE-49F8-97F3-BAED36601F75}
<li>vti_listtitle
<li>SR|library
<li>vti_canmaybeedit
<li>BX|true
<li>vti_listrequirecheckout
<li>BR|false
<li>vti_rss_DisplayOnQuicklaunch
<li>IW|0
<li>vti_rss_DisplayRssIcon
<li>IW|1
<li>vti_rss_ChannelTitle
<li>SW|Editor: Library
<li>vti_rss_ChannelImageUrl
<li>SW|/subsite/_layouts/images/homepage.gif
<li>vti_rss_DayLimit
<li>IW|7
<li>vti_level
<li>IR|1
<li>vti_isbrowsable
<li>BR|true
<li>vti_isscriptable
<li>BR|false
<li>vti_listbasetype
<li>IR|1
<li>vti_modifiedby
<li>SR|SHAREPOINT\system
<li>vti_foldersubfolderitemcount
<li>IR|13
<li>vti_listservertemplate
<li>IR|101
<li>vti_listenableversioning
<li>BR|true
<li>vti_candeleteversion
<li>BR|true
<li>vti_dirlateststamp
<li>TW|28 Mar 2014 08:36:18 -0000
<li>docid_msft_hier_listid
<li>IW|351
<li>docid_msft_hier_list_siteid
<li>SW|bea05316-e059-48a2-890b-c1c22c765367
<li>vti_timelastmodified
<li>TR|19 Mar 2014 13:51:30 -0000
<li>vti_nexttolasttimemodified
<li>TR|06 Mar 2013 13:19:58 -0000
<li>vti_rss_LimitDescriptionLength
<li>IW|0
<li>docid_msft_hier_list_guid
<li>SW|a434f6b1acde49f897f3baed36601f75
</ul>
</ul>
</ul>
</body>
</html>

Regarding the format and content of the response, see this article. If we omit the library URL from the url_list parameter as stated above, the second part of the response, related to the library metadata won’t be included as well.

In this case, the most important information is in the vti_sourcecontrollockid, but vti_sourcecontrolcheckedoutby and vti_sourcecontrollockexpires contains useful information either.

In the second step, the SOAPAction http://schemas.microsoft.com/sharepoint/soap/ICellStorages/ExecuteCellStorageRequest of the CellStorage web service (/_vti_bin/cellstorage.svc/CellStorageService) is invoked, and a content like the one below is sent:

–urn:uuid:8cfcbb22-dd52-4889-b29d-9ff2dcf909b2
Content-ID: <f13ad06d-8530-4af1-8cf3-d6d75c1635d4@tempuri.org>
Content-Transfer-Encoding: 8bit
Content-Type: application/xop+xml;charset=utf-8;type="text/xml; charset=utf-8"

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><RequestVersion Version="2" MinorVersion="0" xmlns="http://schemas.microsoft.com/sharepoint/soap/"/><RequestCollection CorrelationId="{35E42C96-FE02-41FE-B4D8-F7DEC43AF784}" xmlns="http://schemas.microsoft.com/sharepoint/soap/"><Request Url="http://yoursite.company.com/subsite/library/yourdocument.docx" RequestToken="1"><SubRequest Type="ExclusiveLock" SubRequestToken="1"><SubRequestData ExclusiveLockRequestType="ReleaseLock" ExclusiveLockID="{3F9C6295-354F-449A-B38B-C7BA192E3EA2}"/></SubRequest></Request></RequestCollection></s:Body></s:Envelope>
–urn:uuid:8cfcbb22-dd52-4889-b29d-9ff2dcf909b2–

See the two important parameters (document URL and lock ID) above in the request.

In case of success, the server should respond with a package like this one:

–uuid:4f762581-5bb8-4391-95a4-7dc08878025a+id=12
Content-ID: <http://tempuri.org/0&gt;
Content-Transfer-Encoding: 8bit
Content-Type: application/xop+xml;charset=utf-8;type="text/xml"

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><ResponseVersion Version="2" MinorVersion="0" xmlns="http://schemas.microsoft.com/sharepoint/soap/"/><ResponseCollection WebUrl="http://yoursite.company.com/subsite&quot; xmlns="http://schemas.microsoft.com/sharepoint/soap/"><Response Url="http://yoursite.company.com/subsite/library/yourdocument.docx&quot; RequestToken="1" HealthScore="0"><SubResponse SubRequestToken="1" ErrorCode="Success" HResult="0"><SubResponseData/></SubResponse>
36
</Response></ResponseCollection></s:Body></s:Envelope>
37

–uuid:4f762581-5bb8-4391-95a4-7dc08878025a+id=12–

Having this request and response formats we can start creating our own custom JavaScript lock releaser.

I embedded the my JavaScript code into a SharePoint page via the following HTML snippet:

<script type="text/javascript" src="/_layouts/Unlock.js"></script>

<input id="documentUrl" type="text" style="width:600px" value="http://intranet.contoso.com/DocLib/Test.docx"/&gt;
<input type="button" id="submitRequest" onclick="getLockForDoc()" value="Check lock">

Let’s see the functionality in the Unlock.js. I defined the following helper function in JavaScript:

  1. // define String.format function
  2. if (!String.prototype.format) {
  3.     String.prototype.format = function () {
  4.         var args = arguments;
  5.         return this.replace(/{(\d+)}/g, function (match, number) {
  6.             return typeof args[number] != 'undefined'
  7.               ? args[number]
  8.               : match
  9.             ;
  10.         });
  11.     };
  12. }

Then introduced Properties to make parsing the FSE response easier. I had to create my own unescapeEx function to be able to decode the encoded value of the vti_sourcecontrollockid (that is {3F9C6295-354F-449A-B38B-C7BA192E3EA2} instead of the standard %7B3F9C6295-354F-449A-B38B-C7BA192E3EA2%7D), as the standard JavaScript methods (like unescape, decodeURI  or decodeURIComponent) had issues with that. In this function we replace the encoded chars with their decoded values based on the decimal code using a RegEx replace.

  1. function Properties(source) {
  2.     this.source = source;
  3.     this.offspring = [];
  4.  
  5.     this.getPropValue = function (propName) {
  6.         var propValue = "";
  7.         var propNameFullLine = '<li>' + propName + '\n';
  8.         var startPos = this.source.indexOf(propNameFullLine);
  9.         if (startPos > -1) {
  10.             var endPos = this.source.substr(startPos + propNameFullLine.length).indexOf('\n');
  11.             if (endPos > 0) {
  12.                 propValue = this.source.substr(startPos + propNameFullLine.length, endPos);
  13.                 // trim leading chars
  14.                 // http://msdn.microsoft.com/en-us/library/office/ms460937(v=office.14).aspx
  15.                 var strDummy = '<li>SW|';
  16.                 propValue = unescapeEx(propValue.substr(strDummy.length));
  17.             }
  18.         }
  19.  
  20.         return propValue;
  21.     }
  22.  
  23.     // this function is intended to be accessible from the object itself (e.g. private)
  24.     var unescapeEx = function (value) {
  25.         var result = value.replace(/&#([0-9]|[1-9][0-9]|[[01][0-9][0-9]|2[0-4][0-9]|25[0-5]);/g, function (str, match) { return String.fromCharCode(match); });
  26.  
  27.         return result;
  28.     }
  29. }

We get the lock status using the getLockForDoc method. Note, that the URL of the author.dll is hardcoded in this case, so you should update it.

  1. function getLockForDoc() {
  2.  
  3.     var docUrl = $('#documentUrl').val();
  4.     var escapedDocUrl = encodeURI(docUrl);
  5.  
  6.     $.ajax({
  7.         url: 'http://intranet.contoso.com/_vti_bin/_vti_aut/author.dll&#039;,
  8.         type: 'POST',
  9.         contentType: 'application/x-www-form-urlencoded',
  10.         headers: {
  11.             'MIME-Version': '1.0',
  12.             'User-Agent': 'MSFrontPage/14.0',
  13.             'Accept': 'auth/sicily',
  14.             'X-Vermeer-Content-Type': 'application/x-www-form-urlencoded'
  15.         },
  16.         data: 'method=getDocsMetaInfo%3a14%2e0%2e0%2e6009&url%5flist=%5b' + escapedDocUrl + '%5d&listHiddenDocs=false&listLinkInfo=false',
  17.         complete: function (result) {
  18.             if ((result.readyState == 4) && (result.status == 200)) {
  19.                 var rawResponse = result.responseText;
  20.                 var startPos = rawResponse.indexOf('<li>meta_info=\n<ul>');
  21.                 if (startPos > 0) {
  22.                     var endPos = rawResponse.substr(startPos).indexOf('</ul>');
  23.                     if (endPos > 0) {
  24.                         var props = new Properties(rawResponse.substr(startPos, endPos));
  25.                         var lockId = props.getPropValue('vti_sourcecontrollockid');
  26.                         var checkedOutBy = props.getPropValue('vti_sourcecontrolcheckedoutby');
  27.                         var lockExpires = props.getPropValue('vti_sourcecontrollockexpires');
  28.                         if (lockId == "") {
  29.                             alert("File is not locked.");
  30.                         }
  31.                         else {
  32.                             if (confirm(String.format("File is locked by '{0}' until '{1}', LockId = '{2}'.\r\nUnlocking the file can cause problems if the user is still editing the file and would like to save it later.\r\nDo you want to clear the lock?", checkedOutBy, lockExpires, lockId))) {
  33.                                 releaseLock(escapedDocUrl, lockId);
  34.                             }
  35.                         }
  36.                     }
  37.                 }
  38.             }
  39.         }
  40.     });
  41. }

Finally, the lock is released by calling the releaseLock method. The URL is hardcoded in this case as well, so please fix it if you would like to test. The error checking when processing the response is rather simple, you can improve it if you wish.

  1. // message template to unlock a document
  2. var releseLockReq = '\r\n';
  3. releseLockReq += '–urn:uuid:8cfcbb22-dd52-4889-b29d-9ff2dcf909b2\r\n';
  4. releseLockReq += 'Content-ID: <f13ad06d-8530-4af1-8cf3-d6d75c1635d4@tempuri.org>\r\n';
  5. releseLockReq += 'Content-Transfer-Encoding: 8bit\r\n';
  6. releseLockReq += 'Content-Type: application/xop+xml;charset=utf-8;type="text/xml; charset=utf-8"\r\n';
  7. releseLockReq += '\r\n';
  8. releseLockReq += '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><RequestVersion Version="2" MinorVersion="0" xmlns="http://schemas.microsoft.com/sharepoint/soap/"/><RequestCollection CorrelationId="{35E42C96-FE02-41FE-B4D8-F7DEC43AF784}" xmlns="http://schemas.microsoft.com/sharepoint/soap/"><Request Url="{0}" RequestToken="1"><SubRequest Type="ExclusiveLock" SubRequestToken="1"><SubRequestData ExclusiveLockRequestType="ReleaseLock" ExclusiveLockID="{1}"/></SubRequest></Request></RequestCollection></s:Body></s:Envelope>\r\n';
  9. releseLockReq += '–urn:uuid:8cfcbb22-dd52-4889-b29d-9ff2dcf909b2–\r\n';
  10.  
  11. function releaseLock(escapedDocUrl, lockId) {
  12.     $.ajax({
  13.         url: 'http://intranet.contoso.com/_vti_bin/cellstorage.svc/CellStorageService&#039;,
  14.         type: 'POST',
  15.         contentType: 'multipart/related; type="application/xop+xml"; boundary="urn:uuid:8cfcbb22-dd52-4889-b29d-9ff2dcf909b2"; start="<f13ad06d-8530-4af1-8cf3-d6d75c1635d4@tempuri.org>"; start-Info="text/xml; charset=utf-8"',
  16.         headers: {
  17.             'MIME-Version': '1.0',
  18.             'User-Agent': 'Microsoft Office Upload Center 2010 (14.0.6124) Windows NT 6.1',
  19.             'SOAPAction': 'http://schemas.microsoft.com/sharepoint/soap/ICellStorages/ExecuteCellStorageRequest&#039;
  20.         },
  21.         data: String.format(releseLockReq, escapedDocUrl, lockId),
  22.         complete: function (result) {
  23.             var succeeded = false;
  24.             if ((result.readyState == 4) && (result.status == 200)) {
  25.                 var rawResponse = result.responseText;
  26.                 // check the result the primitive way
  27.                 succeeded = (rawResponse.indexOf('ErrorCode="Success"') > -1);
  28.             }
  29.             if (succeeded) {
  30.                 alert('Lock is released.');
  31.             } else {
  32.                 alert('An error has occured during the request. Lock is not released, try again later.');
  33.             }
  34.         }
  35.     });
  36. }

The screenshots below illustrate the functionality of the scripts. First, the lock information is displayed.

image

We receive the feedback after successfully releasing the lock.

image

Note, that this method works only as long as the document is checked out by the current user, otherwise an error will be returned.

It might make not to much sense to use JavaScript to release the lock, since one can achieve the same using Word alone (via simply opening and closing the document). However, I have further plans with the script, and my goal was to get a deeper insight into the internals of the FSE communication protocol and the related stuff. Hopefully others find something useful in the sample as well.

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: