All DDWRT Functions by SharePoint Version

Yesterday I’ve been asked “How can I get a list of all DDWRT extensions that are available in SharePoint 2013?”.

The online documentation is accurate but, unfortunately, a “little” bit outdated: this article by Serge van den Oever is a great source of information, but still refers to SharePoint 2003, therefore some functions are just missing.

I was curious, so I executed this simple PowerShell script to get the signature (name, return type, parameters) of each public, instance method of the Microsoft.SharePoint.WebPartPages.BaseDdwRuntime and of the Microsoft.SharePoint.WebPartPages.DataFormDdwRuntime where ddrwrt extensions are defined.

$spAssm = [Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint")
$baseRuntime = $spAssm.GetType("Microsoft.SharePoint.WebPartPages.BaseDdwRuntime")
$ddwRuntime = $spAssm.GetType("Microsoft.SharePoint.WebPartPages.DataFormDdwRuntime")
$methods = ($baseRuntime.GetMethods("Public,Instance") |? { $_.DeclaringType -eq $baseRuntime -and !$_.IsSpecialName -and ! $_.IsAbstract }) + ($ddwRuntime.GetMethods("Public,Instance") |? { $_.DeclaringType -eq $ddwRuntime -and !$_.IsSpecialName -and ! $_.IsAbstract })

$methods | sort Name |% {
    ac C:\Scripts\DDWRTMethods.txt $_.Name
    Write-Host "Method Name: $($_.Name)"
    Write-Host "  Output: $($_.ReturnType.Name)"
    $_.GetParameters() |% {
        Write-Host "  Input: $($_.Name) ($($_.ParameterType.Name))"
    }
}
Write-Host
Write-Host "# of extensions found: $($methods.Count)"

I ran this script on a SharePoint 2013 box and I got a list of 63 functions (see the output at the bottom of this post).

Executing the very same script against a SharePoint 2007 and a SharePoint 2010 environment led to the results elaborated and represented by the table below (I did not have a SP 2003 farm where I could install PowerShell, so I grabbed the list of DDWRT function from the Serge’s article above).

image

Finally, here’s the list of DDWRT extensions supported by SharePoint 2013.

Method Name: AllowSilverlightPrompt
Output: Boolean
Method Name: AutoHyperLink
Output: String
Input: szStr (String)
Input: preserveWhitespace (Boolean)
Method Name: AutoNewLine
Output: String
Input: inputString (String)
Method Name: ConnEncode
Output: String
Input: szData (String)
Method Name: Counter
Output: String
Method Name: CurrentRights
Output: String
Method Name: DataBind
Output: String
Input: op (String)
Input: controlId (String)
Input: propertyName (String)
Input: eventName (String)
Input: keyField (String)
Input: keyValue (String)
Input: dataField (String)
Method Name: DateTimeTick
Output: Int64
Input: stringDate (String)
Method Name: EcmaScriptEncode
Output: String
Input: stringToEncode (String)
Method Name: EnsureAllowedProtocol
Output: String
Input: urlToCheck (String)
Method Name: EscapeDelims
Output: String
Input: str (String)
Method Name: FieldFilterImageUrl
Output: String
Input: fieldName (String)
Method Name: FieldFilterOptions
Output: String
Input: szName (String)
Method Name: FieldPrefix
Output: String
Method Name: FieldSortImageUrl
Output: String
Input: direction (String)
Method Name: FieldSortParameters
Output: String
Input: szName (String)
Method Name: FilterLink
Output: String
Method Name: FormatDate
Output: String
Input: stringDate (String)
Input: lcid (Int64)
Input: formatFlag (Int64)
Method Name: FormatDateTime
Output: String
Input: stringDate (String)
Input: lcid (Int64)
Input: formatString (String)
Method Name: FormatDateTimeUsingCurrentContext
Output: String
Input: stringDate (String)
Method Name: FormatDateUsingCurrentContext
Output: String
Input: stringDate (String)
Method Name: GenDisplayName
Output: String
Input: theValue (String)
Method Name: GenFireConnection
Output: String
Input: szConnectStr (String)
Input: szOtherPostback (String)
Method Name: GenFireServerEvent
Output: String
Input: szEventStr (String)
Method Name: GenFireServerEventEncode
Output: String
Input: szEventStr (String)
Method Name: GenFireWorkflowStart
Output: String
Input: listName (String)
Input: itemID (String)
Input: workflowId (String)
Input: workflowParams (String)
Method Name: GenFireWorkflowTaskComplete
Output: String
Input: taskID (String)
Input: taskListName (String)
Method Name: GetCurrentBuildVersion
Output: String
Method Name: GetFileExtension
Output: String
Input: targetUrl (String)
Method Name: GetRatingsData
Output: XPathNodeIterator
Input: itemId (String)
Method Name: GetUserID
Output: String
Input: fieldName (String)
Method Name: GetVar
Output: String
Input: szName (String)
Method Name: HtmlDecode
Output: String
Input: stringToDecode (String)
Method Name: HtmlTransHandleUrl
Output: String
Input: szExt (String)
Method Name: HtmlTransProgID
Output: String
Input: szExt (String)
Method Name: IfDebug
Output: Boolean
Method Name: IfHasRights
Output: Boolean
Input: permissions (UInt64)
Method Name: IfNew
Output: Boolean
Input: createdTime (String)
Method Name: IfNewlyUpdated
Output: Boolean
Input: createdTime (String)
Input: lastRefreshTime (String)
Method Name: IsPrivilegedUser
Output: Boolean
Method Name: Limit
Output: String
Input: inputText (String)
Input: maxLength (Int32)
Input: additionalText (String)
Method Name: ListProperty
Output: String
Input: szPropName (String)
Method Name: MapToAll
Output: String
Input: szProgID (String)
Input: szExt (String)
Method Name: MapToApp
Output: String
Input: szProgId (String)
Input: szExt (String)
Method Name: MapToControl
Output: String
Input: szProgID (String)
Input: szExt (String)
Method Name: MapToIcon
Output: String
Input: szProgID (String)
Input: szExt (String)
Method Name: Max
Output: Int32
Input: nodeIter (XPathNodeIterator)
Method Name: Min
Output: Int32
Input: nodeIter (XPathNodeIterator)
Method Name: NameChanged
Output: String
Input: theName (String)
Input: id (Int64)
Method Name: PagingImageUrl
Output: String
Input: direction (String)
Method Name: PresenceEnabled
Output: String
Method Name: Random
Output: String
Input: lowVal (String)
Input: highVal (String)
Method Name: RecycleBinEnabled
Output: Boolean
Method Name: SelectOptions
Output: String
Input: szName (String)
Method Name: SetVar
Output: String
Input: szName (String)
Input: szValue (String)
Method Name: ThreadStamp
Output: String
Method Name: Today
Output: String
Method Name: TodayIso
Output: String
Method Name: UrlBaseName
Output: String
Input: targetUrl (String)
Method Name: UrlDirName
Output: String
Input: targetUrl (String)
Method Name: UrlEncode
Output: String
Input: stringToEncode (String)
Method Name: URLLookup
Output: String
Input: listName (String)
Input: FieldAttributeName (String)
Input: FieldPosition (String)
Method Name: UserLookup
Output: String
Input: UserName (String)
Input: FieldName (String)

# of extensions found: 63

Find large files through Search in SharePoint 2010

How to get a list of large files in a (very large) SharePoint 2010 Farm, sorted by file size?

I discussed about this requirement with my friend and teammate Riccardo Celesti and we ended up with the same answer: use search!

Unfortunately, since the customer’s environment is still running on top of SharePoint Server 2010, the solution was not that immediate: we would need to add a metadata property mapped to the file size (and schedule a full crawl afterwards, which may be not feasible).

Or… it’ SharePoint 2010, right? We still have the FullText SQL Query Language available.

Typically, the SQL Query Language should not be an option, since this feature has been removed in SharePoint 2013 and was already considered obsolete in SharePoint 2010 (take a look at the “tip” at the beginning the MSDN page).

Anyway, this was a “fire and forget” requirement, so here’s what we ended up with:

Add-PSSnapin Microsoft.SharePoint.PowerShell
$site = Get-SPSite http://the-url-of-a-site
$query = new-Object Microsoft.Office.Server.Search.Query.FullTextSqlQuery($site);
$query.ResultTypes = "RelevantResults"
$query.RowLimit = 1000
$query.EnableStemming = $false
$query.TrimDuplicates = $false
$query.QueryText = "SELECT URL, filename, size, path FROM SCOPE() WHERE IsDocument=1 ORDER BY size DESC"
$results = $query.Execute()
$table = New-Object System.Data.DataTable
$table.Load($results["RelevantResults"], "OverwriteChanges")
$table | ogv

 

Hope useful 🙂

Migration to SharePoint 2013: “The Controls collection cannot be modified because the control contains code blocks”

The background

If you have ever written some ASP.NET code making use of server side code blocks, you may have occasionally seen errors like this one:

The Controls collection cannot be modified because the control contains code blocks

This is typically a symptom of some “mess” with code blocks (i.e. <% … %> expressions) and dynamically generated control trees.

You can find a detailed explanation in this post by Rick Strahl.

If you want to quickly reproduce this issue, consider for example the ASPX page below:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApplication2._Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title></title>
</head>
<body>
  <form id="form1" runat="server">
    <div>
      <asp:Panel ID="mainContent" runat="server">
        <% Response.Write("Code Block"); %>
      </asp:Panel>
    </div>
  </form>
</body>
</html>

Here, a container control (the Panel with ID “mainContent”) contains a code block that just writes a few characters to the Response output.

Everything is fine, as long as you do not explicitly modify the Panel’s control tree: in the code snippet below, for example, a Literal control is being added dynamically as a child control of the “mainContent” Panel, which will throw an Exception at runtime for the reasons described above.

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace WebApplication2
{
  public partial class _Default : System.Web.UI.Page
  {
    protected override void OnInit(EventArgs e)
    {
      base.OnInit(e);

      // The line below will throw an exception.
      this.mainContent.Controls.Add(new Literal { Text = "Added dynamically" });

      // The line below will run correctly.
      this.Form.Controls.Add(new Literal { Text = "Added dynamically" });
    }
  }
}

Also, notice that adding the Literal control as a child of the Form control would run just fine: the Form control itself does not contain code blocks, so there’s no clash with dynamically generated control trees.

You can try this yourself: just comment out the second and the third line of the OnInit method respectively and see the results:

image

image

The issue

That said… how is all this related to the Migration from SharePoint 2010 to SharePoint 2013?

Well, it turns out that a small change has been introduced in the behavior of the WebPartPage class, that sits behind most of the out-of-the-box site pages (List Forms, List Views, etc.), and is often used as the base class for custom Site Pages provisioned through Module features.

Since SharePoint 2010, users are allowed to insert Web Parts inside Rich Text areas, as opposed to the classical Web Part Zones.

This does not imply that web parts can really be inserted outside of a web part zone (unless you use a web part as a standard web control, of course, which is a completely different story whatsoever).

Indeed, the Web Part Page creates a hidden web part zone, that is dynamically added to the page controls in order to host web parts inserted into Rich Text areas. The HTML markup that corresponds to these web parts is then moved in the correct position at runtime by the means of a bunch of Javascript code.

If you take a look at how the WebPartPage class accomplishes this, you may notice a private method called EmitHiddenWebPartZone.

In SharePoint 2010, the implementation of this method adds a Panel control as a direct child control of the Form (see below):

private void EmitHiddenWebPartZone()
{
    if (ScriptManager.GetCurrent(this.Page) != null)
    {
        SPWebPartManager sPWebPartManager = this.SPWebPartManager;
        WebPartZone webPartZone = new WebPartZone {
            ID = "wpz",
            PartChromeType = PartChromeType.TitleOnly
        };
        if (((this.Context == null) || (this.Context.Request == null)) || ((this.Context.Request.Form == null) || (string.IsNullOrEmpty(this.Context.Request.Form["wpcmVal"]) && string.IsNullOrEmpty(this.Context.Request.Form["_wpcmWpid"]))))
        {
            webPartZone.AllowLayoutChange = false;
        }
        sPWebPartManager.Register(webPartZone);
        this.wpz = webPartZone;
        Literal child = new Literal();
        Literal literal2 = new Literal();
        child.Text = "<div style='display:none' id='hidZone'>";
        literal2.Text = "</div>";
        UpdatePanel panel = new UpdatePanel {
            ID = "panelZone",
            UpdateMode = UpdatePanelUpdateMode.Conditional
        };
        panel.ContentTemplateContainer.Controls.Add(child);
        panel.ContentTemplateContainer.Controls.Add(webPartZone);
        panel.ContentTemplateContainer.Controls.Add(literal2);
        this.Page.Form.Controls.Add(panel);
        this.panelZone = panel;
        Literal literal3 = new Literal();
        string valueToEncode = string.Empty;
        if (((this.Context != null) && (this.Context.Request != null)) && (this.Context.Request.Form != null))
        {
            valueToEncode = this.Context.Request.Form["wpcmVal"];
        }
        if (valueToEncode == null)
        {
            valueToEncode = string.Empty;
        }
        literal3.Text = "<input type='hidden' id='_wpcmWpid' name='_wpcmWpid' value='' /><input type='hidden' id='wpcmVal' name='wpcmVal' value='" + SPHttpUtility.HtmlEncode(valueToEncode) + "'/>";
        this.Page.Form.Controls.Add(literal3);
    }
}

In SharePoint 2013, the behavior is “almost” identical:

private void EmitHiddenWebPartZone()
{
    if (ScriptManager.GetCurrent(this.Page) != null)
    {
        SPWebPartManager sPWebPartManager = this.SPWebPartManager;
        WebPartZone webPartZone = new WebPartZone {
            ID = "wpz",
            PartChromeType = PartChromeType.TitleOnly
        };
        if (((this.Context == null) || (this.Context.Request == null)) || ((this.Context.Request.Form == null) || (string.IsNullOrEmpty(this.Context.Request.Form["wpcmVal"]) && string.IsNullOrEmpty(this.Context.Request.Form["_wpcmWpid"]))))
        {
            webPartZone.AllowLayoutChange = false;
        }
        sPWebPartManager.Register(webPartZone);
        this.wpz = webPartZone;
        Literal child = new Literal();
        Literal literal2 = new Literal();
        child.Text = "<div style='display:none' id='hidZone'>";
        literal2.Text = "</div>";
        UpdatePanel panel = null;
        if (SPUtility.ContextCompatibilityLevel < 15)
        {
            panel = new UpdatePanel {
                ID = "panelZone",
                UpdateMode = UpdatePanelUpdateMode.Conditional
            };
            panel.ContentTemplateContainer.Controls.Add(child);
            panel.ContentTemplateContainer.Controls.Add(webPartZone);
            panel.ContentTemplateContainer.Controls.Add(literal2);
        }
        Control control = string.IsNullOrEmpty(base.MainContentID) ? null : this.Page.Form.FindControl(base.MainContentID);
        if (control != null)
        {
            if (SPUtility.ContextCompatibilityLevel >= 15)
            {
                control.Controls.Add(child);
                control.Controls.Add(webPartZone);
                control.Controls.Add(literal2);
            }
            else
            {
                control.Controls.Add(panel);
            }
        }
        else if (SPUtility.ContextCompatibilityLevel >= 15)
        {
            this.Page.Form.Controls.Add(child);
            this.Page.Form.Controls.Add(webPartZone);
            this.Page.Form.Controls.Add(literal2);
        }
        else
        {
            this.Page.Form.Controls.Add(panel);
        }
        if (SPUtility.ContextCompatibilityLevel < 15)
        {
            this.panelZone = panel;
        }
        string hiddenFieldInitialValue = string.Empty;
        if (((this.Context != null) && (this.Context.Request != null)) && (this.Context.Request.Form != null))
        {
            hiddenFieldInitialValue = this.Context.Request.Form["wpcmVal"];
        }
        if (hiddenFieldInitialValue == null)
        {
            hiddenFieldInitialValue = string.Empty;
        }
        SPPageContentManager.RegisterHiddenField(this.Page, "_wpcmWpid", "");
        SPPageContentManager.RegisterHiddenField(this.Page, "wpcmVal", hiddenFieldInitialValue);
    }
}

The most significant difference (at least for the purpose of this post) are the lines below:

Control control = string.IsNullOrEmpty(base.MainContentID) ? null : this.Page.Form.FindControl(base.MainContentID);
if (control != null)
{
    if (SPUtility.ContextCompatibilityLevel >= 15)
    {
        control.Controls.Add(child);
        control.Controls.Add(webPartZone);
        control.Controls.Add(literal2);
    }
    else
    {
        control.Controls.Add(panel);
    }
}

Which means “look for a control with an ID of [base.MainContentID] and, if you find it, add the Web Part Zone as a child of that control, otherwise, add the control as a child of the Form control”.

Now, the base class in this case is DeltaPage, and its MainContentID property is set to “PlaceHolderMain” in the DeltaPage instance constructor.

The primary (maybe the only?) reason for this modification is to support the new Minimal Download Strategy feature: the WebPartPage class adds dynamic controls to the PlaceHolderMain, rather than to the Form control, so that they become part of the Delta content associated with it.

As you can see, though, this may introduce some side effects

Let’s try so sum everything up:

  1. Server-side code blocks cannot be inserted inside a control, if the control tree of that control is changed by some other code (i.e. code behind of the page)
  2. In SharePoint 2010, the WebPartPage class manipulates the Form control tree
  3. In SharePoint 2013, the WebPartPage class may, under some circumstances, manipulate the control tree of the PlaceHolderMain control

This means that if:

  1. You have site pages with code blocks messing the page markup
  2. Code blocks are allowed for that page:
    1. Either your page is running in ghosted mode (the page and the master page are both ghosted)…
    2. … or you have excluded the page from Safe Mode execution

Then:

  1. Your page runs fine in SharePoint 2010
  2. Your page will break once migrated to SharePoint 2013 (no matter of the compatibility level the site is running under)

The solution(s)

If you have been so patient to read so far, I guess you deserve some hint about a possible solution to this issue.

I have some, indeed.

#1: Surround the code blocks with a server-side control (i.e. a <div runat=”server” />, a Panel, whatever). This way the control tree of the PlaceHolderMain will not be changed, since the code blocks have been moved to an inner control (btw, credits to Peppe for having found and applied this workaround on a project we have been working together some weeks ago). This is probably the better approach, although it may be a time consuming solution, since you are required to modify the markup of every single page that has code blocks, possibly in several different places.

#2: Do not inherit from WebPartPage, if you don’t need to. This may seem more a trick than a real solution (and… yes, it is), but it’s a quick and dirt approach that you may take into account when you have several pages and when none of these need to inherit from WebPartPage 🙂

#3: Set the MainContentId property to some nonexisting identifier (yes, another trick)

#4: Substitute code blocks with expressions, if possible (taking this to a larger extent, you may even write an expression builder that executes code blocks, but think that it’s a little bit too… expensive :))

And… I have to say #5: Get rid of code blocks. Period.

I would definitely vote for #5 (see below).

The conclusion

You may say that this issue is quite uncommon.

Yes, hopefully this is quite uncommon… but I have seen this a couple of times so far 🙁

I’m saying hopefully because, IMHO, messing the page markup with code blocks is not a nice approach even in plain ASP.NET implementations.

And more, talking about SharePoint, code blocks are not allowed for pages running in Safe Mode, which means all customized pages.

You can relax this constraint, but you open up to big security concerns, so this should be definitely discouraged 🙂

Migration to SharePoint 2013: Unable to change the Master Page of a Publishing Portal

A few days ago I was supporting a customer during the migration of the Corporate Intranet (SharePoint 2010) to SharePoint 2013.

One of the “manual configurations” that we needed to apply after the attach&mount step was to change the master page of a bunch of Team Sites, all having the Publishing Features activated.

Strangely enough, this operation succeeded on most of these sites but one, where we got the dreaded “Unexpected error”.

This error went away after performing a Version Upgrade of the site collection.

Mmhh…. I started investigating 🙂

I was just curious, so I opened up the page markup with the intention to ensure that the behavior of this Application Page was the one I expected.

Now, the “magic” of Compatibility Levels relies on having two versions of the SharePoint Root Hive (14 and 15), and on the SharePoint modules to pick up the correct version of pages and resources based on the Compatibility Level of the site collection.

Therefore, I started comparing the two versions of ChangeSiteMasterPage.aspx.

If you browse the14 version of this page, you should see something similar to the picture below:

image

Whereas this is the corresponding “15” version:

image

As you may notice, these pages are a little bit different:

  1. The 15 version supports Device Channels, which the 14 versions obviously does not (hence the difference in the Site Master Page and the System Master Page sections)
  2. The 15 version supports the propagation of Theme settings to the child webs,  which the 14 versions obviously does not (hence the new Theme section)

See, for example, the following code snippet (representing the Theme section) that is defined only in the 15 version of the page:

<!-- Theme inheritance section -->
            <wssuc:InputFormSection Title="<%$Resources:cms,areachromesettings_themeinheritance_header%>"
                Description="<%$Resources:cms,areachromesettings_themeinheritance_description%>"
                Collapsible="true"
                Collapsed="true"
                runat="server">
                <Template_InputFormControls>
                    <wssuc:InputFormControl runat="server">
                        <Template_Control>
                            <table>
                                <wssawc:InputFormCheckBox ID="inheritThemeCheckbox" LabelText="<%$Resources:cms, areachromesettings_themeinheritance_inherittheme_checkboxtext%>" runat="server"/>
                            </table>
                            <table>
                                <wssawc:InputFormCheckBox ID="resetThemeSubSitesCheckBox" LabelText="<%$Resources:cms, areachromesettings_themeinheritance_resetsubsite_checkboxtext%>" runat="server"/>
                            </table>
                        </Template_Control>
                    </wssuc:InputFormControl>
                </Template_InputFormControls>
            </wssuc:InputFormSection>

 

Now… this is perfectly fine, as long as any logic in the code behind takes the Compatibility Level into account when performing operation on the UI (i.e. referencing controls). This is typically done by checking the CompatibilityLevel property of the SPSite object:

private void InitThemeInheritanceControls(bool isInheriting, bool isRoot, CheckBox inheritCheckbox, CheckBox resetSubSitesCheckbox)
{
    if (base.Site.CompatibilityLevel >= 15)
    {
        if (isRoot)
        {
            isInheriting = false;
            inheritCheckbox.Enabled = false;
        }
        inheritCheckbox.Checked = isInheriting;
        resetSubSitesCheckbox.Checked = false;
    }
}

 

But when no check is performed against the compatibility level, the code behind should rely only on features/controls that are defined in both version of the page, right?

Now, take a look at the OnLoad event handler of the AreaChromeSettingsPage, which is the code behind class for ChangeSiteMasterPage.aspx (see Microsoft.SharePoint.Publishing.Internal.CodeBehind.AreaChromeSettingsPage in
Microsoft.SharePoint.Publishing, Version=15.0.0.0):

protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);
    this.EnsureChildControls();
    if (!this.Page.IsPostBack)
    {
        this.LoadValues();
        if (base.Web.Webs.Count == 0)
        {
            this.resetSystemMasterPageSubSitesCheckBox.Visible = false;
            this.resetSubSitesCheckBox.Visible = false;
            this.resetAlternateCssSubSitesCheckBox.Visible = false;
            this.resetThemeSubSitesCheckBox.Visible = false;
        }
    }
    base.ConfigureCancelButton(this.BtnCancel);
}

 

See what I mean?

This snippet is supposed to disable a bunch of controls (the Check Boxes that allow you to apply the new settings to all child webs) if the context web has no child web at all.

Unfortunately, there’s no CompatibilityLevel check, so the following line:

this.resetThemeSubSitesCheckBox.Visible = false;

will fail if:

  1. The Site is in 14 mode
  2. The Site has child webs

That was exactly my case.

You can easily reproduce this behavior:

  1. Create a new, out-of-the-box Publishing Portal on a SP2010 box
  2. Migrate it to a SP2013 farm
  3. Do not perform any Version Upgrade and try to navigate to the “Change Master Page” page. It should work fine, since the default SP2010 Publishing Portal has a couple of subwebs (Search and Press Releases)
  4. Delete both subwebs and try to load the page again. The page should be broken now

Funny, isnt’t it? 🙁

How to quickly identify large lists with PowerShell

It’s easy, and it’s just one line (without word wrapping J).

Get-SPWebApplication http://webappurl | Get-SPSite | Get-SPWeb |% { $_.Lists | select @{n=“Url”;e={$_.RootFolder.ServerRelativeUrl} }, Title, ItemCount } | sort ItemCount -Descending

Here I’m traversing the contents of a Web Application in order to iterate over each List (in each Web, in each Site of the Web Application):

Get-SPWebApplication http://webappurl | Get-SPSite | Get-SPWeb |% { $_.Lists | … }

Then, I’m using the Select-Object command-let to add a custom object to the pipeline, built from the list data using both standard properties (Title, ItemCount) and a calculated property for the list url:

{ $_.Lists | select @{n=“Url”;e={$_.RootFolder.ServerRelativeUrl} }, Title, ItemCount }

Finally, it’s just a matter of manipulating the custom objects collections, applying sorting, filtering, grouping or any other set operations according to your specific needs.

Here’s the output you may receive:

In addition, you may choose to bring these data into Excel for further analysis, which is extremely easy to achieve adding the Export-CSV command-let at the end of the pipeline.

Here’s a sample Excel spreadsheet generated from the data above, where I’ve applied a custom number filter to the data set and I have created a line chart on the data series:

Grouping by a multi-value Managed Metadata Field using XSLT and a DataFormWebPart

First and foremost, I think that there’s a reason why grouping (generally speaking) is usually disabled when the group-by criteria is on a multi-value field.

Performance is probably the key point here: especially when you are dealing with semi-structured data (i.e. SharePoint list items), you need to extract a set of unique values first, on which, in a second pass, you apply your filtering logics.

Also, but this is just my personal opinion, having a result set where a single item may appear more than once (if it “belongs” to more than one group) is far from optimal. I would definitely prefer to rely on refiners, rather than on pre-pupulated item groups.

And finally… where, it’s a little bit tricky J

Here’s what I have done.

Scenario.

A document library with a Managed Metadata field which allows multiple values, named “OU” (which stands for, guess what, Organizational Unit J).

A DataFormWebPart fetching all items from that library.

And, of course, XSLT!

Implementation.

I started implementing a recursive template that parses the value of the taxonomy field and produces an XML tree.

<xsl:template
name=extractOU>

<xsl:param
name=list />

<xsl:variable
name=thisId
select=substring-before($list, ‘;#’) />

<xsl:variable
name=thisName
select=substring-before(substring-after($list, ‘;#’),’;#’) />

<xsl:variable
name=left
select=substring-after(substring-after($list, ‘;#’),’;#’) />

<ou
id={$thisId}
name={normalize-space($thisName)} />

<xsl:if
test=$left>

<xsl:call-template
name=extractOU>

<xsl:with-param
name=list
select=$left />

</xsl:call-template>

</xsl:if>

</xsl:template>

When applied to a field value such as:

1;#HR;#2;#Finance;#3;#Marketing;#

It would eventually produce an XML fragment similar to this one:

<ou
id=1
name=HR />

<ou
id=2
name=Finance />

<ou
id=3
name=Marketing />

 

I applied this template to the overall result set which is returned by the list query, saving the output into a variable:

<xsl:variable
name=items
select=/dsQueryResponse/Rows/Row />

<xsl:variable
name=groupsTree>

<xsl:for-each
select=$items>

<xsl:call-template
name=extractOU>

<xsl:with-param
name=list
select=concat(normalize-space(@OU.), ‘;#’) />

</xsl:call-template>

</xsl:for-each>

</xsl:variable>

This would generate an XML fragment with duplicate elements:

<ou
id=1
name=HR />

<ou
id=2
name=Finance />

<ou
id=3
name=Marketing />

<ou
id=2
name=Finance />

<ou
id=3
name=Marketing />

<ou
id=1
name=HR />

<ou
id=2
name=Finance />

 

In order to extract a set of unique rows (a “distinct” operation) and to iterate over its output, I had to first load the XML tree into a nodeset:

<xsl:variable
name=groups
select=msxsl:node-set($groupsTree)/* />

 

I used the MSXML node-set function, which is not necessarily the best way to achieve this goal, but unfortunately I could not use XSLT extensions (without extending the DataFormWebPart, which was out of scope here) nor I could use XSLT 2.0, which is not supported as well.

On that node set I could finally apply standard XSLT grouping techniques.

In this case I chose the Muenchian method (here you can find an excellent reference).

I defined a key based on the name attribute of my ou element:

<xsl:key
name=organizational-units
match=ou
use=@name />

And I finally used that key to filter the items within the overall set:

<xsl:for-each
select=$groups[count(. | key(‘organizational-units’, @name)[1]) = 1]>

<xsl:sort
select=@name />

<xsl:variable
name=id
select=@id />

<xsl:variable
name=ou
select=@name />

<div
class=certification well>

<h2
class=subtitle text-center>

<xsl:value-of
select=$ou />

</h2>

<ul
class=unstyled inline>

<xsl:for-each
select=$items[contains(@OU., concat($id,’;#’,$ou))]>

<xsl:sort
select=@Title
order=descending />

<li
style=width:170px>

<div
class=wrapper
style=position:relative; text-align:center>

<xsl:value-of
select=@CertificationIcon0
disable-output-escaping=yes />

<div>

<a
title=@Title
href={@FileRef}
target=_blank>View</a>

</div>

</div>

</li>

</xsl:for-each>

</ul>

<div
style=clear:both></div>

</div>

</xsl:for-each>

 

As a final note, performance implications are quite noticeable here.

I have n+1 operations on the result set (1 to get the groups, n to extract values for the specific group I’m processing).

And I use a contains criteria to get the items back.

I could accept this since this library will only have a bunch of items (a few dozens), but this may not always be the case 😛

 

Moving SharePoint 2013 databases – Issue with different SQL Server Editions

If you are a SharePoint Administrator it’s possible that sometimes you need to move all SharePoint database (i.e. the configuration database, the content databases, the service application databases, etc.) to a completely different SQL Server box.

Well, it’s not something that you do on a daily basis, but you may need to perform this task in at least a couple of circumstances:

  • You are revamping the infrastructure and you have a super-powerful, brand new SQL Server cluster
  • You are performing a SQL Server consolidation (reducing the number of servers/instances)
  • You need to replicate a production environment back into the staging farm (sometimes the opposite is possible as well)

The technique is definitely feasible and is well documented in a number of places (see, for example, this page on Technet: http://technet.microsoft.com/en-us/library/cc512725.aspx).

In a nutshell, you use SQL aliases as a way of indirection.

SharePoint does not resolve the SQL instance by IP address or servername/port, but through a generic name (the alias).

You can modify the alias so that the connection is redirected to another instance, without affecting the SharePoint configuration (a part from the service interruption, of course).

That said (and coming back to the reason for this post) pay a lot of attention to the Edition of your SQL Server Box, even if you have the very same level of upgrades at the source and the target (for example, you move the databases from a SQL Server 2008 R2 + sp1 box to another SQL Server 2008 R2 + sp1 box).

SharePoint does not require the Enteprise Edition of SQL Server, but it leverages Enterpise features if these features are available!!

So if you are trying to move a database from Enterprise to Standard, you may be lucky or not according to whether any enterprise specific configuration has been applied.

Just to make an example, SQL Server Enterprise supports data compression for tables and indexes.

Some SharePoint databases make use of data compression, if it is available (for example, I verified this on the Links Store db used by the Search Service Application on a SP2013 farm).

You can revert to an uncompressed database (table/index), but I guess you will end up completely out of support (you are modifying a database directly).

So… be careful and always perform all verifications beforehand J

Deleting a Site Collection that cannot be deleted

Today I stumbled upon a strange issue on a production SharePoint 2013 Farm.

I had a bunch of site collections created using a batch script, and one of those was unreachable.

It’s quite common (well, at least… it happens sometimes J) that a site creation process may fail, resulting in resources not provisioned correctly.

Since file provisioning is one of the last operation that is performed during site creation, you may get a 404 accessing the site home page.

But this was not the case. I got a 400 response (i.e. Bad Request). I could not even navigate to application pages (which are not “provisioned” as ghosted resources).

The symptoms of something gone wrong were quite evident in several places.

The Central Administration displayed the site in the sites list, but without any reference to the Content Database where it should have been created. No way to remove it using the web interface (all pages displaying information about the site had no content at all).

Ok, let’s clean it up and remove via script.

A simple Get-SPSite returned a valid object. But a subsequent Remove-SPSite failed with the dreaded “Unknown SPRequest error.occurred”.

I had not time to investigate, so I had to find a quick solution, sort of a “force delete” where the site cannot be deleted.

Therefore I used a not-so-well-know operation on the Content Database object: Microsoft.SharePoint.Administration.SPContentDatabase::ForceDeleteSite (see http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.administration.spcontentdatabase.forcedeletesite.aspx).

The PowerShell code is definitely simple:

$site = Get-SPSite http://siteurl

$siteId = $site.Id

$siteDatabase = $site.ContentDatabase 

$siteDatabase.ForceDeleteSite($siteId, $false, $false)

As the documentation clearly states:

This way I managed to remove the corrupt site collection (and recreate it again with the same command I had used for the batch script, which completed successfully).

Hope useful J

It’s new, it’s green

And it’s finally live: the new Green Team web site!

It’s been rebuilt from scratch, using SharePoint 2010 as the CMS publishing infrastructure.

The web design was created independently from the SharePoint-isms using a Responsive Layout pattern based on plain HTML5/CSS3. Then it was “applied”, i.e. converted into SharePoint master pages and page layouts.

Most of the content targeting is driven by Managed Metadata terms and rollup techniques leveraging CQWP and Search.

There’s some multimedia content also, produced internally by our multimedia gurus Smile… they have almost succeeded in making myself an actor (take a look at the video from the home page).

Hope you like it!

Extending Base Xslt Web Parts with custom control bindings

What a long title! Maybe it requires a little bit of explanation. Here it is.

First of all, by Base XSLT Web Parts I mean a whole family of OOTB SharePoint Web Parts that leverage XSLT transformation to produce markup out of XML data coming from a data source. If you dig into the class hierarchy starting, say, from the XsltListViewWebPart or the ContentByQueryWebPart, you’ll find something like this:

image

If you navigate up the hierarchy, you’ll see that several features are implemented by the base DataFormWebPart class. One of these features is the capability to resolve dynamic parameters, i.e. values that come from query string, postback parameters and a few other sources.

More accurately speaking, the sources for dynamic parameters and the logics of parameters substitution are defined by an internal method of DataFormWebPart, named ResolveParameterBindingsToParameterValues.

Below you can find an excerpt of the method implementation:

image

As you can observe, you can actually set the ParameterBindings web part property to an XML string with proper semantics, thus inject dynamic values as XSLT parameters for the rendering transformation.

You can choose from the following sources:

  • Query String
  • Postback Parameters
  • Form Parameters
  • Web Part Connections
  • Web Part Variables
  • Resources
  • Server Variables
  • CAML Variables
  • Controls

Since XSLT and XPath are a powerful query and transformation engine, you will be able to define conditional blocks and data manipulation expressions based on these dynamic bindings.

But there’s more!

Did you notice the last binding source, the Control source?

The way it works is easy: you have to specify the server ID of an ASP.NET control available on the page and the name of one of its properties. The implementation of DataForWebPart will recursively walk the ASP.NET control tree upwards until it finds a control with the specified ID: then, with a couple of Reflection tricks the value of the specified property will be retrieved and made available to the web part rendering.

You can try this behavior adding, say, a TextBox or a DropDownList control on the page and using the value coming from the Text and SelectedValue properties respectively.

What I like most of this approach (hence the reason of this post’s title) is that you are not limited to OOTB web controls: you can “attach” pretty much any kind of control which exposes a readable property in a plain format (complex data types would be serialized using the ToString method, so the usefulness of these properties really depends on the overridden implementation of the ToString method).

By any kind of control I mean any kind of controls, including any custom control that you may develop in order to extend the XSLT* Web Parts capabilities.

From the extensibility point of view, this means that you (as a developer) can write a suite of simple web controls that are not meant to be used for their output markup, but just as a data source for existing, SharePoint web parts. Then, a colleague who is capable of building no-code solutions using SharePoint Designer will be able to leverage your extensions, using CQWPs or CoreResultsWPs without reinventing the wheel (i.e. building everything from scratch).

Nice, isn’t it?

Below you can find a sample implementation of a web control that exposes the current UI culture, and that may be useful in multilingual scenarios where you want your aggregation web parts (CQWPs, etc…) to behave differently according the the selected language.

But you can easily imagine that this technique can be applied in an infinite number of ways!

   1:  namespace GreenTeam.SharePoint2010.Multilanguage
   2:  {
   3:    public class CultureControl : Control
   4:    {
   5:      [WebBrowsable]
   6:      public Int32 UICultureLCID 
   7:      {
   8:        get
   9:        {
  10:          return Thread.CurrentThread.CurrentUICulture.LCID;
  11:        }      
  12:      }
  13:   
  14:      [WebBrowsable]
  15:      public Int32 CultureLCID
  16:      {
  17:        get
  18:        {
  19:          return Thread.CurrentThread.CurrentCulture.LCID;
  20:        }
  21:      }
  22:   
  23:      protected override void Render(HtmlTextWriter writer)
  24:      {
  25:        // Do not emit markup at all!
  26:      }
  27:    }
  28:  }