28 October 2010

Knowledge Base - Migrate KB Documents to new a 2010 KB library

Problem
In 2007 we had a Knowledge Base document library based on the old MS Fab 40 templates, which as you may or may not know do not work in SP 2010. I found that "Khalil" at TechSolutions had rewritten some of the templates to work in 2010 (http://techsolutions.net/Blog/tabid/65/EntryId/17/Fab-40-Templates-for-MOSS-2010.aspx) so I was able to create a new KB site and document library. Now, the problem I had was getting my current KB documents and articles into my new KB library, which had a number of category/keywords/related articles meta-data that we wanted to retain.

Investigation
The old KB library was based on two content types: Knowledge Base Articles and Knowledge Base Documents. The former contained a richtext field called "Article Content" (internal name KBContentField). When I migrated my 2007 KB list into 2010 and tried to view a KB article the Article Content field is not displayed, the page only displays the "Wiki Content" field (internal name WikiField) which is part of the new Wiki Page content type. Similarly, when trying to create a new "Article" in 2010 it would default to creating a new page in the SitePages library (there is a way around that using the folder parameter) but seemed there were too many reasons to just make a new KB library rather than hacking the current one.
The new KB library was based on two content types: Wiki Page and Knowledge Base Document. I tried doing a "move" and "copy" from "Site Content & Structure" page but found that when copying the
"Knowledge Base Articles" items it simply added that content type to my new KB list and I still had the problem that the Article Content field isn't displayed when viewing the article.
So, I decided to write a little Winforms application to migrate the docs and articles to the new KB library.

Solution:
I created a c# Windows Forms application (used this as opposed to a console app just for better control and readability of output) using the SharePoint 2010 Client Object Model (COM). This is my first COM app so took my a little longer than expected but it all worked out well in the end.

Here's my code for the whole procedure. It is rather procedural and unrefined as it was a quick run-once job, but hopefully you can pick out the bits that are useful to you (error trapping and result output code removed for brevity):


using Microsoft.SharePoint.Client;

string serverName = "spserver";
string sourceRelSiteUrl = "sites/kbsource";
string destRelSiteUrl = "sites/kbdest";
string sourceList = "Knowledge Base";
string destList = "Knowledge Base";

// Create client content for source and destination site
using (ClientContext sourceClientContext = new ClientContext(string.Format("http://{0}/{1}/", serverName, sourceRelSiteUrl)))
{
    using (ClientContext destClientContext = new ClientContext(string.Format("http://{0}/{1}/", serverName, destRelSiteUrl)))
    {

            // Get reference to source and destination KB libraries
            var sourceKBList = sourceClientContext.Web.Lists.GetByTitle(sourceList);
            var destKBList = destClientContext.Web.Lists.GetByTitle(destList);

            // Create and run CAML query to get all items from source KB library
            CamlQuery camlQuery = new CamlQuery();
            camlQuery.ViewXml = "<View />";
            ListItemCollection listItems = sourceKBList.GetItems(camlQuery);
            sourceClientContext.Load(sourceKBList);
            // make sure you call back all the list item fields and properties you will need later on
            sourceClientContext.Load(listItems, li => li.Include(pi => pi.Id, pi => pi.DisplayName, pi => pi.ContentType, pi => pi.FieldValuesAsText, pi => pi.FieldValuesAsHtml, pi => pi["KBContentField"], pi => pi["Category"], pi => pi["Description0"], pi => pi["KBRelatedArticles"], pi => pi["KBKeywords"], pi => pi["FileRef"], pi => pi["ContentTypeId"]));
            sourceClientContext.ExecuteQuery();

            // Set destination content type IDs (get these codes from URL ctype param when viewing teh list content type in browser)
            string wikiCTID = "0x0101080026185C104CE31843BF8B563CB5CA3DD3";
            string docCTID = "0x01010091E04918BF0B9A4D9A94E6D01E34FC19000F2C4D385DC79C408EF48636F2A2CE36";
            ContentTypeCollection listCTID = destKBList.ContentTypes;
            ContentType wikiCType = listCTID.GetById(wikiCTID);
            ContentType docCType = listCTID.GetById(docCTID);
            destClientContext.Load(listCTID);
            destClientContext.Load(wikiCType, ct => ct.Id);
            destClientContext.Load(docCType, ct => ct.Id);
            destClientContext.ExecuteQuery();

            foreach (var sourceListItem in listItems)
            {

                    if (sourceListItem.ContentType.Name.ToLower().Equals("knowledge base article"))
                    {
                    // KB Article Item

                        // Create new Wiki page in detination using name of source item
                        File newKBArticle = destKBList.RootFolder.Files.AddTemplateFile(string.Format("/{0}/{1}/{2}.aspx", destRelSiteUrl, destList, sourceListItem.DisplayName), TemplateFileType.WikiPage);

                        // Now get reference to your wiki page list item and set your field values
                        var wikiListItem = newKBArticle.ListItemAllFields;
                        wikiListItem["Article_x0020_Title"] = sourceListItem.DisplayName;
                        wikiListItem["Category"] = sourceListItem["Category"];
                        wikiListItem["Description0"] = sourceListItem["Description0"];
                        wikiListItem["ContentTypeId"] = wikiCType.Id;

                        // This is the bit where we push the old Article Content field into the new Wiki Content field
                        wikiListItem["WikiField"] = sourceListItem.FieldValuesAsHtml["KBContentField"].ToString();
                        wikiListItem.Update();

                        // Don't forget to execute the change to the destination client content
                        destClientContext.ExecuteQuery();
                                
                    }
                    else if (sourceListItem.ContentType.Name.ToLower().Equals("knowledge base document"))
                    {
                    // KB Document item

                        if (sourceListItem.File != null)
                        {
                            // Get source document name
                            string fileName = (string)sourceListItem["FileRef"];
                            if (fileName.LastIndexOf("/") != -1)
                                fileName = fileName.Substring(fileName.LastIndexOf("/") + 1);

                            // Set source document UNC path
                            string fileFullPath = string.Format(@"\\{0}\{1}\{2}\{3}", serverName, sourceRelSiteUrl, sourceList, fileName);
                            FileCreationInformation newFile = new FileCreationInformation();
                                
                            // Get byte array of the document via UNC
                            newFile.Content = System.IO.File.ReadAllBytes(fileFullPath);
                                
                            // Set the Destination URL of the document
                            newFile.Url = string.Format("http://{0}/{1}/{2}/{3}", serverName, destRelSiteUrl, destList, fileName);
                                
                            // Now add the document to the destination library
                            File newKBDoc = destKBList.RootFolder.Files.Add(newFile);

                            // Get a reference to the list item for the document we just added
                            var wikiListItem = newKBDoc.ListItemAllFields;

                            // Set the field values
                            wikiListItem["Article_x0020_Title"] = sourceListItem.DisplayName;
                            wikiListItem["Category"] = sourceListItem["Category"];
                            wikiListItem["Description0"] = sourceListItem["Description0"];
                            wikiListItem["ContentTypeId"] = docCType.Id;
                            wikiListItem.Update();

                            // Don't forget to execute the change to the destination client content
                            destClientContext.ExecuteQuery();

                        }
                    }
 
            }


    }
}

2 comments:

  1. Its basically an alternative approach may be to configure specific queries to the Lists web service on a Share Point site.

    ReplyDelete
  2. Yes, I used the client object model because I was curious, you could of course acheive the same via the server OM and/or the Lists web service. A bulk of the code is the same, or very similar, regardless of OM.

    ReplyDelete