Tuesday, January 15, 2013

SharePoint Document Library: Add document link or Link to a Document

In SharePoint, there’s a way to add document link. Many of use not aware of it. To add the document link you need to add “Link to a Document” Content type in your library. By default content type modification is disabled in any list/library. To enable editing content type you need to go to list/library settings. Once you have enabled the content type editing, you can add/remove content type.

Enable Content Type Modification in List/Library

To enable content type editing go to list/library settings page. Then from the list settings page,  Click “Advanced Settings” and in the advanced settings page, click “Yes” for option “Allow Management of Content Types”. Enabling this option will allow you to modify content type settings. Click Ok after selecting Yes option.
After coming back to list settings page you will find the content modifying option as  shown below:
image

Add Link To Document Content Type to the library

Now from the ‘Add from Existing site content types’ link as shown in the image above, you can add the “Link to Document” content type. click “Add from existing site content types” and from the page you have landed you can the content type as shown below:
image
After adding the content type go back to document library page. You’ll find that when you’ll try to add new item, you’ll get the ‘Link to a Document’ option as shown below:
image
With this link to document item, you can add links to content not only in sharepoint but non-SharePoint content from other sites.

Programmatically Add Link to a document using SharePoint Object Model

Once you enable the ‘link to document’ support in any document library you can add the document programmatically. The following code snippet shows how to add a link to a document content in a library.
public static void AddDocumentLink(string webUrl, string libraryName,string documentPath, string documentName, string documentUrl)
{
    using (var site = new SPSite(webUrl))
    {
        using (var web = site.OpenWeb())
        {
            var contentType = web.AvailableContentTypes["Link to a Document"];
            var docLibrary = web.Lists[libraryName];
                 
            //get full path of the document to add
            var filePath = docLibrary.RootFolder.ServerRelativeUrl;
            if(!string.IsNullOrEmpty(documentPath))
            {
                filePath += "/" + filePath; 
            }
            var currentFolder = web.GetFolder(filePath);

            var files = currentFolder.Files;
            var urlOfFile = currentFolder.Url + "/" + documentName + ".aspx";

            const string format = @"<%@ Assembly Name='{0}' %>
            <%@ Register TagPrefix='SharePoint' Namespace='Microsoft.SharePoint.WebControls' Assembly='Microsoft.SharePoint' %>
            <%@ Import Namespace='System.IO' %>
            <%@ Import Namespace='Microsoft.SharePoint' %>
            <%@ Import Namespace='Microsoft.SharePoint.Utilities' %>
            <%@ Import Namespace='Microsoft.SharePoint.WebControls' %>
                <html>
                    <head> 
                            <meta name='progid' content='SharePoint.Link' /> 
                    </head>
                    <body>
                        <form id='Form1' runat='server'>
                            <SharePoint:UrlRedirector id='Redirector1' runat='server' />
                        </form>
                    </body>
                </html>";

            var builder = new StringBuilder(format.Length + 400);
            builder.AppendFormat(format, typeof(SPDocumentLibrary).Assembly.FullName);

            var properties = new Hashtable();
            properties["ContentTypeId"] = contentType.Id.ToString();

            var file = files.Add(urlOfFile, new MemoryStream(new UTF8Encoding().GetBytes(builder.ToString())), properties, false, false);
            var item = file.Item;
            item["URL"] = documentUrl + ", ";
            item.UpdateOverwriteVersion();
        }
    }
}
The above code snippet is the modified version of what SharePoint does when you add a document link from UI. I have used reflector to view the code and placed here a modified version. I have tested this against SharePoint 2010.

Sunday, January 6, 2013



Cleanup item and file versions in SharePoint using PowerShell

At the SharePoint Conference 2011 I had a discussion with Christian StÃ¥hl and Laura Curtis if it will be possible to cleanup old versions in SharePoint using a script or command line application. I thought to myself that this should be a big task to accomplish but a useful. The slow way to clean up old versions is to loop through all the site collections, webs, lists and items. From each item check the versions and delete them. In SharePoint 2010 Server there is a much smarter way to do this by using a helper that exists in SharePoint Server. The class I’m talking about is called ContentIterator and can be found in Microsoft.SharePoint.Server.Utilities namespace.

The smart thing about the ContentIterator it prevents SharePoint from blocking the database with requests. The only side effect of this is that items in the meantime can be modified because this method is not thread safe.

How does it work?

The class I mentioned before can be used in command line applications, PowerShell script without any problem. The MSDN Example provides a great way of usage that I reused in my code with the addition of the version deletion task. The ContentIterator contains a bunch of methods to process batch updates to SharePoint and the one that will be used here is ProcessListItems.
The following part of the final script does the magic for getting the list items
1string contentIterator = "Cleanup Iterator";
2Console.WriteLine(list.Title);
3
4ContentIterator iterator = new ContentIterator(contentIterator);
5
6string query = (new SPQuery()).ToString();
7iterator.ProcessListItems(list,
8query,
9true,
10delegate(SPListItemCollection items)
11{
12foreach (SPListItem item in items)
13{
14            // ProcessItem deletes all versions
15ProcessItem(item);
16}
17},
18null
19);
The benefit to use this kind of delegate instead of walking the list by using the normal object model is that the list items will be paged and avoid blocking the database objects affected by the query. Paged result means that the query gets a couple of results back and processes the item, after that the next results will be returned from the database. In the ProcessListItems method a CAML query can also specified. That allow me to filter every list for items and documents that was last modified three month ago for example.
The code for cleaning up the versions is also quite easy. SPListItemVersionCollection has an own method for that called DeleteAll and RecycleAll. The difference between those two methods is that RecycleAll will delete the versions and move it to the recycle bin. DeleteAll will instantly delete the versions and they cannot be restored from inside SharePoint. In my case i used DeleteAll Method and the code in the script looks like this:
1<pre>private static void ProcessItem(SPListItem item)
2{
3if (!String.IsNullOrEmpty(item.Title))
4{
5Console.Write("Deleting Versions for {0}", item.Title);
6}
7else
8{
9Console.Write("Deleting Versions for {0}", item.Name);
10}
11Console.Write("   -  Versions {0} -   ", item.Versions.Count);
12    // Delete all versions of the item
13item.Versions.DeleteAll();
14Console.WriteLine("....Done");
15
16}</pre>

Wrap up the C# Code in a PowerShell script

The easiest way to use SharePoint Object Model inside of a PowerShell script is to wrap native C# or VB.net code in a new type. The basic structure of the PowerShell script looks like this:
1# Required assemblies #
2 $Assemblies = ("Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c",
3                                "Microsoft.Office.Server, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c");
4
5 # CSharp Souce Code #
6 $Source = @"
7 using Microsoft.SharePoint;
8 using Microsoft.SharePoint.Administration;
9 using Microsoft.Office.Server.Utilities;
10
11 using System;
12
13 namespace BatchCleaner{
14 // Do some code
15 }
16 “@
17
18# Register new Type #
19 Add-Type -ReferencedAssemblies $Assemblies -TypeDefinition $Source -Language CSharp
This can be done with any C# code. Once the type is registered it can be used.

The usage

The complete PowerShell script can be found can be found here. To use this script first execute the PowerShell script. A new type will be registered to you current PowerShell script session. After that you can use it like a “normal” PowerShell command. The trick is to specify the namespace, class and method you want to use.
1[<namespace>.<class>]::<method>(<List of parameter>).
In our case here I want to filter for files that were last modified three month ago and delete the old versions. The call of that looks like this:
1[BatchCleaner.Cleaner]::Clean("http://yourserver""<Where><Lt><FieldRef Name='Modified'></FiedlRef><Value IncludeTimeValue='FALSE' Type='DateTime'>13/02/2011</Value></Lt></Where>")
In my case I will get a result like this because no file was modified more than three month ago.
Result With CAML Query specified
Result With CAML Query specified
 If the CAML Query will be left out then all files will be queried and the result looks like this.
Result Without CAML
Result Without CAML

Summary

The utilities in Microsoft.Office.Server.Utilities are great to update to lot of items, webs and sites at once. They are even faster than do it using normal object model. They are not thread safe which means they shouldn’t be used in a normal SharePoint development but can do a lot of administration and optimization task. To clean up versions in SharePoint is not able to shrink the database size but can free up space in the database. A database file can only be shrink using database administration. Check out this great Database Maintenance article that provides more information.
In SharePoint 2007 I used to create command line applications in Visual Studio. Nowadays I still use Visual Studio to create command line applications for tasks like this, but with PowerShell I can let it run on the fly in a power shell session and I’m also able to update scripts on the fly instead of recompile the scripts. Depending on the use case I use native PowerShell scripting or c# code inside PowerShell.
On last word of warning I’ve tested this script a couple of times on my development machine and it worked great but I don’t guarantee that it works save in a productive environment. It should but should be tested anyway as all things from the web. The script will also executed over all content databases attached to a web application and all sites, webs and nearly all lists.
This script helps to cleanup old versions after a migration but to plan of document versioning should always be the prefered solution in SharePoint. For this check out Versioning,