Skip Ribbon Commands
Skip to main content

Robin | zevenseas | SharePoint Blog

:

The zevenseas Community > Blogs > Robin | zevenseas | SharePoint Blog > Posts > Want to prevent users from activating certain features?
January 13
Want to prevent users from activating certain features?

<Update>Check out this later post zevenseas Feature Blocker which basically is an update of this post. Or go to CodePlex and download the solution immediatly ;) </Update>

If you want to prevent users from activating certain out-of-the-box features like the Publishing feature on a non-publishing site there isn’t really much you can do about it. When I was solving this problem the first thing that sprung into mind was to attach an event handler on the feature activating method but unfortunately there is no event handler available to do this (MS if you read this.. this is on my O14 wish list as well from now on :)

Next thing that sprung into mind was to write a bit of javascript that would disable the button. Why? Well in the rendered html of the managefeatures page every feature is written down like this:

<TR><TD class="ms-vb2" style="font-weight: bold;"><H3 class="ms-standardheader">Office SharePoint Server Publishing Infrastructure</H3></TD></TR>
<TR><TD class="ms-vb2">Provides centralized libraries, content types, master pages and page layouts and enables page scheduling and other publishing functionality for a site collection.</TD></TR>
</TABLE>
</TD>
<TD class="ms-alternating" style="padding-top: 4px; padding-bottom: 4px;">
    <DIV id='f6924d36-2fa8-4f0b-b16d-06b7250180fa'>
    <input type="button" name="ctl00$PlaceHolderMain$featact$rptrFeatureList$ctl33$btnActivate" value="Activate" 
onclick="javascript:__doPostBack('ctl00$PlaceHolderMain$featact$rptrFeatureList$ctl33$btnActivate','')"
id="ctl00_PlaceHolderMain_featact_rptrFeatureList_ctl33_btnActivate" class="ms-ButtonHeightWidth" />
&nbsp;&nbsp;&nbsp;&nbsp; </DIV> </TD>

So with a piece of nifty javascript we can get the DIVbased on the GUID of the feature. And if we have the DIV, we can also get the assoicated input button and set it to disabled using this:

var siteButton = document.getElementById('f6924d36-2fa8-4f0b-b16d-06b7250180fa').childNodes[0];
siteButton.disabled = true;

The question is how to embed that piece of javascript!

So I had a little discussion with some of my SharePoint friends namely Daniel and Waldek (who now is MVP!) and got two suggestions:

  1. Write a HTTPModule
    • A good example of writing a HTTPModule which does what I want can be found on Codeplex and is SharePoint ActiveX Override (SPAXO). What this thing does is, it adds a bit of javascript that takes care of hiding the Name.dll ActiveX warning. With a little tweaking I got it working like the way I wanted it to work but the nasty habit of HTTPModules is that they intercept every page you are requesting. Considering the fact that I only want to have inject my piece of javascript on the ManageFeatures page I found this approach costing too much performance for the simple thing I wanted it to do.
  2. Write a ControlAdapter
    • Second thing was to write a ControlAdapter. Now I’ve never heard of this and if you haven’t also then let me briefly explain what it does. Using a ControlAdapter you can alter the render method of an existing control. So what you do is you first decide which control you want to alter, in my case it was the FeatureActivator from the Microsoft.SharePoint.WebControls class (that btw sits in the ApplicationPages assembly). Then you write a piece a class that inherits from the ControlAdapter and you override the “Render” method like this:
      public class FeatureActivatorAdapter : ControlAdapter
      {
          protected override void Render(System.Web.UI.HtmlTextWriter writer)
          {
              StringBuilder sb = new StringBuilder();
              HtmlTextWriter htw = new HtmlTextWriter(new StringWriter(sb));
              base.Render(htw);
      
              sb = sb.Append("<script>var siteButton = document.getElementById('f6924d36-2fa8-4f0b-b16d-06b7250180fa').childNodes[0];
      siteButton.disabled = true;</script>"
      ); writer.Write(sb.ToString()); } }

    • Next it’s time to let SharePoint know that you have created an adapter by creating a .browser file that sits in the App_Browsers folder. First you specify which control you are trying to override and secondly you specify which control must be used for the overriding. It looks something like this::
      <browsers>
      <browser refID="Default">
              <controlAdapters>
      <adapter controlType="Microsoft.SharePoint.WebControls.FeatureActivator, Microsoft.SharePoint.ApplicationPages, 
      Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"
      adapterType="PreventPublishing.FeatureActivatorAdapter" /> </controlAdapters> </browser> </browsers>
    • You may have never guessed but this stuff actually works! And now the tricky part.. making this deployable! ;) I’ve never had to put anything in the folder of a WebApplication folder so I was checking all the properties and methods of the WebApplication class and then I found the SPIisSettings class! Time for a new paragraph..

SPIiSettings

This class gives you access to the WebApplication folder of the specified UrlZone (Default/Intranet/Extranet/etc). If you take a look at the following code (that could do a little more polishing, I know..) that is implemented in a web application scoped feature receiver I get a reference to the App_Browsers folder to store the .browser file in. By using the Properties element in the Feature.xml I also get a reference to the .browser file that resides in the feature folder. So by combining the two paths I can use a StreamReader and a StreamWriter to copy the file across.

SPWebApplication webApplication = (SPWebApplication) properties.Feature.Parent;
SPIisSettings iisSettings = webApplication.GetIisSettingsWithFallback(SPUrlZone.Default);

//Setting the destination folder
string destinationPath = iisSettings.Path + @"\App_Browsers\preventpublishing.browser";
FileInfo fi = new FileInfo(destinationPath);

//Getting the folder where the custom .browser file is
string filePath = properties.Feature.Properties["Path"].Value;

using (StreamReader sr = new StreamReader(filePath))
{
    using (StreamWriter sw = fi.CreateText())
    {
        string line;
        // Read and display lines from the file until the end of 
        // the file is reached.
        while ((line = sr.ReadLine()) != null)
        {
            sw.WriteLine(line);
        }
    }
}

And that’s pretty much it! :) After all this.. this is how the end-user sees the page:

Some very useful references that helped me :

 

Technorati Tags: ,,

Comments

Jeroen Ritmeijer

Nice.
System Account on 13/01/2009 07:20

Adam

That is very useful. An easier approach would be to set Hidden="True" in the feature.xml. For example, to disallow users from activating enterprise features on their site collection you would do this:

add Hidden="True" to the <Feature> node of the following three files

Web Application level: <server>\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\FEATURES\PremiumWebApplication\feature.xml

Site Collection level: <server>\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\FEATURES\PremiumSite\feature.xml

Site level: <server>\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\FEATURES\PremiumWeb\feature.xml

I believe this hides the entire feature and description rather than just the button.
System Account on 13/01/2009 08:15

Adam

whoops. there were line breaks in there when I typed it out to make it easier to read but I think you get the idea.
System Account on 13/01/2009 08:16

Niklas

I dont think it is recommended as Adam suggest to modify SharePoint in files like the feature.xml for a built in feature.
Robins solutions is better because you can then change the behaviour for only one web application.

BUT i would suggest to deploy the new .browser file using a time job instead. Copying files with a feature receiver in a multi front end environment will only result in that the files is copied on the front end running the actual receiver and not all of them. Instead create a timer job in the receiver that runs on all front ends and does the actual copying of files.
System Account on 14/01/2009 00:12

Eric Vandekerckhove

Nice and useful article!
Does this adapter disable the control farm-wide and for everyone ?
System Account on 14/01/2009 01:01

Robin

@Adam,

i don't want to touch the feature.xml files because touching those is not supported by Microsoft. So by using this technique it's a) supported and b) has less impact because it's not being edited manually.
System Account on 15/01/2009 03:43

Robin

@Niklas, thanks for the tip. Would look into it!

@Eric, it only disables it for the selected webapplication and selected UrlZone.
System Account on 15/01/2009 03:46

Dan Usher

Robin - Any thoughts on how to just turn off specific features for a particular web application through this approach?  Cheers,dc
System Account on 25/01/2009 11:37

Robin

System Account on 01/02/2009 06:05

Sanjay

we can keep thing simple by just hiding that feature using the Hidden="true" in the feature.xml of that feature

"Everything should be made as simple as possible, but not simpler."
System Account on 10/05/2009 11:26
1 - 10Next
 

 Statistics

 
Views: 8108
Comments: 14
Tags:
Published:1591 Days Ago