Intro

Recently I have worked on a small branding project, we decided to automatically enable some of standard SharePoint features from code during provisioning. We wanted to automatically enable Publishing on a target site and deploy some custom content. As it turned out it is not as easy as it sounds.

Code

Microsoft says:

SharePoint Online includes a set of features that enables you to author and publish rich webpages for your intranet or Internet. These features are housed within the SharePoint Publishing Infrastructure and must be activated prior to use. You can activate them at either the site collection or site level.

First we need to find ids of those features:

It is a very easy task for those who know a little about how SharePoint works.

  1. Go to "Site Settings" -> "Site Collection Features".

  2. Find "SharePoint Server Publishing Infrastructure" and inspect the "Activate"/"Deactivate" button HTML: Feature Id

  3. Go to "Site Settings" -> "Manage site features".

  4. Find "SharePoint Server Publishing" and inspect the "Activate"/"Deactivate" button HTML: Feature Id

This nice trick can be used for any feature.

So now we know that we need to activate two features:

Id Title Scope
f6924d36-2fa8-4f0b-b16d-06b7250180fa SharePoint Server Publishing Infrastructure Site
94c94ca6-b32f-4da9-a9e3-1f3d343d7ecb SharePoint Server Publishing Web

We need to setup a proper client context instance to interact with Office 365 throug CSOM:

var ctx = new ClientContext("<<your site url>>");

var secureStringPassword = new SecureString();
foreach (var ch in Config.Current.Password)
{
    secureStringPassword.AppendChar(ch);
}

ctx.Credentials = new SharePointOnlineCredentials("<<your login>>", secureStringPassword);

Then you have to query a list of site and web features from your Office 365 site instance and add a new feature with the correct scope.

It should be something like this:

ctx.Load(ctx.Site.Features, f => f.Include(
    _ => _.DefinitionId,
    _ => _.DisplayName));

ctx.ExecuteQuery();

var siteFeature = ctx.Site.Features.Cast<Feature>().FirstOrDefault(_ => _.DefinitionId == SiteFeatureId);
if (siteFeature != null)
{
    return;
}

ctx.Site.Features.Add(SiteFeatureId, true, FeatureDefinitionScope.Site);
ctx.ExecuteQuery();

But this code will throw an exception:

Feature with Id 'f6924d36-2fa8-4f0b-b16d-06b7250180fa' is not installed in this farm, and cannot be added to this scope.

Unfortunately, you can't trust SharePoint all the time, in this case you have to use FeatureDefinitionScope.None.

ctx.Site.Features.Add(SiteFeatureId, true, FeatureDefinitionScope.None);

After this small change, everything have to work as expected.

To activate a web-scoped feature we need to do a simmilar thing:

ctx.Load(ctx.Web.Features, f => f.Include(
    _ => _.DefinitionId,
    _ => _.DisplayName));

ctx.ExecuteQuery();

var webFeature = ctx.Web.Features.Cast<Feature>().FirstOrDefault(_ => _.DefinitionId == WebFeatureId);
if (webFeature != null)
{
    return;
}

ctx.Web.Features.Add(WebFeatureId, true, FeatureDefinitionScope.Web);
ctx.ExecuteQuery();

This code will thow the following exception:

Feature with Id '94c94ca6-b32f-4da9-a9e3-1f3d343d7ecb' is not installed in this farm, and cannot be added to this scope.

Now, when we know about the problem with FeatureDefinitionScope we can try to pass different values and see which one works. I found that the following piece of code will do the job:

ctx.Web.Features.Add(WebFeatureId, true, FeatureDefinitionScope.Web); 

Summary

There are lots of strange things inside SharePoint, sometimes you have to experiment to get things done. So when you see something like:

Feature with Id '' is not installed in this farm, and cannot be added to this scope.

Just try different values for FeatureDefinitionScope.


;