Event Receiver to Remove “Recent” from SP2013 Quick Launch

I’m sure removing the Recent heading from the Quick Launch in SharePoint 2013 has been talked about a million times over since SharePoint 2013 was launched. It’s been solved in this way and that way, by hand, with javascript and programmatically. In this post I share the code to remove the heading with the ListAdded event receiver.

With and without the Recent heading on the Quick Launch navigation.
With and without the Recent heading on the Quick Launch navigation.

Event receiver code

It’s based on code provided as an answered on the SharePoint StackExchange website by Remko van Laarhoven. I’ve then wrapped in a list added event receiver so that it is executed each time a new list or library is added. As the list/library is then not available on the Quick Launch I’ve added a couple of lines to then show it.

Update (22nd June 2014): since creating this solution I have since discovered that the Recent heading still is created even with the event receiver triggering when lists/libraries created from templates. I resolved this by adding a sleep before the code to remove the heading is executed.
[code language=”c”]System.Threading.Thread.Sleep(1500);[/code]

This in conjunction with the jQuery method prevent users from ever seeing the Recent Heading whether they are viewing the page or editing the links on the page.
[code language=”js”]
// Hide Recent on Quick Launch
$("#ctl00_PlaceHolderLeftNavBar_QuickLaunchNavigationManager .ms-core-listMenu-root li:contains(‘Recent’)").children().remove();
$("#ctl00_PlaceHolderLeftNavBar_QuickLaunchNavigationManager .ms-core-listMenu-item:contains(‘Recent’)").remove();
[/code]

[code language=”c”]
public class ListAddedEventReceiver : SPListEventReceiver
{
public override void ListAdded(SPListEventProperties properties)
{
base.ListAdded(properties);
SPWeb web = properties.Web;
if (web != null)
{
//Sleep
System.Threading.Thread.Sleep(1500);

//Remove heading
var title = SPUtility.GetLocalizedString("$Resources:core,category_Recent", null, web.Language);
SPNavigationNodeCollection nodes = web.Navigation.QuickLaunch;
foreach (SPNavigationNode node in nodes)
{
if (node.Title.ToLower().Equals(title.ToLower()))
{
// Delete the recent heading node
node.Delete();
break;
}
}
// Show list on the quick launch
SPList list = web.Lists[properties.ListId];
list.OnQuickLaunch = true;
list.Update();
}
}
}
[/code]

Download Remove Recent Heading Solution

For those who don’t want to create the event receiver themselves in Visual Studio or don’t know how to, I have a packaged the solution so that you can deploy the WSP to your environment. For those who don’t know how to use this code, I will write a post explaining how to create this event receiver using Visual Studio from an IT Pros perspective very soon.

jcallaghan.removerecentheading.wsp

As with anything you download from the internet remember to review, rename and test this code/solution before using it in a production environment.

Working with SharePoint’s Second Stage Recycle Bin in PowerShell

I thought I’d share a PowerShell script that I’ve created to perform a few tasks against a Site Collection Second Stage Recycle Bin (SSRB) in SharePoint.

Remove-SecondStageRecycleBinItems.ps1
Remove-SecondStageRecycleBinItems.ps1

The requirement was to delete items that were older than a set number of days from the Second Stage Recycle Bin (SSRB). A record of each item deleted also needed to be added to a report.  But SharePoint can do this already I hear you say…well yes if a Site Collection quotas and the auditing features are used. In this scenario neither could be.

To display items in the Second State Recycle Bin in a table I used this command.

[code lang=”powershell”]$site.Recyclebin | where { $_.ItemState -eq "SecondStageRecycleBin" -and $_.deleteddate -le $dateDiff} | Format-Table -Property Title, Web, DeletedBy, DeletedDate -Autosize -Wrap
[/code]

Then to remove each item from the Recycle Bin I used the delete command.

[code lang=”powershell”]$site.Recyclebin.Delete($_.ID)[/code]

The full script is shared below. Remember to review, rename and test this script before using it in a production environment.

[code lang=”powershell”]
#——————————————————————————–
#
# Remove-SecondStateRecycleBinItems.ps1
#
# Author: James Callaghan (www.jcallaghan.com)
# Date: April 2014
#
# This script will delete items from the Second Stage Recycle Bin that
# are older than XX days.
#
#——————————————————————————–

Add-PSSnapin Microsoft.SharePoint.PowerShell

#Variables
$i = 0

#SharePoint Site Collection URL
$url = "http://apm.dev.jcallaghan.com"
#$url = Read-Host "Enter a valid URL to a SharePoint Site Collection?"
#if($url -eq ""){write-host "No URL provided." -foregroundcolor Red; Exit}

#How many days ago should items be deleted from?
$deleteFrom = -10
#$deleteFrom = Read-Host "Remove items older than how many days?"
#if($deleteFrom -eq ""){write-host "No value provided." -foregroundcolor Red; Exit}

#Create report in script path
$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath
$report = "$($dir)\DeletedSecondStateRecycleBinItems.csv"

#Date calculations
$dateNow = Get-Date
$dateDiff = $dateNow.AddMinutes(-$deleteFrom)
#$dateDiff = $dateNow.AddDays($deleteFrom)

#Display date/times for review in table
$table = @()
$review = New-Object System.Object
$review | Add-Member -type NoteProperty -Name "Date" -Value "Timestamp now"
$review | Add-Member -type NoteProperty -Name "Value" -Value $dateNow
$table += $review
$review = New-Object System.Object
$review | Add-Member -type NoteProperty -Name "Date" -Value "Files older than"
$review | Add-Member -type NoteProperty -Name "Value" -Value $dateDiff
$table += $review
$table | Format-Table –AutoSize

#Connect to the site
$site = Get-SPsite $url

#Report file and first row
New-Item $report -type file -Force | Out-Null
Add-Content $report "Deleted Items: $($dateNow)"
Add-Content $report "Name, Title, Deleted by, Deleted date, Path, File Guid"

#Get items from the Seconday Stage Recycle Bin (SSRB) that are older than are removel period.
$items = $site.Recyclebin | where { $_.ItemState -eq "SecondStageRecycleBin" -and $_.deleteddate -le $dateDiff}
$site.Recyclebin | where { $_.ItemState -eq "SecondStageRecycleBin" -and $_.deleteddate -le $dateDiff} | Format-Table -Property Title, Web, DeletedBy, DeletedDate -Autosize -Wrap

#Confirm there are items to delete
if($items -ne $null){

#Create prompt
$ok = New-Object System.Management.Automation.Host.ChoiceDescription "&OK","Description."
$cancel = New-Object System.Management.Automation.Host.ChoiceDescription "&CANCEL","Description."
$options = [System.Management.Automation.Host.ChoiceDescription[]]($ok, $cancel)
$title = "Confirm"; $message = "Delete items from Second Stage Recycle Bin?"
$result = $host.ui.PromptForChoice($title, $message, $options, 1)

switch ($result) {
0{
#Get items to be deleted
$site.Recyclebin | where { $_.ItemState -eq "SecondStageRecycleBin" -and $_.deleteddate -le $dateDiff} | foreach{
#Add entry to report
Add-Content $report "$($_.LeafName),$($_.Title),$($_.deletedbyname),$($_.deleteddate),$($_.Dirname),$($_.Id)"

#Delete item by ID
$site.Recyclebin.Delete($_.ID)

$i++
}
write-host "$($i) items removed from Second Stage Recycle Bin."
}1{
write-host "Cancelled by user." -foregroundcolor Red
}
}
}else{
write-host "No files were found in the Second Stage Recycle Bin." -foregroundcolor Red
}

#Dispose
$site.dispose();
[/code]

One quirk I found while creating the script was that through the web browser, SharePoint reported the time each file was deleted correctly whereas in PowerShell, the time was not honouring GMT summer time.

British Summer Time  in SharePoint vs. PowerShell
British Summer Time in SharePoint vs. PowerShell

Enjoy and delete carefully!

Some light reading after the SharePoint Conference

I’ve managed to pickup some reading materials while in America that are going to keep me busy for some weeks.

Microsoft SharePoint 2013 Administration

First up was a book (Microsoft SharePoint 2013 Administration) I picked up at the AvePoint during a book signing during the SharePoint Conference.

Two of the Authors Randy Williams and Chris Givens kindly signed it for me – it was really great to meet these two authors after many years of reading their books.

Authors Randy Williams and Chris Givens at AvePoint book signing.
Authors Randy Williams and Chris Givens at AvePoint book signing.

Alcatraz 1259

Second up was a book (Alcatraz 1259) from Alcatraz Island signed by the author Willam G. Baker himself who happened to be there just after finishing his parole aged 81. He is one of the last living cons who served in US Penitentiary Alcatraz and shares his account of life there in the book.

Alcatraz 1259 author William G. Baker during book signing.
Alcatraz 1259 author William G. Baker during book signing.

I hadn’t appreciated the history of the Island, the Penitentiary or the prisoners and guards up until the audio tour. Up until this point I had just seen it as a prison in America that featured in the movie The Rock but the Island has a fascinating past – one that I am looking forward to learning more and more about.

I’ve struggled to put Bill’s account of life on the Island down since we brought it – once done I’m going to find a guards account of life on the Island among others.

MCSA Windows Server 2012 exams

During the SharePoint Conference I took the opportunity to update my Microsoft Certifications – more about that another day.

To complete the Microsoft Certified Solutions Expert (MCSE) in SharePoint I have to pass 70-410, 70-411 and 70-412 also making me a Microsoft Certified Solutions Associate (MCSA) in Windows Server 2012.

MCSA Windows Server 2012 reading
MCSA Windows Server 2012 reading

Happy reading I guess.

Error when creating new Site Collections via Central Administration

A customer recently reported that they were not able to create any new Site Collections within any Web Application in their SharePoint 2013 UAT environment. Instead of being able to create a new Site Collection they repeatedly received the error shown in the image below.

Provider must implement the class 'System.Web.Security.MembershipProvider'.
Provider must implement the class ‘System.Web.Security.MembershipProvider’.

I in order to troubleshoot this issue I tried creating a Site Collection myself while monitoring the ULS logs. An event with an ID of 8307 was appearing in the logs each time I tried to create a new Site Collection. This event had a message of “An exception occurred in Forms claim provider when calling SPClaimProvider.FillResolve(): Provider must implement the class ‘System.Web.Security.MembershipProvider’. (C:\inetpub\wwwroot\wss\VirtualDirectories\34596\web.config line 606)”.

ULS Viewer
ULS Viewer
Event 8307. SharePoint Foundation. Claims Authentication.
Event 8307, SharePoint Foundation, Claims Authentication.

This suggested a problem with the web.config for the Central Administration Web Application and an error with a Membership provider. Forms Based Authentication (FBA) has been used to configure FBA in this environment so I continued exploring this avenue further.

I reviewed the web.config at the line indicated from the event and noticed that there were multiple entries for FBA membership and role providers. I removed these duplicate entries and was able to create Site Collections without any problems then.

As the Forms Based Authentication in this environment was configured using the FBA Configuration Manager I can only think that this was at fault somehow – the odd thing is that the same tool was also used on to configure the production environment which I also confirmed was not also having the same issue.

I would suggest that after using the FBA Configuration Manager to configure FBA that you ensure you can create new Site Collections. It’s certainly something I am going to be including to my deployment checklist!

Easily add jQuery tabs using the “Reusable Content” feature

This post is quite a fun one. Whilst I was working with a customer today someone came up to me and asked if it was possible to add tabs to their content pages to which I gave it a few seconds thought and I responded “sure that’s absolutely possible – leave it with me!”.

I then spent my commute home thinking about how tabs could be delivered for end-users to make use of without them having to meddle around with any code. Sure getting tabs to work in SharePoint is pretty straight forward and is something we’ve all done at least on a couple of occasions but I give more thought about making it easier for the end-users to consume rather than just meeting the customers requirement by putting in a solution that isn’t pretty nor easy to use.

Solution

I eventually decided to use, what I thought was a very simple approach to giving users the option to use tabs. My solution makes use of the tabs from the jQuery UI (http://jqueryui.com/tabs/) library. It starts with a small modification to the master page that is currently being used. The following code should be added before the closing </head> tag.

[code lang=”html”]
<link href="http://code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css" rel="stylesheet" />
<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.js"></script>
<script type="text/javascript" src="http://code.jquery.com/ui/1.10.3/jquery-ui.js"></script>
<script type="text/javascript">
// <![CDATA[
$(function() {
$("#tabs").tabs();
});
// ]]>
</script>
[/code]

I then added the following to the “Reusable Content” list in the root site of the Site Collection where I was adding tabs. Make sure that the “Automatic Update” is unchecked for this piece of reusable content.

Reusable Content item
Reusable Content item
Reusable Content Lists
Reusable Content Lists

Below is the code that should be added to the Reusable HTML field.
[code lang=”html”]
<div id="tabs">
<ul>
<li><a href="#tabs-1">Overtype tab 1 title here</a></li>
<li><a href="#tabs-2">Overtype tab 2 title here</a></li>
<li><a href="#tabs-3">Overtype tab 3 title here</a></li>
</ul>
<div id="tabs-1">Overtype tab 1 content here.</div>
<div id="tabs-2">Overtype tab 2 content here.</div>
<div id="tabs-3">Overtype tab 3 content here.</div>
</div>
[/code]

To add the tabs onto a content page you can simply select the item that has just been added to “Reusable Content” list by clicking on the “Insert” tab whilst editing the page and expanding the “Resumable Content” menu.

Reusable Content menu
Reusable Content menu

Rich text that represents the HTML markup for the tabs is then added onto the page. Each tab is represented by a bullet list item “<li>” and a content area “<div>”. The names of tabs you require can then be added by carefully overtyping the existing tab names. You must be careful not to introduce or remove any markup as this might prevent the tabs from working correctly.

Once you have entered the names of the tabs you can then add the appropriate content by overtyping the content that you wish to include in that tab. This content can consist of rich text such as tables, images and also web parts. Again you must be careful not to introduce or remove any markup. Any tabs that are no longer required can be carefully removed by deleting the bullet list item and content area.

Tabs demonstration
Tabs demonstration

There are other ways to achieve the same result but I thought this was a simple approach using out-of-the-box functionality. Happy tabbing!