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:
1internal static Guid GetFeatureIdFromParams(SPParamCollection Params)
2{
3 Guid empty = Guid.Empty;
4 if (!Params["id"].UserTypedIn)
5 {
6 SPFeatureScope scope;
7 if (!Params["filename"].UserTypedIn)
8 {
9 if (Params["name"].UserTypedIn)
10 {
11 SPFeatureScope scope2;
12 SPFeatureDefinition.GetFeatureIdAndScope(Params["name"].Value + @"\feature.xml", out empty, out scope2);
13 }
14 return empty;
15 }
16 SPFeatureDefinition.GetFeatureIdAndScope(Params["filename"].Value, out empty, out scope);
17 return empty;
18 }
19 return new Guid(Params["id"].Value);
20}
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.
1private SPFeature ActivateDeactivateFeature(SPFeatureCollection features, bool activate, Guid featureId, string urlScope, bool force, bool ignoreNonActive)
2{
3 if (features[featureId] == null && ignoreNonActive)
4 return null;
5
6 if (!activate)
7 {
8 if (features[featureId] != null || force)
9 {
10 Log("Progress: Deactivating Feature {0} from {1}.", featureId.ToString(), urlScope);
11 try
12 {
13 features.Remove(featureId, force);
14 }
15 catch (Exception ex)
16 {
17 Log("WARNING: {0}", ex.Message);
18 }
19 }
20 else
21 {
22 Log("WARNING: " + SPResource.GetString("FeatureNotActivatedAtScope", new object[] { featureId }) + " Use the -force parameter to force a deactivation.");
23 }
24
25 return null;
26 }
27 if (features[featureId] == null)
28 Log("Progress: Activating Feature {0} on {1}.", featureId.ToString(), urlScope);
29 else
30 {
31 if (!force)
32 {
33 SPFeatureDefinition fd = features[featureId].Definition;
34 Log("WARNING: " + SPResource.GetString("FeatureAlreadyActivated", new object[] { fd.DisplayName, fd.Id, urlScope }) + " Use the -force parameter to force a reactivation.");
35 return features[featureId];
36 }
37
38 Log("Progress: Re-Activating Feature {0} on {1}.", featureId.ToString(), urlScope);
39 }
40
41 return features.Add(featureId, force);
42}
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).
1using System;
2using System.IO;
3using Lapointe.SharePoint.STSADM.Commands.OperationHelpers;
4using Microsoft.SharePoint;
5using Microsoft.SharePoint.Administration;
6
7namespace Lapointe.SharePoint.STSADM.Commands.Features
8{
9 public enum ActivationScope
10 {
11 Farm, WebApplication, Site, Web, Feature
12 }
13 public class FeatureHelper
14 {
15 private string m_Url;
16 private bool m_Force;
17 private Guid m_FeatureId = Guid.Empty;
18 private bool m_IgnoreNonActive;
19 private bool m_Activate;
20
21 /// <summary>
22 /// Logs the specified message.
23 /// </summary>
24 /// <param name="message">The message.</param>
25 /// <param name="args">The args.</param>
26 protected virtual void Log(string message, params string[] args)
27 {
28 SPOperation.Log(message, args);
29 }
30
31 /// <summary>
32 /// Gets the feature id from params.
33 /// </summary>
34 /// <param name="Params">The params.</param>
35 /// <returns></returns>
36 internal static Guid GetFeatureIdFromParams(SPParamCollection Params)
37 {
38 Guid empty = Guid.Empty;
39 if (!Params["id"].UserTypedIn)
40 {
41 SPFeatureScope scope;
42 if (!Params["filename"].UserTypedIn)
43 {
44 if (Params["name"].UserTypedIn)
45 {
46 SPFeatureScope scope2;
47 SPFeatureDefinition.GetFeatureIdAndScope(Params["name"].Value + @"\feature.xml", out empty, out scope2);
48 }
49 return empty;
50 }
51 SPFeatureDefinition.GetFeatureIdAndScope(Params["filename"].Value, out empty, out scope);
52 return empty;
53 }
54 return new Guid(Params["id"].Value);
55 }
56
57 /// <summary>
58 /// Activates or deactivates the feature at the specified scope.
59 /// </summary>
60 /// <param name="scope">The scope.</param>
61 /// <param name="featureId">The feature id.</param>
62 /// <param name="activate">if set to <c>true</c> [activate].</param>
63 /// <param name="url">The URL.</param>
64 /// <param name="force">if set to <c>true</c> [force].</param>
65 /// <param name="ignoreNonActive">if set to <c>true</c> [ignore non active].</param>
66 public void ActivateDeactivateFeatureAtScope(ActivationScope scope, Guid featureId, bool activate, string url, bool force, bool ignoreNonActive)
67 {
68 SPOperation.Verbose = true;
69
70 m_FeatureId = featureId;
71 m_Url = url;
72 m_Force = force;
73 m_IgnoreNonActive = ignoreNonActive;
74 m_Activate = activate;
75
76 if (m_FeatureId.Equals(Guid.Empty))
77 throw new SPException("Unable to locate Feature.");
78
79 SPFeatureDefinition feature = SPFarm.Local.FeatureDefinitions[m_FeatureId];
80 if (feature == null)
81 throw new SPException("Unable to locate Feature.");
82
83 if (scope == ActivationScope.Feature)
84 scope = (ActivationScope)Enum.Parse(typeof(ActivationScope), feature.Scope.ToString().ToLowerInvariant(), true);
85
86 if (feature.Scope == SPFeatureScope.Farm)
87 {
88 if (scope != ActivationScope.Farm)
89 throw new SPSyntaxException("The Feature specified is scoped to the Farm. The -scope parameter must be \"Farm\".");
90 ActivateDeactivateFeatureAtFarm(activate, m_FeatureId, m_Force, m_IgnoreNonActive);
91 }
92 else if (feature.Scope == SPFeatureScope.WebApplication)
93 {
94 if (scope != ActivationScope.Farm && scope != ActivationScope.WebApplication)
95 throw new SPSyntaxException("The Feature specified is scoped to the Web Application. The -scope parameter must be either \"Farm\" or \"WebApplication\".");
96
97 if (scope == ActivationScope.Farm)
98 {
99 SPEnumerator enumerator = new SPEnumerator(SPFarm.Local);
100 enumerator.SPWebApplicationEnumerated += enumerator_SPWebApplicationEnumerated;
101 enumerator.Enumerate();
102 }
103 else
104 {
105 if (string.IsNullOrEmpty(m_Url))
106 throw new SPSyntaxException("The -url parameter is required if the scope is \"WebApplication\".");
107 SPWebApplication webApp = SPWebApplication.Lookup(new Uri(m_Url));
108 ActivateDeactivateFeatureAtWebApplication(webApp, m_FeatureId, activate, m_Force, m_IgnoreNonActive);
109 }
110 }
111 else if (feature.Scope == SPFeatureScope.Site)
112 {
113 if (scope == ActivationScope.Web)
114 throw new SPSyntaxException("The Feature specified is scoped to Site. The -scope parameter cannot be \"Web\".");
115
116 SPSite site = null;
117 SPEnumerator enumerator = null;
118 try
119 {
120 if (scope == ActivationScope.Farm)
121 enumerator = new SPEnumerator(SPFarm.Local);
122 else if (scope == ActivationScope.WebApplication)
123 {
124 SPWebApplication webApp = SPWebApplication.Lookup(new Uri(m_Url));
125 enumerator = new SPEnumerator(webApp);
126 }
127 else if (scope == ActivationScope.Site)
128 {
129 site = new SPSite(m_Url);
130 ActivateDeactivateFeatureAtSite(site, activate, m_FeatureId, m_Force, m_IgnoreNonActive);
131 }
132 if (enumerator != null)
133 {
134 enumerator.SPSiteEnumerated += enumerator_SPSiteEnumerated;
135 enumerator.Enumerate();
136 }
137 }
138 finally
139 {
140 if (site != null)
141 site.Dispose();
142 }
143 }
144 else if (feature.Scope == SPFeatureScope.Web)
145 {
146 SPSite site = null;
147 SPWeb web = null;
148 SPEnumerator enumerator = null;
149 try
150 {
151 if (scope == ActivationScope.Farm)
152 enumerator = new SPEnumerator(SPFarm.Local);
153 else if (scope == ActivationScope.WebApplication)
154 {
155 SPWebApplication webApp = SPWebApplication.Lookup(new Uri(m_Url));
156 enumerator = new SPEnumerator(webApp);
157 }
158 else if (scope == ActivationScope.Site)
159 {
160 site = new SPSite(m_Url);
161 enumerator = new SPEnumerator(site);
162 }
163 else if (scope == ActivationScope.Web)
164 {
165 site = new SPSite(m_Url);
166 web = site.AllWebs[Utilities.GetServerRelUrlFromFullUrl(m_Url)];
167 ActivateDeactivateFeatureAtWeb(site, web, activate, m_FeatureId, m_Force, m_IgnoreNonActive);
168 }
169 if (enumerator != null)
170 {
171 enumerator.SPWebEnumerated += enumerator_SPWebEnumerated;
172 enumerator.Enumerate();
173 }
174 }
175 finally
176 {
177 if (web != null)
178 web.Dispose();
179 if (site != null)
180 site.Dispose();
181 }
182 }
183 }
184
185 #region Event Handlers
186
187 /// <summary>
188 /// Handles the SPWebEnumerated event of the enumerator control.
189 /// </summary>
190 /// <param name="sender">The source of the event.</param>
191 /// <param name="e">The <see cref="Lapointe.SharePoint.STSADM.Commands.OperationHelpers.SPEnumerator.SPWebEventArgs"/> instance containing the event data.</param>
192 private void enumerator_SPWebEnumerated(object sender, SPEnumerator.SPWebEventArgs e)
193 {
194 ActivateDeactivateFeatureAtWeb(e.Site, e.Web, m_Activate, m_FeatureId, m_Force, m_IgnoreNonActive);
195 }
196
197 /// <summary>
198 /// Handles the SPSiteEnumerated event of the enumerator control.
199 /// </summary>
200 /// <param name="sender">The source of the event.</param>
201 /// <param name="e">The <see cref="Lapointe.SharePoint.STSADM.Commands.OperationHelpers.SPEnumerator.SPSiteEventArgs"/> instance containing the event data.</param>
202 private void enumerator_SPSiteEnumerated(object sender, SPEnumerator.SPSiteEventArgs e)
203 {
204 ActivateDeactivateFeatureAtSite(e.Site, m_Activate, m_FeatureId, m_Force, m_IgnoreNonActive);
205 }
206
207 /// <summary>
208 /// Handles the SPWebApplicationEnumerated event of the enumerator control.
209 /// </summary>
210 /// <param name="sender">The source of the event.</param>
211 /// <param name="e">The <see cref="Lapointe.SharePoint.STSADM.Commands.OperationHelpers.SPEnumerator.SPWebApplicationEventArgs"/> instance containing the event data.</param>
212 private void enumerator_SPWebApplicationEnumerated(object sender, SPEnumerator.SPWebApplicationEventArgs e)
213 {
214 ActivateDeactivateFeatureAtWebApplication(e.WebApplication, m_FeatureId, m_Activate, m_Force, m_IgnoreNonActive);
215 }
216
217 #endregion
218
219 /// <summary>
220 /// Activates or deactivates the feature.
221 /// </summary>
222 /// <param name="features">The features.</param>
223 /// <param name="activate">if set to <c>true</c> [activate].</param>
224 /// <param name="featureId">The feature id.</param>
225 /// <param name="urlScope">The URL scope.</param>
226 /// <param name="force">if set to <c>true</c> [force].</param>
227 /// <param name="ignoreNonActive">if set to <c>true</c> [ignore non active].</param>
228 /// <returns></returns>
229 private SPFeature ActivateDeactivateFeature(SPFeatureCollection features, bool activate, Guid featureId, string urlScope, bool force, bool ignoreNonActive)
230 {
231 if (features[featureId] == null && ignoreNonActive)
232 return null;
233
234 if (!activate)
235 {
236 if (features[featureId] != null || force)
237 {
238 Log("Progress: Deactivating Feature {0} from {1}.", featureId.ToString(), urlScope);
239 try
240 {
241 features.Remove(featureId, force);
242 }
243 catch (Exception ex)
244 {
245 Log("WARNING: {0}", ex.Message);
246 }
247 }
248 else
249 {
250 Log("WARNING: " + SPResource.GetString("FeatureNotActivatedAtScope", new object[] { featureId }) + " Use the -force parameter to force a deactivation.");
251 }
252
253 return null;
254 }
255 if (features[featureId] == null)
256 Log("Progress: Activating Feature {0} on {1}.", featureId.ToString(), urlScope);
257 else
258 {
259 if (!force)
260 {
261 SPFeatureDefinition fd = features[featureId].Definition;
262 Log("WARNING: " + SPResource.GetString("FeatureAlreadyActivated", new object[] { fd.DisplayName, fd.Id, urlScope }) + " Use the -force parameter to force a reactivation.");
263 return features[featureId];
264 }
265
266 Log("Progress: Re-Activating Feature {0} on {1}.", featureId.ToString(), urlScope);
267 }
268
269 return features.Add(featureId, force);
270 }
271 /// <summary>
272 /// Activates or deactivates the farm scoped feature.
273 /// </summary>
274 /// <param name="activate">if set to <c>true</c> [activate].</param>
275 /// <param name="featureId">The feature id.</param>
276 /// <param name="force">if set to <c>true</c> [force].</param>
277 /// <param name="ignoreNonActive">if set to <c>true</c> [ignore non active].</param>
278 /// <returns></returns>
279 public SPFeature ActivateDeactivateFeatureAtFarm(bool activate, Guid featureId, bool force, bool ignoreNonActive)
280 {
281 SPWebService service = SPFarm.Local.Services.GetValue<SPWebService>(string.Empty);
282 return ActivateDeactivateFeature(service.Features, activate, featureId, "Farm", force, ignoreNonActive);
283 }
284
285 /// <summary>
286 /// Activates or deactivates the web application scoped feature.
287 /// </summary>
288 /// <param name="activate">if set to <c>true</c> [activate].</param>
289 /// <param name="featureId">The feature id.</param>
290 /// <param name="urlScope">The URL scope.</param>
291 /// <param name="force">if set to <c>true</c> [force].</param>
292 /// <param name="ignoreNonActive">if set to <c>true</c> [ignore non active].</param>
293 /// <returns></returns>
294 public SPFeature ActivateDeactivateFeatureAtWebApplication(bool activate, Guid featureId, string urlScope, bool force, bool ignoreNonActive)
295 {
296 SPWebApplication application = SPWebApplication.Lookup(new Uri(urlScope));
297 if (application == null)
298 {
299 throw new FileNotFoundException(SPResource.GetString("WebApplicationLookupFailed", new object[] { urlScope }));
300 }
301 return ActivateDeactivateFeatureAtWebApplication(application, featureId, activate, force, ignoreNonActive);
302 }
303
304 /// <summary>
305 /// Activates or deactivates the web application scoped feature.
306 /// </summary>
307 /// <param name="application">The application.</param>
308 /// <param name="featureId">The feature id.</param>
309 /// <param name="activate">if set to <c>true</c> [activate].</param>
310 /// <param name="force">if set to <c>true</c> [force].</param>
311 /// <param name="ignoreNonActive">if set to <c>true</c> [ignore non active].</param>
312 /// <returns></returns>
313 public SPFeature ActivateDeactivateFeatureAtWebApplication(SPWebApplication application, Guid featureId, bool activate, bool force, bool ignoreNonActive)
314 {
315 return ActivateDeactivateFeature(application.Features, activate, featureId, application.GetResponseUri(SPUrlZone.Default).ToString(), force, ignoreNonActive);
316 }
317
318 /// <summary>
319 /// Activates or deactivates the site scoped feature.
320 /// </summary>
321 /// <param name="activate">if set to <c>true</c> [activate].</param>
322 /// <param name="featureId">The feature id.</param>
323 /// <param name="urlScope">The URL scope.</param>
324 /// <param name="force">if set to <c>true</c> [force].</param>
325 /// <param name="ignoreNonActive">if set to <c>true</c> [ignore non active].</param>
326 /// <returns></returns>
327 public SPFeature ActivateDeactivateFeatureAtSite(bool activate, Guid featureId, string urlScope, bool force, bool ignoreNonActive)
328 {
329 using (SPSite site = new SPSite(urlScope))
330 using (SPWeb web = site.OpenWeb(Utilities.GetServerRelUrlFromFullUrl(urlScope), true))
331 {
332 if (web.IsRootWeb)
333 {
334 return ActivateDeactivateFeatureAtSite(site, activate, featureId, force, ignoreNonActive);
335 }
336 throw new SPException(SPResource.GetString("FeatureActivateDeactivateScopeAmbiguous", new object[] { site.Url }));
337 }
338 }
339
340 /// <summary>
341 /// Activates or deactivates the site scoped feature.
342 /// </summary>
343 /// <param name="site">The site.</param>
344 /// <param name="activate">if set to <c>true</c> [activate].</param>
345 /// <param name="featureId">The feature id.</param>
346 /// <param name="force">if set to <c>true</c> [force].</param>
347 /// <param name="ignoreNonActive">if set to <c>true</c> [ignore non active].</param>
348 /// <returns></returns>
349 public SPFeature ActivateDeactivateFeatureAtSite(SPSite site, bool activate, Guid featureId, bool force, bool ignoreNonActive)
350 {
351 return ActivateDeactivateFeature(site.Features, activate, featureId, site.Url, force, ignoreNonActive);
352 }
353
354 /// <summary>
355 /// Activates or deactivates the web scoped feature.
356 /// </summary>
357 /// <param name="activate">if set to <c>true</c> [activate].</param>
358 /// <param name="featureId">The feature id.</param>
359 /// <param name="urlScope">The URL scope.</param>
360 /// <param name="force">if set to <c>true</c> [force].</param>
361 /// <param name="ignoreNonActive">if set to <c>true</c> [ignore non active].</param>
362 /// <returns></returns>
363 public SPFeature ActivateDeactivateFeatureAtWeb(bool activate, Guid featureId, string urlScope, bool force, bool ignoreNonActive)
364 {
365 using (SPSite site = new SPSite(urlScope))
366 using (SPWeb web = site.OpenWeb())
367 {
368 return ActivateDeactivateFeatureAtWeb(site, web, activate, featureId, force, ignoreNonActive);
369 }
370 }
371
372 /// <summary>
373 /// Activates or deactivates the web scoped feature.
374 /// </summary>
375 /// <param name="site">The site.</param>
376 /// <param name="web">The web.</param>
377 /// <param name="activate">if set to <c>true</c> [activate].</param>
378 /// <param name="featureId">The feature id.</param>
379 /// <param name="force">if set to <c>true</c> [force].</param>
380 /// <param name="ignoreNonActive">if set to <c>true</c> [ignore non active].</param>
381 /// <returns></returns>
382 public SPFeature ActivateDeactivateFeatureAtWeb(SPSite site, SPWeb web, bool activate, Guid featureId, bool force, bool ignoreNonActive)
383 {
384 return ActivateDeactivateFeature(web.Features, activate, featureId, web.Url, force, ignoreNonActive);
385 }
386
387 }
388}
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 "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