MOSS MVP

I've moved my blog to http://blog.falchionconsulting.com!. Please update your links. This blog is no longer in use--you can find all posts and comments at my new blog; I will no longer be posting to this site and comments have been disabled.

Wednesday, November 26, 2008

My New PowerShell Cmdlets for SharePoint 2007: Feedback Requested!

Okay all you PowerShell superstars out there that have been using my STSADM commands - I need your help!  I've decided to teach myself PowerShell and see if I can't leverage some of my STSADM experiences/code to expose some SharePoint functionality and I need some people who really know this PowerShell stuff to please take a look at what I've got so far and let me know if I'm way off base or not.

My initial stab includes 11 new cmdlets (10 Get and 1 Add):

  • Get-SPContentType-gl
  • Get-SPContentTypeUsages-gl
  • Get-SPFarm-gl
  • Get-SPField-gl
  • Add-SPList-gl
  • Get-SPList-gl
  • Get-SPSite-gl
  • Get-SPUserProfileManager-gl
  • Get-SPPrivacyPolicyManager-gl
  • Get-SPWeb-gl
  • Get-SPWebApplication-gl

The first thing you'll hopefully notice is that I've adopted a naming convention similar to what I did with my STSADM commands - I looked everywhere for best practices on naming custom cmdlets to avoid name clashes but couldn't find a darn thing (documentation for creating cmdlets is absolutely horrible!).  What does everyone think of this approach?  I tried to at least keep the verb-product+noun nomenclature so that you'd still be able to easily see all the get commands without having to also search -gl (so rather than gl-get-spfarm which would break a search for get-*).

I don't currently have help completed for all the commands, only a couple of them as I wanted to wait until I got some feedback before investing too much time in this.  I guess I should first ask if people see the need for this?  Am I wasting my time?  I want to learn PowerShell either way so I'll create stuff regardless but I'd like to be able to share with the community for some mutual benefits.

So, if you know PowerShell and have time to play with what I've created thus far (take a look at the code too and see if I'm doing anything stupid) please feel free to download the source and installer:

  • Setup Package: Lapointe.SharePoint.PowerShell.Commands.Setup.msi
    • I'm currently NOT including my custom STSADM extensions in the install package so you'll need to download and install those separately as they are a required dependency (see the links at the top of the page) - I may change this later so that the installer installs the STSADM extensions but I just don't want to have to worry about that at the moment (for now I'm just pushing out builds for both products at the same time).
  • Source Code: SPPoSH.zip
    • Crack it open, be honest, be brutal, but please, be constructive :)

Keep in mind that this is just an initial "Alpha" release to see what people think - if you've followed my blog any or used my STSADM extensions then you can probably guess that there'll be a lot more of these cmdlets in the future so I want to make sure that I'm taking the right approach.  Also - if you have any ideas for cmdlets that would be really helpful please let me know and I'll see if I can put something together as part of my learning exercise.

Thanks again to anyone who is able to help me out with this!

Update 12/14/2008: Based on feedback received (thanks Harley!) I've made one significant modification - the Get-SPSite-gl and Get-SPWeb-gl cmdlets now return back wrapper objects: SPSiteInfo and SPWebInfo, respectively.  These wrapper objects allow the caller to not have to worry about calling Dispose() which is particularly useful when the results have been filtered.  The Info objects contain virtually all the properties that their equivalent SP classes contain.  You can still get to the actual object by calling the SPBase property or the GetSPObject().

Saturday, November 15, 2008

Deactivating Features at Different Scopes Using STSADM

I just posted about one of my new commands, gl-activatefeature, which covers activating features at different scopes using STSADM.  That article covers all the code necessary to not only implement the activation, but also the deactivation.  So creating the next command, gl-deactivatefeature, was as simple as copying the container code for the activation command and changing a few simple lines of code.  This command works exactly like the other except that it deactivates the Feature instead of activating it.

The help for the command is shown below:

C:\>stsadm -help gl-deactivatefeature

stsadm -o gl-deactivatefeature


Deactivates a feature at a given scope.

Parameters:
        {-filename <relative path to Feature.xml> |
         -name <feature folder> |
         -id <feature Id>}
        [-scope <farm | webapplication | site | web | feature> (defaults to Feature)]
        [-url <url>]
        [-force]
        [-ignorenonactive]

The following table summarizes the command and its various parameters:

Command Name Availability Build Date
gl-deactivatefeature WSS v3, MOSS 2007 Released: 11/15/2008

Parameter Name Short Form Required Description Example Usage
filename f Yes, if -name or -id is not provided

Path to feature must be a relative path to the 12\Template\Features directory. Can be any standard character that the Windows system supports for a file name.

Note:  If the feature file is not found on disk, the following error message is displayed: “Failed to find the XML file at location '12\Template\Features\<file path>'.”

-filename "MyFeature\feature.xml"

-f "MyFeature\feature.xml"
name n Yes, if -filename or -id is not provided Name of the feature folder located in the 12\Template\Features directory -name "MyFeature"

-n "MyFeature"
id   Yes, if -filename or -name is not provided

GUID that identifies the feature to activate

Note:  If the ID is specified but the feature does not exist, the following error message is returned: "Feature '<id>' is not installed in this farm, and cannot be added to this scope." 

-id "21d186e1-7036-4092-a825-0eb6709e9280"
scope s No The scope to look at when deactivating the Feature.  Valid values are "Farm", "WebApplication", "Site", "Web", or "Feature".  If "Feature" is specified then the scope of the Feature will be used.

Note: Be careful when using a scope of "Web" (or "Feature" when the Feature is scoped to Web) as this will work recursively upon not just the single web but all sub-webs as well.
-scope site

-scope s
url   No

URL of the Web application, site collection, or Web site to which the feature is being deactivated with respect to the provided scope.  So if the Feature is scoped to Web and you pass in a scope of Site then all webs within the Site Collection of the provided URL will have the Feature deactivated.  If the scope is Farm then an URL is not required.

-url http://portal
force   No

Forces the deactivation of the feature if already deactivated.

-force
ignorenonactive ignore No This will prevent the Feature from attempting a deactivation if it is not already activated thus avoiding errors about the Feature not being activated at the particular scope. -ignorenonactive

-ignore

The following is an example of how to deactivate a Site Collection scoped Feature on every site collection within a web application:

stsadm -o gl-deactivatefeature -name MyCustomFeature -scope webapplication -url http://mysites -force

The following is an example of how to deactivate a Web scoped Feature on every web within a web application where the Feature is already activated:

stsadm -o gl-deactivatefeature -name MyCustomFeature -scope webapplication -url http://portal -ignorenonactive

Activating Features at Different Scopes Using STSADM

How many times have you had a Feature, either out-of-the-box or custom, that you have needed to activate at lots of different scopes or re-activate at lots of different scopes?  To do this you may have found a way to get the list of site collections or webs and then somehow used that list in conjunction with the STSADM activatefeature command or worse you manually went to every site or web and manually activated or re-activated the Feature - this is extremely tedious and error prone as you may miss a site or web.  Another common scenario has to do with "My Sites" - perhaps you've written a custom Feature that configures a users my site when created and now you've made a change to that Feature and need to reactivate the Feature on all existing My Sites.  Doing this in the past was a pain - but not any more thanks to my new command: gl-activatefeature.

The specific scenario that prompted me to write this command was the need to re-activate a custom Feature everywhere it was currently activated without activating it anywhere it wasn't already activated.  I needed to do this because I had added a couple of event receivers to an already deployed Feature but I didn't know where that Feature was already activated and didn't wish to activate it if not already activated.  So I had two three core issues to solve - the first was to enable the activation (and eventually deactivation) of a Feature at the various scopes (Farm, Web Application, Site, and Web); the second was to be able to conditionally force a re-activation only if the Feature is already activated and not do anything if not activated; the third was to be able to iterate over various scopes - Farm, Web Application, Site, or Web (so if the Feature is scoped to Site and the user passes in a scope of Web Application then I need to look at every Site Collection within the specified Web Application).  I also wanted to have the parameters of the command work just like the OOTB command (along with any additional parameters I'd need).

So the first thing I need to do was make sure that I could get the Feature ID which would be used throughout the code - but I wanted the user to be able to pass in the ID (the easy part), the name, or the filename - just like the OOTB command.  I took a look at how the OOTB command worked by using Reflector and found a simple method that I was able to refactor slightly:

internal static Guid GetFeatureIdFromParams(SPParamCollection Params)
{
    Guid empty = Guid.Empty;
    if (!Params["id"].UserTypedIn)
    {
        SPFeatureScope scope;
        if (!Params["filename"].UserTypedIn)
        {
            if (Params["name"].UserTypedIn)
            {
                SPFeatureScope scope2;
                SPFeatureDefinition.GetFeatureIdAndScope(Params["name"].Value + @"\feature.xml", out empty, out scope2);
            }
            return empty;
        }
        SPFeatureDefinition.GetFeatureIdAndScope(Params["filename"].Value, out empty, out scope);
        return empty;
    }
    return new Guid(Params["id"].Value);
}

Once I had the Feature ID I could now use this to conditionally add or remove (activate or deactivate) the Feature from the appropriate scope.  The way you activate a Feature programmatically is to simply call the Add or Remove methods of an SPFeatureCollection object.  You can get this object from either the SPFarm, SPWebApplication, SPSite, or SPWeb objects - each containing a "Features" property which exposes the collection object.

private SPFeature ActivateDeactivateFeature(SPFeatureCollection features, bool activate, Guid featureId, string urlScope, bool force, bool ignoreNonActive)
{
    if (features[featureId] == null && ignoreNonActive)
        return null;
 
    if (!activate)
    {
        if (features[featureId] != null || force)
        {
            Log("Progress: Deactivating Feature {0} from {1}.", featureId.ToString(), urlScope);
            try
            {
                features.Remove(featureId, force);
            }
            catch (Exception ex)
            {
                Log("WARNING: {0}", ex.Message);
            }
        }
        else
        {
            Log("WARNING: " + SPResource.GetString("FeatureNotActivatedAtScope", new object[] { featureId }) + "  Use the -force parameter to force a deactivation.");
        }
 
        return null;
    }
    if (features[featureId] == null)
        Log("Progress: Activating Feature {0} on {1}.", featureId.ToString(), urlScope);
    else
    {
        if (!force)
        {
            SPFeatureDefinition fd = features[featureId].Definition;
            Log("WARNING: " + SPResource.GetString("FeatureAlreadyActivated", new object[] { fd.DisplayName, fd.Id, urlScope }) + "  Use the -force parameter to force a reactivation.");
            return features[featureId];
        }
 
        Log("Progress: Re-Activating Feature {0} on {1}.", featureId.ToString(), urlScope);
    }
 
    return features.Add(featureId, force);
}

One of the first things I do in this method is check if the user has chosen to ignore situations when the Feature is not already active - I do that by checking the item indexer and seeing if it returns null: features[featureId] == null.  If null is returned then the Feature is not activated.

Once I had the two methods above I could then create all the support code which basically just determines which scopes to consider based on the Feature scope and the user provided scope.  I also use my cool SPEnumerator class that I created a while back to help make iterating nice and easy.  I wrapped all this code up into a single helper class that I could then use with both my gl-activatefeature and gl-deactivatefeature commands (I'll cover the gl-deactivatefeature command in the next post).

using System;
using System.IO;
using Lapointe.SharePoint.STSADM.Commands.OperationHelpers;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
 
namespace Lapointe.SharePoint.STSADM.Commands.Features
{
    public enum ActivationScope
    {
        Farm, WebApplication, Site, Web, Feature
    }
    public class FeatureHelper
    {
        private string m_Url;
        private bool m_Force;
        private Guid m_FeatureId = Guid.Empty;
        private bool m_IgnoreNonActive;
        private bool m_Activate;
 
        /// <summary>
        /// Logs the specified message.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <param name="args">The args.</param>
        protected virtual void Log(string message, params string[] args)
        {
            SPOperation.Log(message, args);
        }
 
        /// <summary>
        /// Gets the feature id from params.
        /// </summary>
        /// <param name="Params">The params.</param>
        /// <returns></returns>
        internal static Guid GetFeatureIdFromParams(SPParamCollection Params)
        {
            Guid empty = Guid.Empty;
            if (!Params["id"].UserTypedIn)
            {
                SPFeatureScope scope;
                if (!Params["filename"].UserTypedIn)
                {
                    if (Params["name"].UserTypedIn)
                    {
                        SPFeatureScope scope2;
                        SPFeatureDefinition.GetFeatureIdAndScope(Params["name"].Value + @"\feature.xml", out empty, out scope2);
                    }
                    return empty;
                }
                SPFeatureDefinition.GetFeatureIdAndScope(Params["filename"].Value, out empty, out scope);
                return empty;
            }
            return new Guid(Params["id"].Value);
        }
 
        /// <summary>
        /// Activates or deactivates the feature at the specified scope.
        /// </summary>
        /// <param name="scope">The scope.</param>
        /// <param name="featureId">The feature id.</param>
        /// <param name="activate">if set to <c>true</c> [activate].</param>
        /// <param name="url">The URL.</param>
        /// <param name="force">if set to <c>true</c> [force].</param>
        /// <param name="ignoreNonActive">if set to <c>true</c> [ignore non active].</param>
        public void ActivateDeactivateFeatureAtScope(ActivationScope scope, Guid featureId, bool activate, string url, bool force, bool ignoreNonActive)
        {
            SPOperation.Verbose = true;
 
            m_FeatureId = featureId;
            m_Url = url;
            m_Force = force;
            m_IgnoreNonActive = ignoreNonActive;
            m_Activate = activate;
 
            if (m_FeatureId.Equals(Guid.Empty))
                throw new SPException("Unable to locate Feature.");
 
            SPFeatureDefinition feature = SPFarm.Local.FeatureDefinitions[m_FeatureId];
            if (feature == null)
                throw new SPException("Unable to locate Feature.");
 
            if (scope == ActivationScope.Feature)
                scope = (ActivationScope)Enum.Parse(typeof(ActivationScope), feature.Scope.ToString().ToLowerInvariant(), true);
 
            if (feature.Scope == SPFeatureScope.Farm)
            {
                if (scope != ActivationScope.Farm)
                    throw new SPSyntaxException("The Feature specified is scoped to the Farm.  The -scope parameter must be \"Farm\".");
                ActivateDeactivateFeatureAtFarm(activate, m_FeatureId, m_Force, m_IgnoreNonActive);
            }
            else if (feature.Scope == SPFeatureScope.WebApplication)
            {
                if (scope != ActivationScope.Farm && scope != ActivationScope.WebApplication)
                    throw new SPSyntaxException("The Feature specified is scoped to the Web Application.  The -scope parameter must be either \"Farm\" or \"WebApplication\".");
 
                if (scope == ActivationScope.Farm)
                {
                    SPEnumerator enumerator = new SPEnumerator(SPFarm.Local);
                    enumerator.SPWebApplicationEnumerated += enumerator_SPWebApplicationEnumerated;
                    enumerator.Enumerate();
                }
                else
                {
                    if (string.IsNullOrEmpty(m_Url))
                        throw new SPSyntaxException("The -url parameter is required if the scope is \"WebApplication\".");
                    SPWebApplication webApp = SPWebApplication.Lookup(new Uri(m_Url));
                    ActivateDeactivateFeatureAtWebApplication(webApp, m_FeatureId, activate, m_Force, m_IgnoreNonActive);
                }
            }
            else if (feature.Scope == SPFeatureScope.Site)
            {
                if (scope == ActivationScope.Web)
                    throw new SPSyntaxException("The Feature specified is scoped to Site.  The -scope parameter cannot be \"Web\".");
 
                SPSite site = null;
                SPEnumerator enumerator = null;
                try
                {
                    if (scope == ActivationScope.Farm)
                        enumerator = new SPEnumerator(SPFarm.Local);
                    else if (scope == ActivationScope.WebApplication)
                    {
                        SPWebApplication webApp = SPWebApplication.Lookup(new Uri(m_Url));
                        enumerator = new SPEnumerator(webApp);
                    }
                    else if (scope == ActivationScope.Site)
                    {
                        site = new SPSite(m_Url);
                        ActivateDeactivateFeatureAtSite(site, activate, m_FeatureId, m_Force, m_IgnoreNonActive);
                    }
                    if (enumerator != null)
                    {
                        enumerator.SPSiteEnumerated += enumerator_SPSiteEnumerated;
                        enumerator.Enumerate();
                    }
                }
                finally
                {
                    if (site != null)
                        site.Dispose();
                }
            }
            else if (feature.Scope == SPFeatureScope.Web)
            {
                SPSite site = null;
                SPWeb web = null;
                SPEnumerator enumerator = null;
                try
                {
                    if (scope == ActivationScope.Farm)
                        enumerator = new SPEnumerator(SPFarm.Local);
                    else if (scope == ActivationScope.WebApplication)
                    {
                        SPWebApplication webApp = SPWebApplication.Lookup(new Uri(m_Url));
                        enumerator = new SPEnumerator(webApp);
                    }
                    else if (scope == ActivationScope.Site)
                    {
                        site = new SPSite(m_Url);
                        enumerator = new SPEnumerator(site);
                    }
                    else if (scope == ActivationScope.Web)
                    {
                        site = new SPSite(m_Url);
                        web = site.AllWebs[Utilities.GetServerRelUrlFromFullUrl(m_Url)];
                        ActivateDeactivateFeatureAtWeb(site, web, activate, m_FeatureId, m_Force, m_IgnoreNonActive);
                    }
                    if (enumerator != null)
                    {
                        enumerator.SPWebEnumerated += enumerator_SPWebEnumerated;
                        enumerator.Enumerate();
                    }
                }
                finally
                {
                    if (web != null)
                        web.Dispose();
                    if (site != null)
                        site.Dispose();
                }
            }
        }
 
        #region Event Handlers
 
        /// <summary>
        /// Handles the SPWebEnumerated event of the enumerator control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="Lapointe.SharePoint.STSADM.Commands.OperationHelpers.SPEnumerator.SPWebEventArgs"/> instance containing the event data.</param>
        private void enumerator_SPWebEnumerated(object sender, SPEnumerator.SPWebEventArgs e)
        {
            ActivateDeactivateFeatureAtWeb(e.Site, e.Web, m_Activate, m_FeatureId, m_Force, m_IgnoreNonActive);
        }
 
        /// <summary>
        /// Handles the SPSiteEnumerated event of the enumerator control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="Lapointe.SharePoint.STSADM.Commands.OperationHelpers.SPEnumerator.SPSiteEventArgs"/> instance containing the event data.</param>
        private void enumerator_SPSiteEnumerated(object sender, SPEnumerator.SPSiteEventArgs e)
        {
            ActivateDeactivateFeatureAtSite(e.Site, m_Activate, m_FeatureId, m_Force, m_IgnoreNonActive);
        }
 
        /// <summary>
        /// Handles the SPWebApplicationEnumerated event of the enumerator control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="Lapointe.SharePoint.STSADM.Commands.OperationHelpers.SPEnumerator.SPWebApplicationEventArgs"/> instance containing the event data.</param>
        private void enumerator_SPWebApplicationEnumerated(object sender, SPEnumerator.SPWebApplicationEventArgs e)
        {
            ActivateDeactivateFeatureAtWebApplication(e.WebApplication, m_FeatureId, m_Activate, m_Force, m_IgnoreNonActive);
        }
 
        #endregion
 
        /// <summary>
        /// Activates or deactivates the feature.
        /// </summary>
        /// <param name="features">The features.</param>
        /// <param name="activate">if set to <c>true</c> [activate].</param>
        /// <param name="featureId">The feature id.</param>
        /// <param name="urlScope">The URL scope.</param>
        /// <param name="force">if set to <c>true</c> [force].</param>
        /// <param name="ignoreNonActive">if set to <c>true</c> [ignore non active].</param>
        /// <returns></returns>
        private SPFeature ActivateDeactivateFeature(SPFeatureCollection features, bool activate, Guid featureId, string urlScope, bool force, bool ignoreNonActive)
        {
            if (features[featureId] == null && ignoreNonActive)
                return null;
 
            if (!activate)
            {
                if (features[featureId] != null || force)
                {
                    Log("Progress: Deactivating Feature {0} from {1}.", featureId.ToString(), urlScope);
                    try
                    {
                        features.Remove(featureId, force);
                    }
                    catch (Exception ex)
                    {
                        Log("WARNING: {0}", ex.Message);
                    }
                }
                else
                {
                    Log("WARNING: " + SPResource.GetString("FeatureNotActivatedAtScope", new object[] { featureId }) + "  Use the -force parameter to force a deactivation.");
                }
 
                return null;
            }
            if (features[featureId] == null)
                Log("Progress: Activating Feature {0} on {1}.", featureId.ToString(), urlScope);
            else
            {
                if (!force)
                {
                    SPFeatureDefinition fd = features[featureId].Definition;
                    Log("WARNING: " + SPResource.GetString("FeatureAlreadyActivated", new object[] { fd.DisplayName, fd.Id, urlScope }) + "  Use the -force parameter to force a reactivation.");
                    return features[featureId];
                }
 
                Log("Progress: Re-Activating Feature {0} on {1}.", featureId.ToString(), urlScope);
            }
 
            return features.Add(featureId, force);
        }
        /// <summary>
        /// Activates or deactivates the farm scoped feature.
        /// </summary>
        /// <param name="activate">if set to <c>true</c> [activate].</param>
        /// <param name="featureId">The feature id.</param>
        /// <param name="force">if set to <c>true</c> [force].</param>
        /// <param name="ignoreNonActive">if set to <c>true</c> [ignore non active].</param>
        /// <returns></returns>
        public SPFeature ActivateDeactivateFeatureAtFarm(bool activate, Guid featureId, bool force, bool ignoreNonActive)
        {
            SPWebService service = SPFarm.Local.Services.GetValue<SPWebService>(string.Empty);
            return ActivateDeactivateFeature(service.Features, activate, featureId, "Farm", force, ignoreNonActive);
        }
 
        /// <summary>
        /// Activates or deactivates the web application scoped feature.
        /// </summary>
        /// <param name="activate">if set to <c>true</c> [activate].</param>
        /// <param name="featureId">The feature id.</param>
        /// <param name="urlScope">The URL scope.</param>
        /// <param name="force">if set to <c>true</c> [force].</param>
        /// <param name="ignoreNonActive">if set to <c>true</c> [ignore non active].</param>
        /// <returns></returns>
        public SPFeature ActivateDeactivateFeatureAtWebApplication(bool activate, Guid featureId, string urlScope, bool force, bool ignoreNonActive)
        {
            SPWebApplication application = SPWebApplication.Lookup(new Uri(urlScope));
            if (application == null)
            {
                throw new FileNotFoundException(SPResource.GetString("WebApplicationLookupFailed", new object[] { urlScope }));
            }
            return ActivateDeactivateFeatureAtWebApplication(application, featureId, activate, force, ignoreNonActive);
        }
 
        /// <summary>
        /// Activates or deactivates the web application scoped feature.
        /// </summary>
        /// <param name="application">The application.</param>
        /// <param name="featureId">The feature id.</param>
        /// <param name="activate">if set to <c>true</c> [activate].</param>
        /// <param name="force">if set to <c>true</c> [force].</param>
        /// <param name="ignoreNonActive">if set to <c>true</c> [ignore non active].</param>
        /// <returns></returns>
        public SPFeature ActivateDeactivateFeatureAtWebApplication(SPWebApplication application, Guid featureId, bool activate, bool force, bool ignoreNonActive)
        {
            return ActivateDeactivateFeature(application.Features, activate, featureId, application.GetResponseUri(SPUrlZone.Default).ToString(), force, ignoreNonActive);
        }
 
        /// <summary>
        /// Activates or deactivates the site scoped feature.
        /// </summary>
        /// <param name="activate">if set to <c>true</c> [activate].</param>
        /// <param name="featureId">The feature id.</param>
        /// <param name="urlScope">The URL scope.</param>
        /// <param name="force">if set to <c>true</c> [force].</param>
        /// <param name="ignoreNonActive">if set to <c>true</c> [ignore non active].</param>
        /// <returns></returns>
        public SPFeature ActivateDeactivateFeatureAtSite(bool activate, Guid featureId, string urlScope, bool force, bool ignoreNonActive)
        {
            using (SPSite site = new SPSite(urlScope))
            using (SPWeb web = site.OpenWeb(Utilities.GetServerRelUrlFromFullUrl(urlScope), true))
            {
                if (web.IsRootWeb)
                {
                    return ActivateDeactivateFeatureAtSite(site, activate, featureId, force, ignoreNonActive);
                }
                throw new SPException(SPResource.GetString("FeatureActivateDeactivateScopeAmbiguous", new object[] { site.Url }));
            }
        }
 
        /// <summary>
        /// Activates or deactivates the site scoped feature.
        /// </summary>
        /// <param name="site">The site.</param>
        /// <param name="activate">if set to <c>true</c> [activate].</param>
        /// <param name="featureId">The feature id.</param>
        /// <param name="force">if set to <c>true</c> [force].</param>
        /// <param name="ignoreNonActive">if set to <c>true</c> [ignore non active].</param>
        /// <returns></returns>
        public SPFeature ActivateDeactivateFeatureAtSite(SPSite site, bool activate, Guid featureId, bool force, bool ignoreNonActive)
        {
            return ActivateDeactivateFeature(site.Features, activate, featureId, site.Url, force, ignoreNonActive);
        }
 
        /// <summary>
        /// Activates or deactivates the web scoped feature.
        /// </summary>
        /// <param name="activate">if set to <c>true</c> [activate].</param>
        /// <param name="featureId">The feature id.</param>
        /// <param name="urlScope">The URL scope.</param>
        /// <param name="force">if set to <c>true</c> [force].</param>
        /// <param name="ignoreNonActive">if set to <c>true</c> [ignore non active].</param>
        /// <returns></returns>
        public SPFeature ActivateDeactivateFeatureAtWeb(bool activate, Guid featureId, string urlScope, bool force, bool ignoreNonActive)
        {
            using (SPSite site = new SPSite(urlScope))
            using (SPWeb web = site.OpenWeb())
            {
                return ActivateDeactivateFeatureAtWeb(site, web, activate, featureId, force, ignoreNonActive);
            }
        }
 
        /// <summary>
        /// Activates or deactivates the web scoped feature.
        /// </summary>
        /// <param name="site">The site.</param>
        /// <param name="web">The web.</param>
        /// <param name="activate">if set to <c>true</c> [activate].</param>
        /// <param name="featureId">The feature id.</param>
        /// <param name="force">if set to <c>true</c> [force].</param>
        /// <param name="ignoreNonActive">if set to <c>true</c> [ignore non active].</param>
        /// <returns></returns>
        public SPFeature ActivateDeactivateFeatureAtWeb(SPSite site, SPWeb web, bool activate, Guid featureId, bool force, bool ignoreNonActive)
        {
            return ActivateDeactivateFeature(web.Features, activate, featureId, web.Url, force, ignoreNonActive);
        }
 
    }
}

The help for the command is shown below:

C:\>stsadm -help gl-activatefeature

stsadm -o gl-activatefeature


Activates a feature at a given scope.

Parameters:
        {-filename <relative path to Feature.xml> |
         -name <feature folder> |
         -id <feature Id>}
        [-scope <farm | webapplication | site | web | feature> (defaults to Feature)]
        [-url <url>]
        [-force]
        [-ignorenonactive]

The following table summarizes the command and its various parameters:

Command Name Availability Build Date
gl-activatefeature WSS v3, MOSS 2007 Released: 11/15/2008

Parameter Name Short Form Required Description Example Usage
filename f Yes, if -name or -id is not provided

Path to feature must be a relative path to the 12\Template\Features directory. Can be any standard character that the Windows system supports for a file name.

Note:  If the feature file is not found on disk, the following error message is displayed: “Failed to find the XML file at location '12\Template\Features\<file path>'.”

-filename "MyFeature\feature.xml"

-f "MyFeature\feature.xml"
name n Yes, if -filename or -id is not provided Name of the feature folder located in the 12\Template\Features directory -name "MyFeature"

-n "MyFeature"
id   Yes, if -filename or -name is not provided

GUID that identifies the feature to activate

Note:  If the ID is specified but the feature does not exist, the following error message is returned: "Feature '<id>' is not installed in this farm, and cannot be added to this scope." 

-id "21d186e1-7036-4092-a825-0eb6709e9280"
scope s No The scope to look at when activating the Feature.  Valid values are "Farm", "WebApplication", "Site", "Web", or "Feature".  If "Feature" is specified then the scope of the Feature will be used.

Note: Be careful when using a scope of "Web" (or "Feature" when the Feature is scoped to Web) as this will work recursively upon not just the single web but all sub-webs as well.
-scope site

-s site
url   No

URL of the Web application, site collection, or Web site to which the feature is being activated

with respect to the provided scope.  So if the Feature is scoped to Web and you pass in a scope of Site then all webs within the Site Collection of the provided URL will have the Feature activated.  If the scope is Farm then an URL is not required.
-url http://portal
force   No

Forces the re-activation of the feature if already activated. This causes any custom code associated with the feature to rerun.

-force
ignorenonactive ignore No This will prevent the Feature from being activated if it is not already activated thus triggering a reactivation where already activated. -ignorenonactive

-ignore

The following is an example of how to activate a Site Collection scoped Feature on every site collection within a web application:

stsadm -o gl-activatefeature -name MyCustomFeature -scope webapplication -url http://mysites -force

The following is an example of how to re-activate a Web scoped Feature on every web within a web application where the Feature is already activated:

stsadm -o gl-activatefeature -name MyCustomFeature -scope webapplication -url http://portal -ignorenonactive