Can we unlock a ShortTerm lock via CSOM for SP2013
-
07-10-2020 - |
質問
We are using SP2013 with OWA installed. Users often complain about a file stored in SharePiont being locked by another user not in office. Error is "The file "xxxx" is locked for exclusive use by domain\user"
. Microsoft KB said the lock will be released after the file is idle for 10 minutes. We all know that it is not true. It usually take days for the lock release.
After some investigation, I found these kind of lock is called "ShortTerm" Lock (SPFile.SPCheckedOutStatus.ShortTerm). The lock can be released by PowerShell:
$w = get-spweb "http://sharepoint.com"
$l = $w.lists["Shared document"]
$i = $l.GetItemById(123)
$i.File.ReleaseLock($i.File.LockId)
However, above command require server access. It is not applicable to us because we have very restrictive policy. We cannot just walk in the server room and run the command.
Is it possible to process the lock release with CSOM or Javascript under the permission of Site Collection Admin?
解決
AFAIK, nothing in the client-side API can allow this.
However, there's an old technology called "Front-Page RPC" (the name itself shows you how old it is!) that may be still available on SP2013 (even if it's kind of deprecated/not really documented). It is still there since it's used by Office applications to communicate with SharePoint.
FP RPC is a kind of WebDav protocol, i.e. it's HTTP requests (mostly POST requests) you must send to the server. I have a sample in VB.NET (sorry, it's VB.NET, I don't find the C# version in my archives):
Public Sub UncheckoutDocument(ByVal SiteUrl As String, ByVal FileUrl As String)
SendPostRequest(SiteUrl + "/_vti_bin/_vti_aut/author.dll", _
"method=uncheckout+document%3a6%2e0%2e2%2e5523&service%5fname=%2fsites%2fcompta&document%5fname=" & HttpUtility.UrlEncode(FileUrl) & "&force=true&rlsshortterm=true")
End Function
Private Function SendPostRequest(ByRef Url As String, ByRef Body As String) As String
Dim Request As WinHttp.WinHttpRequest
Request = New WinHttp.WinHttpRequestClass
Request.Open("POST", Url, False)
Request.SetCredentials("user", "password", 0)
Request.SetRequestHeader("Content-Type", "application/x-www-form-urlencoded")
Request.SetRequestHeader("X-Vermeer-Content-Type", "application/x-www-form-urlencoded")
Request.SetRequestHeader("MIME-Version", "1.0")
Request.SetRequestHeader("User-Agent", "MSFrontPage/6.0")
Request.Send(Body)
Return Request.ResponseText
End Function
The problem here is with authentication. The code above is for Windows authentication. It won't work for Office 365.
What you have to do is remove the line Request.SetCredentials("user", "password", 0)
to set instead an authentication cookie. You can probably get that authentication cookie first by using, for instance, the MsOnlineClaimsHelper
class I've just found at http://www.wictorwilen.se/Post/How-to-do-active-authentication-to-Office-365-and-SharePoint-Online.aspx.
他のヒント
If you're looking for a client-side solution for Sharepoint 2013 that can be executed in JavaScript, I found some good stuff on this blog that I have updated on mine.
To unblock the short term lock for a file:
- Send a request to
_vti_bin/_vti_aut/author.dll
with special headers/body to find thelockid
- Send a request to
_vti_bin/cellstorage.svc/CellStorageService
with a formatted body that includes thelockid
- The file is unlocked
Tested for Sharepoint 2013 On-Promise only. I don’t know if this solution works for Sharepoint Online or other version.
Please note that I use $SP().ajax()
from SharepointPlus, but it’s equivalent to the $.ajax()
from jQuery.
// full path to the document
var docUrl = "https://website.com/website/Doc_Library/Test.docx";
// start by querying author.dll to find the lockid and the user who locked it
$SP().ajax({
url: 'https://website.com/website/_vti_bin/_vti_aut/author.dll',
headers:{
"Content-Type": "application/x-www-form-urlencoded",
"MIME-Version": "1.0",
"Accept": "auth/sicily",
"X-Vermeer-Content-Type": "application/x-www-form-urlencoded"
},
body: 'method=getDocsMetaInfo%3a14%2e0%2e0%2e6009&url%5flist=%5b' + encodeURIComponent(docUrl) + '%5d&listHiddenDocs=false&listLinkInfo=false',
}).then(function(source) {
// go thru the source page returned to find the lockid and current user
var nextLine = false;
var ret = { "lockid":"", "user":"" };
source.split("\n").forEach(function(line) {
if (line.indexOf("vti_sourcecontrollockid") !== -1) nextLine="lockid"; // vti_sourcecontrollockid -> the lockid to use later
else if (line.indexOf("vti_sourcecontrolcheckedoutby") !== -1) nextLine="user"; // vti_sourcecontrolcheckedoutby -> username of the user who locked it
else if (nextLine !== false) {
ret[nextLine] = line.slice(7).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); });
nextLine = false;
}
});
if (!ret.lockid) { alert("Not Locked") }
else {
// compose a request based on what Microsoft Office sends to the Sharepoint server
// found using Fiddler
var releaseLockReq = '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><RequestVersion Version="2" MinorVersion="2" xmlns="'+docUrl+'" UseResourceID="true" UserAgent="{1984108C-4B93-4EEB-B320-919432D6E593}" UserAgentClient="msword" UserAgentPlatform="win" Build="16.0.8201.2102" MetaData="1031" RequestToken="1"><SubRequest Type="ExclusiveLock" SubRequestToken="1"><SubRequestData ExclusiveLockRequestType="ReleaseLock" ExclusiveLockID="'+ret.lockid+'"/></SubRequest></Request></RequestCollection></s:Body></s:Envelope>';
// we send it to the webservice cellstorage.svc
$SP().ajax({
url:'https://website.com/website/_vti_bin/cellstorage.svc/CellStorageService',
body:releaseLockReq,
headers:{
'Content-Type':'text/xml; charset=UTF-8',
'SOAPAction': "http://schemas.microsoft.com/sharepoint/soap/ICellStorages/ExecuteCellStorageRequest"
}
})
.then(function(res) {
if (res.indexOf('ErrorCode="Success"') !== -1) alert("Success") // the file has been unlocked
else alert("Failed")
})
}
})