Fix broken Managed metadata service and more after CU update

After installing the September CU for SharePoint 2013 Server I have several problems.

The two most annoying things:

  • The Managed Metadata Service went down, giving me no managed navigation and no access to term store.
  • The User Profile Synchronization Service stopped and refused to start.

After trying numerous things (including recreation of the Managed Metadata Service application…) I found this message in the ULS logs:

w3wp.exe (0x1D78) 0x21A0 SharePoint Server Taxonomy ca3r Monitorable Error encountered in background cache check System.Security.SecurityException: Requested registry access is not allowed.     at Microsoft.Win32.RegistryKey.OpenSubKey(String name, Boolean writable)

Googling this error gave me some input! There is a command in psconfig.exe with the purpose of setting correct permissions on SharePoint registry keys.

Running

psconfig -cmd secureresources

resolved all my issues.

Here is the blog post that guided me to the correct solution: http://sharepointologic.blogspot.se/2014/02/managed-metadata-service-not-working.html

Managed metadata and KQL Search

Using metadata on your information is a great way of categorizing it. It also enables you to drill up and down in information by the means of the term hierarchy.

By leveraging search, you can gather information from all the site collections, even web applications in your farm (as long as they use the same Search Service Application at least).

Let’s take a look at a basic example of how to create a method that using KQL will gather all items tagged with a specific term.

First, here is the complete method:

public DataTable GetItemsByTag(Term term, string[] selectProperties, string filterManagedProperyName)
{
     KeywordQuery kq = new KeywordQuery(SPContext.Current.Site);
     kq.TrimDuplicates = false;
     kq.SelectProperties.AddRange(selectProperties);
     kq.QueryText = String.Format("{0}:\"GP0|#{1}\"", term.TermGuid, filterManagedProperyName)

     SearchExecutor se = new SearchExecutor();
     var result = se.ExecuteQuery(kq);
     return result.Filter("TableType", KnownTableTypes.RelevantResults).FirstOrDefault().Table;
}

So, a very basic method that just returns the relevant results based on the term passed in. Of course, to be able to render the results we later on would have to iterate the DataTable rows and perhaps generate some more easy to handle objects that we could pass to our view manager.

Some clarifications about the code:
“filterManagedProperyName” should the the name of the managed property created for the metadata column (the one with ows_taxId in it).

The “GP0|#” part of the QueryText makes sure we only get items tagged with the passed in term. If you do not add “GP0|#” in from of the term GUID, you will also get results for the parent terms (since in the managed property also parts of the term hierarchy is stored)

That was it for today!

Avoid duplicated webparts on Page Layout feature reactivation

We’ve all been there:
You have created some nice Page Layouts, added some webparts to the WebPartZones via the Elements.xml file, deployed the solution and activated the feature.

Now it is just a matter of time before the client asks for a new Page Layout, or for a change in one of the existing one.

“Fine”, you think – makes the changes, builds, uploads the .wsp to the server, runs Update-SPSolution and reactivates the feature.

Disaster! All your webparts now are added two times to all new pages created! The client gets furious and you can’t stop hitting yourself.

Don’t worry! There is a quite easy solution to this problem:

Add a FeatureReceiver to the feature containing your Page Layouts by “Right Click on the Feature in Visual Studio -> Add Event Receiver”

In the FeatureActivatedEvent write the following:

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
    var site = properties.Feature.Parent as SPSite;
    FixDuplicateWebParts(site, "YOURPAGELAYOUTPREFIX");
}

Replace YOURPAGELAYOUTPREFIX with the prefix you hopefully have used in the filename of your webparts.

Now all you have to do is implementing the method

        private void FixDuplicateWebParts(SPSite site, string prefix)
        {
            try
            {
                SPList list = site.GetCatalog(SPListTemplateType.MasterPageCatalog);
                SPListItemCollection items = list.Items;
                List<string> webParts = new List<string>();

                // find the right Page Layout
                for (var i = items.Count - 1; i >= 0; i--)
                {
                    var item = items[i];
                    if (item.Name.IndexOf(prefix,
                        StringComparison.CurrentCultureIgnoreCase) > -1 && item.Name.IndexOf(".aspx",
                        StringComparison.CurrentCultureIgnoreCase) > -1)
                    {
                        SPFile file = item.File;
                        file.CheckOut();
                        // get the Web Part Manager for the Page Layout
                        using (SPLimitedWebPartManager wpm =
                            file.GetLimitedWebPartManager(PersonalizationScope.Shared))
                        {
                            // iterate through all Web Parts and remove duplicates
                            int nbrOfWebParts = wpm.WebParts.Count - 1;
                            for (var j = nbrOfWebParts; j >= 0; j--)
                            {
                                using (var stringWriter = new StringWriter())
                                {
                                    using (var xw = new XmlTextWriter(stringWriter))
                                    {
                                        System.Web.UI.WebControls.WebParts.WebPart wp =
                                        wpm.WebParts[j];

                                        wpm.ExportWebPart(wp, xw);
                                        xw.Flush();

                                        string md5Hash = GetMD5(stringWriter.ToString());
                                        if (webParts.Contains(md5Hash))
                                        {
                                            wpm.DeleteWebPart(wp);
                                        }
                                        else
                                        {
                                            webParts.Add(md5Hash);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }

                file.CheckIn(string.Empty);
                if ((file.Level == SPFileLevel.Draft) && (file.CheckOutType == SPFile.SPCheckOutType.None))
                {
                    file.Publish(string.Empty);

                    try
                    {
                        if (file.Item.ModerationInformation.Status == SPModerationStatusType.Pending)
                        {
                            file.Approve(string.Empty);
                        }
                    }
                    catch (System.Exception)
                    { //Suppress 
                    }
                }
            }
            catch (Exception e)
            {
                Logger.WriteLog(Logger.Category.Unexpected, this.ToString(), e.Message);
            }
        }

This code in turn requires this method:

        public static string GetMD5(string Value)
        {
            if (Value == "") return "";
            MD5CryptoServiceProvider x = new MD5CryptoServiceProvider();
            byte[] bs = System.Text.Encoding.UTF8.GetBytes(Value);
            bs = x.ComputeHash(bs);
            x.Clear();  // dispose
            System.Text.StringBuilder s = new System.Text.StringBuilder();

            foreach (byte b in bs)
            {
                s.Append(b.ToString("x2").ToLower());
            }

            return s.ToString();
        }

That’s it! When you reactivate the feature it makes sure that not two identical WebParts will be present in a Page Layout.

Targeted Provisioning of Document Templates with XML

I had a request to provision Document Templates to Document Libraries in SharePoint 2013. The requirement included the ability to relatively easy add or update the Document Templates, as well as target a Document Template to all Site Collections, or only to Site Collections under a specific Managed path.

The solution is based on an XML file, that was added to the CONFIG folder in the 15 hive, template office documents added to the Layouts folder and an Event Receiver that parses the XML and creates appropriate Content Types for the Document Templates.

The XML looks like this:

<?xml version="1.0" encoding="utf-8" ?>
<DocumentTemplates Prefix="Baseic" ParentContentType="0x01010055DCB6CC4FB44A548DEE1113E5000917">
  <DocumentTemplate Target="collaboration">
    <ContentType Name="Collaboration Guideline" Group="Collaboration" Description="Create a Collaboration Guide" />
    <Template Location="/_layouts/15/Baseic.2013/DocumentTemplates/CollabGuide.docx" />
  </DocumentTemplate>
  <DocumentTemplate>
    <ContentType Name="Protocol" Group="Common" Description="Create a protocol" />
    <Template Location="/_layouts/15/Baseic.2013/DocumentTemplates/Protocol.docx" />
  </DocumentTemplate>
</DocumentTemplates>

As you can see, I specify a Parent Content Type that all the created ones will inherit from. I do this because in my scenario all documents in the solution must have a couple of fields added to them. I also specify a Prefix that will be added before the actual Content Type name.

For each Document Template we specify a Content Type and a Template.

  • The Content Type Node contains information about what the Content Type will be called, to which group it will be added and a description.
  • The Template Node specifies the location of the document to add as a template to the new Content Type

This file will be uploaded to a subfolder of the CONFIG directory (C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\CONFIG).

The template files will, as you can see in the XML be added to a subfolder of the Layouts folder (C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\TEMPLATE\LAYOUTS)

Now to the magic: The feature event receiver that makes it all happen!

The feature is Site Scoped, since we will really just want to add our Content Types to the root Web.

using System;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using System.IO;
using System.Linq;
using System.Xml.Linq;

namespace Baseic.2013.Features.ProvisionDocTemplates
{
    [Guid("9798f1d6-4e4a-407d-8989-ad91e9f04e03")]
    public class ProvisionDocTemplatesEventReceiver : SPFeatureReceiver
    {
        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            // Get Rootweb
            var site = new SPSite((properties.Feature.Parent as SPSite).ID);
            var web = site.OpenWeb();

            // Locate XML file with definitions
            var definitionsFile = SPUtility.GetVersionedGenericSetupPath("CONFIG\\Baseic\\DocTemplatesDefinition.xml", 15);
            
            // Parse file
            XDocument definitionsDocument;
            using (StreamReader oReader = new StreamReader(definitionsFile))
            {
                definitionsDocument = XDocument.Load(oReader);
            };

            var contentTypePrefix = (from c in definitionsDocument.Descendants("DocumentTemplates") select c).First().Attribute("Prefix").Value;
            var parentContentTypeId = (from c in definitionsDocument.Descendants("DocumentTemplates") select c).First().Attribute("ParentContentType").Value;
            var documentTemplates = from c in definitionsDocument.Descendants("DocumentTemplate") select c;

            // Get the parent content type from web
            var parentContentType = web.AvailableContentTypes[new SPContentTypeId(parentContentTypeId)];

            if (parentContentType == null)
            {
                // Logger.WriteError(this.ToString(), "Failed to apply content type: Failed to find parent content type");
                web.Dispose();
                site.Dispose();
                return;
            }

            // Call provisioning method for each CT to deploy 
            foreach (var docTemplate in documentTemplates)
            {
                var target = docTemplate.Attribute("Target");

                // Depending on the Target, provison or not
                if (target == null)
                {
                    CreateContentTypeAndAddDocTemplate(web, contentTypePrefix, parentContentType, docTemplate);
                }
                else if (site.ServerRelativeUrl.Split('/')[1].ToLowerInvariant().Equals(target.Value.ToLowerInvariant())) // the split gives us the managed path of the current sitecollection
                {
                    CreateContentTypeAndAddDocTemplate(web, contentTypePrefix, parentContentType, docTemplate);
                }
            }

            // Make sure to dispose
            web.Update();
            web.Dispose();
            site.Dispose();
        }

        private void CreateContentTypeAndAddDocTemplate(SPWeb web, string contentTypePrefix, SPContentType parentContentType, XElement docTemplate)
        {
            try
            {
                // Parse node data
                var contentTypeNode = (from c in docTemplate.Descendants("ContentType") select c).First();

                var newContentTypeName = string.Concat(contentTypePrefix, " ", contentTypeNode.Attribute("Name").Value);
                var newContentTypeGroup = contentTypeNode.Attribute("Group").Value;
                var newContentTypeDescription = contentTypeNode.Attribute("Description").Value;

                var docTemplateLocation = (from c in docTemplate.Descendants("Template") select c).First().Attribute("Location").Value;

                // Add or update Content Type
                var contentType = web.AvailableContentTypes[newContentTypeName]; 
  
                if (contentType == null) // Does not exist
                {
                    var newContentType = new SPContentType(parentContentType, web.ContentTypes, newContentTypeName);
                    web.ContentTypes.Add(newContentType);

                    newContentType.DocumentTemplate = docTemplateLocation;
                    newContentType.Group = newContentTypeGroup;
                    newContentType.Description = newContentTypeDescription;

                    newContentType.Update(true);
                }
                else // Does exists, update
                {
                    contentType = web.ContentTypes[contentType.Id];

                    contentType.DocumentTemplate = docTemplateLocation;
                    contentType.Group = newContentTypeGroup;
                    contentType.Description = newContentTypeDescription;

                    contentType.Update(true);
                }
            }
            catch (Exception e)
            {
                // Logger.WriteError(this.ToString(), e.Message);
            }
        }
    }
}

So basically, as you can see, it all comes down to using some nice Linq statements to get the correct stuff out of the XML file and then create or update Content Types on the root Web.

You can activate the feature through e.g. the WebTemplate’s ONET.XML or PowerShell.

A heads up though, this code requires that the specified Parent Content Type does already exist on the root Web.

For part two in this blog post I will show you how to bind these new Content Types to a Document Library, using the same XML file!

Best of luck
Robert

The First Post

In this blog I will write about findings, problems and solutions related to SharePoint development