Save typing when Manipulating SharePoint Solution Files in PowerShell


 

Introduction

 

One of the things that annoyed me with PowerShell and SharePoint are the following commands:-

  • Add-SPSolution
  • Update-SPSolution
  • Actually anything that requires a –LiteralPath parameter

Don’t get me wrong I think that using PowerShell to admin SharePoint is fantastic but these commands bug me because they require a full path to work correctly. Unfortunately you don’t seem to be able to use the full stop (or period) to represent the current directory.

So you cannot do something like this:-

Add-SPSolution .\mysharepoint.wsp;

Instead you need to do this:-

Add-SPSolution c:\thisisareallylongpath\release1\section2\mysharepoint.wsp

Solution

Anyway, that got me thinking surely there is a better way and I can save myself some typing.

The solution is to use the command Get-Location.

So instead you can do this:-

$curdir = Get-Location;
Add-SPSolution–LiteralPath $curdir”\mysharepoint.wsp”;

Actually there is a PowerShell Alias called gl which is a shortcut for the Get-Location command.

So the shortest version in terms of saving your typing fingers is this:-

$curdir = gl;
Add-SPSolution –LiteralPath $curdir”\mysharepoint.wsp”;

Really hope that helps save you some time, also love to hear any other alternatives, there is bound to be an even better way of doing this.

Introducing SPUrlExpressionBuilder to resolve SharePoint URL Tokens


Introduction

One of the great things when developing with SharePoint is being able to use URL tokens such as:-

  • ~SiteCollection – this will resolve to the current site collection root
  • ~Site – this will resolve to the current web root

For an extensive list of SharePoint URL tokens, take a look at Namwar Rizvi’s SharePoint Insight blog post, http://sharepoint-insight.com/2008/12/01/list-of-sharepoint-url-tokens/.

If you have done much with creating or modifying Master Pages or Page Layouts then these tokens become very useful to locate resources such as images, XSL or CSS files.

For example, to reference an image which is stored in a SharePoint Publishing web’s Site Collection Images folder you could use something like this:-

~SiteCollection/SiteCollectionImages/myimage.png.

Recently, I have been working with multiple language user interfaces and wanted to be able to display a different image based on the language. One of the steps to do this was to work out how to process these URL Tokens.

Solution

This is where the SPUrlExpressionBuilder class comes to the rescue. This class is part of the Microsoft.SharePoint.Publishing.WebControls assembly.

To evaluate a string with a url token like ~SiteCollectionUrl/Images/MyImage.png you can use the following SPUrlExpressionBuilder class’s method EvaluateUrlExpression().

string imagePath = SPUrlExpressionBuilder.EvaluateUrlExpression("~SiteCollection/Images/myimage.png").ToString();

 

SharePoint Feature Receiver token replacement only works with Guids using lower case characters


Introduction

This week I have been writing my first proper SharePoint 2010 Service Application.

Part of that solution is using a Feature Receiver to install the various components that make up a Service Application. These include:-

  • Service
  • Service Proxy
  • Service Application
  • Service Application Proxy
  • Service Instance

Tip: Andrew Connell’s chapter in the Real World SharePoint 2010 book is an excellent resource for getting the plumbing of the service application setup.

One of the issues came about at deployment time. Now that I write this, I do remember hearing about this issue before when during a SharePoint 2010 Developer Bootcamp. Of course I didn’t take enough notice and then hit the problem when I decided to update the GUID for the feature receiver.

The following error was displayed when deploying the solution:-

Error occurred in deployment step ‘Add Solution’: Failed to create receiver object from assembly “ITSP.SPMonitoringStatusPackage, Version=1.0.0.0, Culture=neutral, PublicKeyToken=de085cbd57463aa2”, class “$SharePoint.Type.edd0669b-2393-4fe6-988d-17a2De06c6e4.FullName$” for feature “ITSP.SPMonitoringStatusPackage_ITSP.SPMonitoringStatusInstaller” (ID: a5ede1b3-cbd6-4918-9f31-4322603295f5).: System.ArgumentNullException: Value cannot be null.
Parameter name: type
at System.Activator.CreateInstance(Type type, Boolean nonPublic)
at Microsoft.SharePoint.Administration.SPFeatureDefinition.get_ReceiverObject()

Screenshot:-

guid-featurereceiverdeployerror

The deployment show that any error occured because it could not create a feature receiver object from assembly “ITSP.SPMonitoringStatusPackage, Version=1.0.0.0, Culture=neutral, PublicKeyToken=de085cbd57463aa2” with the class “$SharePoint.Type.{GUID}.FullName$”.

The token, “$SharePoint.Type.{GUID}.FullName$”, should be replaced by Visual Studio as part of the build process. However this does not seem to happen with the GUID value that I was using (see below).

In this example the {GUID} value is specified against the feature receiver class using an attribute as below (please note the capital letters have been added for this example):-

guid-featurereceiveruppercase

The item that uses this GUID attribute for the Feature Receiver Class can be seen by double-clicking on the feature Visual Studio SPI object:-

featureceiver-spi

If you then look at the properties of the feature Visual Studio SPI object, you see the section as below:-

featureceiver-spi-properties

As you can see the Receiver Class has a value of:-

$SharePoint.Type.edd0669b-2393-4fe6-988d-17a2De06c6e4.FullName$

 

Solution

The issue is that upper case characters are being used and changing these to use lower case alphabetic characters for the GUID fixes the issue.

The following changes need to be made.

  1. Change the Feature Receiver’s GUID attribute.
 1: [Guid("edd0669b-2393-4fe6-988d-17a2de06c6e4")]

 

 2:     public class ITSPSPMonitoringStatusInstallerFeatureReceiver : SPFeatureReceiver

 

2. Update the Feature Receiver Visual Studio SPI object’s Receiver Class property to:-

featureceiver-spi-properties-fixed

Save all the files, rebuild, deploy and the solution is deployed successfully, hurrah!

feature-installed-successfully

 

Hope that helps.

Why the out of the Box Content Query Web Part Item Style names friendly and custom styles aren’t


Introduction

 

So firstly this post comes about because I have been working with the Content Query Web Part quite a lot recently and this particular behaviour has been bugging me for ages.

However I have just got back from the European SharePoint Best Practices Conference where Christine Wheeler did some great sessions on the Content Query Web Part and I was hoping to ask her why the following behaviour happens.

However, just before I put my hand up she bet me to it and asked the audience if anyone knew why custom item styles don’t have friendly names and out of the box styles do.

So I thought I really should find out why.

 

Background

So what the heck am I talking about?

Well its this section of the Content Query Web Part that is the problem:-

image

This custom item style “CustomItemStyle” template has an unfriendly name compared to the other out of the box styles.

This screen is reached in the Content Query Web Part by the following:-

  • insert a content query web part on the page
  • Modify its properties
  • Expand the Presentation grouping and you will see two drop-down boxes
  • Group Style and Item Style

image

The “Out of the Box” item styles are displayed as readable descriptive list items.

What commonly happens is that new styles need to be created as none of the out of the box styles give the format that is required. It is possible to create your own style by :-

  • opening SharePoint Designer
  • create a copy of the XSLT style sheet ItemStyle.xslt found in Site Collection Root/Style Library/XSLT Style sheets
  • copy an existing item style template <xsl:template></xsl:template> image
  • give the <xsl:template name attribute a new name and change the match attribute to contain the same text as the name attribute.

Further information can be found in this great post (http://blogs.msdn.com/b/ecm/archive/2006/10/25/configuring-and-customizing-the-content-query-web-part.aspx) on the Microsoft ECM Blog.

When you have added a new style, then you will see the something like below.image

 

There does not seem to be away to control the description and give a friendly name. Looking through the components that make up the Content Query Web Part it would make sense that one of the following style sheets would help:-

  • Header.xslt
  • ItemStyle.xslt
  • ContentQueryMain.xslt

image

I would have expected ContentQueryMain.xslt to have some XSLT that would perform a look up which we could then add to allowing custom friendly ItemStyle descriptions.

 

Investigation

So I started thinking well how does the Content Query Web Part get this information?

The best way to find out is to reflect the code.

The Microsoft.SharePoint.Publishing.dll assembly is where the ContentByQueryWebPart lives so I fired up .NET Reflector and started to disassembly the Microsoft.SharePoint.Publishing.dll

The first place to look was at the class Microsoft.SharePoint.Publishing.WebControls.ContentByQueryToolPart the ToolPart is the control which displays the interface when you modify the web part settings on a SharePoint page.

The control has the following function, populateItemStylesDropDown() this calls back into the parent web part control using function PopulatedItemDropDown passing in the path ~SiteCollection/Style Library/XSL Style Sheets/ItemStyle.xslt.

image

image

The PopulateDropDown is then called with the XSLT Style Sheet link and also the “ItemStyle” string. It is this function where all the item styles are discovered and added to the drop-down list box.

image

If you look at the highlighted section the code tries to resolve a string within a resource file using the Item Style name attribute and prefixing the Item Style name with “ItemStyle”.

If a string is not returned then the template name found in the XSLT is used and hence this is why we see unfriendly style names with custom Content Query Item Styles.

So where are these Item Style names stored?

Well the function Resources.GetString() function is called and the Resources object is also found in the Microsoft.SharePoint.Publishing assembly as Type Microsoft.SharePoint.Publishing.Internal.Resources.

image

The class has a default constructor which defines the details of the resource file to open. It happens that the resources are actually held within the Assembly Microsoft.SharePoint.Publishing.Intl.dll which is found in c:\program files\microsoft office servers\14\bin directory.

image

The Microsoft.SharePoint.Publishing.Intl.dll has embedded resources, one of them being Microsoft.SharePoint.Publishing.Strings.resources, this contains all the Item Style resource strings.

image

image

 

So there you go, that is how the Content Query Web Part displays readable Item style descriptions!

The next question is how do we go about doing our own version of this?

That is something that I haven’t thought about yet. I would be interested in hearing from you on how you would approach this issue.

Open PDF files from a Document Library in a New Window


Update: 10th June 2012

A few people have posted comments about the script not working for SharePoint 2010. This code was built for SharePoint 2007. However I have been through the code and an updated version can be found below.

Apologies that it has taken a while to respond.

The SharePoint 2010 Code version works slightly differently and uses the jQuery Live method and JavaScript OnMouseOver event to update the PDF’s a tags.

Introduction

One of our clients wanted a way to ensure that Adobe PDF’s did not open in the current browser window.

By default a PDF opened into Adobe Acrobat will open in the current browser window.

This presents a usability problem as user’s lose the reference to the site they were in also other applications, such as Word and Excel, do not behave in this way.

How Acrobat opens a document is controlled within the Acrobat client. Unfortunately to change this through a mechanism such as Group Policy is not a simple task and hence did not meet their timescales.

Just so you know, Acrobat’s PDF browser behaviour is adjusted here :-

  • Open Acrobat Reader (C:\Program Files (x86)\Adobe\Reader 10.0\Reader\acrord32.exe)
  • Click Edit->;;Preferences
  • Choose Internet (down the left-hand side)
  • Unselect “Display PDF in Browser”
  • Click OK

acrobat-browsersetting

;

Unfortunately this was not possible for the client, so I started to look at alternative solutions. First thing is that the client wanted something that was quick (the fix was required in a day) as they had committed to launching the internal site quickly. The requirement was for a single page and therefore the solution did not need to scale.

The idea that I came up with was to use jQuery to find the PDF hyperlinks on the page and then add a target attribute with a value of “_new”.

This was pretty straightforward and using a content editor web part the JavaScript was quickly added to the page to prove the approach.

simpleopeninnewwindowjscode

;

Of course its never as simple as that and when this approach was tried then it worked well for a standard document library but the page had views that were grouping content by certain columns and the jQuery could not find the ; link to bind to.

When the grouping function is used on a view then the content within a group is lazy loaded using JavaScript functions within the init.js SharePoint library. So therefore the jQuery will not find the ; tags because they don’t exist yet.

One option I thought of was to try and run the code as late as possible. So I starting to look into how SharePoint JavaScript function were called. One thing I had noticed was how the content within a group was loaded in after the document had loaded.

Looking at the SharePoint JavaScript files I found init.js. SharePoint seems to add functions to an array called ‘_spBodyOnLoadFunctionNames’ using the function _spBodyOnLoadFunctionNames.push(). This array is passed into a function called “ProcessDefaultOnLoad()” within init.js. These functions are then called one at a time by looping through the array.

So another line within the $(document).ready() function was added :-

_spBodyOnLoadFunctionName.push(refreshPDFLinks());

However, this still didn’t work.

The grouping function when it expands a node updates the html to display these links to the various documents, so I wanted to find a way to bind to an event which would be called when changes to the html were made. After some more research I couldn’t find a way to throw an event which would work in IE and Firefox.

I then thought we’ll why don’t we delay the call to look for the PDF links by a second or two. The important thing was that the delay was an asynchronous call that did not block the other scripts from running.

In the end the setTimeout() JavaScript function was used. This function was bound to all the expanders for the document library. Fortunately with jQuery the binding was pretty straightforward with the line:-

$(documentLibExpGroup).bind('click', function(){
refreshPDFLinkTimeOut = setTimeout("refreshPDFLinks()",1500);
});

This approach works though it is not great for the following reasons :-

  • it is not scalable and need to be added to each page that the functionality is required for.
  • the timeout needs to be long enough to load the PDFs but not so long so that the link is not updated in time.

Anyway with those caveats in mind here is:-

The SharePoint 2007 Source Code.

<script src="http://ajax.microsoft.com/ajax/jQuery/jquery-1.4.4.min.js">
</script>
<script language"javascript" type="text/javascript">
/* PDF Link to new window function */
$(document).ready(function () {

    var refreshPDFLinkTimeOut=null;
    //bind function to document library expand events
    bindToDocumentLibraryEvents();
    //_spBodyOnLoadFunctionNames.push(&quot;refreshPDFLinks&quot;);
    refreshPDFLinks();
});

function bindToDocumentLibraryEvents() {

    var documentLibExpGroup=$("a[onclick*='ExpCollGroup']");
    $(documentLibExpGroup).bind('click', function(){
        refreshPDFLinkTimeOut = setTimeout("refreshPDFLinks()",2000);
    });
    alert"Bound to " + documentLibExpGroup.length + " Document Lib Group Switch");
    return true;
}

function refreshPDFLinks() {
    fixPDFUrls();
    //var pdfTags = getPDFUrls();
    //updatePDFUrls(pdfTags);
    //alert(&quot;Found &quot; + pdfTags.length + &quot; PDF Links&quot;);
    return true;
}

function getPDFUrls() {
    var pdfUrlTags = $"a[onclick*='DispEx'][onfocus*='OnLink'][href*='pdf']");

    var fixTags = new Array();
    for (var i = 0; i <;; pdfUrlTags.length; i++) {
            fixTags.push($(pdfUrlTags[i]));
    }

    return fixTags;
}

function fixPDFUrls() {
    var pdfUrlTags = $("a[onclick*='DispEx'][onfocus*='OnLink'][href*='pdf']");
    alert("Found " + pdfUrlTags.length + " PDF Links");
    $(pdfUrlTags).live('click', function() {updatePDFUrls(this);});
    return true;
}

function updatePDFUrls(pdfUrlTags) {
    for (var i = 0; i <;; pdfUrlTags.length; i++) {
        $(pdfUrlTags[i]).attr('target','_new');
    }

    return true;
}

function updatePdfTargetUrl(e) {
    $(e).attr('target','_new');
    return false;
}

/* PDF Link to new window function */
</script>

The SharePoint 2010 Source Code

<script type="text/javascript" src="http://ajax.microsoft.com/ajax/jQuery/jquery-1.7.1.min.js">
</script>;
<script type="text/javascript" language="javascript">
/*

/* PDF Link to new window function */
$(document).ready(function () {

    var refreshPDFLinkTimeOut=null;
    //bind function to document library expand events
    bindToDocumentLibraryGroupByEvents();
    refreshPDFLinks();
});

function bindToDocumentLibraryGroupByEvents() {

    var documentLibExpGroup=$("a[onclick*='ExpCollGroup']");
    $(documentLibExpGroup).live('mouseover', function(event){
        refreshPDFLinkTimeOut = setTimeout("refreshPDFLinks()",2000);
    });
    //alert("Bound to " + documentLibExpGroup.length + " Document Lib Group Switch");
    return true;
}

function refreshPDFLinks() {
	fixPDFUrls();
    return true;
}

function fixPDFUrls() {
    var pdfUrlTags = $"a[onclick*='DispEx'][onfocus*='OnLink'][href*='pdf']");
    $(pdfUrlTags).live('mouseover', function (event) { updatePdfTargetUrl($(this)); });
    return true;
}

//updates the tag, adding the _new property to the target to open new window
// and disabling the SharePoint DispEx onclick function to stop the pdf from being opened in the current browser window.
function updatePdfTargetUrl(e) {
    $(e).attr('target', '_new');
    $(e).prop('onclick', null).attr('onclick',null);
	return false;
}

/* PDF Link to new window function */
</script>

Usage

To use this piece of JavaScript do the following:-

  • Download the Source code JavaScript file
  • Browse to the SharePoint page that you wish to add the PDF functionality to.
  • Edit the SharePoint Page
  • Add a Content Editor Web Part to the page
  • Edit the Content Editor Web part and open its source view
  • Open the source code file, copy the contents of the javascript
  • Paste the contents in the Content Editor Web part’s source.
  • Save the Web part and the Page
  • Test out the functionality.
  • If nothing happens then check that the jQuery library can be accessed as it uses the Microsoft AJAX site to get the appropriate version.

Links