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 😛

 

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

Italian SharePoint and Office Conference 2013 – What’s new in SharePoint Designer 2013 Workflows

(again, for my non-Italian readers, I’m switching to Italian language here)

What’s new in SharePoint Designer 2013 Workflows è la terza ed ultima sessione del “trittico” sui workflow che presenteremo alla SharePoint and Office Conference 2013.

Il titolo lascia immaginare quale sarà lo strumento che io e Riccardo useremo durante la sessione.

Uno strumento con cui si realizzano workflow dichiarativi – è sempre stato così, solamente che ora sono dichiarativi anche quelli sviluppati in Visual Studio J – in modo flessibile ed espressivo.

E questa è invece un’enorme novità, se pensate che su piattaforma 2010 non c’era verso (se non con forzature non prive di difetti) di definire loop e tantomeno blocchi di esecuzione non sequenziali.

Anche flussi semplice come quello rappresentato qui sotto diventavano “scomodi” per ragioni di struttura, ancor prima che di complessità nella logica.


Per inciso, questo qui su è un diagramma Visio, che si presenta così *nella* design surface di SPD, perfettamente modificabile quanto ad azioni e relative proprietà.

Insomma, persa(*) una Design View, ecco che ne spunta un’altra 😛

 

(*) Design view and Split view (MSDN)

Description of the change.

SharePoint Designer 2010 has three views for editing HTML and ASPX pages: Code view, Design view, and Split view. Design view and Split view are removed from SharePoint Designer 2013. The removal of Design view and Split view affects the features of SharePoint Designer 2013 that are used for editing Web Parts and master pages. If you edit pages in SharePoint Designer 2013, you must use Code view.

Reason for the change.

Compared to current versions of Internet Explorer, Design view is an older technology that does not support many new HTML5 and CSS tags.

Migration path.

If you edit pages in Code view, you can press F12 to preview the page in the browser. Alternatively, you can use Visual Studio to edit pages.

If you want to visually design or brand your site, and you want a WYSIWYG (“what you see is what you get”) page-editing experience, you can use any professional HTML editor, such as Microsoft Expression Web. Then you can import your HTML files into SharePoint Server 2013 by using the new Design Manager, which is a feature included in publishing sites, such as the Publishing Portal Site Collection site template.

Italian SharePoint and Office Conference 2013 – What’s new with Workflow in SharePoint 2013

(for my non-Italian readers, I’m swithcing to Italian language here, but you can use some online translation service to have this post translated… and have fun with the results J)

Credo che tutti sappiate che il 5-6-7 marzo si terrà a Milano la SharePoint and Office Conference.

Sul sito dell’evento trovate ogni tipo di informazione, dai contenuti alla logistica.

E ovviamente potete (dovete J) registrarvi, sfruttando, se siete veloci J, l’early bird ancora valido per alcuni giorni dal momento in cui scrivo.

Quest’anno terrò quattro sessioni, in coppia con Paolo Pialorsi, Riccardo Celesti ed Elisabetta Sasselli (in rigoroso ordine di agenda J).

Sempre nello stesso rigoroso ordine di cui sopra, inizio quindi a parlarvi della prima: What’s new with Workflow in SharePoint 2013.

La prima in cui sono coinvolto io, ma la seconda di un filotto di tre, tutte incentrate sul tema Workflow, affrontato da prospettive e con premesse diverse.

Paolo inizierà le danze ed introdurrà Workflow Foundation 4.x: chi ha utilizzato (configurato, sviluppato o… troubleshootato, se si può dire) workflow su SharePoint 2010 potrebbe non aver mai avuto modo di giocare con WF4, che è invece alla base di quanto Paolo ed io illustreremo nella sessione successiva.

Le novità legate all’ambito Workflow su SP2013 sono rilevanti (e forse dire rilevanti è poco).

Se mi aveste chiesto, un annetto fa, la mia previsione su questo tema, avrei scommesso in un’introduzione di WF4 (era scontato) su un’architettura analoga all’attuale, in cui SharePoint fa da host del nuovo workflow runtime, sfruttandone ovviamente le novità e i miglioramenti.

Avrei sbagliato!

Perché i workflow in SharePoint 2010 erano così:

E con qualche *lieve* facelifting sono diventati circa così:

Per i più attenti… spot the difference J

Dedicheremo un po’ di tempo a queste slide, nella nostra sessione.

E vedremo insieme che cosa si porta dietro la nuova architettura, sia dal punto di vista dell’infrastruttura (deployment, monitoring, governance) e soprattutto (visto che Paolo è un dev e io, nonostante i miei continui sforzi, un pochettino ancora J) dal punto di vista dello sviluppatore: nuovo framework, nuove API, nuovi tool, nuove modalità.

Ci si vede a Milano!

SharePoint MVP 2013

Yesterday I received an email from my MVP Lead.
I have been renewed as a SharePoint Server MVP for the fifth year in a row 🙂
It’s a great honor and a huge pleasure for me.

I wish to congratulate with all new and renewed MVPs.

And special compliments to Igor (my friend, colleague and boss @ Green Team) and Robi Voncina, whom I met in Slovenia during the local SharePoint Events and who has been awarded for the first time.
Thank you!