Materiales de mi sesión en el pasado SharePoint Saturday Madrid

El pasado 7 de Mayo tuvo lugar en Madrid el SharePoint Saturday. Un extraordinario evento organizado por las distintas comunidades de SharePoint de nuestro país, y que fue todo un éxito, como demostraron las mas de 170 personas que acudieron.

Tuve la suerte de participar como speaker, hablando del framework del PnP para SharePoint, y que os recomiendo encarecidamente.

Os dejo en mi GitHub los materiales de la sesion, tanto el PPT como las demos que hice durante la sesion.

Para acabar, agredecer de nuevo a los patrocinadores, al equipo organizador… ah! y a mi empresa ClearPeople, por ayudarme a participar.


[Office 365] Managing Search Settings using CSOM

This article will show you how you can manage the different search settings using CSOM. It will configure the search settings in the same way that you can do from the “Search settings” option in the Site settings:


Note that, obviously, the Search Settings that will apply, are the ones defined at Web level. The Site collection level only applies if in the Web level you are selecting: “Use the same results page settings as my parent.”, and actually, what it does is to copy the values to the Web site level.

This is how the Search settings page looks:


The “Configure Search Navigation” option is only available from Web Search settings, and that means you cannot inherit search navigation nodes (as far as I’ve seen). In order to add new Navigation links, the best option is to use the Pnp Core project, and use a handy extension for the Web object, so you can do something like:

   1: web.AddNavigationNode(

   2:     "News Relative", 

   3:     new Uri("/sites/search/Pages/NewsResults.aspx", UriKind.Relative), 

   4:     string.Empty, OfficeDevPnP.Core.Enums.NavigationType.SearchNav, 

   5:     true);

Note that if the page is not in the same Site collection, you have to set the “isExternal” param to true, or you will get an exception when saving changes.

For the other settings, there are no specific CSOM code to configure them. However, SharePoint saves that information in some PropertyBags, so we just need to Update that PropertyBags values with the right information. Here are the PropertyBags that SharePoint uses:

At Site collection search settings level:


Remember that these settings doesn’t apply to the RootWeb. They are only used to copy these values when a subsite is created and it inherits the settings from their parent.

At Web search settings level:


Considering this, all you have to do to configure Search Settings from CSOM, is to set the property bag value, and again, we can use another Web extension from Pnp Core project:

   1: web.SetPropertyBagValue(

   2:     "SRCH_SB_SET_WEB", 

   3:     @"

   4:         {

   5:             ""Inherit"" : false,

   6:             ""ResultsPageAddress"" : ""sites/docssearch/pages/results.aspx"",

   7:             ""ShowNavigation"" : true

   8:         }

   9:     "

  10:     );

Finally, keep in mind that all these settings apply mainly to the Default search text box control, as you can override some of them from the Search box web part:


[Office 365] Updating user profile from CSOM updates only some properties


Since this update ( on SharePoint client libraries, we can update User Profile properties using CSOM (prior to that, you could use the “old” user profile web service). However, under some circumstances, you can see that some of the properties have been updated, and some others have not.

You are using some code like this to create the PeopleManager

   1: this.peopleManager = new PeopleManager(this.SharepointClientContext);

Then, you have multiple calls to SetSingleValueProfileProperty to update different properties

   1: this.peopleManager.SetSingleValueProfileProperty(

   2:                                 accountName, 

   3:                                 userProfilePropertyName, 

   4:                                 propertyValue);

And finally, you commit the changes to SharePoint:

   1: this.SharepointClientContext.ExecuteQuery();


It seems that when you are trying to update different properties and you do a final ExecuteQuery, the SharePoint client library actually splits the update query into different requests to the client.svc/ProcessQuery service. I don’t know the exact number of properties where the Client library split into 2 or more requests, but in my case, I was trying to update 18 user profile properties, and considering the first request had 11 properties, seems quite clear that it will split the query if you are updating more than 11 properties in a single ExecuteQuery

I realised about that using the magic from Fiddler. Image below shows the 2 requests, after only one ExecuteQuery call:


Each request sent some XML in the body request like:


So, in my case, the first request was failing because one user profile property was not created in the SharePoint tenant, and I was able to see the error in the response body:


However, as the second request worked fine (all the properties in the body xml existed and the new value was right), the Client library is not raising an exception, so, the code worked fine, but the result was that some properties (the ones included in the first request) were not updated, and some others (second request) were updated.

In my opinion, this behaviour is kind of bug on the Client component, as it should raise an Exception if any partial request fails (or at least, warn it somehow)

Luis Manez


Using the OOB Taxonomy picker control just from JavaScript code in SharePoint Online

In this blog post I’m gonna show you how to use the OOB Taxonomy picker control without any server side control or code, just using JavaScript.

As you probably know, the Taxonomy picker control is the TaxonomyWebTaggingControl class, that looks like:

image image

However, all the core functionality of this control is actually done by JavaScript so, in some cases, can be useful how to reuse all of this functionality without any server side stuff, for example, imagine you can add a Taxonomy picker control using a Script editor web part, letting the user pick a Term and then execute some custom JS code.

If someone is curious, I got all this knowledge with some reflection from the TaxonomyWebTaggingControl class, and some hours reading a lot of JS code that SharePoint generates when you work with Taxonomy fields (no magic Smile)

First thing you need to know is that almos all of the Taxonomy picker control functionality is inside the Script “scriptforwebtaggingui.js”, and if you are working with Taxonomy, likely you’ll also need the file “sp.taxonomy.js”, so, ensure you have a reference to this files an option to this could be using the ScriptLink, if you can edit the MasterPage:

   1: <SharePoint:ScriptLink ID="ScriptLink2" name="scriptforwebtaggingui.js" OnDemand="true" runat="server" Localizable="false" />

   2: <SharePoint:ScriptLink ID="ScriptLink1" name="sp.taxonomy.js" OnDemand="true" runat="server" Localizable="false" />    

or, you can use the SP.SOD framework. Here is the JS code generated by the first ScriptLink above:

   1: RegisterSod("scriptforwebtaggingui.js", "https:u002fu002fcdn.sharepointonline.comu002f10477u002f_layoutsu002f15u002f16.0.2930.1217u002fscriptforwebtaggingui.js");

   2: RegisterSodDep("scriptforwebtaggingui.js", "sp.js");

   3: RegisterSodDep("scriptforwebtaggingui.js", "sp.ui.rte.js");

   4: RegisterSodDep("scriptforwebtaggingui.js", "scriptresources.resx");

From HTML code, the Taxonomy picker control needs 2 html tags:

  • One tag is a hidden tag, and will save the selected terms in the format: Label|Term_GUID;OtherLabel|TermGUID…
  • The other tag is a DIV container, where JS code will inject the control, the show picker image, autocomplete options, and so on
   1: <input name="countryTaxControl" type="hidden" id="countryTaxControl">

   2: <div id="countryTaxControlContent" class="ms-taxonomy ms-taxonomy-height ms-taxonomy-width"></div>  

Now, you need to Initialize the control from more JavaScript code. As you likely will need the Taxonomy JS code and a SharePoint context, this is a good way to ensure all the JS files needed are loaded first:

   1: SP.SOD.executeFunc('sp.js', 'SP.ClientContext', function () {

   2:     SP.SOD.executeFunc('sp.taxonomy.js', 'SP.Taxonomy.TaxonomySession', init);

   3: });

Then, in the Init function, let’s initialize the Taxonomy picker control:

   1: var tagUI = document.getElementById(taxControlContentId);

   2: if (tagUI) {

   3:     tagUI['InputFieldId'] = taxControlInputId;

   4:     tagUI['SspId'] = sspId;

   5:     tagUI['GroupId'] = '00000000-0000-0000-0000-000000000000';

   6:     tagUI['TermSetId'] = termSetId;

   7:     tagUI['AnchorId'] = '00000000-0000-0000-0000-000000000000';

   8:     tagUI['IsMulti'] = isMulti;

   9:     tagUI['AllowFillIn'] = false;

  10:     tagUI['WidthCSS'] = 'ms-taxonomy-width';

  11:     tagUI['JavascriptOnValidation'] = "";

  12:     tagUI['Lcid'] = 1033;

  13:     tagUI['IsSpanTermSets'] = false;

  14:     tagUI['IsSpanTermStores'] = false;

  15:     tagUI['IsIgnoreFormatting'] = false;

  16:     tagUI['IsIncludeDeprecated'] = false;

  17:     tagUI['IsIncludeUnavailable'] = false;

  18:     tagUI['IsIncludeTermSetName'] = false;

  19:     tagUI['IsAddTerms'] = false;

  20:     tagUI['IsIncludePathData'] = false;

  21:     tagUI['IsUseCommaAsDelimiter'] = true;

  22:     tagUI['Disable'] = false;

  23:     tagUI['ExcludeKeyword'] = false;

  24:     tagUI['WebServiceUrl'] = _spPageContextInfo.webServerRelativeUrl + 'u002f_vti_binu002fTaxonomyInternalService.json';

  25:     tagUI['FieldName'] = fieldName;

  26:     tagUI['FieldId'] = '00000000-0000-0000-0000-000000000000';

  27:     tagUI['DisplayPickerButton'] = showPickerButton;

  28:     var _emmTaggingLoadCall;

  29:     if (_emmTaggingLoadCall == null) {

  30:         _emmTaggingLoadCall = true;

  31:         SP.SOD.executeFunc('ScriptForWebTaggingUI.js', 'Microsoft.SharePoint.Taxonomy.ScriptForWebTaggingUI.taggingLoad',

  32:             function () {

  33:                 Microsoft.SharePoint.Taxonomy.ScriptForWebTaggingUI.resetEventsRegistered();

  34:             });

  35:     }

  36:     SP.SOD.executeFunc('ScriptForWebTaggingUI.js', 'Microsoft.SharePoint.Taxonomy.ScriptForWebTaggingUI.onLoad',

  37:         function () {

  38:             Microsoft.SharePoint.Taxonomy.ScriptForWebTaggingUI.onLoad(taxControlContentId);

  39:         });

  40: }

Most of the params are self-explained, and also are the same params in the TaxonomyWebTaggingControl class. Interesting params are:

  • taxControlContentId: this is the ID from the DIV container in the HTML I showed you above.
  • InputFieldId: ID of the Hidden control.
  • SspId: This is the TermStore ID. I’ll show you later how to get that ID from more JS code.
  • termSetId: Term set ID that you want to bind the control. You can get it using sp.taxonomy.js, for example, get the ID from the TermSet name.

Here is the code to get the Term Store ID (SspId):

   1: var context = new SP.ClientContext.get_current();

   2: var session = SP.Taxonomy.TaxonomySession.getTaxonomySession(context);

   3: var termStore = session.getDefaultSiteCollectionTermStore();        


   5: context.load(session);

   6: context.load(termStore);


   8: context.executeQueryAsync(

   9:     function () {

  10:         sspId = termStore.get_id();

  11:         sspId = sspId.toString();

  12:         //configure controls ...

  13:     },

  14:     function () {

  15:         console.log("Unable to access Managed Metadata Service.")

  16:     }

  17: );

Finally, let’s say you wanna set a value before loading the control, for example, you get the Field value or a User profile Taxonomy property, and you want to set the control with that value. In this case, you just have to set the Hidden control value, like:

   1: $("#regionTaxControl").val("SharePoint|123413456-aaaa-aa11-aa22-123456789012;Javascript|123413456-aaaa-aa11-aa22-123456789012");

You have to do that before configuring the control, before calling the:

   1: Microsoft.SharePoint.Taxonomy.ScriptForWebTaggingUI.onLoad(taxControlContentId);

and that’s all ! you will get all the Taxonomy picker functionality and styling (obviously, you have to manage what you wanna do with the selected values, the loading and saving functionality is not done by the control)

Last note

If you’re using SharePoint apps and want to use the Taxonomy picker, you can use the code from AMS Office code, where Richard diZerega has made a great job creating a JS control with almost same functionality that the OOB Taxonomy picker control.

Also, that code can be used inside SharePoint, as is using just jQuery and SP JSOM. I did it and worked fine. However, that control has some lacks comparing with the OOB, like for example, you can’t use the keyboard to select the Terms, only the mouse (users are use to type, use the arrows, and enter tab to select a Term). This can’t be done with the code sample (unless I did some wront, didn’t worked that way to me). Of course, this is some sample code, and you have the entire source to adapt it to your needs.

Hope it helps!

Luis Manez

Adding Windows Azure Active Directory to an existing ASP.NET app

If you want to secure your Azure ASP.NET app using Azure Active Directory, Visual Studio 2013 does a great job if you create a new one. Here you can find a good article about how to do it.

However, is not so easy and we don’t have too much help if we want to add Azure AD to an existing app. This post tries to help with that, basically, create a new project configured with Azure AD, and then follow the following steps using the new one as reference:

First you’ll need to add these packages to your project:

   1: <package id="EntityFramework" version="6.1.0" targetFramework="net45" />

   2: <package id="Microsoft.AspNet.Identity.Core" version="2.0.0" targetFramework="net45" />

   3: <package id="Microsoft.AspNet.Identity.EntityFramework" version="2.0.0" targetFramework="net45" />

   4: <package id="Microsoft.IdentityModel.Clients.ActiveDirectory" version="1.0.0" targetFramework="net45" />

   5: <package id="System.IdentityModel.Tokens.ValidatingIssuerNameRegistry" version="4.5.1" targetFramework="net45" />

Copy the Utils folder that contains the class DatabaseIssuerNameRegistry and the Models classes (see image below):


Note: You’ll need an SQL Database for the Authentication stuff. It seems that in recent versions, the identity providers are storing some info about tokens there. In this article from great Vittorio Bertocci there are more information:

Copy IdentityConfig.cs to App_Start


Copy AccountController


In the global.asax file:

Copy the method: WSFederationAuthenticationModule_RedirectingToIdentityProvider

In the Application_Start() add:


Copy the Account Views


Copy the Home/UserProfile view


Update the Home controller with the UserProfile action. You will see that you need to add some constants that are defined in the HomeController class

   1: [Authorize]        

   2: public async Task<ActionResult> UserProfile()

   3: {

   4:             string tenantId = ClaimsPrincipal.Current.FindFirst(TenantIdClaimType).Value;


   6:             // Get a token for calling the Windows Azure Active Directory Graph

   7:             AuthenticationContext authContext = new AuthenticationContext(String.Format(CultureInfo.InvariantCulture, LoginUrl, tenantId));

   8:             ClientCredential credential = new ClientCredential(AppPrincipalId, AppKey);

   9:             AuthenticationResult assertionCredential = authContext.AcquireToken(GraphUrl, credential);

  10:             string authHeader = assertionCredential.CreateAuthorizationHeader();

  11:             string requestUrl = String.Format(

  12:                 CultureInfo.InvariantCulture,

  13:                 GraphUserUrl,

  14:                 HttpUtility.UrlEncode(tenantId),

  15:                 HttpUtility.UrlEncode(User.Identity.Name));


  17:             HttpClient client = new HttpClient();

  18:             HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl);

  19:             request.Headers.TryAddWithoutValidation("Authorization", authHeader);

  20:             HttpResponseMessage response = await client.SendAsync(request);

  21:             string responseString = await response.Content.ReadAsStringAsync();

  22:             UserProfile profile = JsonConvert.DeserializeObject<UserProfile>(responseString);


  24:             return View(profile);

  25: }

Copy LoginPartial view


Add this code to _Layout.cshtml file


OK. Now you need to configure some bits in the Azure portal. Basically, what you need to do is adding permissions for your app in the Azure AD.

In Azure Management Portal, go to the Active Directory and Applications, and Add a new Application:




The sign on URL is where your app will go when you navigate to your app without being logged in. Usually will be you same home page (has to be SSL)

The App ID URI has to be your Azure Active Directory URL, followed for some app name or ID. You can see your Azure AD URL managing your Azure AD in the Domains section. If you are in the Azure tenant for Office 365, the AD URL will be the same Office 365 main URL:

Then, in the Configure page, you have to add a Key:


Save the config and you’ll see the Key value:


You also need to copy the Client ID


If you want to allow Read/Write Azure AD data, you need to configure permissions. If you have copy the HomeController as I told you before, the UserProfile action is trying to read the Active Directory, so, you need to give it permissions or you will get an error.


We’re done with the Azure management portal, now, grab the Client ID and Key value, and come back to the project, let’s update the web.config file with all the Identity providers and config.

In ConfigSections:

   1: <section name="system.identityModel" type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=, Culture=neutral, PublicKeyToken=B77A5C561934E089" />

   2: <section name="" type="System.IdentityModel.Services.Configuration.SystemIdentityModelServicesSection, System.IdentityModel.Services, Version=, Culture=neutral, PublicKeyToken=B77A5C561934E089" />

Add the ConnectionString. Here you can set a local DB value, and later, when you publish to Azure, Visual Studio will detect that you’re using a connection string, and will show you the assistant to connect to the SQL DB in Azure.

   1: <connectionStrings>

   2:   <add name="DefaultConnection" connectionString="Data Source=(LocalDb)v11.0;AttachDbFilename=|DataDirectory|aspnet-MyLocalDB-20140522115400.mdf;Initial Catalog=aspnet-MyLocalDB-20140522115400;Integrated Security=True" providerName="System.Data.SqlClient" />

   3: </connectionStrings>

Note: If you try to connect to Azure SQL DB from Visual Studio, you probably will need to configure the Allowed IP Addresses. You have more info in this article:

Add these AppSettings, review all the values that comes from the config you’ve done in Azure portal:

   1: <add key="ida:FederationMetadataLocation" value="" />

   2: <add key="ida:Realm" value="APP ID URI created in Azure portal" />

   3: <add key="ida:AudienceUri" value="APP ID URI created in Azure portal" />

   4: <add key="ida:ClientID" value="12431234212342123412341243" />

   5: <add key="ida:Password" value="AKSHFD8943DRKSH8HKFD49" />


   1: <location path="Account">

   2:   <system.web>

   3:     <authorization>

   4:       <allow users="*" />

   5:     </authorization>

   6:   </system.web>

   7: </location>

Adding in System.Web


Add (again, review all the values)

   1: <system.identityModel>

   2:   <identityConfiguration>

   3:     <issuerNameRegistry type="YOUR-PROJECT-NAMESPACE.Utils.DatabaseIssuerNameRegistry, NAMESPACE" />

   4:     <audienceUris>

   5:       <add value="SAME APP ID URI" />

   6:     </audienceUris>

   7:     <securityTokenHandlers>

   8:       <add type="System.IdentityModel.Services.Tokens.MachineKeySessionSecurityTokenHandler, System.IdentityModel.Services, Version=, Culture=neutral, PublicKeyToken=b77a5c561934e089" />

   9:       <remove type="System.IdentityModel.Tokens.SessionSecurityTokenHandler, System.IdentityModel, Version=, Culture=neutral, PublicKeyToken=b77a5c561934e089" />

  10:     </securityTokenHandlers>

  11:     <certificateValidation certificateValidationMode="None" />

  12:   </identityConfiguration>

  13: </system.identityModel>

   1: <system.webServer>

   2:   <modules>

   3:     <add name="WSFederationAuthenticationModule" type="System.IdentityModel.Services.WSFederationAuthenticationModule, System.IdentityModel.Services, Version=, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler" />

   4:     <add name="SessionAuthenticationModule" type="System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="managedHandler" />

   5:   </modules>

   6: </system.webServer>

   1: <>

   2:   <federationConfiguration>

   3:     <cookieHandler requireSsl="true" />

   4:     <wsFederation passiveRedirectEnabled="true" issuer=" AD URL/wsfed" realm="APP ID URI" requireHttps="true" />

   5:   </federationConfiguration>

   6: </>

And that’s all !! remember to take care of all the URLs and IDs, but if you publish to Azure, you should get the Azure AD login page.

Extra ball !!

OK. You’ve done that, however, when you go to your home page, you find a redirect to the “old style” Forms authentication Login.aspx page… WTH !!

No panic, you’re not gonna believe the reason, but ensure you don’t have the WebMatrix.Data.dll in your BIN folder… yes! it’s true


I found the solution in this post:

Take care because you can remove the DLL from your project, but if it’s already in your Azure website, I suggest that you browse the BIN folder in the Azure site using Visual Studio Server explorer to ensure the DLL is not there (remove it manually if needed).

Hope it helps!!

Luis Manez


[Office 365] Selecting webpart title from Display Template

Let’s say we have a Search Results webpart showing latest 5 news with a wonderful Webpart title saying “News”. It would be pretty interesting that we can click on the webpart title and jump to a custom page that shows all the News article.

OK, SharePoint does a great job with Display templates, where you can control the chrome of all the results and each item, however the webpart title is not rendered directly by the Display template engine, so, if we want to modify the Webpart Title we’d need some jQuery magic from the Control display template.


We can use our custom Control Display template, and inside, we can select the Webpart title using jQuery. The selector we need looks like that:

   1: //

   2: var MainWebPartSelector = "#" + ctx.ClientControl.get_id();

   3: var titleElement = $($(MainWebPartSelector).parent().siblings().find(" span")[0]);

   4: //

That way, adding more jQuery we can replace the titleElement with all the html needed, like a link to the News page.

Hope it helps!

Luis Manez


European SharePoint Conference – I was there (and took some notes)


Just arrived from the European SharePoint Conference 14 in Barcelona. This has been my first SP European event and if I have to summarized in one word, would be “amazing”. First I’ve been able to chat again with some of the Spanish SharePointers that I din´t see for a while, and second, cos I could meet in person, some of the European SP rock stars!

The organisation has been quite good, the quality of the speakers, really good, and the sessions quite good, although we expected more in some sessions, that didn’t´t include the last updates in their demos.

About the content, no new secrets, more on Office 365, more on Yammer, and more Apps, apps, apps. I´ve been pretty impressive that most of the demos were done using iPads, just highlighting the importance of the multi-device and mobile first for Microsoft.

Most of the speakers have available their presentations in their blogs, so, you can take a look to the sessions in the conference site, and look for their blogs.

I also found interesting that Microsoft is listening us. If you wanna say something about Office 365, this is the place:

Finally, just say thanks to my company, Content and Code, for let me attend the event, and last mention for my fellow Chris O’Brien, that was included, again, between the Top 25 European SharePoint Influencers. Congrats mate!!


[Office 365] Enable / Disable Feature from Power Shell using CSOM

Here we have 2 PowerShell scripts to Enable-Disable a feature using CSOM

Enable Feature
   1: ##########################################################################################

   2: #    Name:            Enable-SPOFeature

   3: #    Description:    This script enables a feature using CSOM

   4: #    Usage:            .Enable-SPOFeature -User "" -Password "Password" -Url "" -Feature "4aec7207-0d02-4f4f-aa07-b370199cd0c7" -Scope Site -Sandbox $true -Force $true

   5: #    Creator:        Luis Manez

   6: ##########################################################################################


   8: param([string]$user, [string]$password, [string]$url, [string]$feature, [ValidateSet("Site","Web")][string]$scope, [bool]$sandbox, [bool]$force)


  10: Add-Type -Path "c:Program FilesCommon Filesmicrosoft sharedWeb Server Extensions15ISAPIMicrosoft.SharePoint.Client.dll" 


  12: if ($user -eq "") {

  13:     Write-Host "User parameter not provided, exiting" -ForegroundColor Red

  14:     exit 1;

  15: }

  16: if ($password -eq "") {

  17:     Write-Host "Password parameter not provided, exiting" -ForegroundColor Red

  18:     exit 1;

  19: }

  20: if ($url -eq "") {

  21:     Write-Host "Url parameter not provided, exiting" -ForegroundColor Red

  22:     exit 1;

  23: }

  24: if ($feature -eq "") {

  25:     Write-Host "Feature parameter not provided, exiting" -ForegroundColor Red

  26:     exit 1;

  27: }

  28: if ($scope -eq "") {

  29:     Write-Host "Scope parameter not provided, exiting" -ForegroundColor Red

  30:     exit 1;

  31: }

  32: if ($sandbox -eq $null) {

  33:     Write-Host "Sanbox parameter not provided, exiting" -ForegroundColor Red

  34:     exit 1;

  35: }

  36: if ($force -eq $null) {

  37:     $force = $false;

  38: }


  40: function GetClientContext($url, $user, $password) {


  42:     $securePassword = ConvertTo-SecureString $password -AsPlainText -Force


  44:     $context = New-Object Microsoft.SharePoint.Client.ClientContext($url) 

  45:     $credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($user, $securePassword) 

  46:     $context.Credentials = $credentials


  48:     return $context

  49: }


  51: # MAIN CODE

  52: #


  54: $featureId = [GUID]($feature)

  55: $clientContext = GetClientContext $url $user $password

  56: write-host "Conected to SharePoint OK"


  58: if ($scope.ToLower() -eq "web") {

  59:         $features = $clientContext.Web.Features

  60: }

  61: else {

  62:     $features = $clientContext.Site.Features

  63: }


  65: $featureDefinitionScope = [Microsoft.SharePoint.Client.FeatureDefinitionScope]::Farm

  66: if ($sandbox) {

  67:     $featureDefinitionScope = [Microsoft.SharePoint.Client.FeatureDefinitionScope]::Site

  68: }


  70: $clientContext.Load($features)

  71: $clientContext.ExecuteQuery()



  74: $features.Add($featureId, $force, $featureDefinitionScope)

  75: try {

  76:     $clientContext.ExecuteQuery()

  77:     write-host "Feature activated"

  78: }

  79: catch {

  80:     write-host "An error ocurred activating Feature. Error detail: $($_)"

  81: }

  • User: user in the Office 365 tenant
  • Password: user password
  • Url: Full URL to the site or web
  • Scope: Site / Web. Scope of the feature that we want to activate.
  • Feature: Feature Id.
  • Force: Re-enable feature if is already activated
  • Sandbox: true/false. Basically this param indicates if the solution where the feature lives, is a Farm solution, or a Sandbox solution.

About the Sandbox param. This is a little confuse if we take a look to the MSDN documentation

If we look at the FeatureDefinitionScope enum, we have:

   1: public enum FeatureDefinitionScope

   2: {

   3:     None,

   4:     Farm,

   5:     Site,

   6:     Web

   7: }

However, the documentation in the Add method says that you can only use 2 values: Farm and Site. This param can make you be wrong, because you can think that is the Scope of the feature, so, if you want to activate a Web scope feature, you’d try with the Web value in the enum type. However, this param indicates the scope of the Solution that contains the feature: Farm or Sandbox solution, so, the possible values are only Farm and Site.

Microsoft, Why are you using an Enum with 4 values in a function that only accept 2 possible values from the Enum ??? OK… that’s another question…

Let’s see now how we can use the same approach to Disable a Feature.

Disable Feature
   1: ##########################################################################################

   2: #    Name:            Disable-SPOFeature

   3: #    Description:    This script disables a feature using CSOM

   4: #    Usage:            .Disable-SPOFeature -User "" -Password "Password" -Url "" -Feature "4aec7207-0d02-4f4f-aa07-b370199cd0c7" -Scope Site

   5: #    Creator:        Luis Manez

   6: ##########################################################################################


   8: param([string]$user, [string]$password, [string]$url, [string]$feature, [ValidateSet("Site","Web")][string]$scope)


  10: Add-Type -Path "c:Program FilesCommon Filesmicrosoft sharedWeb Server Extensions15ISAPIMicrosoft.SharePoint.Client.dll" 


  12: if ($user -eq "") {

  13:     Write-Host "User parameter not provided, exiting" -ForegroundColor Red

  14:     exit 1;

  15: }

  16: if ($password -eq "") {

  17:     Write-Host "Password parameter not provided, exiting" -ForegroundColor Red

  18:     exit 1;

  19: }

  20: if ($url -eq "") {

  21:     Write-Host "Url parameter not provided, exiting" -ForegroundColor Red

  22:     exit 1;

  23: }

  24: if ($feature -eq "") {

  25:     Write-Host "Feature parameter not provided, exiting" -ForegroundColor Red

  26:     exit 1;

  27: }

  28: if ($scope -eq "") {

  29:     Write-Host "Scope parameter not provided, exiting" -ForegroundColor Red

  30:     exit 1;

  31: }


  33: function GetClientContext($url, $user, $password) {


  35:     $securePassword = ConvertTo-SecureString $password -AsPlainText -Force


  37:     $context = New-Object Microsoft.SharePoint.Client.ClientContext($url) 

  38:     $credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($user, $securePassword) 

  39:     $context.Credentials = $credentials


  41:     return $context

  42: }


  44: # MAIN CODE

  45: #


  47: $featureId = [GUID]($feature)

  48: $clientContext = GetClientContext $url $user $password

  49: write-host "Conected to SharePoint OK"


  51: if ($scope.ToLower() -eq "web") {

  52:         $features = $clientContext.Web.Features

  53: }

  54: else {

  55:     $features = $clientContext.Site.Features

  56: }


  58: $featureDefinitionScope = [Microsoft.SharePoint.Client.FeatureDefinitionScope]::Farm

  59: if ($sandbox) {

  60:     $featureDefinitionScope = [Microsoft.SharePoint.Client.FeatureDefinitionScope]::Site

  61: }


  63: $clientContext.Load($features)

  64: $clientContext.ExecuteQuery()



  67: $features.Remove($featureId, $false)

  68: try {

  69:     $clientContext.ExecuteQuery()

  70:     write-host "Feature deactivated"

  71: }

  72: catch {

  73:     write-host "An error ocurred deactivating Feature. Error detail: $($_)"

  74: }

That’s all, hope it helps!!

Luis Manez


[Office 365] Using image renditions from Display Templates in SharePoint Online 2013

In this post I will show you how to use image renditions from a custom Display template. If you are new with Display temlates, I suggest you to read the following article from Chris O’Brien, and if you are starting with image renditions, this article from Waldek Mastykarz is perfect.

In this sample, I’m using a Search results webpart (not a CBSWP, that is not available on SharePoint Online), with a custom display template. The display template is configured with this Managed properties:

   1: ctx['DisplayTemplateData']['ManagedPropertyMapping'] = { 'Title': ['Title'], 

   2:     'Path': ['Path'], 'Description': ['Description'], 

   3:     'EditorOWSUSER': ['EditorOWSUSER'], 

   4:     'LastModifiedTime': ['LastModifiedTime'], 

   5:     'CollapsingStatus': ['CollapsingStatus'], 

   6:     'DocId': ['DocId'], 'HitHighlightedSummary': ['HitHighlightedSummary'], 

   7:     'HitHighlightedProperties': ['HitHighlightedProperties'], 

   8:     'FileExtension': ['FileExtension'], 

   9:     'ViewsLifeTime': ['ViewsLifeTime'], 'ParentLink': ['ParentLink'], 

  10:     'DisplayAuthor': ['DisplayAuthor'], 

  11:     'Picture URL': ['PublishingImage', 'PublishingRollupImage', 'Rollup Image'], 

  12:     'ArticleByLineOWSTEXT': ['ArticleByLineOWSTEXT'] };

If we want to get the value of a Managed property on the display template, we can do something like that (of course, the property has to be set on the display template first):

   1: ctx.CurrentItem.Path

or even something like that:

   1: ctx.CurrentItem["Path"]

but it’s a better option to use the following function:

   1: $getItemValue(ctx, "Picture URL")

with this function, we are getting a Srch.valueInfo object, that contains some interesting properties, as we can see in the next image:


  • value: the value of the property
  • isEmpty and isNull: quite useful to check if the property has a value
  • propertylookupName: this is the name of the slot that we are using to store the Managed property.
  • propertyMappings: and array with all the managed properties that we are including in the slot. As we can on the ManagedPropertyMapping array defined on the display template, the slot “Picture URL”, is mapped with 3 managed properties.
  • managedPropertyName: this is the managed property source of the value. In this case, we are mapping the slot with 3 properties, and the value is coming from the “PublishingImage” property.

with this object, we can use a SharePoint JS function to get the image URL with the rendition that we want to apply:

   1: var image = $getItemValue(ctx, "Picture URL");

   2: var imageUrlWithRendition = Srch.U.getImageSourceWithRendition(image, 190, 120)

This code will return the image URL with the width and height parameters:


Unfortunately, the SharePoint JSOM is almost undocumented on the MSDN, so, to find out this function and much more, I suggest you to take a look to all the OOB display templates, and use the Developer tools in Chrome to debug the client code and test the results of some functions.

With the value returned for the function, we can compose other IMG tag, and the result will be an image using the rendition that we want: 190×120.

Also, when you are working with display template, you will find useful to use the Diagnostic display template, which shows you all the managed properties used in the template, with its value and other information. This template is only available for the Content By Search webpart, but with a couple of changes, you can use the template in the Search Resutls webpart if you are working with SharePoint Online. This is the diagnostic template code for the Search Results WP

   1: function ULScqk(){var o=new Object;o.ULSTeamName="Search Server";o.ULSFileName="Item_Diagnostic.js";return o;}

   2: function DisplayTemplate_6be7c3604a7e4519a210e962b4ed867c(ctx) {ULScqk:;

   3:   var ms_outHtml=[];

   4:   var cachePreviousTemplateData = ctx['DisplayTemplateData'];

   5:   ctx['DisplayTemplateData'] = new Object();

   6:   DisplayTemplate_6be7c3604a7e4519a210e962b4ed867c.DisplayTemplateData = ctx['DisplayTemplateData'];


   8:   ctx['DisplayTemplateData']['TemplateUrl']='~sitecollectionu002f_catalogsu002fmasterpageu002fDisplay Templatesu002fSearchu002fItem_Diagnostic.js';

   9:   ctx['DisplayTemplateData']['TemplateType']='Item';

  10:   ctx['DisplayTemplateData']['TargetControlType']=['Content Web Parts'];

  11:   this.DisplayTemplateData = ctx['DisplayTemplateData'];


  13:   ctx['DisplayTemplateData']['ManagedPropertyMapping']={'Picture URL':['PublishingImage', 'PictureURL', 'PictureThumbnailURL'], 'Path':null, 'SecondaryFileExtension':null, 'ContentTypeId':null, 'Line 1':['Title'], 'Line 2':['Description'], 'Line 3':['ArticleByLineOWSTEXT'], 'Line 4':[], 'Line 5':[], 'Line 6':[], 'Line 7':[], 'Line 8':[], 'Line 9':[], 'Line 10':[]};

  14:   var cachePreviousItemValuesFunction = ctx['ItemValues'];

  15:   ctx['ItemValues'] = function(slotOrPropName) {ULScqk:;

  16:     return Srch.ValueInfo.getCachedCtxItemValue(ctx, slotOrPropName)

  17: };


  19: ms_outHtml.push('',''

  20: );

  21: var encodedId = $htmlEncode(ctx.ClientControl.get_nextUniqueId() + "_Diagnostic_");


  23: var linkURL = $getItemValue(ctx, "Path");

  24: linkURL.overrideValueRenderer($urlHtmlEncode);


  26: var line1 = $getItemValue(ctx, "Line 1");

  27: var pictureURL = $getItemValue(ctx, "Picture URL");

  28: pictureURL.overrideValueRenderer($urlHtmlEncode);

  29: var pictureId = encodedId + "picture";

  30: var pictureMarkup = pictureURL //Srch.ContentBySearch.getPictureMarkup(pictureURL, 100, 100, ctx.CurrentItem, "cbs-picture3LinesImg", line1, pictureId);


  32: window.cbsDiagnostic_RenderPropertyMappings = function(valueInfoObj)

  33: {ULScqk:;

  34:     var combinedManagedPropertiesMapping = "";

  35:     if(!$isNull(valueInfoObj) && !$isNull(valueInfoObj.propertyMappings) && !$isNull(valueInfoObj.propertyMappings.length))

  36:     {

  37:         for (var i = 0; i < valueInfoObj.propertyMappings.length; i++)

  38:         {

  39:             var managedPropertyName = valueInfoObj.propertyMappings[i];

  40:             combinedManagedPropertiesMapping += i == 0 ? managedPropertyName : String.format(Srch.Res.edisc_MultiValueFormat, managedPropertyName);

  41:         }

  42:     }

  43:     return $htmlEncode(combinedManagedPropertiesMapping);

  44: }


  46: var itemContainerTitle = null;

  47: var canBuildItemContainerTitle = !$isNull(ctx.CurrentItemIdx) && !$isNull(ctx.CurrentGroup) && 

  48:     !$isNull(ctx.CurrentGroup.ResultRows) && !$isNull(ctx.CurrentGroup.ResultRows.length) &&

  49:     !isNaN(ctx.CurrentGroup.ResultRows.length) && !isNaN(ctx.CurrentItemIdx);

  50: if(canBuildItemContainerTitle)

  51: {

  52:     itemContainerTitle = String.format($resource("item_Diagnostic_ItemTitleFormat"), ctx.CurrentItemIdx + 1, ctx.CurrentGroup.ResultRows.length);

  53: }


  55: var containerId = encodedId + "container";

  56: var pictureSlotContainerId = encodedId + "pictureSlotContainer";

  57: var pictureContainerId = encodedId + "pictureContainer";

  58: var pictureLinkId = encodedId + "pictureLink";

  59: var pathContainerId = encodedId + "pathContainer";

  60: ms_outHtml.push(''

  61: ,'        <ul class="cbs-diagnostic-Container" id="', containerId ,'" data-displaytemplate="ItemDiagnostic">'

  62: ,'            <li>'

  63: );

  64: if(!$isEmptyString(itemContainerTitle))

  65: {

  66: ms_outHtml.push(''

  67: ,'                <h3 class="cbs-diagnostic-ContainerTitle">', $htmlEncode(itemContainerTitle) ,'</h3>'

  68: );

  69: }

  70: ms_outHtml.push(''

  71: ,'                <ul id="', pictureSlotContainerId ,'" class="cbs-diagnosticSlot">'

  72: ,'                    <li class="cbs-diagnosticSlotName">', $htmlEncode($resource("item_Diagnostic_PictureSlot")) ,'</li>'

  73: ,'                    <li class="cbs-diagnosticItemProperty">'

  74: ,'                        <ul class="cbs-diagnosticItemContainer">'

  75: ,'                            <li class="cbs-diagnosticItemName">', $htmlEncode($resource("item_Diagnostic_Value")) ,'</li>'

  76: );

  77: if(pictureURL.isEmpty)

  78: {

  79: ms_outHtml.push(''

  80: ,'                            <li class="cbs-diagnosticItemValue">&#160;</li>'

  81: );

  82: }

  83: else

  84: {

  85: ms_outHtml.push(''

  86: ,'                            <li class="cbs-diagnosticItemValue">', $htmlEncode(pictureURL.value) ,'</li>'

  87: );

  88: }

  89: ms_outHtml.push(''

  90: ,'                        </ul>'

  91: ,'                    </li>'

  92: ,'                    <li class="cbs-diagnosticItemProperty">'

  93: ,'                        <ul class="cbs-diagnosticItemContainer">'

  94: ,'                            <li class="cbs-diagnosticItemName">', $htmlEncode($resource("item_Diagnostic_Preview")) ,'</li>'

  95: ,'                            <li class="cbs-diagnosticItemValue">'

  96: ,'                                <div class="cbs-picture3LinesImageContainer" id="', pictureContainerId ,'">'

  97: ,'                                    <a class="cbs-pictureImgLink" href="', linkURL ,'" title="', line1 ,'" id="', pictureLinkId ,'">'

  98: ,'                                        ', pictureMarkup ,''

  99: ,'                                    </a>'

 100: ,'                                </div>'

 101: ,'                            </li>'

 102: ,'                        </ul>'

 103: ,'                    </li>'

 104: ,'                    <li class="cbs-diagnosticItemProperty">'

 105: ,'                        <ul class="cbs-diagnosticItemContainer">'

 106: ,'                            <li class="cbs-diagnosticItemName">', $htmlEncode($resource("item_Diagnostic_MappedManagedProperty")) ,'</li>'

 107: ,'                            <li class="cbs-diagnosticItemValue">', $htmlEncode(pictureURL.managedPropertyName) ,'</li>'

 108: ,'                        </ul>'

 109: ,'                    </li>'

 110: ,'                    <li class="cbs-diagnosticItemProperty">'

 111: ,'                        <ul class="cbs-diagnosticItemContainer">'

 112: ,'                            <li class="cbs-diagnosticItemName">', $htmlEncode($resource("item_Diagnostic_PropertyMappings")) ,'</li>'

 113: ,'                            <li class="cbs-diagnosticItemValue">', cbsDiagnostic_RenderPropertyMappings(pictureURL)  ,'</li>'

 114: ,'                        </ul>'

 115: ,'                    </li>'

 116: ,'                </ul>'

 117: ,'            </li>'

 118: ,'            <li>'

 119: ,'                <ul id="', pathContainerId ,'" class="cbs-diagnosticSlot">'

 120: ,'                    <li class="cbs-diagnosticSlotName">', $htmlEncode($resource("item_Diagnostic_PathSlot")) ,'</li>'

 121: ,'                    <li class="cbs-diagnosticItemProperty">'

 122: ,'                        <ul class="cbs-diagnosticItemContainer">'

 123: ,'                            <li class="cbs-diagnosticItemName">', $htmlEncode($resource("item_Diagnostic_Value")) ,'</li>'

 124: ,'                            <li class="cbs-diagnosticItemValue">', $htmlEncode(linkURL.value) ,'</li>'

 125: ,'                        </ul>'

 126: ,'                    </li>'

 127: ,'                </ul>'

 128: ,'            </li>'

 129: );

 130: for(var lineNum = 1; lineNum <= 10; lineNum++)

 131: {

 132:     var lineValueInfo = $getItemValue(ctx, String.format("Line {0}", lineNum));

 133:     if(!$isNull(lineValueInfo) && !$isEmptyString(cbsDiagnostic_RenderPropertyMappings(lineValueInfo)))

 134:     {

 135:         var lineId = String.format("{0}line{1}", encodedId, lineNum);

 136:         var slotName = String.format($resource("item_Diagnostic_SlotNameFormat"), lineNum);

 137: ms_outHtml.push(''

 138: ,'            <li>'

 139: ,'                <ul id="', lineId ,'" class="cbs-diagnosticSlot">'

 140: ,'                    <li class="cbs-diagnosticSlotName">', $htmlEncode(slotName) ,'</li>'

 141: ,'                    <li class="cbs-diagnosticItemProperty">'

 142: ,'                        <ul class="cbs-diagnosticItemContainer">'

 143: ,'                            <li class="cbs-diagnosticItemName">', $htmlEncode($resource("item_Diagnostic_Value")) ,'</li>'

 144: );

 145:         if(lineValueInfo.isEmpty)

 146:         {

 147: ms_outHtml.push(''

 148: ,'                            <li class="cbs-diagnosticItemValue">&#160;</li>'

 149: );

 150:         }

 151:         else

 152:         {

 153: ms_outHtml.push(''

 154: ,'                            <li class="cbs-diagnosticItemValue">', lineValueInfo ,'</li>'

 155: );

 156:         }

 157: ms_outHtml.push(''

 158: ,'                        </ul>'

 159: ,'                    </li>'

 160: ,'                    <li class="cbs-diagnosticItemProperty">'

 161: ,'                        <ul class="cbs-diagnosticItemContainer">'

 162: ,'                            <li class="cbs-diagnosticItemName">', $htmlEncode($resource("item_Diagnostic_MappedManagedProperty")) ,'</li>'

 163: ,'                            <li class="cbs-diagnosticItemValue">', $htmlEncode(lineValueInfo.managedPropertyName) ,'</li>'

 164: ,'                        </ul>'

 165: ,'                    </li>'

 166: ,'                    <li class="cbs-diagnosticItemProperty">'

 167: ,'                        <ul class="cbs-diagnosticItemContainer">'

 168: ,'                            <li class="cbs-diagnosticItemName">', $htmlEncode($resource("item_Diagnostic_PropertyMappings")) ,'</li>'

 169: ,'                            <li class="cbs-diagnosticItemValue">', cbsDiagnostic_RenderPropertyMappings(lineValueInfo)  ,'</li>'

 170: ,'                        </ul>'

 171: ,'                    </li>'

 172: ,'                </ul>'

 173: ,'            </li>'

 174: );

 175:     }

 176: }

 177: ms_outHtml.push(''

 178: ,'        </ul>'

 179: ,'    '

 180: );


 182:   ctx['ItemValues'] = cachePreviousItemValuesFunction;

 183:   ctx['DisplayTemplateData'] = cachePreviousTemplateData;

 184:   return ms_outHtml.join('');

 185: }

 186: function RegisterTemplate_6be7c3604a7e4519a210e962b4ed867c() {ULScqk:;


 188: if ("undefined" != typeof (Srch) &&"undefined" != typeof (Srch.U) &&typeof(Srch.U.registerRenderTemplateByName) == "function") {

 189:   Srch.U.registerRenderTemplateByName("Item_Diagnostic", DisplayTemplate_6be7c3604a7e4519a210e962b4ed867c);

 190: }


 192: if ("undefined" != typeof (Srch) &&"undefined" != typeof (Srch.U) &&typeof(Srch.U.registerRenderTemplateByName) == "function") {

 193:   Srch.U.registerRenderTemplateByName("~sitecollectionu002f_catalogsu002fmasterpageu002fDisplay Templatesu002fSearchu002fItem_Diagnostic.js", DisplayTemplate_6be7c3604a7e4519a210e962b4ed867c);

 194: }


 196:         $includeLanguageScript("~sitecollectionu002f_catalogsu002fmasterpageu002fDisplay Templatesu002fSearchu002fItem_Diagnostic.js", "~sitecollection/_catalogs/masterpage/Display Templates/Language Files/{Locale}/CustomStrings.js");


 198: }

 199: RegisterTemplate_6be7c3604a7e4519a210e962b4ed867c();

 200: if (typeof(RegisterModuleInit) == "function" && typeof(Srch.U.replaceUrlTokens) == "function") {

 201:   RegisterModuleInit(Srch.U.replaceUrlTokens("~sitecollectionu002f_catalogsu002fmasterpageu002fDisplay Templatesu002fSearchu002fItem_Diagnostic.js"), RegisterTemplate_6be7c3604a7e4519a210e962b4ed867c);

 202: }

And finally, if you are working with image renditions, you can provisioning your renditions using server side code, like Waldek shows in his article, and even tough I have not try it, it should work on sandbox, but also, you can do it declaratively, deploying the file “PublishingImageRenditions.xml” to the Master page gallery using a Module.

The “PublishingImageRenditions.xml” looks like that:

   1: <SiteImageRenditions xmlns="" xmlns:i="">

   2:   <NextId>6</NextId>

   3:   <Renditions>

   4:    <ImageRendition>

   5:     <Height>120</Height>

   6:     <Id>5</Id>

   7:     <Name>My custom rendition</Name>

   8:     <Version>1</Version>

   9:     <Width>190</Width>

  10:    </ImageRendition>

  11:     <ImageRendition>

  12:       <Height>100</Height>

  13:       <Id>1</Id>

  14:       <Name>Display Template Picture 3 Lines</Name>

  15:       <Version>1</Version>

  16:       <Width>100</Width>

  17:     </ImageRendition>

  18:     <ImageRendition>

  19:       <Height>100</Height>

  20:       <Id>2</Id>

  21:       <Name>Display Template Picture On Top</Name>

  22:       <Version>1</Version>

  23:       <Width>304</Width>

  24:     </ImageRendition>

  25:     <ImageRendition>

  26:       <Height>220</Height>

  27:       <Id>3</Id>

  28:       <Name>Display Template Large Picture</Name>

  29:       <Version>1</Version>

  30:       <Width>468</Width>

  31:     </ImageRendition>

  32:     <ImageRendition>

  33:       <Height>68</Height>

  34:       <Id>4</Id>

  35:       <Name>Display Template Video</Name>

  36:       <Version>1</Version>

  37:       <Width>120</Width>

  38:     </ImageRendition>

  39:   </Renditions>

  40: </SiteImageRenditions>

My advice is that you create your image renditions using the User Interface, and then download the file and add it to your Visual Studio Module. If you edit the file manually, take care with the nodeNextId” !!

The module for the xml file would be:

   1: <Elements xmlns="">

   2:   <Module Name="MO_ImageRenditions" Url="_catalogs/masterpage" RootWebOnly="TRUE">

   3:     <File Path="MO_ImageRenditionsPublishingImageRenditions.xml" Url="PublishingImageRenditions.xml" Type="GhostableInLibrary" Level="Published" ReplaceContent="true">

   4:       <Property Name="ContentTypeId" Value="0x01010012BCF119622FF14793A8A38D5831F25C" />

   5:       <Property Name="ContentType" Value="Document" />

   6:     </File>

   7:   </Module>

   8: </Elements>

And that’s all, with this, your display template will show an image using the desired rendition.

Hope it helps, and let me know any comment.

Luis Manez


Deploying Managed Metadata Fields declaratively in SharePoint 2013 Online (Office 365)

In this article, we will see how we can deploy Managed metadata fields in a declarative way in SharePoint online, showing a new and different approach than in Farm solutions (the old way).

Basically this approach tries to cover the needed of deploying Managed metadata fields from a custom web template using a Sandbox solution. As you probably know, if you want to do this in a farm solution, you will find yourself following this post from Wictor Wilen ( This is perfect for a Farm solution, but is not a valid approach for Sandbox solutions and Office 365 because the Microsoft.Sharepoint.Taxonomy.dll is not allowed. Apparently, the only option that we have is to create the Managed Metadata fields using the UI (or maybe some others imaginative solutions with remote events and client code), but if our solution is a custom web template that will be used to create new site collections, that means that the user has to create all the Managed metadata fields for every site collection… sounds not good!

However, Wictor shows us some interesting things about the Schema of a Managed metadata field, that we also see using SharePoint Manager from a Managed metadata field created from the SharePoint UI.

Here is the complete Schema XML of a Managed metadata field created manually:

   1: <?xml version="1.0" encoding="utf-8" ?>

   2: <Field Type="TaxonomyFieldType" 

   3:        DisplayName="LMLRoleSingleValue" 

   4:        List="{81bbf7e0-763f-4e61-98bc-5fd2c7e9c69c}" 

   5:        WebId="4a38c107-bf70-48fc-87b0-ae101d14c970" 

   6:        ShowField="Term1033" 

   7:        Required="FALSE" 

   8:        EnforceUniqueValues="FALSE" 

   9:        Group="LML" 

  10:        ID="{7baffc59-449d-4743-883b-0b92c5bdd4d6}" 

  11:        SourceID="{4a38c107-bf70-48fc-87b0-ae101d14c970}" 

  12:        StaticName="LMLRoleSingleValue" 

  13:        Name="LMLRoleSingleValue" 

  14:        Version="1">

  15:   <Default></Default>

  16:   <Customization>

  17:     <ArrayOfProperty>

  18:       <Property>

  19:         <Name>SspId</Name>

  20:         <Value xmlns:q1="" p4:type="q1:string" xmlns:p4="">2567ccb0-68f2-46f6-8f4f-36b0bb507422</Value>

  21:       </Property>

  22:       <Property>

  23:         <Name>GroupId</Name>

  24:       </Property>

  25:       <Property>

  26:         <Name>TermSetId</Name>

  27:         <Value xmlns:q2="" p4:type="q2:string" xmlns:p4="">2cd1c0fb-88af-4066-a4fc-4c9734160eb5</Value>

  28:       </Property>

  29:       <Property>

  30:         <Name>AnchorId</Name>

  31:         <Value xmlns:q3="" p4:type="q3:string" xmlns:p4="">00000000-0000-0000-0000-000000000000</Value>

  32:       </Property>

  33:       <Property>

  34:         <Name>UserCreated</Name>

  35:         <Value xmlns:q4="" p4:type="q4:boolean" xmlns:p4="">false</Value>

  36:       </Property>

  37:       <Property>

  38:         <Name>Open</Name>

  39:         <Value xmlns:q5="" p4:type="q5:boolean" xmlns:p4="">false</Value>

  40:       </Property>

  41:       <Property>

  42:         <Name>TextField</Name>

  43:         <Value xmlns:q6="" p4:type="q6:string" xmlns:p4="">{0ea51395-dcf0-4740-aa9f-2ab8e93bb798}</Value>

  44:       </Property>

  45:       <Property>

  46:         <Name>IsPathRendered</Name>

  47:         <Value xmlns:q7="" p4:type="q7:boolean" xmlns:p4="">false</Value>

  48:       </Property>

  49:       <Property>

  50:         <Name>IsKeyword</Name>

  51:         <Value xmlns:q8="" p4:type="q8:boolean" xmlns:p4="">false</Value>

  52:       </Property>

  53:       <Property>

  54:         <Name>TargetTemplate</Name>

  55:       </Property>

  56:       <Property>

  57:         <Name>CreateValuesInEditForm</Name>

  58:         <Value xmlns:q9="" p4:type="q9:boolean" xmlns:p4="">false</Value>

  59:       </Property>

  60:       <Property>

  61:         <Name>FilterAssemblyStrongName</Name>

  62:         <Value xmlns:q10="" p4:type="q10:string" xmlns:p4="">Microsoft.SharePoint.Taxonomy, Version=, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Value>

  63:       </Property>

  64:       <Property>

  65:         <Name>FilterClassName</Name>

  66:         <Value xmlns:q11="" p4:type="q11:string" xmlns:p4="">Microsoft.SharePoint.Taxonomy.TaxonomyField</Value>

  67:       </Property>

  68:       <Property>

  69:         <Name>FilterMethodName</Name>

  70:         <Value xmlns:q12="" p4:type="q12:string" xmlns:p4="">GetFilteringHtml</Value>

  71:       </Property>

  72:       <Property>

  73:         <Name>FilterJavascriptProperty</Name>

  74:         <Value xmlns:q13="" p4:type="q13:string" xmlns:p4="">FilteringJavascript</Value>

  75:       </Property>

  76:     </ArrayOfProperty>

  77:   </Customization>

  78: </Field>

Let’s see the values that we need to focus on:






TaxonomyFieldType (field with a single value)/ TaxonomyFieldTypeMulti (field with multiple terms)


Name of the field


Static name (usually the same as name)


Display name that will show in the UI


Value setted by SP when deploys the field. We can skip this attribute


Value setted by SP when deploys the field. We can skip this attribute


Term1033 (Term + LCID that we are using)


Mandatory field


Only one list item with this value in the field?


Group to show the field in the UI


Value setted by SP when deploys the field. We can skip this attribute


Value setted by SP when deploys the field. We can skip this attribute


GUID of the Managed Metadata Service Application


GUID of the TermSet you want to bind the field


true/false (user can insert new values, only if the TermSet is defined as open)


GUID of the Hidden Note field

There are 3 values that we have no control over them:

· SspId: when you create an Office 365 tenant (you buy it), the Managed metadata service application is created with a name like: “Taxonomy_Zz2Z7zzzZzZzzzzZZzz2zz==” and a GUID. Obviously this value is different from each tenant y we don’t have any control on it.

We can see the GUID from the UI:


· TermSetId: This GUID is assigned when you create a new TermSet. If we create the TermSet manually, we can’t set this GUID. However, as we will see later, we can deploy our Term Store including TermSets and Term, specifying the GUID. So, let’s say that this value will be well-known for us.

· TextField: When SharePoint creates a MM field, actually is creating 2 fields, one is the MM field with the Schema showed above, but it creates a second Field of type Note, and a Hidden field. Again, using SharePoint Manager we can see a field, that usually has the same name as the MM field, but adding a “_0”


The Schema of the Hidden Note field is:

   1: <Field Type="Note"

   2:     DisplayName="LMLRole_0"

   3:     StaticName="d005416fced9454aaf7937757ad56c02"

   4:     Name="d005416fced9454aaf7937757ad56c02"

   5:     ID="{775a2c68-7660-42e5-890c-3cab91dbd88a}"

   6:     ShowInViewForms="FALSE"

   7:     Required="FALSE"

   8:     Hidden="TRUE"

   9:     CanToggleHidden="TRUE" />


So we can deploy this hidden Note field in our sandbox solution, and that way, the TextField value will be a well-known value as well.

Note: When SharePoint deploys the Elements.xml file with our 2 fields: MM and Note field, it follows a sequential order, for this reason, is a requirement that the Note field is before in the Elements.xml than the Taxonomy field. If not, when SP create the Taxonomy field, he won’t find the TextField ID because is not provisioned yet. You won’t get an error, but SP will create its own Note field, and the Taxonomy field will not be mapped correctly.

The only field that we can’t assign by ourselves and will change from one Office 365 tenant to other is the SspId, but later will talk a little more about it.

Now, we are going to see how we can deploy our Taxonomy, and set fixed GUIDs for TermSets and Terms. To achieve this, I think the best approach is to write some CSOM code in a power shell script, which we will run as a part of the deployment. I usually work with a Taxonomy xml file created using the Power Shell commands from Gary Lapointe ( That way, you can create all your taxonomy in your local environment, and later, export it to an XML file, that will be deployed in the Office 365 tenant using CSOM.

The next code is not complete but shows the path that you can follow to create TermSets and Terms using CSOM inside PowerShell:

# Get the password as a SecureString and load the client libraries

$securePassword = ConvertTo-SecureString $Password -AsPlainText -Force

Add-Type -Path "c:Program FilesCommon Filesmicrosoft sharedWeb Server Extensions15ISAPIMicrosoft.SharePoint.Client.dll"

Add-Type -Path "c:Program FilesCommon Filesmicrosoft sharedWeb Server Extensions15ISAPIMicrosoft.SharePoint.Client.Runtime.dll"

Add-Type -Path "c:Program FilesCommon Filesmicrosoft sharedWeb Server Extensions15ISAPIMicrosoft.SharePoint.Client.Taxonomy.dll"

# Get the client context

$context = New-Object Microsoft.SharePoint.Client.ClientContext($url)

$credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($user, $securePassword)

$context.Credentials = $credentials

# Get the Taxonomy Session and first Term store

$taxonomySession = [Microsoft.SharePoint.Client.Taxonomy.TaxonomySession]::GetTaxonomySession($context);




$termStores = $taxonomySession.TermStores



$termStore = $termStores[0]


# Create a Group

$group = $termStore.CreateGroup($name, $groupGuid);



# Create a Term Set

$termSet = $group.CreateTermSet($name, $id, $termStore.DefaultLanguage);

# Create a Term

$term = $termSet.CreateTerm($name, $lcid, $id);

Well, as I said before, we still have a disadvantage. If we have different environments and Office 365 tenants (test, production…) we have to change manually the SspId before creating the WSP file in every Managed metadata field, which can be hard in some scenarios, but remember, in my opinion is still better than force the user to create all the Taxonomy fields manually on each site collection. Anyway, I’m so lucky for working with Chris O’brien who has solved this inconvenient using MSBuild, and I’m sure will blog about it very short. I will update this article linking Chris’ blog.

Hope it helps, and let me know any comment.

Luis Manez