Scoping a SharePoint Feature for a Specific Web Application

This is something that came up at a client’s site yesterday, which I thought should be a trivial problem to solve. As always, it ended up being a little more complex than I originally anticipated. So everyone knows that a SharePoint solution can either be globally scoped, meaning that all web applications in the farm can leverage it, or it can be scoped at a specific web application. But what about features contained within these solutions?

Assume you have a custom .WSP solution that contains 1 feature and which is scoped for your web application on port 81 (e.g. http://localhost:81). One would assume that by default, if the solution is only made available to the Web Application on port 81 (scoped at web app level) that it would be the same for the feature contained within it, meaning that this feature, assuming it it scoped at the site collection level, would only be accessible by site collections within the Web Application contained in the Web Application on port 81. Well, in fact it is not. A feature is always made available to all web applications in a farm, even if its associated solution is targeted at a unique web application.

For this article, I will be using new SharePoint 2010 Foundation Single Server Farm. My farm contains two web applications: one deployed on port 80 and a second one deployed on port 81. From the following picture we can clearly see that I have a custom solution named “MySP2010Solution.wsp” scoped at the web application level and which has been deployed against my web application on port 80.

Webappscopedsolution

This solution contains a single feature, which in turn contains a web part. The feature is scoped at the site collection level. Since my solution was deployed against my web application on port 80, it is expected to see my feature show up in every site collection Under that web application:

featureport80

What one may not expect however, is to also find this feature available to every site collection in my web application hosted on port 81. After all, the solution was never deployed to it in the first place. However, navigating to the site collection feature page of a site collection Inside the web application on port 81 does show my feature and allows me to activate it.

featureport81

The following schema summarizes what is really happening in my environment:

schema

How it Works in the Background

In order to better understand why SharePoint is behaving this way, we need to first understand what really happens in the background when we deploy a solution that contains a feature. When deploying a solution that contains a feature, SharePoint automatically creates a folder in <14 Hive>\TEMPLATES\FEATURES for your custom feature contained within your solution:
Featurelocaldisk
Because this feature is stored physically on disk, it is by default made available by all web applications. Upon activating the feature from the Web interface, SharePoint will create an entry in the Features table of the content database associated with the current Web Application.

SQLFeatureTable

Options

So if you are in the business of managing several web applications for different clients, how can you let one client deploy a web application scoped solution without having it appear on all of your other clients’ web application? Well there are really 3 options made available to you:

1 – Hide the feature by default and activate it via PowerShell

The first option is to make your feature hidden by default, and then to use PowerShell to activate it manually only on the site collections that require it. This is what I consider a nasty workaround. The beauty about SharePoint feature is that you wish to let Site Collection admins manage the features to be activated themselves. With this approach, first off the site collection admins need to know that the feature exists because remember, it will be hidden from them via the web interface, and second they will need to contact the IT admin team so that they can activate it via PowerShell everytime they need a hidden feature activated. This adds a burden on both the site collection administators ad the IT admins responsible for SharePoint.

If you wish to make a feature hidden, simply navigate to its associated folder under <14 Hive>\TEMPLATE\FEATURES\, edit the Feature.xml by adding the property hidden=”true” to the Feature node. Save the file, and do an IISReset on the server. Remember to perform this on every server in the farm.
HideaFeatureSP

2 – Context Feature Logo

The second option is probably a even worst idea than the first one, but I list it here anyway in case it fits someone’s business scenario. Whenever you create a custom feature, you can specify a custom “logo” (icon) for it. By default, if no logo has been specified for a feature, SharePoint uses the default half spinwheel icon (GenericFeature.gif).
GenericFeatureicon

The idea here is to specify a custom icon for your feature, but that would be hosted at the root of each web application. Still unclear on what I mean right? Imagine we had two icons, a Green light icon, and a red “cancel” circle icon. We would ensure that the feature displays the cancel icon on all site collections contained within a Web Application for which we did not intent to deploy the feature onto, and the green icon on the ones where the feature was intended for the users to consume.

Let’s show you a graphical example of what I mean. By simply modifying the Feature.xml file for my feature and adding the following property to the Feature node

FeatureFakeIcon

I was able to tell SharePoint to go and check the root of the current Web Application for a Library called “Icons”, which contains a file named MySPWebPart.png file and to use that image as my feature icon. In my scenario, if a user is to navigate to the Site Collection feature page on the Web Application hosted on port 80 (where the feature was intended for) they would be presented with the green light icon, meaning that this feature is meant for them to consume:

GoodFeatureCanConsume

However, if a user was to navigate to the site collection feature page on a site collection hosted within the Web Application on port 81 (whee the feature was not intended to be deployed). they would be presented with the cancel icon, meaning that this feature is not intended for them to consume:

BadFeatureDoNotConsume

This solution can cause a LOT of maintenance nightmare for your IT Admin team, and relies 100% on the user’s discipline to not activate features that are not meant for them to consume. It salso requires you to create individual icons for every feature you create and to have them stored in the icons Library of each root web of your Web Applications.

3 – Make your Feature Dépendent of a Web Application Scoped One

This solution is by far the best option you have. You could even combine it with Option #2 to get even more out of it for your clients. The idea here is to create an empty Feature, have it scoped at the Web Application level, and make all other features dépendent on it. For example, if we wish to make sure the features contained within our custom MySP2010Solution.wsp can only be activated on site collections contained within the Web Application hosted on port 80, we can make our custom feature (containing our web part) dependent on a web application scoped feature that would only be activated on that web application. Users who would navigate to the site collection feature page on the Web Application hosted on port 81 would still see the feature, but as soon as they would try to activate it, SharePoint would present them with a message saying that a dependent feature was not activated.

Let’s have a closer look at that solution. Within Visual Studio, add a new feature to your project (let’s call it Controller for Web App Port 80) and scope it at the Web Application level.
ControllerFeature

Then open the Feature Wizard for the feature that contains our Web Part. At the bottom of the main screen, there’s a section called Feature activation dependencies. Expand that section and click on the Add… button.
Featureependency

You will be brought to a dialog box that let’s you pick any of the other features included in the current solution, and make your feature dependent on them. From the list, select the “Controller for Web App Port 80” Feature we just created and click on the Add button
pickdependency

You can now go ahead and deploy your solution. By default the feature will be made available to all web applications in your farm, and can be activated from anywhere. What we need to do now, is go in central administration and de-activate the Controller for Web App Port 80 feature on the Web Application hosted on port 81. To do this, in central administration, navigate to Application Management > Manage Web Applications. From the list of web application, pick the Web Application that is hosted on port 81, and in the ribbon, click on the Manage Features button.
ManageWebAppFeatureCA
Clicking this button will launch a dialog window listing all features that have been activated against this Web Application. Make sure the Controller for Web App Port 80 is deactivated.

ControllerDeactivated

By deactivating this feature you prevent anyone from being allowed to enable dependent features on the Web Application hosted on port 81. Even if the feature is still listed in the site collection feature, anyone trying to activate this feature on that web application will get a message from SharePoint:

Preventactivationmessage

While this may not be a pretty option still, it at least prevents the feature from being activated within a Web Application it was not intended for.

Find Where a SharePoint Feature is Activated using PowerShell

I had a situation come up where I need to figure out where, in a web application containing several webs and site collection​s, a specific feature was activated. I developed the following PowerShell script to help automate the process and display exactly where a specific feature is activate. Simply pass it the url of the web application you wish to look in, as well as the id of your feature. The script will automatically determine if your feature is scoped at the site collection or at the web level and will print out on screen a list of all site collections or webs where it is activated. Enjoy!

$myFeature = Get-SPFeature $featureId
if($myFeature.Scope -eq “Site”)
{
foreach($siteCollection in $spApp.Sites)
{
foreach($featureRef in $siteCollection.Features)
{
$curFeature = Get-SPFeature $featureRef.DefinitionId
if($curFeature -eq $myFeature)
{
write-host “Found at site collection:” $siteCollection.Url -ForegroundColor green
}
}
$siteCollection.Dispose()
}
}
else
{
foreach($siteCollection in $spApp.Sites)
{
foreach($web in $siteCollection.AllWebs)
{
foreach($featureRef in $web.Features)
{
$curFeature = Get-SPFeature $featureRef.DefinitionId
if($curFeature -eq $myFeature)
{
write-host “Found at Web:” $web.Url -ForegroundColor green
}
}
$web.Dispose()
}
$siteCollection.Dispose()
}
}​