Fundaments of planning your beautiful SharePoint web site

This article is all about preparation. It’s about the thinking and planning you need to do if you’re going to successfully build your wonderful, unique and striking website on the SharePoint platform.

I’ve been helping customers implement SharePoint solutions for quite a while. Life gets interesting when those customers want to use SharePoint to host their public website or an intranet of published content. SharePoint is a great platform with a host of powerful features that make it a solid choice for large or complex websites, sites that have to deal with large volumes of traffic or simply sites that need real business processes wrapped around the publishing model. Much of my time in these scenarios is spent helping the customer prepare and plan, and I’d like to share some of my experience.

We’ve planned the site…

site plan

How many times do you visit the customer and they greet you with an enthusiastic ‘We’ve planned the site and documented it for you’? I get twitchy when I hear that, because it usually involves me being handed a piece of paper that looks like the picture above. That would be great if it was a starting point, but many times the customer genuinely believes that’s all they need to tell me and the creative agency, and from that tiny post-it note our creative minds will issue forth the next great site on the web.

SharePoint rewards attention to detail

You can’t treat SharePoint like an old web server and expect to get away with it. Treat a web site as a vague collection of folders with pages in that present content of some kind to the reader and you will quickly find yourself in a pit of despair. There are four areas that demand your attention – attend to these with diligence and the technical solutions will be much easier to determine.

Before you start, read Don’t make me think by Steve Krug and The Elements of Content Strategy by Erin Kissane. Both are quick reads that will help you immeasurably.

Content Strategy

Content Strategy is all about identifying what content you have, describing it, identifying who owns it and what its lifecycle is. It’s about discerning the difference between a product datasheet, press release, case study and staff biography. In SharePoint terms, it’s all about content types. What information do we store and how? What columns constitute a press release, and is it based on an article page or an item?

I find that full-service creative agencies that are used to writing copy, be it for print or the web will understand this already. Creative agencies that are more focused on visual design, be it for the web or otherwise tend to struggle with the concepts of content strategy. Once you’ve got them on board, however, their lives are much easier as well: Now we know that we have those four kinds of content then the creative agency can choose to design unique ways to display them.

Design

Knowing what the different kinds of content are will invariably help the creative guys. Now they know that they have to design ways to present each of the different kinds of content – a product page will look very different from the Chief Exec’s Blog. This will help you to answer SharePoint questions like how many masterpages and page layouts and will start to guide your thinking in terms of site structure.

User Experience

Not only do we want to know what the site looks like, we need to think about how users will interact with it. Do we want to use clever icons for navigation? Do we need to present content based on what we know about the user – age, gender, role, etc? We will almost certainly need to build something bespoke to deliver the user experience which means we need requirements so put plenty of detail into describing how things will be expected to work.

Information Architecture

There are some great books on IA out there. SharePoint places additional constraints on projects though: Perhaps our security needs mean that we must create separate site collections for content. Maybe we want radically different design for certain content which means different masterpages and separate sites. Certainly we should avoid simply pouring all our content into one large pot, but if we need to aggregate items on our homepage what implications does that have on our structure?

Measure twice, cut once

If all the steps above sound like a lot of planning then you’d be correct. However, convincing the customer to pay for a planning phase up front will save everyone time and money later. It’s important to make sure that the creative agency understands that the planning phase is critical to them as well – why rush off and design something beautiful when any of those four elements above may throw the whole design into disarray?

The SharePoint Solution

Each of the four areas influences one another and each in turn influences your SharePoint solution design. Technical and budgetary constraints in this area will undoubtedly cause you to revise your plans, but without the information gathered in those four areas of planning you won’t have enough detail to accurately specify and estimate the project, let alone deliver it successfully. In order to deliver, we as practitioners need to understand those four key areas, especially if our customers don’t.

Useful Reading

Books you may find useful when tackling those four planning areas:

Displaying a SharePoint 2010 library on a page in a different site within the same site collection

One of our customers contacted us the other day with a problem. They wanted to put a view of a document library that was located in the top level site of a site collection onto the landing pages of all the second level sites in that collection. The customer had consulted the internet hive mind and found a blog post with instructions which had been diligently followed and yet whenever a user clicked ‘New’ on the ribbon bar an error occurred.

After a bit of poking and prodding on my local test system I not only replicated the fault but also found the cure. Since none of the other sites on the internet that I found mentioned this, I thought I’d better write it up for the world.

 

The screenshot below shows you our goal. The ‘Main Site Shared Documents’ web part actually points to a document library that is in the site above the one we are in.

Group Site with external document library web part

Step 1: Export the original list web part with SharePoint Designer 2010

In order to place the view we need on our page we need a web part. To get that we have to edit the page of the view of our document library in SharePoint designer.

Navigate to the view of your document library and in the ribbon part choose ‘Modify In SharePoint Designer (Advanced) from the drop down ‘Modify View’ menu.

modify view in designer

 

Once you have the page open in SharePoint Designer, you need to select the XsltListViewWebPart that is showing the contents of the library.

xsltlistviewwebpart

With the web part selected you will be able to switch Designer’s ribbon to the Web Part tab and choose ‘To File’ from the Save Web Part panel. Saving to the Site Gallery won’t work – I tried and SharePoint complained every time I added the web part to a page.

When you save the web part SharePoint Designer will throw up a dialog box:

export web part question

You need to answer ‘Yes’ to this question because we want the web part to always point at our target document library, no matter where we use it.

save web part to file

Step 2: Add the web part to your page

Edit your target page and add a web part to your intended zone. When the web part panel comes up, you will see ‘Upload a Web Part’ in small text beneath the Categories panel and clicking that will expand the control to give you a file browser.

Browse to the web part you saved from SharePoint Designer and then Upload the file. The web part picker will close at this point. Don’t panic!

upload a web part

Go through the steps to add a web part again and this time in the Categories panel you will have a new item at the top called Imported Web Parts. Select that and your web part will appear in the list so you can add it to the page.

add an imported web part

Step 3: Change the toolbar setting on the web part

This is the important bit! When you add the new web part to your page everything appears to work fine. Documents from the document library open fine and clicking the ‘Add a new document’ link will open the upload dialog as they should. However, if you use the ribbon bar then you will notice very quickly that it is not the correct ribbon bar for the library – the items on the New menu will be wrong (if you have custom content types that’s easier to spot!) and trying to use the buttons will cause errors.

The solution to this conundrum turned out to be straightforward – simply change the toolbar setting of the web part. If you choose ‘Full Toolbar’ or ‘Show Toolbar’ you will see the erroneous behaviour; choose ‘Summary Toolbar’ or ‘No Toolbar’ and all will work fine.

broken menu with web part settings

The setting shown above on the web part will give you a broken menu (the other web part on the page is a calendar, so I get appointments!)

working menu with web part settings

The setting shown above works just fine.

Making SharePoint 2010 search pages work with a proper master page

If you talk to those who know me about my pet hates in SharePoint, the search pages will come up every time. It’s not that I hate search itself – that’s great, but the minimal.master is just plain annoying. It makes the search centre a black hole into which you can fall but not navigate out of – there’s no navigation and the portal site connection doesn’t work.

I’ve been meaning to get search working with a real master page (step forwards, v4.master) for a while and never got around to it. However, I needed to fix it for a demo and having done so I thought I’d document the steps involved for the world.

To set the scene, here are some screenshots of the default Enterprise Search Centre pages:

default searchcentredefault searchresultsdefault peopleresults

If you climb into site settings and change the Site Master Page to v4.master, this is what you get:

v4 master search page

That’s not very usable, is it. Moreover, if you click the breadcrumbs/portal site connection icon in the top bar you see the following:

v4 master search page with search box

That’s just barking mad, frankly. However, it does give us a solid clue as to how we go about fixing the page.

Step 1: Fix the layout pages

I had a rummage around the search centre’s masterpage folder and found the cause of our problems. Some bright spark decided to use the PlaceHolderTitleBreadcrumb as the content area to put the search box in. That speaks more about the structure of the minimal.master page than the search layout pages, to be fair. It is most definitely not where it should be, however.

Using SharePoint Designer, open your Search Centre SharePoint site and look in the _catalogs\masterpage folder. In there you will see three page layouts: SearchMain.aspx, SearchResults.aspx and ReopleSearchResults.aspx.

sharepoint designer masterpages highlighted

Let’s do SearchMain first – the site landing page. Right-click the file and choose Edit File in Advanced Mode

Step 1a: Move the search box to the right place

Find the line that reads:

<asp:Content ContentPlaceHolderID="PlaceHolderTitleBreadcrumb" runat="server">

You want to cut the markup that is inside that content placeholder:

<SharePoint:UIVersionedContent UIVersion="3" runat="server">
<ContentTemplate>
<div style="height:100%; width:100%;padding-left: 18px; padding-top: 50px; padding-bottom: 10px;">
<center>
<div style="width: 510px">
<SPSWC:ListBoundTabStrip ID="Tab" runat="server" PersistQueryString="true" CSSClassNamePrefix="ms-sctab" ListName="<%$Resources:Microsoft.Office.Server.Search,SearchCenterOnet_SearchCenterListName%>" ResourceIdforListName="$Resources:Microsoft.Office.Server.Search,SearchCenterOnet_SearchCenterListName" UnselectedTabTrimLength="-1"></SPSWC:ListBoundTabStrip>
<div style="padding-top: 0px">
</ContentTemplate>
</SharePoint:UIVersionedContent>
<SharePoint:UIVersionedContent UIVersion="4" runat="server">
<ContentTemplate>
<div class="srch-sb-main">
<div class="srch-sb-results4">
<div>
<SPSWC:ListBoundTabStrip ID="Tab1" runat="server" CSSFileName="Themable/search.css" PersistQueryString="true" CSSClassNamePrefix="ms-sctab" ListName="<%$Resources:Microsoft.Office.Server.Search,SearchCenterOnet_SearchCenterListName%>" ResourceIdforListName="$Resources:Microsoft.Office.Server.Search,SearchCenterOnet_SearchCenterListName" UnselectedTabTrimLength="-1"></SPSWC:ListBoundTabStrip>
</div>
<div class="srch-sb-results6">
</ContentTemplate>
</SharePoint:UIVersionedContent>
<WebPartPages:WebPartZone runat="server" AllowPersonalization="false" FrameType="TitleBarOnly" Title="<%$Resources:Microsoft.Office.Server.Search,LayoutPageZone_TopZone%>" ID="TopZone" Orientation="Vertical" QuickAdd-GroupNames="Search" QuickAdd-ShowListsAndLibraries="false"><ZoneTemplate>
<WpNs0:SearchBoxEx runat="server" __MarkupType="xmlmarkup" WebPart="true" __WebPartId="{C00D0719-CF8A-47A2-9E31-FA3F6A38948F}" >
<WebPart xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.microsoft.com/WebPart/v2">
  <Title>Search Box</Title>
  <FrameType>None</FrameType>
  <Description>Displays a search box that allows users to search for information.</Description>
  <IsIncluded>true</IsIncluded>
  <ZoneID>TopZone</ZoneID>
  <PartOrder>1</PartOrder>
  <FrameState>Normal</FrameState>
  <Height />
  <Width>800px</Width>
  <AllowRemove>true</AllowRemove>
  <AllowZoneChange>true</AllowZoneChange>
  <AllowMinimize>true</AllowMinimize>
  <AllowConnect>true</AllowConnect>
  <AllowEdit>true</AllowEdit>
  <AllowHide>true</AllowHide>
  <IsVisible>true</IsVisible>
  <DetailLink />
  <HelpLink />
  <HelpMode>Modeless</HelpMode>
  <Dir>Default</Dir>
  <PartImageSmall />
  <MissingAssembly>Cannot import this Web Part.</MissingAssembly>
  <PartImageLarge />
  <IsIncludedFilter />
  <ExportControlledProperties>true</ExportControlledProperties>
  <ConnectionID>00000000-0000-0000-0000-000000000000</ConnectionID>
  <ID>g_c00d0719_cf8a_47a2_9e31_fa3f6a38948f</ID>
  <GoImageUrl xmlns="urn:schemas-microsoft-com:SearchBoxEx">/_layouts/images/gosearch30.png</GoImageUrl>
  <GoImageUrlRTL xmlns="urn:schemas-microsoft-com:SearchBoxEx">/_layouts/images/gosearchrtl30.png</GoImageUrlRTL>
  <GoImageActiveUrl xmlns="urn:schemas-microsoft-com:SearchBoxEx">/_layouts/images/gosearchhover30.png</GoImageActiveUrl>
  <GoImageActiveUrlRTL xmlns="urn:schemas-microsoft-com:SearchBoxEx">/_layouts/images/gosearchrtlhover30.png</GoImageActiveUrlRTL>
  <ShowAdvancedSearch xmlns="urn:schemas-microsoft-com:SearchBoxEx">true</ShowAdvancedSearch>
  <DropDownModeEx xmlns="urn:schemas-microsoft-com:SearchBoxEx">HideScopeDD</DropDownModeEx>
  <IsMysiteSearchBox xmlns="urn:schemas-microsoft-com:SearchBoxEx">false</IsMysiteSearchBox>
  <TextBoxWidth xmlns="urn:schemas-microsoft-com:SearchBoxEx">368</TextBoxWidth>
  <ShowPerferenceLink xmlns="urn:schemas-microsoft-com:SearchBoxEx">true</ShowPerferenceLink>
  <ShowQuerySuggestions xmlns="urn:schemas-microsoft-com:SearchBoxEx">true</ShowQuerySuggestions>
  <SearchBoxVisual xmlns="urn:schemas-microsoft-com:SearchBoxEx">SearchCenterDefault</SearchBoxVisual>
  <AdvancedSearchPageURL xmlns="urn:schemas-microsoft-com:SearchBoxEx">/searchcentre/Pages/advanced.aspx</AdvancedSearchPageURL>
  <SearchResultPageURL xmlns="urn:schemas-microsoft-com:SearchBoxEx">results.aspx</SearchResultPageURL>
  <RegisterStyles xmlns="urn:schemas-microsoft-com:SearchBoxEx">true</RegisterStyles>
  <ShouldTakeFocusIfEmpty xmlns="urn:schemas-microsoft-com:SearchBoxEx">true</ShouldTakeFocusIfEmpty>
</WebPart>
</WpNs0:SearchBoxEx>
</ZoneTemplate></WebPartPages:WebPartZone>
</div>
</div>
<SharePoint:UIVersionedContent UIVersion="3" runat="server">
<ContentTemplate>
</center>
</ContentTemplate>
</SharePoint:UIVersionedContent>
</div>

That code needs to be pasted into the PlaceHolderMain content area, just after the markup to open the content area:

<asp:Content ContentPlaceHolderID=”PlaceHolderMain” runat=”server”>

Save the file and take a look, and you should see this:

edited search centre 1

It’s not quite right – everything’s shifted to the left. We’ll come back to that because it’s not fixable by editing the layout pages.

You’ll also notice that there are no breadcrumbs at all so no portal connection.

Step 1b: Fix the breadcrumbs

We’ll need to get the correct code for the breadcrumbs from another layout page. I grabbed it from the article page layout. It needs to go inside the PlacholderTitleBreadcrumb content area markup.

<SharePointWebControls:VersionedPlaceHolder UIVersion="3" runat="server">
<ContentTemplate>
<asp:SiteMapPath ID="siteMapPath" runat="server" SiteMapProvider="CurrentNavigation" RenderCurrentNodeAsLink="false" SkipLinkText="" CurrentNodeStyle-CssClass="current" NodeStyle-CssClass="ms-sitemapdirectional"/>
</ContentTemplate>
</SharePointWebControls:VersionedPlaceHolder>
<SharePointWebControls:UIVersionedContent UIVersion="4" runat="server">
<ContentTemplate>
<SharePointWebControls:ListSiteMapPath runat="server" SiteMapProviders="CurrentNavigation" RenderCurrentNodeAsLink="false" PathSeparator="" CssClass="s4-breadcrumb" NodeStyle-CssClass="s4-breadcrumbNode" CurrentNodeStyle-CssClass="s4-breadcrumbCurrentNode" RootNodeStyle-CssClass="s4-breadcrumbRootNode" NodeImageOffsetX=0 NodeImageOffsetY=353 NodeImageWidth=16 NodeImageHeight=16 NodeImageUrl="/_layouts/images/fgimg.png" HideInteriorRootNodes="true" SkipLinkText="" />
</ContentTemplate>
</SharePointWebControls:UIVersionedContent>

However, if you save the page and view it now you’ll get an error, because that markup references controls that the page doesn’t know about. In order to register them, we need to add a line into the section at the top of the page code that registers tag prefixes:

<%@ Register Tagprefix="SharePointWebControls" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

The SearchMain.aspx layout page will  now work with the v4.master selected.

You’ll notice, however, that the ribbon isn’t quite right. That’s because it’s actually in twice – one in the v4.master and once in SearchMain.

Step 1c: Fix the ribbon

Find the following markup:

<asp:Content ContentPlaceHolderID="SPNavigation" runat="server">

After that line you’ll see another that reads:

<SharePoint:UIVersionedContent UIVersion="4" runat="server">

Right-click the tag and choose ‘select Tag’ from the context menu. Remove the highlighted code and you should be left with an empty SPNavigation content area. If you look at the page now, the ribbon works as it should.

Run through the same steps with the other search pages. I’m a bit paranoid, so whilst I’m pretty sure the content of the PlaceHolderTitleBreadcrumb is the same for all the layout pages, I always copy and paste the code within the page itself, then add the breadcrumb code afterwards.

You should now have a Search Centre that looks like this:

edited search centre 2edited searchresultsedited peopleresults

Step 2: Fix the master page

I said that the annoying shift to the right couldn’t be fixed in the layout pages. We need to edit the master page for that (and/or the styles if you like, but the master page is quicker for just one site).

Remember: Editing the v4.master page will prevent it being updated if Microsoft release SharePoint patches that include a new version, as we’ve modified the local copy in the site. If you want to be a good SharePoint admin, create a new master page for the search site!

This bit’s easy. Search for the line:

<div class="s4-ca s4-ca-dlgNoRibbon" id="MSO_ContentTable">

Edit it to read:

<div class="s4-ca s4-ca-dlgNoRibbon" id="MSO_ContentTable"  style="margin-left:0;">

All we’ve done is added a local styling rule to override the ones in the main stylesheets and set the left margin on that element to zero.

Save the master page and you should now get pages that look like this:

edited search centre 3edited searchresults 2edited peopleresults 2

Still to do…

Before you all shout at me, I know that the breadcrumbs don’t display like they ought to. I need to look into that – I remember reading somewhere that they work differently on pages in a page library than elsewhere. I need to do some research and see if I can fix that. Hey – isn’t ditching the minimal.master enough for you?

Next on the hit list – Access Services sites using minimal.master!

Content Types programmatically added to SharePoint libraries not appearing on New menu

This one caused some consternation, I can tell you. As usual, the solution could be found on the great wide web, but it took some digging, so as usual I am repeating it here.

As part of a SharePoint migration we did recently, we replaced a SharePoint 2007 feature that the client was using (which added content types to libraries from a central list) with a mix of content type replication and PowerShell to add the content types to the libraries.

The code below scans through any site collections whose url begins with our $hostheader variable, then adds the list of content types to the Shared Document library in every web in the site collection. It’s a simple modification of some code on Phil Childs’ Get-SPScripts.com site and I take no credit for it – it’s all Phil’s work.

# Specify name of the library to look for in each site
#
$hostheader = "http://mywebapp*"
$lookForList = "Shared Documents"
#find our site collections and run through each in turn
# get all the site collections, then step into each site in each site collection
# find the list with the name in the var lookforlist
# then change content types on the list specified
#
# note - this will only find site collections where the url starts with the host header var
#
get-spsite where {$_.url -like $hostheader}| Get-SPWeb -Limit all | ForEach-Object {
    write-host "Checking site:"$_.Title
    #Make sure content types are allowed on the list specified
    $docLibrary = $_.Lists[$lookForList]
    if ($docLibrary -ne $null)
    {
        $docLibrary.ContentTypesEnabled = $true
        $docLibrary.Update()
        # Add site content types to the list
                # change the name in the quotes to the name of your content type
                #
        $ctToAdd = $site.RootWeb.ContentTypes["Word Document"]
        $ct = $docLibrary.ContentTypes.Add($ctToAdd)
        write-host "Content type" $ct.Name "added to list" $docLibrary.Title
                # 
        # Add second site content types to the list
                # change the name in the quotes to the name of your content type
                #
        $ctToAdd = $site.RootWeb.ContentTypes["Excel Spreadsheet"]
        $ct = $docLibrary.ContentTypes.Add($ctToAdd)
        write-host "Content type" $ct.Name "added to list" $docLibrary.Title
                # 
        # Add third site content types to the list
                # change the name in the quotes to the name of your content type
                #
        $ctToAdd = $site.RootWeb.ContentTypes["PowerPoint Presentation"]
        $ct = $docLibrary.ContentTypes.Add($ctToAdd)
        write-host "Content type" $ct.Name "added to list" $docLibrary.Title
                # 
        # Update the library object to commit changes
                #
        $docLibrary.Update()
    }
    else
    {
        write-host "The list" $lookForList "does not exist in site" $_.Title
    }
}

When we ran this through, however, whilst the content types were added correctly to the libraries, the New menu failed to list them.

Much (and I mean much!) digging revealed the cause to be down to a property in the library (spList.RootFolder.UniqueContentTypeOrder) that isn’t automatically set when we add the content types using code (which makes sense when you think about it…). However, all our fiddling with PowerShell failed to work. Adding a content type to the property (which is an array of content types) steadfastly refused to work.

We then found a post on the TechNet forums which appeared to give the answer in the form of C# code. We spent a long time on this, so to cut it short: You can’t simply add an item to the UniqueContentTypeOrder property – you have to set it to Null and rebuild it. The trouble is that in order to do that you have to stuff in an object that has an iList interface. Try as we might, we couldn’t create a PowerShell object that would allow us to store an array of ContentTypes and present the iList interface to the UniqueContentTypeOrder property to set the New menu values. Many people said that ArrayList or SortedList should do it, but they didn’t.

In the end, then I got our devs to knock up a rough and ready command line tool based on the code in the TechNet post. It’s really rough and ready, so I won’t post it here. Follow the posting and get your own tame devs to do the same. I only hope that this article becomes easier to find on the web to save you guys some time.

Content type replication not working on imported or migrated site collections

A while ago I posted about a hidden feature that was needed if you want to use Managed Metadata columns in your SharePoint 2010 sites. We were doing some 2007-2010 migration work for a client recently that also involved exporting and importing sites and site collections to rework the content structure. Once we’d got the new structure sorted we discovered that content type replication was not occurring on the site collections we had imported. Some comparison of working and non-working SPSite properties with PowerShell later, we discovered that the culprit was the same hidden TaxonomyFieldAdded (ID 73ef14b1-13a9-416b-a9b5-ececa2b0604c) feature as I noted earlier. See my earlier post for instructions on enabling the feature.

As an addendum to my earlier post, which has code to enable the feature on a single site, Andy gave me the following snippet that will do it on all sites:

Get-SPSite -limit ALL |foreach{ Enable-SPFeature "73ef14b1-13a9-416b-a9b5-ececa2b0604c" -url $_.URL }

I don’t mind hidden features, per se, but when they’re not even logically named so I can associate them with their purpose, it’s annoying and time wasting. Why can’t we simply have a Content Type Subscriber feature that’s not hidden?

Demonstrating Avviso at NEBytes on 23rd February 2011

avvisohead

I am really chuffed to have been invited back to NEBytes for a follow-up to my last session on content publishing with SharePoint 2010. This time I’ll be demonstrating Avviso and talking about the thought processes that lead to its development, the problems we are trying to solve and where we’d like to go next.

If you’d like more information about Avviso, take a look at my recent post. To see more about NEBytes and their events, check out their web site.

Powershell to find missing features in SharePoint 2010

When migrating from SharePoint 2007 to 2010, no matter how hard you try there’s always the chance the the content database upgrade process will throw out errors about features being referenced that are not present in the farm. We have used Stefan Goßner’s WssAnalyzeFeatures and WSSRemoveFeatureFromSite (see his original article) to track down the references and exterminate them. It’s not the fastest thing on two legs though, and I have a fondness for having my SharePoint 2010 tooling in PowerShell because of the flexibility it gives me.

Here then, with a big hat-tip to Stefan, is the PowerShell to replicate his functionality. We differ slightly, in that I wanted one command to purge both site- and web-referenced features in one shot rather than calling a command with a scope switch. I have two functions – get-spmissingfeatures takes a Site Collection url as a parameter and scans through site and web feature collections, listing any features with a null definition property. The second function, remove-spmissingfeatures takes the same url and this time finds and removes the errant features in one pass.

Get-SpMissingFeatures:

function get-spmissingfeatures([string]$siteurl)
{
  $site = get-spsite $siteurl

  foreach ($feature in $site.features) {
    if ($feature.definition -eq $null) {
       write-host "Missing site feature:"
       write-host $feature.DefinitionId
       write-host $feature.parent
#      $site.features.remove($featureid)
    }
  }

  $webs = $site | get-spweb -limit all
  foreach ($web in $webs) {
  foreach ($feature in $web.features) {
    if ($feature.definition -eq $null) {
       write-host "Missing web feature:"
       write-host $web.url
       write-host $feature.DefinitionId
       write-host $feature.parent
#      $site.features.remove($featureid)
    }
  }

  }

}

Remove-SPMissingFeatures:

function remove-spmissingfeatures([string]$siteurl)
{
  $site = get-spsite $siteurl

  foreach ($feature in $site.features) {
    if ($feature.definition -eq $null) {
       write-host "Missing site feature:"
       write-host $feature.DefinitionId
       write-host $feature.parent
      $site.features.remove($feature.DefinitionId)
    }
  }

  $webs = $site | get-spweb -limit all
  foreach ($web in $webs) {
  foreach ($feature in $web.features) {
    if ($feature.definition -eq $null) {
       write-host "Missing web feature:"
       write-host $web.url
       write-host $feature.DefinitionId
       write-host $feature.parent
      $web.features.remove($feature.DefinitionId)
    }
  }

  }
}

Enabling the TaxonomyFieldAdded feature to fix ManagedMetadata Column errors

We’re working on a solution at the moment that uses a custom site definition. For various reasons we stated with the Blank Site definition and worked from there. Our customisations include content types using custom columns that link to managed metadata term sets. We create all those through features – great! The tricky bit came when after deployment our managed metadata columns were greyed out. Examining the column we say an error telling us that the feature supporting the functionality was not activated.

What feature?

After quite a bit of hunting it turns out that there is a site collection feature called TaxonomyFieldAdded (ID 73ef14b1-13a9-416b-a9b5-ececa2b0604c) that is needed to allow the column to connect to the term set. The problem is that it’s a hidden feature so you can’t switch it on through the web site. Obviously we can enable it programmatically, but for anybody who has this problem (can’t connect a custom column to a term set) here is some simple PowerShell to enable it:

get-spfeature | where {$_Id –eq “73ef14b1-13a9-416b-a9b5-ececa2b0604c”} | enable-spfeature –url <site collection url>

Powershell script to rename files for use as SharePoint 2010 User Profile thumbnails

User profile photos have changed in SharePoint 2010 in that they are now stored in a single image library in the MySite Host root site collection. They have also changed in that when you change the profile photo, SharePoint takes the file and creates three new images at specific sizes, then discards the file you gave it. These files have specific names to link them to the user account and come in small, medium and large flavours.

We’ve just delivered a solution to a customer that involved heavy customisation of the Profile page for users. This also involved replacing the large thumbnail version of the profile picture with one which met our size requirements.

The customer had a large group of image files all named in the pattern <firstname surname>.<extension> which had been loaded in to SharePoint as profile pictures. We wanted a quick way to replace the large thumbnail with our own version.

Enter Powershell, stage left. The script below is a little rough and ready but works great. It gets a directory listing, splits the filename and then looks up in AD to see if there’s a user that matches the filename (sans extension). If it finds a match it renames the file to match the pattern <domain>_<SamAccountName>_LThumb.<extension> to match the profile picture naming convention.

As I said, it’s a little rough and ready but I place here for the greater good. You need the ActiveDirectory powershell module to use this. It’s available on Server 2008 and above, and Windows 7 if you install the remote management tools.

The Active Directory Powershell Blog is a great resource for this stuff!

#import-module ActiveDirectory
if (-not (get-module -name activedirectory)) {
    write-host "This script requires the ActiveDirectory powershell modules to run"
    exit
}
$domain="mydomain"
$filesuffix = "LThumb"
$files = get-childitem
foreach ($file in $files) {
    $filesplit = $file.Name.split(".")
    $fullname = $filesplit[0]
    $fileext = $filesplit[1]
    write-host "Searching for:" $fullname
    $user = get-aduser -Filter { Name -eq $fullname }
    if ($user.SamAccountName -eq $Null) {
        write-host "Not Found!"
    } else {
        $newfilename = $domain+"_"+$user.SamAccountName+"_"+$filesuffix+"."+$fileext
        write-host "Renaming:" $file.name "New name:" $newfilename
        rename-item $file.Name $newfilename
    }
}