CSOM ServerVersion for the SharePoint 2013 November/December 2014 CU

In a recent experience I’ve found out that both the November and December 2014 CU for an on-premise environment are returning the same ServerVersion when requested via CSOM: 15.0.4675.1000.
For both CU’s the correct version is reflected in Central Admin:
– November CU: 15.0.4667.1000
– December CU: 15.0.4675.1000
See Steve Chen’s blog for a full list of all SharePoint 2013 version numbers.

If you have specific requirements in CSOM which depend on the December CU to be installed, you might want to check the ServerVersion property of the “ClientContext” object before you make the call but due to the fact it will return the November CU number for both the November/December CU this can result in unexpected exceptions. Known exceptions which will occur are:
– Using the SecondaryOwner property on the “Site” object;
– Updating the LocaleId property on the “RegionalSettings” object.

The below method provides a work-around for this issue and will return the correct ServerVersion. I know it’s not the best solution to test on an exception message, but it’s the only available work-around so far that solved my issue.

/// <summary>
/// Gets the correct SharePoint version number with the difference between the
/// November and December 2014 CU (OOTB both return the same version number)
/// </summary>
/// <param name="siteUrl">The URL of the site collection</param>
/// <returns>The SharePoint version number</returns>
public Version GetSharePointVersion(string siteUrl)
{
    Version novemberCU = new Version("15.0.4667.1000");
    Version decemberCU = new Version("15.0.4675.1000");

    using (ClientContext context = new ClientContext(siteUrl))
    {
        context.ExecuteQuery(); // Required to initiate the ServerVersion object
        Version sharePointVersion = context.ServerVersion;

        // Additional check needed for November/December CU
        if (sharePointVersion == novemberCU)
        {
            // The SecondaryContact property is only available in December CU
            context.Load(context.Site.SecondaryContact);
            try
            {
                context.ExecuteQuery();
            }
            catch (Exception ex)
            {
                if (ex.Message.ToLower().Contains("field or property") && ex.Message.ToLower().Contains("does not exist"))
                {
                    // Property doesn't exist, this is the November CU
                    return novemberCU;
                }
            }
            // Property exists, this is the December CU
            return decemberCU;
        }
        else
        {
            // Return the original version (it's neither the November/December CU)
            return sharePointVersion;
        }
    }
}

Setting the default value of a Managed Metadata Column from CSOM

With the upcoming possibilities of CSOM for SharePoint 2013, setting de default value Managed Metadata Columns is often part of the provisioning of site collections, apps or documents.

The code snippet below shows how the default value can be set assuming that you already have retrieved the Field definition of the site column wherefore you want to set the default value (and that you know the term label and ID).

You need a reference to the Microsoft.SharePoint.Client, Microsoft.SharePoint.Client.Runtime and Microsoft.SharePoint.Client.Taxonomy for this code to work:

string termLabel = "My Term";
Guid termId = new Guid("{590861F6-2E60-4198-A9CC-7D39158BF66E}");

TaxonomyField taxonomyField = Context.CastTo<TaxonomyField>(field);
Context.Load(taxonomyField, t => t.DefaultValue);
Context.ExecuteQuery(); // Get the Taxonomy Field

TaxonomyFieldValue defaultValue = new TaxonomyFieldValue();
defaultValue.WssId = -1;
defaultValue.Label = termLabel;
// GUID should be stored lowercase, otherwise it will not work in Office 2010
defaultValue.TermGuid = termId.ToString().ToLower();

// Get the Validated String for the taxonomy value
var validatedValue = taxonomyField.GetValidatedString(defaultValue);
Context.ExecuteQuery(); 

// Set the selected default value for the site column
taxonomyField.DefaultValue = validatedValue.Value;
taxonomyField.UserCreated = false;
taxonomyField.UpdateAndPushChanges(true);
Context.ExecuteQuery();

Provision Managed Metadata fields from a (remote) app

In many cases Managed Metadata fields are used to attach metadata to documents/items and provisioning these fields without using Full-Trust-Code is possible using CSOM and CAML (either from an app or from a remote tool). This solution works for both SharePoint 2013 on-premise and O365.

I’ve created the method “AddTaxonomyField” to provision the TaxonomyField because a Taxonomy field exists of two fields, whereof one hidden and the two are connected to each other. The method returns the TaxonomyField that is just created so you can continue configuring it.

For this code to work you need to reference the Microsoft.SharePoint.Client, Microsoft.SharePoint.Client.Runtime and Microsoft.SharePoint.Client.Taxonomy.

internal TaxonomyField AddTaxonomyField(string displayName, string internalFieldName, string groupName, bool required, bool multi, bool showNewForm, bool showEditForm, bool showDisplayForm)
{
    Guid noteFieldId = Guid.NewGuid();
    Guid taxFieldId = Guid.NewGuid();

    string noteField = string.Format(@"
        <Field ID=""{0}""
                Name=""{1}TaxHTField0""
                StaticName=""{1}TaxHTField0""
                DisplayName=""{2}_0""
                Type=""Note""
                Required=""FALSE""
                Hidden=""TRUE""
                ShowInViewForms=""FALSE""
                CanToggleHidden=""TRUE""
                Overwrite=""TRUE""
                SourceID=""http://schemas.microsoft.com/sharepoint/v3""/>", 
        noteFieldId,
        internalFieldName,
        displayName,
        taxFieldId,
        groupName,
        required.ToString().ToUpper(),
        multi.ToString().ToUpper(),
        showEditForm.ToString().ToUpper(),
        showNewForm.ToString().ToUpper(),
        showDisplayForm.ToString().ToUpper());
    string taxField = string.Format(@"
        <Field ID=""{3}""
                Name=""{1}""
                StaticName=""{1}""
                Group=""{4}""
                DisplayName=""{2}""
                Type=""TaxonomyFieldType""
                ShowField=""Term1033""
                Required =""{5}""
                Mult=""{6}""
                Overwrite=""TRUE""
                ShowInDisplayForm=""{9}""
                ShowInEditForm=""{7}""
                ShowInNewForm=""{8}""
                ShowInFileDlg=""TRUE""
                SourceID=""http://schemas.microsoft.com/sharepoint/v3"">
        <Default></Default>
        <Customization>
            <ArrayOfProperty>
            <Property>
                <Name>IsPathRendered</Name>
                <Value xmlns:q7=""http://www.w3.org/2001/XMLSchema"" p4:type=""q7:boolean"" xmlns:p4=""http://www.w3.org/2001/XMLSchema-instance"">
                false
                </Value>
            </Property>
            <Property>
                <Name>TextField</Name>
                <Value xmlns:q6=""http://www.w3.org/2001/XMLSchema"" p4:type=""q6:string"" xmlns:p4=""http://www.w3.org/2001/XMLSchema-instance"">
                {0}
                </Value>
            </Property>
            </ArrayOfProperty>
        </Customization>
        </Field>",
                noteFieldId,
                internalFieldName,
                displayName,
                taxFieldId,
                groupName,
                required.ToString().ToUpper(),
                multi.ToString().ToUpper(),
                showEditForm.ToString().ToUpper(),
                showNewForm.ToString().ToUpper(),
                showDisplayForm.ToString().ToUpper());
    Field noteSPField = Context.Web.Fields.AddFieldAsXml(noteField, true, AddFieldOptions.AddFieldInternalNameHint);
    Context.Load(noteSPField);
    Field taxSPfield = Context.Web.Fields.AddFieldAsXml(taxField, true, AddFieldOptions.AddFieldInternalNameHint);
    Context.ExecuteQuery();
    return Context.CastTo<TaxonomyField>(taxSPfield);
}

After creating the TaxonomyField you can update it’s settings (e.g. connect it to the Managed Metadata Service). Make sure to update the termstore related ID’s with your own (or retrieve them dynamically):

// Create the Taxonomy Field
TaxonomyField taxField = AddTaxonomyField("My Field", "MyField", "My Columns", true, false, true, true, true);

// Update these ID's with your own Managed Metadata ID's
Guid anchorId = new Guid("{12E721A6-576B-4842-9DE2-CFAB24DBBC16}");
Guid termStoreId = new Guid("{9362E986-1061-4BA1-988F-2698BEBD1D63}");
Guid termSetId = new Guid("{40729487-AC5B-40DF-87AB-DF172487DA41}");
                    
taxField.AnchorId = anchorId;
// If this is set to true terms that are not validated will be created
taxField.CreateValuesInEditForm = false;
// If this is set to true the user will be given the option to add new terms
taxField.Open = false;
// Id of the term store 
taxField.SspId = termStoreId;
// If this is set to a URL the items will link to that URL on display
taxField.TargetTemplate = string.Empty;
// This assumes you have a group and term set created.  Normally you would pick the specific termset you want
taxField.TermSetId = termSetId;

taxField.UserCreated = false;
taxField.Update();

Context.ExecuteQuery();

Surface Pro 3, limited WIFI connection after resume from sleep / hibernate

[Update 17-Jan-2015] This issue seems to be resolved in the January 2015 firmware update (at least for me): firmware update details.

A post about a non-SharePoint subject: issues with limited connectivity using WIFI on the Surface Pro 3 after putting the Surface in sleep/hibernate state. A lot of posts are already done and Microsoft already released multiple fixes, but for a lot of Surface users it doesn’t fix the issue.

This post describes a workaround to automatically reset (stop/start) the WIFI connection when the Surface is started from sleep/hibernate:

  1. First you need to enable script execution via Powershell: Open Powershell and execute the command: Set-ExecutionPolicy Unrestricted
  2. Create a new Powershell script on C:\ (e.g. restartwifi.ps1)
  3. Add the following command: restart-netadapter -InterfaceDescription ‘<your wifi card name>‘ -Confirm:$false
    You can find your WIFI card name via Device Manager -> Network adapters.
  4. Create a new Scheduled task via Task Scheduler
  5. Give the task a name, for example: “Restart WIFI after sleep or hibernate”
  6. Set it up to run as the “SYSTEM” account
  7. Under the “Triggers” tab, add a new Trigger
  8. Begin the task: On an event
    Log: System
    Source: Power-Troubleshooter
    EventID 1
  9. Go to the “Actions” tab, add a new Action
  10. Action: Start a program
    Program/script: powershell.exe
    Add arguments (optional): -File “C:\restartwifi.ps1”
  11. Under the “Conditions” tab, uncheck the “Start the task only if the computer is on AC power” checkbox, otherwise this workaround will only work when your Surface is charging
  12. Click OK and save the scheduled task
  13. If you run the scheduled task you can see that your WIFI adapter is restarted next to your system clock
  14. Now put your Surface to sleep/hibernate and start it up again. The limited network connection issue is now fixed 🙂

Provision site collection in SharePoint Online (O365) via remote PowerShell

In SharePoint Online (O365), you can use remote PowerShell to create site collections when you have tenant-admin permissions.

First you need to download and install the SharePoint Online Management Shell.

Run the Management Shell and connect to your tenant-admin url:

Connect-SPOService -Url https://YOURTENANT-admin.sharepoint.com -credential USERNAME@YOURTENANT.onmicrosoft.com

The Management Shell will now ask for your password. After that you can create a site collection using the below command:

New-SPOSite -Url “https://YOURTENANT.sharepoint.com/sites/SITENAME” -Owner “USERNAME@YOURTENANT.onmicrosoft.com” -StorageQuota “500” -Title “My Site created via Remote PowerShell”

Full details of the available parameters can be found on MSDN.

Default column values and the Document Template

When you update the default column values for a content type, these new values are not pushed to the document template of the content type if you’re using a customized document template (e.g. mytemplate.dotx). New documents created using the document template will still have the old default values attached to it. To push the changes to the customized document template you can execute one of these steps:

  • Via the UI: Open the “Advanced Settings” page of the content type and click “OK”;
  • Via code: Get you SPContentType and execute this code (this simulates the UI step):
SPContentType ct = SPContext.Current.Site.RootWeb.ContentTypes[YOURCONTENTTYPEID];
ct.DocumentTemplate = ct.DocumentTemplate;
ct.Update(false);

Copying items with Managed Metadata fields between site collections

When you download a document with taxonomy fields from site collection A and then upload it again into site collection B, the taxonomy values on the documents might not be set correctly. The values are displayed, but in the wrong column. To prevent this, two steps can be taken by the user:

  • After the download, remove the “Document Properties and Personal Information” from the document via the “Inspect” option in Office. When the document is uploaded again, the default values from the new site collection will be used.
  • Copy the document from site collection A using the OOTB “Copy To” functionality. Now SharePoint takes care of setting the taxonomy fields correctly.

Some background information: the taxonomy field value is linked to the “TaxonomyHiddenList” of site collection A via the ID in the hidden list. Once the document is moved, the ID remains the same but on site collection B this ID might be linked to another term as the “TaxonomyHiddenList” values can be different.

Managed Metadata and the Office Document Information Panel (DIP)

When you update any Managed Metadata column values from either CSOM of Full Trust Code, you need to make sure to make the Taxonomy value GUID lower cased. Otherwise it will cause the Office Document Information Panel to display an invalid default value (in red). Example code to update a default value, while ensuring the GUID is lowercased is shown below:

SPField field = SPContext.Current.Web.Fields["CustomMMSfield"];
TaxonomyField taxonomyField = (TaxonomyField)field;
TaxonomyFieldValue defaultValue = new TaxonomyFieldValue(taxonomyField);
defaultValue.PopulateFromLabelGuidPair("Netherlands|17E7FDD7-9AAF-4942-BCE1-16f06E40E085");
defaultValue.WssId = -1;
// GUID should be stored lowercase, otherwise it will not work in Office
defaultValue.TermGuid = defaultValue.TermGuid.ToLower();
// Set the selected default value for the site column
taxonomyField.DefaultValue = defaultValue.ValidatedString;
taxonomyField.Update(true);

Please note that the above code is Full Trust Code. Read this post to know how to set default values from CSOM.