Stopping SharePoint from Messing Up Protocol-Relative Links Added with SharePoint Designer

Protocol-relative links are a standard way to avoid mixed content warnings (“This page contains both secure and nonsecure items”) when viewing SharePoint pages. However, if you’re editing a page in SharePoint Designer and try to add one, you’ll find that it doesn’t work forever – SharePoint will eventually mess the link up, usually when you least expect it.

In this video I demonstrate the problem and explain a solution I found to stop SharePoint from messing up protocol-relative links added with SharePoint Designer.

How to fix broken navigation links after SharePoint upgrade or migration

I was recently testing a SharePoint 2007-to-2013 upgrade for a client and was reminded of a problem where links can break in the upgraded/migrated site if the base URL changes along the way.

Here was the scenario:

  1. Client had a 2007 SharePoint site with a base URL similar to http://intranet.
  2. We did a database-attach upgrade to SharePoint 2013 and set up a base URL for testing similar to http://SP-2013:12000 (using a port number rather than host header).
  3. In the upgraded site, some of the links to pages and sub-sites were still referencing the old URL at http://intranet. Other links worked fine and referenced the new URL.

Root cause:

Navigation links that were added by site owners as relative links in the old site continued to work, and links that were added and managed automatically by SharePoint continued to work as well.

Links that were added as absolute links in the old site are what failed to migrate over because they were effectively “hard-coded” to reference the old URL.

Resolution:

The easiest fix was to use a PowerShell script that examined every site collection and site within our upgraded web application and fixed the links in the navigation menus. To do this, the script would look for absolute links that referenced the old base URL and update those links to be relative so they’d work in the new environment.

I checked online and found a few scripts to do this, but they all had problems. Among the problems I found were:

  • Poor coding practices (i.e. not disposing of objects, improper checking of URLs, etc.)
  • Only going 1 or 2 levels deep in the navigation hierarchy and missing deeper links.
  • Checking and fixing links in the Quick Launch menu or the top navigation menu but not both.

So, as a PowerShell guy, I decided to write my own script and avoid the problems I listed above. The client and I ran it earlier today, and all the links were fixed and work fine now.

Here’s the script I used:

###############################################################################
# Configuration variables.
###############################################################################

$WebAppUrl = 'http://newsite.contoso.com'
$OldHostName = 'intranet'

###############################################################################
# Script execution logic.
###############################################################################

function UpdateMenuLinks($topNode) {
	$count = 0
	foreach ($node in $topNode.Children) {
		$uri = New-Object System.Uri($node.Url, [System.UriKind]::RelativeOrAbsolute)
		if ($uri.IsAbsoluteUri -and $uri.Host -eq $OldHostName) {
			$node.Url = $uri.PathAndQuery
			$node.Update()
			$count++
		}
		$subCount = UpdateMenuLinks($node)
		$count += $subCount
	}
	return $count
}

$webApp = Get-SPWebApplication $WebAppUrl -ErrorAction Stop

$webApp.Sites | foreach {
	$site = $_
	try {
		$site.AllWebs | foreach {
			$web = $_
			try {
				"Checking nav links in $($web.Url) ..."
				$fixedTopLinkCount = UpdateMenuLinks($web.Navigation.TopNavigationBar.Parent)
				$fixedLeftLinkCount = UpdateMenuLinks($web.Navigation.QuickLaunch.Parent)
				if ($fixedTopLinkCount -gt 0 -or $fixedLeftLinkCount -gt 0) {
					" - Updated $($fixedLeftLinkCount) quick launch link(s) and $($fixedTopLinkCount) top menu link(s)."
				}
				else {
					" - No links updated."
				}
			}
			finally {
				$web.Dispose()
			}
		}
	}
	finally {
		$site.Dispose()
	}
}

"Done. Finished checking all sites in web application."

The script produces output like this (cut down a lot to save space):

Checking nav links in http://newsite.contoso.com/sites/it ...
- Updated 1 quick launch link(s) and 2 top menu link(s).
Checking nav links in http://newsite.contoso.com/sites/finance ...
- Updated 3 quick launch link(s) and 2 top menu link(s).
Done. Finished checking all sites in web application.

There are two variables to set at the top. One is the URL of the target web application where you want the links fixed (your upgraded/migrated site), and the other is the old host name (base URL without the http/https part) that needs to be searched for.

If you choose to copy and use this script, you do so AT YOUR OWN RISK. I will not be held liable for anything that happens in your environment. As always, I recommend TESTING things like this before you use it in a production environment.

Lastly, this only fixes navigation links in the Quick Launch and top navigation menus. It does not fix links within content such as pages or web parts or in code such as JavaScript code. You’ll either need to fix those manually or write another script to address those things. In my case it wasn’t necessary to automate fixing links to that level, so I didn’t worry about it.

Price about to go up on my SharePoint 2013 timer job development course

I’m about to add new content to my training course on SharePoint 2013 timer job development, and the price will go up on November 24 when the new content is published.

Get lifetime access to the course now – at the current, cheaper price – and receive the updated content at no additional cost when it’s published! Just use the link below.

https://www.udemy.com/developing-custom-timer-jobs-for-sharepoint-2013/

Reporting Options for SharePoint List Data

I came across a blog post today that summarizes the various reporting options for SharePoint and wanted to share it since it’s pretty decent: http://sharepoint-community.net/profiles/blogs/reporting-options-for-sharepoint-lists

Here’s an even shorter summary of reporting options. See the link above for details and links.

  • List views
  • Microsoft Excel / Microsoft Access (client applications and SharePoint services)
  • Custom views or pages created in SharePoint Designer
  • SSRS (SQL Server Reporting Services)
  • PerformancePoint (dashboards / KPIs)
  • Microsoft Visio (not covered in the article but described here)

You can also create a totally custom solution, for example an ASP.NET reporting solution that uses ActiveReports or some other reporting technology.

One of my personal favorites is Microsoft Excel. Excel has three powerful add-ons (all free) that allow normal business users (non-IT people) to do some pretty robust reporting:

Power Query is particularly nice. It’s a newer add-on that provides richer options for getting external data into Excel. For example, it’s aware of SharePoint lists and allows you to pull list data into Excel and even filter, sort, and manipulate it to a certain extent before you use it in tables, charts, or PowerPivot.

Power View is another nice add-on that provides some decent visualizations and basically lets you create some canned reports as worksheets within Excel.

PowerPivot has been around a little longer and essentially lets you work with your data model in Excel as a “mini database.” You can use it to view data in tables, relate the tables together to create joined records, and create some summarized values (called “measures”) for using in pivot tables and charts.

Information Governance: What It Is and Why You Need It

As an information management professional, I’m always interested in trends and ideas around how businesses can get more value from information. However, there’s so much information captured and stored nowadays that many organizations just “hoard” it and don’t do anything truly strategic with it.

If you want to combat information hoarding and do something more strategic, check out the article I published today on information governance:

https://www.linkedin.com/today/post/article/20140905142116-45935709-information-governance-what-it-is-and-why-you-need-it?trk=prof-post

 

Anonymous API Access for Office 365 Public Sites

I was recently working on a contact form for an Office 365 (SharePoint Online) public site and needed to allow anonymous access to my site’s REST API. My contact form was submitting data to a “Contact Us” list on my site using JavaScript and REST. It worked great when I was logged in, but as soon as I logged out I’d get this error from the API: “Access denied. You do not have permission to perform this action or access this resource.

The “Use Remote Interfaces” Permission

By default, SharePoint’s client-side APIs (like CSOM and REST) are configured to require a permission called “Use Remote Interfaces” to use them. Otherwise you’ll get the “Access Denied” error I mentioned above. The problem is you can’t assign that permission to anonymous users.

Normally you can work around this by turning off the “Require Use Remote Interfaces” setting for the API on a per-site collection basis. However, that setting is not available in Office 365 public sites. According to Microsoft’s documentation, the reason it’s not available is that clicking “Make Website Online” for your public site is supposed to adjust that setting for you.

Here’s the exact quote from their documentation:

On the home page of the site collection, choose MAKE WEBSITE ONLINE near the upper right corner of the page. This action also turns off the Use Remote Interfaces permission requirement.

The only problem is it doesn’t work. Either the functionality was changed after the documentation was written, or the documentation was never correct in the first place. Not sure.

The Fix (which, ironically, relies on the client-side API)

As it turns out, the answer to allowing anonymous access to the API was in the API itself.

The SPSite object in SharePoint’s server-side API has a method called UpdateClientObjectModelUseRemoteAPIsPermissionSetting (yes, the name is really that long) which updates this setting. Passing false to this method will turn off the permission requirement for the API. I wasn’t sure if this same method would work in the client-side object model, but I figured I had nothing to lose and tried calling it anyway. Turns out that was the answer.

While logged into my public site as an authenticated user (so I could use the APIs), I created a temporary page in my site and added the following JavaScript snippet to it:

<script type="text/javascript">
ExecuteOrDelayUntilScriptLoaded(function() {
   var ctx = new SP.ClientContext();
   var site = ctx.get_site();
   site.updateClientObjectModelUseRemoteAPIsPermissionSetting(false);
   ctx.executeQueryAsync(
      function() { alert('success') },
      function() { alert('error') }
   );
}, 'sp.js');
</script>

The alerts that say “success” and “error” were simply to show me quickly whether it worked or not when I loaded the page. Mine said “success” and worked the first time, but if yours says “error” for some reason then I suggest using your browser’s debugging tools to see what the actual response was from the API. This code was for one time use, so it wasn’t worth building a bunch of elaborate error-handling into the code itself.

Also, I used IE 11 to edit my page and run the JavaScript snippet even though I normally use Chrome. Chrome sometimes has problems with the timing of the ExecuteOrDelayUntilScriptLoaded() function. There are fixes for that, but again, it wasn’t worth messing around with for code I was only going to run once.

Required Permissions: I believe you need to be a site collection administrator to successfully execute the API code snippet above, but if anyone finds out otherwise please let me know and I’ll update this note.

Fair Warning: As you can see in the JavaScript snippet, opening up the APIs is an “all or none” prospect. I can’t, for example, open up the API for a single list or for a single set of functionality. I can open up all of it or none of it. So be absolutely sure if you do this that you’ve got permissions set up correctly to everything in your public site so people can’t access stuff you don’t want them to. The APIs do respect the permissions of whoever is using them (including anonymous users).

Conclusion

That’s it! After running the code snippet above one time, the client-side APIs in my public site were now accepting calls from anonymous users. As of today I have two contact forms on my public site, both of which use the REST API to submit data to behind-the-scenes lists for processing.