URL to a location inside a PDF

Enhancing SharePoint breadcrumbs when navigating deeply nested folders

I had an interesting challenge to improve breadcrumbs in navigating a deeply folder nested library. The nice breadcrumb dropdown icon is actually gone in SP2013 by default; that can easily be re-enabled via CSS. an onMouseOver event action in JavaScript can eliminate the need to click on it. However to improve the breadcrumb navigation, I created an SPWeb feature to enhance breadcrumbs by overriding the OnPreRender event. The OnPreRender should only occur if the ‘Visible’ property of the web control is set to true. The PreRender event is raised just before the page is about to render its contents. This is the last chance we have to modify the page output before it is received by the browser.

I deployed this web Scoped solution that overrides the breadcrumbs with OnPreRender page processing, and provides the full folder path, clickable for each link. It recursively searches the page for the “TitleBreadcrumb” control, and builds a replacement, adding an a href, folder link name, and breadcrumb image iteratively. It only affects the page when you are navigating within a library.

namespace MyDocLibraryBreadcrumb
{
public class MyDocLibBreadcrumbDelegateControl : WebControl 
{
protected override void OnPreRender(EventArgs e)
{
try
{
base.OnPreRender(e);
// Get the path to the current folder
string path = Context.Request.QueryString["RootFolder"];
// if there's no path then there is nothing to do; it implies are are not in the context of the library 
if (String.IsNullOrEmpty(path))
{
return;
}
// Let's get the current folder
SPWeb web = SPContext.Current.Web;
SPFolder currentFolder = web.GetFolder(path);
// Let's find the breadcrumb control on the current page - it's a ListProperty control where the property is  "TitleBreadcrumb". 
Control c = Utils.FindRecursive(Page.Controls, ctrl => ctrl is ListProperty && ((ListProperty)ctrl).Property == "TitleBreadcrumb");
// If not found, nothing to do, and we are not likely in a library. 
if (c == null)
return;
// Let's subsitute the OOTB breadcrumb control with our replacement enhanced one
var parent = c.Parent;
var index = parent.Controls.IndexOf(c);
parent.Controls.RemoveAt(index);
parent.Controls.AddAt(index, new LiteralControl { Text = GetReplacementBreadCrumbOutput(currentFolder) });
}
catch (Exception ex)
{
// log errors quietly 
Utils.WriteMyLog(Utils.GetErrorInfo(ex));
}
}
/// SPFolder is the parameter to create navigation to
/// returns the HTML output
private string GetReplacementBreadCrumbOutput(SPFolder folder)
{
List<BreadcrumbNodeData> nodes = new List<BreadcrumbNodeData>();
// Collect a path from current folder to its root folder
SPFolder nodeFolder = folder;
while (nodeFolder != null)
{
// If we're in folder use the folder name as a title. If not use a library title enstead.
BreadcrumbNodeData node  = new BreadcrumbNodeData();
node.Url = nodeFolder.ServerRelativeUrl;
if (string.IsNullOrEmpty(nodeFolder.ParentFolder.Url))
{
if (nodeFolder.DocumentLibrary != null)
nodes.Add(new BreadcrumbNodeData
{Title = nodeFolder.DocumentLibrary.Title, Url = nodeFolder.ServerRelativeUrl});
}
else
{
nodes.Add(new BreadcrumbNodeData { Title = nodeFolder.Name, Url = nodeFolder.ServerRelativeUrl });
}
nodeFolder = string.IsNullOrEmpty(nodeFolder.ParentFolder.Url) ? null : nodeFolder.ParentFolder;
}
// Reverse the collected path because the root folder must be on the left in bredcrumb
nodes.Reverse();
// Create an HTML output similar to original. An arrow image we've created from the original
string htmlOutput = String.Empty;
foreach (var node in nodes)
{
if (node != nodes.Last())
htmlOutput +=
String.Format(
@"<A href=""{0}"">{1}</A> <IMG style=""vertical-align:middle"" alt=: src=""/_layouts/images/MyDocLibraryBreadcrumb/breadcrumb_arrow.png""/> ", node.Url, node.Title);
else
{
htmlOutput += node.Title;
}
}
return htmlOutput;
}
}
/// temporary class to holds navigation node data
public class BreadcrumbNodeData
{
/// Title for URL (it will be a folder name)
public string Title { get; set; }
/// Url to navigate on click (it will be a server relative URL of the folder)
public string Url { get; set; }
}
public class Utils
{
public static string GetErrorInfo(Exception ex)
{
string result = "ex.Message=" + ex.Message;
result += ex.InnerException == null ? "|ex.StackTrace=" + ex.StackTrace : String.Empty;
if (ex.InnerException != null)
result += "[INNER EXCEPTION: ]" + GetErrorInfo(ex.InnerException);
return result;
}
public static void WriteMyLog(string text)
{
SPDiagnosticsService.Local.WriteTrace(0,
new SPDiagnosticsCategory("MyDocLibBreadcrumbDelegateControl", TraceSeverity.High,
EventSeverity.Error),
TraceSeverity.High, text, null);
}
/// Finds a control based on provided criteria inside controls hierarchy
/// <param name="controls">A Controls collection to start search</param>
/// <param name="criteria">A criteria to return control</param>
/// <returns>The founded control </returns>
public static Control FindRecursive(ControlCollection controls, Func<Control, bool> criteria)
{
foreach (Control control in controls)
{
if (criteria(control))
return control;
var innerControl = FindRecursive(control.Controls, criteria);
if (innerControl != null)
return innerControl;
}
return null;
}
}
}

Report on every library in a SharePoint Farm

Report on every library in a SharePoint Farm

It is very useful to be able to examine attributes from every library in a SharePoint farm. Here’s how to generate a report of useful library summary information, such as what level of versioning is enabled, which libraries are configured for QuickLinks and which have Content Types enabled, and how many documents are within each library. I use pipe-separated fields, in case commas are encountered in library fields, such as titles.

Add-PSSnapin "Microsoft.SharePoint.PowerShell" -ErrorAction SilentlyContinue
$mylogfile="L:PowerShellCOREreportingLibSettings.csv"
$envrun="Prod"			# selects environment to run in
if ($envrun -eq "Dev")
{
$siteUrl = "http ://SharePointDev/"
}
elseif ($envrun -eq "Prod")
{
$siteUrl = "http ://sharepoint/"
}
else
{
Write-Host "ENVIRONMENT SETTING NOT VALID: script terminating..."
$siteUrl =  $null;
return;
}
Write-Host "script starting" 
$myheader = "STARTING: $(get-date)"
$Sep="|"
add-content $mylogfile "Site$($sep)Web$($sep)Library$($sep)FileCount$($sep)CTEnabled$($sep)VerMajor$($sep)VerMinor$($sep)QuickLaunch$($sep)MajVerLimit$($sep)MinorVerLimit$($sep)UniquePerms"
$WA=Get-spWebApplication $siteUrl;
foreach ($site in $wa.sites)
{
write-host $site.Url
foreach ($web in $site.allwebs)
{
if ($true) #useful placeholder for filtering. 
{
Write-Host -foregroundcolor darkgreen "$($site.id) - $($site.Url) - $($site.contentdatabase.id) - $($site.contentdatabase.name)"   
for ($i=0;$i -lt $web.Lists.Count;$i++)
{ 
$JPLib=$web.Lists[$i];
$A_Lib_Count++;
$SkipLib=$true; #true
if ( ($JPlib.BaseType -ne "DocumentLibrary") -or ($JPlib.hidden) )
{
# forget the rest and return to top
Write-Host -foregroundcolor green "fast test skipping Library: $($JPlib)";   
}
elseif ($JPLib.Title -Match "SitesAssets|Photo|Image|CustomizedsReports|Templates|Pages|Picture|cache|style|Slide")
{
# forget the rest and return to top
Write-Host -foregroundcolor red "fast test skipping Library because it mentions $Matches: $($JPlib)";   
}
elseif ($JPLib.BaseTemplate -ne "DocumentLibrary")   #alternatively, only skip if -eq XMLForm
{
# forget the rest and return to top
Write-Host -foregroundcolor red "fast skipping Library because it is not of base DocumentLibrary, it is BaseType:$($JPlib.basetemplate): $($JPlib.title)";   
}
elseif (($JPLib.ThumbnailsEnabled) -or ($JPLib.DefaultView -eq "AllSlides"))
{
# forget any library with thumbnails, these are not normal doclibs, and return to top
Write-Host -foregroundcolor red "fast test skipping Library because it has Thumbnails/Slides $($JPlib)";   
}
else
{  $SkipLib=$false;	}
if (!$SkipLib)
{
#write-Host -foregroundcolor green "Processing Library: $($JPlib)";   
$LineOut = "$($site.url)$($sep)$($web.title)$($sep)$($JPLib.title)$($sep)$($JPLib.items.count)$($sep)$($JPLib.ContentTypesEnabled)$($sep)$($JPLib.get_EnableVersioning())$($sep)$($JPlib.get_EnableMinorVersions())$($sep)$($JPLib.OnQuickLaunch)$($sep)$($JPLib.MajorVersionLimit)$($sep)$($JPLib.MajorWithMinorVersionsLimit)$($sep)$($JPLib.HasUniqueRoleAssignments)"
Write-Host $lineOut
add-content $mylogfile $LineOut
}
}
} #foreach site
} #if $true/Siteurl is not null, if environment setup is valid
} #foreach letter
###########################

Adjusting Quicklinks Programmatically in SharePoint

Use PowerShell to set Quicklinks Programmatically in SharePoint

Wouldn’t it be great to hide all lists of a certain type in a farm? Perhaps Tasks, Calendars or Discussions, or all of the above. Progrmmatically, they can be hidden or exposed on navigation using set_OnQuickLaunch(). Here’s how a given library is hidden from quick launch:

$LIB.set_OnQuickLaunch($false)

Let’s now do it across a full web application; all the site collections, sites, and for a set of libraries.

Add-PSSnapin "Microsoft.SharePoint.PowerShell" -ErrorAction SilentlyContinue
Start-SPAssignment –Global 
$mylogfile="C:logfolderongoinglogfile.txt"
$envrun="Prod"			# selects environment to run in
if ($envrun -eq "Dev")
{
$siteUrl = "http://devdocs.sharepoint.com"
$LibsToFlip = "Tasks,Site Pages,Calendar,Documents"
$LibsToFlipArray = $LibsToFlip.Split(“,”)
}
elseif ($envrun -eq "Prod")
{
$siteUrl = "http://docsny.sharepoint.com"
$LibsToFlip = "Tasks,Site Pages,Calendar,Documents,Team Discussion"
$LibsToFlipArray = $LibsToFlip.Split(“,”)
}
else
{
Write-Host "ENVIRONMENT SETTING NOT VALID: script terminating..."
$siteUrl =  $null;
return;
}
Write-Host "Quick Launch Flip script starting $(get-date)" 
if ($siteurl)
{
$rootSite = New-Object Microsoft.SharePoint.SPSite($siteUrl)
$spWebApp = $rootSite.WebApplication 
foreach($site in $spWebApp.Sites)
{
write-host $site.Url
#  if ($site.Url -like "$siteurl/personal/*")  
if ($site.Url -like "$siteurl*")  
{
$rootSite = New-Object Microsoft.SharePoint.SPSite($siteUrl);
$rootWeb = $rootSite.RootWeb;
$webs= $site.AllWebs;
$WebsCount = $webs.count;
for ($wi=0; $wi -lt $WebsCount; $wi++)
{
$web = $webs[$wi]
$changed = $false;
$lists = $web.Lists;
$listcount = $lists.count;
for ($li=0; $li -lt $listcount; $li++)
{ 
$JPLib = $lists[$li]
if ($libsToFlipArray -contains $JPLib.Title )
{
WRITE-HOST -ForegroundColor darkgreen "$($JPLib.Title) in $($web.url)"
$JPLIB.set_OnQuickLaunch($false)
$JPLib.Update()
$changed = $true;
}
}
if ($changed)
{
#$Web.update()
}
}
}
}
}
Stop-SPAssignment –Global
###########################

Set Portal link for navigating above a Site Collection

Set Portal link for navigating

One of the annoyances in SharePoint is that there is no easy way for end users to navigate to outside the site collection. Creating a Portal Connection is one easy way. However configuration is manual. Here’s a way to automate this connection.

First, we get the Web Application object, then use that to iterate through all the Site Collection objects.  the -Limit All ensures we process all, otherwise it will default to the top 20.  Then we set the Portal name and link.  note that no update() is required if you use the methods below.  If you instead update the properties directly, an object update() is required.

Add-PSSnapin "Microsoft.SharePoint.PowerShell" -ErrorAction SilentlyContinue
# Script changes the letter heading in each site collection - Joel Plaut
$WebApp = Get-SPWebApplication "http ://sharepoint";
$SA = $WebApp | Get-SPSite -Limit all
foreach ($MySite in $SA)
{
Write-Host "Fixing Portal for $($MySite.url)" -ForegroundColor darkred
$MySite.set_Portalname("Home")
$MySite.set_PortalURL("http ://sharepoint")
}