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

0  

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 :)

0  

PowerShell Profiles and Processor Architectures

I’m using PowerShell profiles quite extensively, especially because I’m typically working on several (virtual) machines and profiles are definitely easy to keep settings in sync.

Profiles are independent from the Processor Architecture, which means that you should be careful when loading snap-ins or invoking features that rely on a specific architecture.

One way to ensure that your profile scripts run smoothly on both x86 and x64 processes is to apply conditional logics based on the PROCESSOR_ARCHITECTURE environment variable ($env:PROCESSOR_ARCHITECTURE).

I would suggest you to perform this check even if you think that you will only be executing scripts with the x64 Shell.

You may think “no, I’ll never be using the x86 version of PowerShell”… you’re probably right, until you try to launch a script from within the Visual Studio IDE :(

Uffa

0  

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 :)

0  

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? :(

1  

PowerShell “here” strings

I’m using verbatim strings in C# quite a lot. Even when I have no explicit need, for example because there’s no escape sequence nor line break and I could just avoid typing the extra @ at the beginning.

I definitely like the fact that there’s a similar syntactic behavior in PowerShell too.

These are called “here” strings (more information here).

Although these two features have much in common, you have to pay some more attention when using here-strings in PowerShell, since the behavior is not *exactly* the same you get when you write C# code.

For example, line breaks are mandatory.

The “start sequence” tokens are indeed @”+CRLF or @’+CRLF.

And the same applies to “end sequence” tokens as well (CRLF+@” or CRLF+@’ respectively).

And in case you are wondering what’s the difference between the single quote vs. double quote syntax, well… the single quote version disables escape sequences, whereas the double quote version does not.

Strings have always been tricky.

When I started writing C/C++ code against the Windows API I was struggling with LPSTR, LPTSTR, LPCSTR, LPCTSTR to name a few (probably any combination of characters here would be a valid name, I’m sure there’s a typedef or a MACRO definition somewhere even for LPABCSTR :) ).

Then came COM and BSTR and smart pointers and… and that was real pain!!

0  

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:

0  

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 :P

 

0  

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

0  

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

0