Click here to Skip to main content
Click here to Skip to main content

Force SharePoint Document Unlocked / Checked In

By , 8 Mar 2013
 

SharePointItemUnlocker/Screenshot.jpg

Introduction

SharePoint places locks on files in document libraries when you check them out or open them. Sometimes the user's PC crashes, or they lose their internet connection, or solar flares hit the server room, and the lock cannot be released no matter how hard an admin tries. SharePoint calls this the CheckOutStatus which, for this article, I will call a lock since that is effectively what the status is describing.

Some have already written code in a console application to force a check-in of the document. But it only works for long-term locks (check outs). A short-term lock is placed on the document when it's not checked out but the client application is locking it. If, for reasons explained in the Background section, the short-term lock isn't released, you either have to reboot the server or wait until the lock's expiration.

The client application might renew the checkout's expiration time; for example, Office programs renew locks in 10 minute intervals. If the client program doesn't renew the lock, SharePoint is supposed to release it. However, if the client application crashes or a couple other scenarios play out, people end up waiting hours or even days for their document to be unlocked. And to my knowledge, there is no way to unlock a short-term lock through the API (probably by design).

Using this application, you can unlock long-term and short-term locks quickly and easily.

Background

You may want to read this blog post on SharePoint lock types. It does a great job of describing checkout statuses and when they're applied.

A word on short-term locks:

Sometimes when trying to check out a document, they will get an error saying "The file [filename] is checked out or locked for editing by [username]"; if your authentication cookie expired, sometimes [username] is you! This error is well documented by Microsoft here, but the only workaround is to sit and wait for SharePoint to unlock it, which is supposed to take 10 minutes, but sometimes takes hours or days!

As explained in the KB article, this happens when you try to edit the document in the client program and then the program crashes. Also, I've found that this happens when using WebDAV (Explorer) view to open documents without checking them out. It can also happen through the web site if you don't check out the document before editing it.

Normally, you can close the client application and the short-term lock gets released, but sometimes, especially if authentication cookies expire or the application crashes, there are problems, and the lock isn't released. Or maybe that naughty user opened the document without checking it out and then went to lunch, then there's no way to force it free if you can't get into their computer! Even powering off the computer doesn't work sometimes.

I've seen this cause strange things. When you try to check out a document, you might get an error saying "The file [filename] is not checked out."!! Well duh, that's why I'm trying to check it out...I don't fully understand that one, but I think SharePoint is trying to check out the document, realizes it has a CheckOutStatus of ShortTerm, and instead tries to check it in (speculation).

Using the Code

You must run this code from the SharePoint server since it uses the SharePoint object model and not Web Services. When you open the program, you just enter the URL of the site, including the site collection, the name of the document library (as shown in the URL, not the display name), the full file name of the document, and click "Find" to find the document and get the checkout status information on it.

Here's the code:

private void FindButton_Click(object sender, EventArgs e)
{
    this.StatusField.Text = "Searching..." + Environment.NewLine;
    this.StatusField.Refresh();
    this.LockStatusField.Text = string.Empty;
    try
    {
        Uri siteUri = new Uri(this.SiteUrlField.Text.Trim());
        using (SPWeb web = new SPSite(siteUri.ToString()).OpenWeb())
        {
            bool libraryFound = false;
            foreach (SPList list in web.Lists)
            {
                if (list.BaseType == SPBaseType.DocumentLibrary && string.Equals(
                    list.RootFolder.Url, this.libraryField.Text.Trim(),
                    StringComparison.OrdinalIgnoreCase))
                {
                    libraryFound = true;
                    this.statusField.Text += "Found library. Searching for document..." +
                        Environment.NewLine;
                    this.statusField.Refresh();

                    SPQuery NameQuery = new SPQuery();
                    NameQuery.Query = 
                        "<Where><Eq><FieldRef Name='FileLeafRef' /><Value Type='File'>" +
                        this.filenameField.Text.Trim() + "</Value></Eq></Where>";
                    NameQuery.RowLimit = 1;
                    if (recursiveField.Checked)
                        NameQuery.ViewAttributes = @"Scope=""Recursive""";
                    if (recursiveAllField.Checked)
                        NameQuery.ViewAttributes = @"Scope=""RecursiveAll""";
                    if (!string.IsNullOrEmpty(folderNameField.Text.Trim()))
                    {
                        try 
                        {	        
                            SPFolder parentFolder = list.ParentWeb.GetFolder(
                                siteUri.ToString() + "/" + list.RootFolder.Url + "/" +
                                folderNameField.Text.Trim());
                            this.statusField.Text += "Found folder." + Environment.NewLine;
                            NameQuery.Folder = parentFolder;
                        }
                        catch (Exception)
                        {
                            this.statusField.Text += "Could not find folder: '" +
                               siteUri.ToString() + "/" + list.RootFolder.Url + "/" +
                               folderNameField.Text.Trim();
                        }
                    }

                    SPListItemCollection documents = list.GetItems(NameQuery);
                    if (documents.Count > 0 && documents[0].Name.ToLower() == 
                        filenameField.Text.Trim().ToLower())
                    {
                        SPListItem item = documents[0];
                        this.statusField.Text += "Found document at: '" + item.Url + "'" +
                            Environment.NewLine;

                        ReportItemCheckoutStatus(item);

                        if (item.File.CheckOutStatus != SPFile.SPCheckOutStatus.None)
                            this.unlockButton.Enabled = true;
                        else
                            this.unlockButton.Enabled = false;
                        itemToUnlock = item;
                    }
                    else
                    {
                        statusField.Text += "Could not find document: '" + 
                            this.filenameField.Text.Trim() + "'.";
                    }

                    break;
                }
            }
            if (libraryFound == false)
                StatusField.Text += &quot;Could not find library: '&quot; + 
                                    this.LibraryField.Text.Trim() + &quot;'.&quot;;
        }
    }
    catch (Exception ex)
    {
        StatusField.Text += Environment.NewLine + &quot;Error occurred. Details: &quot; + 
            Environment.NewLine + ex.Message + Environment.NewLine + ex.StackTrace;
    }
}

It finds the SPWeb from the URL, loops through all the lists in the web (I've found looping is faster than using GetList()), builds a CAML query to find the document, and if the document is found, it reports the checkout status information on it.

The "Unlock" button does different things depending on whether it's a long-term or short-term lock. If it's a long-term lock, it just forces the document to be checked back in. If it's a short-term lock, it has to go to the content database.

Disclaimer: Directly modifying the SharePoint content database is not recommended or supported by Microsoft (but works well in this case).

If the file has a short-term lock, the only solution I could find to release it is by directly modifying the content database. It does a simple update to the CheckoutExpires column in the AllDocs table for the SharePoint document:

private void UnlockButton_Click(object sender, EventArgs e)
{
    if (itemToUnlock == null)
    {
        this.StatusField.Text += "No file to unlock!" + Environment.NewLine;
        return;
    }

    this.StatusField.Text = "Unlocking..." + Environment.NewLine;
    this.StatusField.Refresh();

    if (itemToUnlock.File.CheckOutStatus == SPFile.SPCheckOutStatus.ShortTerm)
    {
        if (string.IsNullOrEmpty(itemToUnlock.UniqueId.ToString()))
        {
            StatusField.Text += "File has a short-term lock and " + 
                                "UniqueId cannot be determined for database update.";
            return;
        }

        StatusField.Text += "Unsupported and not recommended WARNING." + Environment.NewLine;
        string message = "WARNING!! The selected file has a short-term " + 
                         "lock on it. It should be released in 10 minutes. " +
                         "You can force the item to be unlocked by setting " + 
                         "the checkout to expire immediately in the content database. " +
                         "Modifying the database is not supported by Microsoft " + 
                         "and is not recommended. Do you wish to cancel?";
        if (MessageBox.Show(message, "WARNING", 
            MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning, 
            MessageBoxDefaultButton.Button1) == DialogResult.No)
        {
            try
            {
                UpdateItemCheckoutExpiration(itemToUnlock);
            }
            catch (Exception ex)
            {
                StatusField.Text += Environment.NewLine + 
                    "Error occurred. Details: " +
                    Environment.NewLine + ex.Message + 
                    Environment.NewLine + ex.StackTrace;
                return;
            }

            StatusField.Text += "Database updated. File should now be unlocked.";
            // wait 2 seconds for the file to get unlocked
            System.Threading.Thread.Sleep(2000);
            ReportItemCheckoutStatus(itemToUnlock);
        }
    }
    else if (itemToUnlock.File.CheckOutStatus != SPFile.SPCheckOutStatus.None)
    { // long-term lock, check it in manually
        try
        {
            itemToUnlock.File.CheckIn("Forced Checkin");

            this.StatusField.Text += "File checked in." + Environment.NewLine;
            ReportItemCheckoutStatus(itemToUnlock);
        }
        catch (Exception ex)
        {
            StatusField.Text += Environment.NewLine + "Error occurred. Details: " +
                Environment.NewLine + ex.Message + Environment.NewLine + ex.StackTrace;
            return;
        }
    }
    else
    {
        this.StatusField.Text += "File doesn't have a lock." + Environment.NewLine;
    }

    if (itemToUnlock.File.CheckOutStatus == SPFile.SPCheckOutStatus.None)
        this.UnlockButton.Enabled = false;
}

Here's the code to update the content database:

private void UpdateItemCheckoutExpiration(SPListItem item)
{
    SqlConnection contentDatabaseConnection = null;
    try
    {
        contentDatabaseConnection = new SqlConnection(
                 item.Web.Site.ContentDatabase.DatabaseConnectionString);
        contentDatabaseConnection.Open();

        string UpdateCommandText = string.Format("UPDATE dbo.AllDocs SET " + 
               "CheckoutExpires = '{0:yyyy-MM-dd HH:mm:ss:fff}' WHERE Id = '{1}'", 
               DateTime.Now.ToUniversalTime(), item.UniqueId.ToString());
        SqlCommand UpdateCommand = new SqlCommand(UpdateCommandText, 
                                   contentDatabaseConnection);
        SqlDataAdapter contentDataAdapter = new SqlDataAdapter();
        contentDataAdapter.UpdateCommand = UpdateCommand;
        contentDataAdapter.UpdateCommand.ExecuteNonQuery();
        contentDatabaseConnection.Close();
    }
    catch (Exception)
    {
        throw;
    }
    finally
    {
        if (contentDatabaseConnection != null && 
                 contentDatabaseConnection.State != ConnectionState.Closed)
            contentDatabaseConnection.Close();
    }
}

Points of Interest

Microsoft's recommendation to simply "wait 10 minutes" for the this error to be fixed is ridiculous, and many times doesn't work. I've spent nearly an entire day looking for ways to fix this, and I hope I save someone out there the frustration and time on this one.

History

  • First release.
  • Updated 10-28-10: Added recursive controls and tweaked code to recursively search folders in a document library. Added ability to search a specific folder for files.
  • Updated 06-14-2012: Added exception handling to ReportItemCheckoutStatus to handle stupid SharePoint exceptions when trying to call CheckedOutDate.
  • Updated 03-08-2013: Fixed a bug trying to search for a site with spaces in the URL. Added a manifest to request UAC elevation if UAC is enabled (fixes a security error if run without administrator privileges).

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

jabit
Software Developer Veracity
United States United States
Member
I'm a .NET developer, fluent in C# and VB.NET with a focus on SharePoint and experience in WinForms, WPF, Silverlight, ASP.NET, SQL Server. My roots come from a support/system administrator role so I know my way around a server room as well.
 
I have a passion for technology and I love what I do.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
Hint: For improved responsiveness ensure Javascript is enabled and choose 'Normal' from the Layout dropdown and hit 'Update'.
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionContains illegal character '%'memberwolf79016 Mar '13 - 6:52 
AnswerRe: Contains illegal character '%'memberjabit6 Mar '13 - 7:07 
GeneralRe: Contains illegal character '%'memberwolf79016 Mar '13 - 7:29 
AnswerRe: Contains illegal character '%'memberjabit8 Mar '13 - 8:34 
QuestionError Searching Site CollectionmemberMember 982725311 Feb '13 - 10:02 
AnswerRe: Error Searching Site Collectionmemberjabit11 Feb '13 - 11:03 
GeneralRe: Error Searching Site CollectionmemberMember 982725311 Feb '13 - 11:10 
GeneralRe: Error Searching Site Collectionmemberjabit11 Feb '13 - 11:17 
GeneralRe: Error Searching Site CollectionmemberMember 982725311 Feb '13 - 11:36 
GeneralRe: Error Searching Site Collectionmemberjabit6 Mar '13 - 7:09 
AnswerRe: Error Searching Site Collectionmemberjabit8 Mar '13 - 8:35 
QuestionSorry for same question as others but I'm also getting "Web Application at xxxx.com could not be found. [modified]memberTed Larsen10 Dec '12 - 10:48 
AnswerRe: Sorry for same question as others but I'm also getting "Web Application at xxxx.com could not be found.memberjabit4 Feb '13 - 6:46 
QuestionLibrary not found because of spaces in the library namememberMember 96517383 Dec '12 - 1:38 
SuggestionRe: Library not found because of spaces in the library namememberjabit3 Dec '12 - 8:01 
GeneralRe: Library not found because of spaces in the library namememberMember 96517384 Dec '12 - 1:04 
QuestionRe: Library not found because of spaces in the library namememberjabit4 Dec '12 - 5:35 
QuestionWeb Application can't be foundmemberMike023519 Nov '12 - 16:43 
AnswerRe: Web Application can't be foundmemberjabit19 Nov '12 - 16:58 
AnswerRe: Web Application can't be foundmemberjabit8 Mar '13 - 8:39 
QuestionGreat Application!memberDylan3326 Sep '12 - 10:36 
QuestionWorked perfectlymemberSandraDePalma14 Sep '12 - 2:53 
Question"Unhandled exception has occurred in your application" errormemberValerygata30 May '12 - 0:56 
AnswerRe: "Unhandled exception has occurred in your application" errormemberjabit30 May '12 - 5:22 
GeneralRe: "Unhandled exception has occurred in your application" errormemberValerygata30 May '12 - 23:47 
GeneralRe: "Unhandled exception has occurred in your application" errormemberjabit31 May '12 - 6:51 
GeneralRe: "Unhandled exception has occurred in your application" errormemberValerygata11 Jun '12 - 21:03 
GeneralRe: "Unhandled exception has occurred in your application" errormemberjabit12 Jun '12 - 5:31 
GeneralRe: "Unhandled exception has occurred in your application" errormemberValerygata12 Jun '12 - 22:29 
GeneralRe: "Unhandled exception has occurred in your application" errormemberjabit13 Jun '12 - 7:51 
GeneralRe: "Unhandled exception has occurred in your application" errormemberValerygata13 Jun '12 - 23:20 
GeneralRe: "Unhandled exception has occurred in your application" error [modified]memberjabit14 Jun '12 - 6:32 
GeneralRe: "Unhandled exception has occurred in your application" errormemberValerygata14 Jun '12 - 22:09 
QuestionNot understanding the status messagememberTodd Crenshaw9 Apr '12 - 7:03 
AnswerRe: Not understanding the status message [modified]memberjabit9 Apr '12 - 9:34 
Questionhttpsmemberlodbroktest0121 Dec '11 - 2:56 
QuestionRe: httpsmemberjabit21 Dec '11 - 3:20 
GeneralMy vote of 5memberglennho2 Nov '10 - 5:23 
GeneralRe: My vote of 5memberjabit2 Nov '10 - 8:38 
GeneralThanks You are goodmemberinsasy1 Nov '10 - 2:58 
GeneralRe: Thanks You are goodmemberjabit2 Nov '10 - 8:39 
GeneralMy files live in a folder inside the documemt Librarymemberinsasy26 Oct '10 - 14:32 
GeneralRe: My files live in a folder inside the documemt Librarymemberjabit27 Oct '10 - 4:55 
GeneralRe: My files live in a folder inside the documemt Librarymemberjabit28 Oct '10 - 7:41 
GeneralMy vote of 4memberwd6015227 Jul '10 - 11:06 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130516.1 | Last Updated 8 Mar 2013
Article Copyright 2010 by jabit
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid