Karine Bosch’s Blog

On SharePoint

The SharePoint Framework

Microsoft just announced the new development framework to develop against SharePoint. Don’t get worried, don’t throw your Add-ins through the windows, they are there to stay. :)  The SharePoint framework is just another tool in your toolbox to to ease the client-side development of pages and (web) parts. This framework will be deployed to SharePoint Online. It is not sure yet if it will be released on SharePoint 2016 on premise, but anyway not on SharePoint 2013 on premise.

You can read more about this brand new SharePoint framework here.

The framework itself is not released yet, but you can start preparing to get started: start learning TypeScript and React, and become familiar with tools like node.js, Gulp, Yeoman, and Git. Waldek Mastykarz has just posted a nice writeup on all these tools and techniques. And more details can be found on the blog of Chris O’Brien.

As a side note, I had the chance to play with a “beta” version during the DevKitchen that was organized in Brussels, a few weeks ago. The SharePoint product group came over to Brussels to give us an introduction to this very new SharePoint framework. And together with the product group, the creme of the european SharePoint community came to Brussels to participate to this event. This was just a week before the explosions, AND in the neighbourhood of one of them. I still get goose bumps when I think about what could have happened to the SharePoint community.

Keep your eyes on the net, because you will see popping up a lot of posts the coming weeks and months:)

Have fun!

May 5, 2016 Posted by | SharePoint Online | Leave a comment

Do not use SPUtility.CreateNewDiscussions(SPListItemCollection, title) but use SPUtility.CreateNewDiscussions(SPList, title) method instead

Internally the method SPUtility.CreateNewDiscussions(SPListItemCollection, title) executes the SPListItemCollection.Add method to add a new discussion item to the list.

CreateNewDiscussion-bad

The SPListItemCollection.Add method is know for loading the whole list item collection into memory before adding a new Discussion list item. This can cause poor performance and even throttling for large lists.

It is recommended to use the SPUtility.CreateNewDiscussions(SPList, title) method instead, as this method internally executes the SPList.ItemAdd() method.

CreateNewDiscussion-good

SPList.ItemAdd() does not load all list items in memory but executes a dummy CAML query to retrieve an empty SPListItemCollection to which a new item is added.

March 10, 2016 Posted by | SharePoint 2010 | Leave a comment

Office Dev PnP survey

The Office Dev PnP program has done a great job on building sample code and scenarios to guide SharePoint developers away from Full Trust code solutions to the add-in model. Through the past year the guidance started to evolve to other areas like ffice 365 APIs, Office Add-ins and unified APIs. PnP program has now evolved as open source community effort with both internal and external contributors.

This program is open source and driven by the community, with both internal and external contributors.

Every SharePoint developer gains from this initiative, and therefore I’m a big fan. and therefore I want to ask for your cooperation to fill out the survey http://aka.ms/officedevpnpsurvey.

Thanks for your cooperation!

 

March 9, 2016 Posted by | SharePoint 2010 | Leave a comment

Repair content type retention policies

Recently I was at a customer who implemented retention policies on content types. They have about 20 content types and a site collection with hundreds of sub sites with thousands of documents.

Becky Bertram wrote a nice detailed article on how to define retention policies for SharePoint 2010.

Problem description

When retention policies are applied on content types, there are 2 timer jobs that run (by default during the weekend):

  • Information management policy timer job: by default, runs on friday 11 PM. The job goes through libraries that have policies applied. It calculates the expiration date for every item.
  • Expiration policy timer job: by default, runs on saturday 11 PM. This job executes the action part of the retention policy. For example, if the action is to move expired documents to the recycle bin, expired documents will be deleted; if the action is set to move the documents to a send-to location, the expired documents will be moved.

My customer explained that when the  timer jobs ran for the first time, they ran for several hours and then just stopped running. As of then the jobs ran weekly, but with a lot of similar errors in the ULS logs:

"Error processing expiration of content in list <list name> in site <url to sp site>. 
 Error: Invalid field name. {b0227f1a-b179-4d45-855b-a18f03706bcb}".
"Error processing expiration of content in list <list name> in site <url to sp site>. 
 Error: Invalid field name. {acd16fdf-052f-40f7-bb7e-564c269c9fbc}".

From this post you can see that these guids refer to out of the box SharePoint fields:

Exempt from Policy b0227f1a-b179-4d45-855b-a18f03706bcb _dlc_Exempt
Expiration Date acd16fdf-052f-40f7-bb7e-564c269c9fbc _dlc_ExpireDate

When I tried to take a look at the Compliance Details of a document, I got the following error message: “column  ‘_dlc_exempt’ does not exist. It may have been deleted by another user”.

The Compliance Details menu option is only available on the context menu when retention policies are active for the specific document:

Compliance Details

After investigation I found out that part of the sites had the retention policies correctly applied, and part of the sites had not.

In the rest of the article I will use the term “active retention policy”. It means that retention policies are defined on content types, and that these content types are in use on a document library.

Solution

I found a very interesting article on retention policies on the net to get me started, so kudos to Mike Berryman. I started my investigation to repair the site collection based on this article.

When a SharePoint web has active retention policies, it should have the following properties on its property bag:

  • allowslistpolicy
  • dlc_sitehaspolicy
  • dlc_sitehasexpirationpolicy
  • dlc_webhasexpirationpolicy

As some SharePoint sites were working correctly and some not, I corrected each SPWeb as follows:

$web.AllowUnsafeUpdates = $true
if (!($web.Properties.ContainsKey("allowslistpolicy")))
{
    $web.Properties.Add("allowslistpolicy", $true)
}
if (!($web.Properties.ContainsKey("dlc_sitehasexpirationpolicy")))
{
    $web.Properties.Add("dlc_sitehasexpirationpolicy", $true)
}
if (!($web.Properties.ContainsKey("dlc_sitehaspolicy")))
{
    $web.Properties.Add("dlc_sitehaspolicy", $true)
}
if (!($web.Properties.ContainsKey("dlc_webhasexpirationpolicy")))
{
    $web.Properties.Add("dlc_webhasexpirationpolicy", $true)
}
$web.Properties.Update()
$web.Update()

A library with active retention policies should have the following hidden fields:

  • _dlc_Exempt
  • _dlc_ExpireDateSaved
  • _dlc_ExpireDate

I used the XML definition of these fields in order to create the missing fields:

$displayName_exempt = "Exempt from Policy"
$schemaXml_exempt = "<Field ID='{B0227F1A-B179-4D45-855B-A18F03706BCB}' 
    Name='_dlc_Exempt' StaticName='_dlc_Exempt' DisplayName='_dlc_Exempt' 
    SourceID='http://schemas.microsoft.com/sharepoint/v3' Group='Document and Record Management Columns' 
    Type='ExemptField' Indexed='FALSE' Hidden='TRUE' CanToggleHidden='TRUE' 
    ShowInNewForm='FALSE' ShowInEditForm='FALSE' ShowInFileDlg='FALSE' ShowInDisplayForm='FALSE' Required='FALSE' 
    Sealed='TRUE' ReadOnly='TRUE' OverwriteInChildScopes='TRUE'/>"
 
$displayName_expireDateSaved = "Original Expiration Date"
$schemaXml_expireDateSaved = "<Field ID='{74E6AE8A-0E3E-4DCB-BBFF-B5A016D74D64}' 
    Name='_dlc_ExpireDateSaved' StaticName='_dlc_ExpireDateSaved' DisplayName='_dlc_ExpireDateSaved' 
    SourceID='http://schemas.microsoft.com/sharepoint/v3' Group='Document and Record Management Columns' 
    Type='DateTime' Indexed='FALSE' Hidden='TRUE' CanToggleHidden='TRUE' ShowInNewForm='FALSE' 
    ShowInEditForm='FALSE' ShowInFileDlg='FALSE' ShowInDisplayForm='FALSE' Required='FALSE' 
    Sealed='TRUE' ReadOnly='TRUE' OverwriteInChildScopes='TRUE' />"

$displayName_expireDate = "Expiration Date"
$schemaXml_expireDate = "<Field ID='{ACD16FDF-052F-40F7-BB7E-564C269C9FBC}' 
   Name='_dlc_ExpireDate' StaticName='_dlc_ExpireDate' DisplayName='_dlc_ExpireDate' 
   SourceID='http://schemas.microsoft.com/sharepoint/v3' Group='Document and Record Management Columns' 
   Type='DateTime' Indexed='TRUE' Hidden='TRUE' CanToggleHidden='TRUE' 
   ShowInNewForm='FALSE' ShowInEditForm='FALSE' ShowInFileDlg='FALSE' ShowInDisplayForm='FALSE' Required='FALSE' 
   Sealed='TRUE' ReadOnly='TRUE' OverwriteInChildScopes='TRUE' />"

As some SharePoint libraries were working correctly and some not, I corrected each SPList as follows:

function RepairField($lib, $fieldInternalName $fieldDisplayName, 
       $schemaXml) { 
   $field = $null
   try
   {
      $field = $lib.Fields.GetFieldByInternalName($fieldInternalName)
   }
   catch {}
            
   if ($field -eq $null)
   {
       $lib.Fields.AddFieldAsXml($schemaXml)
       $field = $lib.Fields[$fieldInternalName]
       if ($field -ne $null)
       {
            $field.Title = $fieldDisplayName
            $field.Update()
       }
   }
}       
       
RepairField($lib, "_dlc_exempt", 
    $displayName_exempt, 
    $schemaXml_exempt)
RepairField($lib, "_dlc_ExpireDateSaved", 
    $displayName_expireDateSaved, 
    $schemaXml_expireDateSaved)
RepairField($lib, "_dlc_ExpireDate", 
    $displayName_expireDate, 
    $schemaXml_expireDate)
$lib.Update()

Each document on which a retention policy applies, has the following properties in its property bag:

  • ItemRetentionFormula
  • _dlc_ItemStageId
  • _dlc_ItemScheduleId

I corrected the items as follows:

$files = $lib.RootFolder.Files
foreach ($file in $files)
{
    $item = $file.Item
            
    # add the property ItemRetentionFormula to the property bag
       write-host ("set the retention properties  for item ID " + $item["ID"])
    $item["Modified"] = $item["Created"]

    if ($item.Properties.ContainsKey("ItemRetentionFormula") -eq $false)
    {
        $item.Properties.Add("ItemRetentionFormula", $true)            
    }
    if ($item.Properties.ContainsKey("_dlc_ItemStageId") -eq $false)
    {
        $item.Properties.Add("_dlc_ItemStageId", $true)
    }
    if ($item.Properties.ContainsKey("_dlc_ItemScheduleId") -eq $false)
    {
        $item.Properties.Add("_dlc_ItemScheduleId", $true)
    }
                    
    # clear the item stage id
    $item.Properties["_dlc_ItemStageId"] = ""
    # set the schedule type
    $item.Properties["_dlc_ItemScheduleId"] = $null
    # set the property to contain the formula
    $item.Properties["ItemRetentionFormula"] = $null
    $file.Update()  
    $item.SystemUpdate()
}

For completeness, at the end I disposed the SPWeb object:

$web.AllowUnsafeUpdates = $false
$web.Dispose()

Remarks:

  • I used the server-side object model.
  • As I had to work on the production environment, I had to write the code in PowerShell, but you can also do it in C#.

 

 

 

 

January 4, 2016 Posted by | SharePoint 2010 | Leave a comment

SharePoint 2007 out of support

Last week I received the question “Is SharePoint 2007 still supported?”

My first reaction was “euh, what??” SharePoint 2010 mainstream support has just ended, so SharePoint 2007 is surely not supported anymore. But the customer referred to this page, saying that SharePoint 2007 was under support till October 10th 2017: https://support.microsoft.com/nl-be/lifecycle/search?sort=PN&alpha=SharePoint%20Server%202007&Filter=FilterNO

My colleague Peter Loete and I took a closer look to the support page, and this is the table from which the customer deducted that SharePoint 2007 was still under support:

SP2007 support

So, no mainstream support anymore for SP2007 SP3: it ended on September 10th 2012. Extended support, at the other side, is still available till October 10th 2017.

What’s included in extended support? You can read more about it here.

October 18, 2015 Posted by | SharePoint 2010 | Leave a comment

SharePoint 2010 mainstream support ends on October 13th 2015

Although a lot of my customers still are on SharePoint 2010, mainstream support ends for all SharePoint 2010 installations with SP2 on October 13th 2015. If you’re not yet on SP2, you can still download it from here.

If you are a Microsoft Premier customer, you get extended support. You can read the details here.

You can find official information on the product lifecycle of SharePoint 2010 here.

October 18, 2015 Posted by | SharePoint 2010 | Leave a comment

Add or Modify SharePoint 2013 Search Topology using a PowerShell built User Interface

In SharePoint 2013, there is no real user interface to modify the search topology. Well, there is, but you can only use for a single server farm. If you have more servers in your SharePoint farm, you have to do this through PowerShell.

One of my South-African Premier Field Engineer colleagues Scott Stewart developed a tool on top of PowerShell WITH UI to create or modify a search topology.

Read more about it here: “Add or Modify SharePoint 2013 Search Topology using a PowerShell built User Interface

A few screenshots to get you curious:)

Search topology tool

Search topology

It looks like a very promising tool! Have fun with it!

May 22, 2015 Posted by | Search, SharePoint 2013 | 1 Comment

Creating fields using CSOM in SharePoint 2013

Recently, I was working on a project where we wanted to create SharePoint fields using the .NET client object model (CSOM). You can easily find examples on the internet on how to create a text field, but we had a lot of trouble to find out how to create a calculated field.

Calculated fields can be created in a number of flavors. The following code snippet generates a calculated field that will show employee data as follows:

Karine Bosch (id 82176)

string formula = "<Formula>=FirstName&amp; \" \" &amp;LastName&amp; \" (id: \" &amp;EmployeeID&amp; \" \"</Formula>"
      + "<FieldRefs>"
      + "<FieldRef Name='FirstName' />"
      + "<FieldRef Name='LastName' />"
      + "<FieldRef Name='EmployeeID' />"
      + "</FieldRefs>";

string schemaCalculatedField = "<Field ID='<GUID>' Type='Calculated' Name='FullName' StaticName='FullName' 
   DisplayName='Full Name' ResultType='Text' Required='TRUE' ReadOnly='TRUE'>" + formula + "</Field>";
Field fullNameField = demoList.Fields.AddFieldAsXml(schemaCalculatedField, true, AddFieldOptions.AddInternalNameHint);
clientContext.ExecuteQuery();

You can read more on how to create other type of fields using CSOM here.

Tip

To learn how to provision a calculated field, I created this type of field in a blank site through the UI of SharePoint. Then I saved the site as template. This saves the site as a .wsp and stores it in the Solution gallery. From there you can download it and import it in Visual Studio.

Import wsp

Once the .wsp imported, you will notice that all fields, content types, list instances and other elements that make up your site are translated into CAML and features:

Import wsp 2

Click on the elements.xml in the Fields folder to see the definition of all fields.

 

May 14, 2015 Posted by | CSOM, SharePoint 2013 | Leave a comment

Setting the value of a lookup field using CSOM

Today I had a hard time to find out how to set the value of a lookup field using CSOM. In most of the code samples I found on the internet, the LookupId is set hard coded, and this is not what I needed. Additionally, in some cases the lookup list was situated on the root web, and in another case I had to query a different field than the Title field. I ended up writing the following method:

public static FieldLookupValue GetLookupValue(ClientContext clientContext, string value, 
   string lookupListName, string lookupFieldName, string lookupFieldType, bool onRootWeb)
{
     List list = null;
     FieldLookupValue lookupValue = null;

     if (onRootWeb)
     {
          list = clientContext.Site.RootWeb.Lists.GetByTitle(listName);
     }
     else
     {
          list = clientContext.Web.Lists.GetByTitle(listName);
     }

     if (list != null)
     {
         CamlQuery camlQueryForItem = new CamlQuery();
         camlQueryForItem.ViewXml = string.Format(@"<View>
                  <Query>
                      <Where>
                         <Eq>
                             <FieldRef Name='{0}'/>
                             <Value Type='{1}'>{2}</Value>
                         </Eq>
                       </Where>
                   </Query>
            </View>", lookupFieldName, lookupFieldType, value);

          listItemCollection listItems = list.GetItems(camlQueryForItem);
          clientContext.Load(listItems, items => items.Include
                                            (listItem => listItem["ID"],
                                             listItem => listItem[lookupFieldName]));
          clientContext.ExecuteQuery();

          if (listItems != null)
          {
              ListItem item = listItems[0];
              lookupValue = new FieldLookupValue();
              lookupValue.LookupId = Int.Parse(item["ID"].ToString());
          }             
     }

     return lookupValue;
}

This method works on both SharePoint 2010 and 2013.

I hope this code snippet can help others.

Enjoy!

 

May 11, 2015 Posted by | CSOM, SharePoint 2010 | 2 Comments

New release of CAML Designer available with new functionality!

042813_0652_Newversiono1.png

This weekend we released a new version of the CAML Designer.We solved the bug that occurred when more than one TaxonomyField was selected in the Where clause; but we also include the following new functionality:

– The <Membership> element

– REST snippets including a CAML query

We are very proud with these new additions, and we hope you like it!

 

 The <Membership> element

The <Membership> element already exists for ages but it’s only now that we introduce it in the CAML Designer.

Whenever you select a field of user type in the Where clause, you will now get an additional radio button “Membership”:

User field

When you click the Membership option, you get an additional dropdown where you can select one of the following options:

  • CurrentUserGroups: this will retrieve all tasks that are assigned to groups to which the current user belongs.
  • SPWeb.Groups: this will retrieve all tasks that are assigned to groups.
  • SPWeb.AllUsers: this will retrieve all tasks that have been assigned to users and not to groups.
  • SPWeb.Users: this will retrieve all tasks that have been assigned to users that don’t belong to a specific group but that have been granted access to the site directly.
  • SPGroup: this will retrieve tasks that have been assigned to a specific group.

User field - membership

We’ve also foreseen a small description field that explains the selected option:

User field - membership CurrentUserGroups

When selecting the SPGroup option, another additional dropdown becomes available. This dropdown will list all groups:

User field - membership SPGroup

Selecting a group from this dropdown will retrieve all tasks that have been assigned to the selected group. The CAML query looks like the following:

   <Where>
      <Membership Type='SPGroup' ID='5'>
         <FieldRef Name='AssignedTo' />
      </Membership>
   </Where>

When you select the CurrentUserGroups option, the CAML query will look as follows:

  <Where>
      <Membership Type='CurrentUserGroups'>
         <FieldRef Name='AssignedTo' />
      </Membership>
   </Where>

For the SPWeb.Groups option, the CAML query looks like this:

  <Where>
      <Membership Type='SPWeb.Groups'>
         <FieldRef Name='AssignedTo' />
      </Membership>
   </Where>

For the SPWeb.AllUsers options, the following CAML query is generated:

  <Where>
      <Membership Type='SPWeb.AllUsers'>
         <FieldRef Name='AssignedTo' />
      </Membership>
   </Where>

And when you select the SPWeb.Users option, you will get the following CAML:

  <Where>
      <Membership Type='SPWeb.Users'>
         <FieldRef Name='AssignedTo' />
      </Membership>
   </Where>

Credits go to Christoper Clementen with his blog post: http://christopherclementen.wordpress.com/2012/08/27/caml-query-membership-attribute where he clearly explains how the <Membership> element works.

 

REST snippets executing CAML queries

A second new functionality are the REST snippets with CAML queries. In most cases you can use the well known REST requests using the select and filter criteria to retrieve list items. But there are a few cases where such a REST request will return an error. These cases are:

  • filtering on taxonomy fields
  • the <Membership> element

In these cases your REST snippet must include a CAML query. Additionally this REST request needs to be executed as a POST. This is well described in this blog post of CleverWorkarounds.

Such a REST snippet generated by the CAML Designer looks like this:

 

$.ajax({ 
   url: _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getbytitle('Developers')/GetItems(query=@v1)?"
        + "@v1={\"ViewXml\":\"<View><ViewFields><FieldRef Name='Title' /><FieldRef Name='FirstName' /><FieldRef Name='Company' /></ViewFields>"
        + "<Query><Where><Eq><FieldRef Name='Technology' /><Value Type='TaxonomyFieldType'>SharePoint</Value></Eq></Where></Query></View>\"}", 
   type: "POST", 
   headers: { 
         "X-RequestDigest": $("#__REQUESTDIGEST").val(), 
         "Accept": "application/json;odata=verbose", 
         "Content-Type": "application/json; odata=verbose" 
   }, 
   success: function (data) { 
      if (data.d.results) { 
         // TODO: handle the data  
         alert('handle the data'); 
      } 
   }, 
   error: function (xhr) { 
      alert(xhr.status + ': ' + xhr.statusText); 
   } 
});

When filtering on membership, the REST snippet will look like this:

$.ajax({ 
   url: _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getbytitle('Tasks')/GetItems(query=@v1)?@v1={\"ViewXml\":\"<View><Query><Where><And><Neq><FieldRef Name='Status' /><Value Type='Choice'>Completed</Value></Neq><Membership Type='SPWeb.Groups'><FieldRef Name='AssignedTo' /></Membership></And></Where></Query></View>\"}", 
   type: "POST", 
   headers: { 
         "X-RequestDigest": $("#__REQUESTDIGEST").val(), 
         "Accept": "application/json;odata=verbose", 
         "Content-Type": "application/json; odata=verbose" 
   }, 
   success: function (data) { 
      if (data.d.results) { 
         // TODO: handle the data  
         alert('handle the data'); 
      } 
   }, 
   error: function (xhr) { 
      alert(xhr.status + ': ' + xhr.statusText); 
   } 
}); 

May 22, 2014 Posted by | CAML Designer, SharePoint 2013 | 2 Comments

Follow

Get every new post delivered to your Inbox.

Join 197 other followers