Working with SPWeb(Info) Objects Using PowerShell
I know, I know, I’m way behind on documenting my PowerShell cmdlets – I will be striving to get them done as soon as possible. I’ve already documented one of them, the Get-SPSite cmdlet, and I will now continue with the Get-SPWeb cmdlet. Hopefully I’ll be able to wrap up the others much quicker as they are a lot simpler – then I can finally start building new ones
Like the Get-SPSite cmdlet the Get-SPWeb cmdlet addresses some common issues found with working with SPWeb objects. For additional details about the disposal problem when working with many common SharePoint objects via PowerShell see the post about the Get-SPSite cmdlet. For those that aren’t familiar with the SPWeb object (Microsoft.SharePoint.SPWeb), it’s the equivalent programmatic element for working with sites within site collections.
First lets look at the SPWebInfo object and some its uses, the Get-SPWeb cmdlet is extremely simple so I’ll save it for last. Consider the following code snippet:
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint") $site = New-Object Microsoft.SharePoint.SPSite("http://portal") foreach ($web in $site.AllWebs) { Write-Host $web.Url }
The above code results in a memory leak. If you don’t re-loop through each SPWeb object in the collection and dispose of the object by calling the “Dispose()” method you will end up with unmanaged resources left in memory that could eventually cause issues if you do a lot of processing like this (eventually the GC will dispose of the objects but that could take quite some time). The same issue is true for the SPSite object.
Another option would be to use a different approach to get all the SPWeb objects within a site collection, an approach that can be used for more dynamic querying of objects and returns back an object that would not require disposal – an SPWebInfo object. Here’s an example of how you could do something similar to the above using my Get-SPSite cmdlet and the AllWebs property that the SPSiteInfo object exposes:
$site = Get-SPSite("http://portal") foreach ($web in $site.AllWebs) { Write-Host $web.Url }
The above code still uses the AllWebs property but in this case it’s part of my SPSiteInfo object so rather than exposing a collection of SPWeb objects it returns a collection of SPWebInfo objects. As a result you no longer need to dispose of the objects returned.
The one obvious downside of this approach is that when you access the property I have to loop through all the webs internally and copy their data to my custom object before I can return back to the calling application. Typically though, we’re more concerned about flexibility and ease of use rather than performance when performing the simple administrative tasks that we’d be looking to perform using PowerShell.
What I like about the approach I put together is that I can now do filtered queries without having to worry about whether or not I disposed of the objects. Here’s an example of how to find all the webs that use a specific site template (in this case “STS”):
$site = Get-SPSite("http://portal") $webs = $site.AllWebs | where -FilterScript {$_.WebTemplate -eq "STS"} foreach ($web in $webs) { Write-Host $web.Url }
Or I can simply get a specific SPWebInfo (and therefore the SPWeb) object by calling the Get-SPWeb cmdlet directly (I don’t currently support wildcards so this is only helpful for getting a specific web instance):
$web = Get-SPWeb "http://devthekey" Write-Host $web.WebTemplate
It’s important to remember that the SPWebInfo object is meant to be read-only as most of the properties are just copies of the variables but there are some exceptions such as the SPListCollection object returned by the Lists property or the SPRecycleBinItemCollection object returned by the RecycleBin property. In general, if you have to call the Update() method of the SPWeb object to save your changes then you have to use the actual SPWeb object, otherwise you can work directly with the SPWebInfo object and forego the need to instantiate and dispose of the SPWeb object.
What about when you do need to access the actual SPWeb object? There are two approaches for this: the first is to use the SPBase property which will create a new SPWeb instance and store that instance as a private member variable for future access to the property thus avoiding the overhead of creating another instance on subsequent calls; the second is to use the GetSPObject() method which creates a new instance of the SPWeb object but does not store a copy so it’s a nice easy way to get an entirely new instance of the actual SPWeb object (useful for when you’ve made a change which requires a reload due to caching). In both cases you are responsible for disposing of the returned object.
The following code snippet shows the SPWebInfo class:
1: using System;
2: using System.Collections;
3: using System.Collections.Generic;
4: using System.Globalization;
5: using System.Linq;
6: using System.Text;
7: using System.Web.Configuration;
8: using Microsoft.SharePoint;
9: using Microsoft.SharePoint.Administration;
10: using Microsoft.SharePoint.Navigation;
11: using Microsoft.SharePoint.Utilities;
12: using Microsoft.SharePoint.Workflow;
13:
14: namespace Lapointe.SharePoint.PowerShell.Commands.Proxies
15: {
16: public class SPWebInfo : ISPInfo
17: {
18: private SPSite m_Site;
19: private SPWeb m_Web;
20: private List<SPWebInfo> m_Webs;
21: private Guid m_FirstUniqueAncestorWeb;
22: private Guid m_FirstUniqueRoleDefinitionWeb;
23: private SPListCollection m_Lists;
24: private SPListTemplateCollection m_ListTemplates;
25: private SPFeatureCollection m_Features;
26:
27: internal SPWebInfo(SPWeb web)
28: {
29: ID = web.ID;
30: Alerts = web.Alerts;
31: AllowAnonymousAccess = web.AllowAnonymousAccess;
32: AllowAutomaticASPXPageIndexing = web.AllowAutomaticASPXPageIndexing;
33: AllowRssFeeds = web.AllowRssFeeds;
34: AllowUnsafeUpdates = web.AllowUnsafeUpdates;
35: AllProperties = web.AllProperties;
36: AllRolesForCurrentUser = web.AllRolesForCurrentUser;
37: AllUsers = web.AllUsers;
38: AllWebTemplatesAllowed = web.AllWebTemplatesAllowed;
39: AlternateCssUrl = web.AlternateCssUrl;
40: AlternateHeader = web.AlternateHeader;
41: AnonymousPermMask64 = web.AnonymousPermMask64;
42: AnonymousState = web.AnonymousState;
43: ASPXPageIndexed = web.ASPXPageIndexed;
44: ASPXPageIndexMode = web.ASPXPageIndexMode;
45: AssociatedGroups = web.AssociatedGroups;
46: AssociatedMemberGroup = web.AssociatedMemberGroup;
47: AssociatedOwnerGroup = web.AssociatedOwnerGroup;
48: AssociatedVisitorGroup = web.AssociatedVisitorGroup;
49: Audit = web.Audit;
50: AuthenticationMode = web.AuthenticationMode;
51: Author = web.Author;
52: AvailableContentTypes = web.AvailableContentTypes;
53: AvailableFields = web.AvailableFields;
54: CacheAllSchema = web.CacheAllSchema;
55: Configuration = web.Configuration;
56: ContentTypes = web.ContentTypes;
57: Created = web.Created;
58: CurrencyLocaleID = web.CurrencyLocaleID;
59: CurrentChangeToken = web.CurrentChangeToken;
60: CurrentUser = web.CurrentUser;
61: CustomMasterUrl = web.CustomMasterUrl;
62: DataRetrievalServicesSettings = web.DataRetrievalServicesSettings;
63: Description = web.Description;
64: try
65: {
66: DocTemplates = web.DocTemplates;
67: }
68: catch (SPException) {}
69: EffectiveBasePermissions = web.EffectiveBasePermissions;
70: EffectivePresenceEnabled = web.EffectivePresenceEnabled;
71: EventHandlersEnabled = web.EventHandlersEnabled;
72: EventReceivers = web.EventReceivers;
73: ExecuteUrl = web.ExecuteUrl;
74: Exists = web.Exists;
75: ExternalSecurityProviderSetting = web.ExternalSecurityProviderSetting;
76: Fields = web.Fields;
77: FieldTypeDefinitionCollection = web.FieldTypeDefinitionCollection;
78: Files = web.Files;
79: //FirstUniqueAncestor = web.FirstUniqueAncestor;
80: Folders = web.Folders;
81: Groups = web.Groups;
82: HasExternalSecurityProvider = web.HasExternalSecurityProvider;
83: HasUniqueRoleAssignments = web.HasUniqueRoleAssignments;
84: HasUniqueRoleDefinitions = web.HasUniqueRoleDefinitions;
85: IncludeSupportingFolders = web.IncludeSupportingFolders;
86: IsADAccountCreationMode = web.IsADAccountCreationMode;
87: IsADEmailEnabled = web.IsADEmailEnabled;
88: IsRootWeb = web.IsRootWeb;
89: Language = web.Language;
90: LastItemModifiedDate = web.LastItemModifiedDate;
91: Locale = web.Locale;
92: MasterUrl = web.MasterUrl;
93: Modules = web.Modules;
94: Name = web.Name;
95: Navigation = web.Navigation;
96: NoCrawl = web.NoCrawl;
97: //SPWeb ParentWeb = web;
98: ParentWebId = web.ParentWebId;
99: ParserEnabled = web.ParserEnabled;
100: PortalMember = web.PortalMember;
101: PortalName = web.PortalName;
102: PortalSubscriptionUrl = web.PortalSubscriptionUrl;
103: PortalUrl = web.PortalUrl;
104: PresenceEnabled = web.PresenceEnabled;
105: Properties = web.Properties;
106: Provisioned = web.Provisioned;
107: PublicFolderRootUrl = web.PublicFolderRootUrl;
108: QuickLaunchEnabled = web.QuickLaunchEnabled;
109: RecycleBin = web.RecycleBin;
110: RegionalSettings = web.RegionalSettings;
111: RequestAccessEmail = web.RequestAccessEmail;
112: RequestAccessEnabled = web.RequestAccessEnabled;
113: ReusableAcl = web.ReusableAcl;
114: RoleAssignments = web.RoleAssignments;
115: RoleDefinitions = web.RoleDefinitions;
116: RootFolder = web.RootFolder;
117: ServerRelativeUrl = web.ServerRelativeUrl;
118: Site = web.Site.ID;
119: SiteAdministrators = web.SiteAdministrators;
120: SiteGroups = web.SiteGroups;
121: SiteLogoDescription = web.SiteLogoDescription;
122: SiteLogoUrl = web.SiteLogoUrl;
123: SiteUserInfoList = web.SiteUserInfoList;
124: SiteUsers = web.SiteUsers;
125: SyndicationEnabled = web.SyndicationEnabled;
126: Theme = web.Theme;
127: ThemeCssUrl = web.ThemeCssUrl;
128: Title = web.Title;
129: TreeViewEnabled = web.TreeViewEnabled;
130: Url = web.Url;
131: UserIsSiteAdmin = web.UserIsSiteAdmin;
132: UserIsWebAdmin = web.UserIsWebAdmin;
133: Users = web.Users;
134: ViewStyles = web.ViewStyles;
135: //SPWebCollection Webs = web;
136: WebTemplate = web.WebTemplate;
137: WebTemplateId = web.WebTemplateId;
138: WorkflowTemplates = web.WorkflowTemplates;
139: }
140:
141: /// <summary>
142: /// Returns a newly created instance of the object on the first access. Subsequent accesses will utilize an internal member variable.
143: /// The caller is responsible for disposing of the returned SPWeb object and it's parent SPSite object (web.Site.Dispose()).
144: /// </summary>
145: /// <value>The SP base.</value>
146: public IDisposable SPBase
147: {
148: get
149: {
150: if (m_Web == null)
151: {
152: if (m_Site == null)
153: m_Site = new SPSite(Site);
154:
155: m_Web = m_Site.OpenWeb(ID);
156: }
157: return m_Web;
158: }
159: }
160:
161: /// <summary>
162: /// Returns a newly created instance of the object every time without storing an internal member variable for subsequent access.
163: /// The caller is responsible for disposing of the returned SPWeb object and it's parent SPSite object (web.Site.Dispose()).
164: /// </summary>
165: /// <returns></returns>
166: public IDisposable GetSPObject()
167: {
168: return new SPSite(Site).OpenWeb(ID);
169: }
170:
171: public SPAlertCollection Alerts { get; internal set; }
172: public bool AllowAnonymousAccess { get; internal set; }
173: public bool AllowAutomaticASPXPageIndexing { get; internal set; }
174: public bool AllowRssFeeds { get; internal set; }
175: public bool AllowUnsafeUpdates { get; internal set; }
176: public Hashtable AllProperties { get; internal set; }
177: public SPRoleDefinitionBindingCollection AllRolesForCurrentUser { get; internal set; }
178: public SPUserCollection AllUsers { get; internal set; }
179: public bool AllWebTemplatesAllowed { get; internal set; }
180: public string AlternateCssUrl { get; internal set; }
181: public string AlternateHeader { get; internal set; }
182: public SPBasePermissions AnonymousPermMask64 { get; internal set; }
183: public SPWeb.WebAnonymousState AnonymousState { get; internal set; }
184: public bool ASPXPageIndexed { get; internal set; }
185: public WebASPXPageIndexMode ASPXPageIndexMode { get; internal set; }
186: public IList<SPGroup> AssociatedGroups { get; internal set; }
187: public SPGroup AssociatedMemberGroup { get; internal set; }
188: public SPGroup AssociatedOwnerGroup { get; internal set; }
189: public SPGroup AssociatedVisitorGroup { get; internal set; }
190: public SPAudit Audit { get; internal set; }
191: public AuthenticationMode AuthenticationMode { get; internal set; }
192: public SPUser Author { get; internal set; }
193: public SPContentTypeCollection AvailableContentTypes { get; internal set; }
194: public SPFieldCollection AvailableFields { get; internal set; }
195: public bool CacheAllSchema { get; internal set; }
196: public short Configuration { get; internal set; }
197: public SPContentTypeCollection ContentTypes { get; internal set; }
198: public DateTime Created { get; internal set; }
199: public int CurrencyLocaleID { get; internal set; }
200: public SPChangeToken CurrentChangeToken { get; internal set; }
201: public SPUser CurrentUser { get; internal set; }
202: public string CustomMasterUrl { get; internal set; }
203: public SPDataRetrievalServicesSettings DataRetrievalServicesSettings { get; internal set; }
204: public string Description { get; internal set; }
205: public SPDocTemplateCollection DocTemplates { get; internal set; }
206: public SPBasePermissions EffectiveBasePermissions { get; internal set; }
207: public bool EffectivePresenceEnabled { get; internal set; }
208: public bool EventHandlersEnabled { get; internal set; }
209: public SPEventReceiverDefinitionCollection EventReceivers { get; internal set; }
210: public string ExecuteUrl { get; internal set; }
211: public bool Exists { get; internal set; }
212: public string ExternalSecurityProviderSetting { get; internal set; }
213: public SPFeatureCollection Features
214: {
215: get
216: {
217: if (m_Features != null)
218: return m_Features;
219:
220: using (SPSite site = new SPSite(Site))
221: using (SPWeb web = site.OpenWeb(ID))
222: {
223: m_Features = web.Features;
224: }
225: return m_Features;
226:
227: }
228: }
229: public SPFieldCollection Fields { get; internal set; }
230: public SPFieldTypeDefinitionCollection FieldTypeDefinitionCollection { get; internal set; }
231: public SPFileCollection Files { get; internal set; }
232: public Guid FirstUniqueAncestorWeb
233: {
234: get
235: {
236: if (m_FirstUniqueAncestorWeb != Guid.Empty)
237: return m_FirstUniqueAncestorWeb;
238:
239: using (SPSite site = new SPSite(Site))
240: using (SPWeb web = site.OpenWeb(ID))
241: using (SPWeb ancestor = web.FirstUniqueAncestorWeb)
242: {
243: m_FirstUniqueAncestorWeb = ancestor.ID;
244: }
245: return m_FirstUniqueAncestorWeb;
246:
247: }
248: }
249: public Guid FirstUniqueRoleDefinitionWeb
250: {
251: get
252: {
253: if (m_FirstUniqueRoleDefinitionWeb != Guid.Empty)
254: return m_FirstUniqueRoleDefinitionWeb;
255:
256: using (SPSite site = new SPSite(Site))
257: using (SPWeb web = site.OpenWeb(ID))
258: using (SPWeb ancestor = web.FirstUniqueRoleDefinitionWeb)
259: {
260: m_FirstUniqueRoleDefinitionWeb = ancestor.ID;
261: }
262: return m_FirstUniqueRoleDefinitionWeb;
263:
264: }
265: }
266: public SPFolderCollection Folders { get; internal set; }
267: public SPGroupCollection Groups { get; internal set; }
268: public bool HasExternalSecurityProvider { get; internal set; }
269: public bool HasUniqueRoleAssignments { get; internal set; }
270: public bool HasUniqueRoleDefinitions { get; internal set; }
271: public Guid ID { get; internal set; }
272: public bool IncludeSupportingFolders { get; internal set; }
273: public bool IsADAccountCreationMode { get; internal set; }
274: public bool IsADEmailEnabled { get; internal set; }
275: public bool IsRootWeb { get; internal set; }
276: public uint Language { get; internal set; }
277: public DateTime LastItemModifiedDate { get; internal set; }
278: public SPListCollection Lists
279: {
280: get
281: {
282: if (m_Lists != null)
283: return m_Lists;
284:
285: using (SPSite site = new SPSite(Site))
286: using (SPWeb web = site.OpenWeb(ID))
287: {
288: m_Lists = web.Lists;
289: }
290: return m_Lists;
291:
292: }
293: }
294: public SPListTemplateCollection ListTemplates
295: {
296: get
297: {
298: if (m_ListTemplates != null)
299: return m_ListTemplates;
300:
301: using (SPSite site = new SPSite(Site))
302: using (SPWeb web = site.OpenWeb(ID))
303: {
304: m_ListTemplates = web.ListTemplates;
305: }
306: return m_ListTemplates;
307:
308: }
309: }
310: public CultureInfo Locale { get; internal set; }
311: public string MasterUrl { get; internal set; }
312: public SPModuleCollection Modules { get; internal set; }
313: public string Name { get; internal set; }
314: public SPNavigation Navigation { get; internal set; }
315: public bool NoCrawl { get; internal set; }
316: public SPWeb ParentWeb { get; internal set; }
317: public Guid ParentWebId { get; internal set; }
318: public bool ParserEnabled { get; internal set; }
319: public bool PortalMember { get; internal set; }
320: public string PortalName { get; internal set; }
321: public string PortalSubscriptionUrl { get; internal set; }
322: public string PortalUrl { get; internal set; }
323: public bool PresenceEnabled { get; internal set; }
324: public SPPropertyBag Properties { get; internal set; }
325: public bool Provisioned { get; internal set; }
326: public string PublicFolderRootUrl { get; internal set; }
327: public bool QuickLaunchEnabled { get; internal set; }
328: public SPRecycleBinItemCollection RecycleBin { get; internal set; }
329: public SPRegionalSettings RegionalSettings { get; internal set; }
330: public string RequestAccessEmail { get; internal set; }
331: public bool RequestAccessEnabled { get; internal set; }
332: public SPReusableAcl ReusableAcl { get; internal set; }
333: public SPRoleAssignmentCollection RoleAssignments { get; internal set; }
334: public SPRoleDefinitionCollection RoleDefinitions { get; internal set; }
335: public SPFolder RootFolder { get; internal set; }
336: public string ServerRelativeUrl { get; internal set; }
337: public Guid Site { get; internal set; }
338: public SPUserCollection SiteAdministrators { get; internal set; }
339: public SPGroupCollection SiteGroups { get; internal set; }
340: public string SiteLogoDescription { get; internal set; }
341: public string SiteLogoUrl { get; internal set; }
342: public SPList SiteUserInfoList { get; internal set; }
343: public SPUserCollection SiteUsers { get; internal set; }
344: public bool SyndicationEnabled { get; internal set; }
345: public string Theme { get; internal set; }
346: public string ThemeCssUrl { get; internal set; }
347: public string Title { get; internal set; }
348: public bool TreeViewEnabled { get; internal set; }
349: public string Url { get; internal set; }
350: public bool UserIsSiteAdmin { get; internal set; }
351: public bool UserIsWebAdmin { get; internal set; }
352: public SPUserCollection Users { get; internal set; }
353: public SPViewStyleCollection ViewStyles { get; internal set; }
354: public List<SPWebInfo> Webs
355: {
356: get
357: {
358: if (m_Webs != null)
359: return m_Webs;
360:
361: m_Webs = new List<SPWebInfo>();
362: using (SPSite site = new SPSite(Site))
363: using (SPWeb web = site.OpenWeb(ID))
364: {
365: foreach (SPWeb subWeb in web.Webs)
366: {
367: try
368: {
369: m_Webs.Add(new SPWebInfo(subWeb));
370: }
371: finally
372: {
373: subWeb.Dispose();
374: }
375: }
376: }
377: return m_Webs;
378:
379: }
380: }
381: public string WebTemplate { get; internal set; }
382: public int WebTemplateId { get; internal set; }
383: public SPWorkflowTemplateCollection WorkflowTemplates { get; internal set; }
384: }
385: }
The following is the code of the core Get-SPWeb cmdlet:
1: using System;
2: using System.Collections.Generic;
3: using System.Management.Automation;
4: using Lapointe.SharePoint.PowerShell.Commands.OperationHelpers;
5: using Lapointe.SharePoint.PowerShell.Commands.Proxies;
6: using Lapointe.SharePoint.PowerShell.Commands.Validators;
7: using Lapointe.SharePoint.STSADM.Commands.OperationHelpers;
8: using Microsoft.SharePoint;
9:
10: namespace Lapointe.SharePoint.PowerShell.Commands.Webs
11: {
12: [Cmdlet(VerbsCommon.Get, "SPWeb", SupportsShouldProcess=true, DefaultParameterSetName = "Url")]
13: public class GetSPWebCommand : PSCmdletBase
14: {
15: /// <summary>
16: /// Gets or sets the URL.
17: /// </summary>
18: /// <value>The URL.</value>
19: [Parameter(
20: ParameterSetName = "Url",
21: Mandatory = true,
22: Position = 0,
23: ValueFromPipeline = true,
24: ValueFromPipelineByPropertyName = true,
25: HelpMessage = "The URL of the web to return")]
26: [ValidateNotNullOrEmpty]
27: [ValidateUrl(false)]
28: public string[] Url { get; set; }
29:
30:
31: /// <summary>
32: /// Processes the record.
33: /// </summary>
34: protected override void ProcessRecordEx()
35: {
36: foreach (string url in Url)
37: {
38: string siteUrl = url.TrimEnd('/');
39: using (SPSite site = new SPSite(siteUrl))
40: using (SPWeb web = site.AllWebs[Utilities.GetServerRelUrlFromFullUrl(siteUrl)])
41: {
42: WriteObject(new SPWebInfo(web));
43: }
44: }
45: }
46: }
47: }
The following is the full help for the cmdlet.
PS C:\> get-help get-spweb -full
NAME
Get-SPWeb
SYNOPSIS
Gets one or more SPWebInfo objects representing a SharePoint 2007 Web.
SYNTAX
Get-SPWeb [-Url] <String[]> [-WhatIf] [-Confirm] [<CommonParameters>]
DETAILED DESCRIPTION
Pass in a comma separated list of URLs or a string array of URLs to obtain a collection of SPWebInfo objects. Thes
e objects do not need to be disposed.
The SPWebInfo object that is returned contains almost all of the same properties of the SPWeb object but does not r
equire disposal and should be generally considered read-only. You can get to the actual SPWeb object by using the
SPBase property or the GetSPObject() method. The SPBase property results in a copy of the SPWeb object being persi
sted in the SPWebInfo object for faster access on future calls. Always remember to dispose of the SPWeb object if
used. Some collection properties may be directly updated without the need to access the SPSite object.
Copyright 2008 Gary Lapointe
> For more information on these PowerShell cmdlets:
> http://stsadm.blogspot.com/
> Use of these cmdlets is at your own risk.
> Gary Lapointe assumes no liability.
PARAMETERS
-Url <String[]>
Specifies the URL of the web(s) to retrieve. Wildcards are not permitted. If you specify multiple URLs, use com
mas to separate the URLs.
Required? true
Position? 1
Default value
Accept pipeline input? true (ByValue, ByPropertyName)
Accept wildcard characters? false
-WhatIf
Required? false
Position? named
Default value
Accept pipeline input? false
Accept wildcard characters? false
-Confirm
Required? false
Position? named
Default value
Accept pipeline input? false
Accept wildcard characters? false
<CommonParameters>
This cmdlet supports the common parameters: -Verbose, -Debug,
-ErrorAction, -ErrorVariable, and -OutVariable. For more information,
type, "get-help about_commonparameters".
INPUT TYPE
String
RETURN TYPE
Collection of SPWebInfo objects.
NOTES
For more information, type "Get-Help Get-SPWeb -detailed". For technical information, type "Get-Help Get-SPW
eb -full".
-------------- EXAMPLE 1 --------------
C:\PS>$web = get-spweb -url http://portal
This example returns back a single SPWebInfo object.
RELATED LINKS
http://stsadm.blogspot.com
|
Note that if you receive an exception during the execution of this cmdlet simply pass in the “-debug” parameter in order to display the full stack trace which you can use to either debug yourself or report back to here to help me improve the code.
Working with SPSite(Info) Objects Using PowerShell
One of the first PowerShell cmdlets I built, Get-SPSite, addresses some common issues found with working with SPSite objects. I struggled with how I could provide a means to quickly and easily get SPSite objects while at the same time helping administrators so they don’t have to worry (as much) about object disposal. For those that aren’t familiar with the SPSite object (Microsoft.SharePoint.SPSite), it’s the equivalent programmatic element for working with site collections.
What I eventually ended up creating (thanks to some good advice from Harley Green) was a simple wrapper object which encapsulates most of the key properties of the SPSite object thus allowing basic reporting and decision making processing without the need to worry about disposing of the object. Consider the following code snippet:
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Sharepoint")
$webapp = [Microsoft.SharePoint.Administration.SPWebApplication]::Lookup("http://portal")
foreach ($site in $webapp.Sites) {
Write-Host $site.Url
}
The above code results in a memory leak. If you don’t re-loop through each SPSite object in the collection and dispose of the object by calling the “Dispose()” method you will end up with unmanaged resources left in memory that could eventually cause issues if you do a lot of processing like this (eventually the GC will dispose of the objects but that could take quite some time).
Another option would be to use a different approach to get all the SPSite objects within a web application, an approach that can be used for more dynamic querying of objects and returns back an object that would not require disposal – an SPSiteInfo object. Here’s an example of how you could do something similar to the above using my Get-SPSite cmdlet:
foreach ($site in get-spsite -url http://portal*) {
Write-Host $site.Url
}
The one obvious downside of this approach is that the wildcard means that I have to inspect every single site collection within the farm to figure out where there are matches so if you’re looking for performance this definitely isn’t the best approach. Typically though, we’re more concerned about flexibility and ease of use rather than performance when performing the simple administrative tasks that we’d be looking to perform using PowerShell.
What I like about the approach I put together is that I can now do filtered queries without having to worry about whether or not I disposed of the objects. Here’s an example of how to find all the site collections within the farm where the storage size is greater than 80% of the quota:
get-spsite -url * | where -filterscript {$_.Usage.Storage -ge $_.Quota.StoragemaximumLevel*.8 -and $_.Quota.StorageMaximumLevel -ne 0} | select Url,@{Name="Storage";expression={$_.Usage.Storage/1MB}}
In the above I can simply call my Get-SPSite cmdlet, filter out all items where the current storage is less than 0.8 of the maximum level if set (StorageMaximumLevel is not 0), and then display the URL and the current size, in megabytes, of the the remaining site collections.
It’s important to remember that the SPSiteInfo object is meant to be read-only as most of the properties are just copies of the variables but there are some exceptions such as the SPRecycleBinItemCollection object returned by the RecycleBin property or the SPFeatureCollection object returned by the Features property. In general, if you have to call the Update() method of the SPSite object to save your changes then you have to use the actual SPSite object, otherwise you can work directly with the SPSiteInfo object and forego the need to instantiate and dispose of the SPSite object.
Okay, so working with properties is pretty easy and we can do some nice reports using them and even access the web application using the WebApplication property or the webs using the AllWebs collection property (all without having to dispose any of the returned objects – the AllWebs property returns a collection of SPWebInfo objects) but what about when you do need to access the actual SPSite object? There are two approaches for this: the first is to use the SPBase property which will create a new SPSite instance and store that instance as a private member variable for future access to the property thus avoiding the overhead of creating another instance on subsequent calls; the second is to use the GetSPObject() method which creates a new instance of the SPSite object but does not store a copy so it’s a nice easy way to get an entirely new instance of the actual SPSite object (useful for when you’ve made a change which requires a reload due to caching). In both cases you are responsible for disposing of the returned object.
The following code snippet shows the SPSiteInfo class:
1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Management.Automation;
5: using System.Text;
6: using Microsoft.SharePoint;
7: using Microsoft.SharePoint.Administration;
8: using Microsoft.SharePoint.Workflow;
9:
10: namespace Lapointe.SharePoint.PowerShell.Commands.Proxies
11: {
12: public class SPSiteInfo : ISPInfo
13: {
14: private List<SPWebInfo> m_AllWebs;
15: private SPSite m_Site;
16:
17: internal SPSiteInfo(SPSite site)
18: {
19: ID = site.ID;
20: AllowRssFeeds = site.AllowRssFeeds;
21: AllowUnsafeUpdates = site.AllowUnsafeUpdates;
22: ApplicationRightsMask = site.ApplicationRightsMask;
23: Audit = site.Audit;
24: CatchAccessDeniedException = site.CatchAccessDeniedException;
25: CertificationDate = site.CertificationDate;
26: ContentDatabase = site.ContentDatabase;
27: CurrentChangeToken = site.CurrentChangeToken;
28: DeadWebNotificationCount = site.DeadWebNotificationCount;
29: ExternalBinaryIds = site.ExternalBinaryIds;
30: Features = site.Features;
31: HostHeaderIsSiteName = site.HostHeaderIsSiteName;
32: HostName = site.HostName;
33: IISAllowsAnonymous = site.IISAllowsAnonymous;
34: Impersonating = site.Impersonating;
35: IsPaired = site.IsPaired;
36: LastContentModifiedDate = site.LastContentModifiedDate;
37: LastSecurityModifiedDate = site.LastSecurityModifiedDate;
38: LockIssue = site.LockIssue;
39: Owner = site.Owner;
40: Port = site.Port;
41: PortalName = site.PortalName;
42: PortalUrl = site.PortalUrl;
43: Protocol = site.Protocol;
44: Quota = site.Quota;
45: ReadLocked = site.ReadLocked;
46: ReadOnly = site.ReadOnly;
47: RecycleBin = site.RecycleBin;
48: try
49: {
50: RootWeb = new SPWebInfo(site.RootWeb);
51: }
52: catch (Exception) {}
53: SearchServiceInstance = site.SearchServiceInstance;
54: SecondaryContact = site.SecondaryContact;
55: ServerRelativeUrl = site.ServerRelativeUrl;
56: SyndicationEnabled = site.SyndicationEnabled;
57: SystemAccount = site.SystemAccount;
58: UpgradeRedirectUri = site.UpgradeRedirectUri;
59: Url = site.Url;
60: Usage = site.Usage;
61: try
62: {
63: UserAccountDirectoryPath = site.UserAccountDirectoryPath;
64: }
65: catch (UnauthorizedAccessException) { }
66: UserToken = site.UserToken;
67: WarningNotificationSent = site.WarningNotificationSent;
68: WebApplication = site.WebApplication;
69: //WorkflowManager = site.WorkflowManager;
70: WriteLocked = site.WriteLocked;
71: Zone = site.Zone;
72:
73: }
74:
75: /// <summary>
76: /// Returns a newly created instance of the object on the first access. Subsequent accesses will utilize an internal member variable.
77: /// The caller is responsible for disposing of the returned object.
78: /// </summary>
79: /// <value>The SP base.</value>
80: public IDisposable SPBase
81: {
82: get
83: {
84: if (m_Site == null)
85: m_Site = new SPSite(ID);
86:
87: return m_Site;
88: }
89: }
90:
91: /// <summary>
92: /// Returns a newly created instance of the object every time without storing an internal member variable for subsequent access.
93: /// The caller is responsible for disposing of the returned object.
94: /// </summary>
95: /// <returns></returns>
96: public IDisposable GetSPObject()
97: {
98: return new SPSite(ID);
99: }
100:
101: public bool AllowRssFeeds { get; internal set; }
102: public bool AllowUnsafeUpdates { get; internal set; }
103: public List<SPWebInfo> AllWebs
104: {
105: get
106: {
107: if (m_AllWebs != null)
108: return m_AllWebs;
109:
110: m_AllWebs = new List<SPWebInfo>();
111: using (SPSite site = new SPSite(ID))
112: {
113: foreach (SPWeb web in site.AllWebs)
114: {
115: try
116: {
117: m_AllWebs.Add(new SPWebInfo(web));
118: }
119: finally
120: {
121: web.Dispose();
122: }
123: }
124: }
125: return m_AllWebs;
126: }
127: }
128: public SPBasePermissions ApplicationRightsMask { get; internal set; }
129: public SPAudit Audit { get; internal set; }
130: public bool CatchAccessDeniedException { get; internal set; }
131: public DateTime CertificationDate { get; internal set; }
132: public SPContentDatabase ContentDatabase { get; internal set; }
133: public SPChangeToken CurrentChangeToken { get; internal set; }
134: public short DeadWebNotificationCount { get; internal set; }
135: public SPExternalBinaryIdCollection ExternalBinaryIds { get; internal set; }
136: public SPFeatureCollection Features { get; internal set; }
137: public bool HostHeaderIsSiteName { get; internal set; }
138: public string HostName { get; internal set; }
139: public Guid ID { get; internal set; }
140: public bool IISAllowsAnonymous { get; internal set; }
141: public bool Impersonating { get; internal set; }
142: public bool IsPaired { get; internal set; }
143: public DateTime LastContentModifiedDate { get; internal set; }
144: public DateTime LastSecurityModifiedDate { get; internal set; }
145: public string LockIssue { get; internal set; }
146: public SPUser Owner { get; internal set; }
147: public int Port { get; internal set; }
148: public string PortalName { get; internal set; }
149: public string PortalUrl { get; internal set; }
150: public string Protocol { get; internal set; }
151: public SPQuota Quota { get; internal set; }
152: public bool ReadLocked { get; internal set; }
153: public bool ReadOnly { get; internal set; }
154: public SPRecycleBinItemCollection RecycleBin { get; internal set; }
155: public SPWebInfo RootWeb { get; internal set; }
156: public SPServiceInstance SearchServiceInstance { get; internal set; }
157: public SPUser SecondaryContact { get; internal set; }
158: public string ServerRelativeUrl { get; internal set; }
159: public bool SyndicationEnabled { get; internal set; }
160: public SPUser SystemAccount { get; internal set; }
161: public Uri UpgradeRedirectUri { get; internal set; }
162: public string Url { get; internal set; }
163: public SPSite.UsageInfo Usage { get; internal set; }
164: public string UserAccountDirectoryPath { get; internal set; }
165: public SPUserToken UserToken { get; internal set; }
166: public bool WarningNotificationSent { get; internal set; }
167: public SPWebApplication WebApplication { get; internal set; }
168: //public SPWorkflowManager WorkflowManager { get; internal set; }
169: public bool WriteLocked { get; internal set; }
170: public SPUrlZone Zone { get; internal set; }
171:
172: }
173: }
The following is the code of the core Get-SPSite cmdlet:
1: using System;
2: using System.Collections.Generic;
3: using System.Management.Automation;
4: using Lapointe.SharePoint.PowerShell.Commands.OperationHelpers;
5: using Lapointe.SharePoint.PowerShell.Commands.Validators;
6: using Microsoft.SharePoint;
7: using Microsoft.SharePoint.Administration;
8: using Lapointe.SharePoint.PowerShell.Commands.Proxies;
9:
10: namespace Lapointe.SharePoint.PowerShell.Commands.SiteCollections
11: {
12: [Cmdlet(VerbsCommon.Get, "SPSite", SupportsShouldProcess=true, DefaultParameterSetName = "Url")]
13: public class GetSPSiteCommand : PSCmdletBase
14: {
15: /// <summary>
16: /// Gets or sets the URL.
17: /// </summary>
18: /// <value>The URL.</value>
19: [Parameter(
20: ParameterSetName = "Url",
21: Mandatory = true,
22: Position = 0,
23: ValueFromPipeline = true,
24: ValueFromPipelineByPropertyName = true,
25: HelpMessage = "The URL of the site to return. Supports wildcards.")]
26: [ValidateNotNullOrEmpty]
27: [ValidateUrl(true)]
28: public string[] Url { get; set; }
29:
30:
31: /// <summary>
32: /// Processes the record.
33: /// </summary>
34: protected override void ProcessRecordEx()
35: {
36: foreach (string url in Url)
37: {
38: if (!WildcardPattern.ContainsWildcardCharacters(url))
39: {
40: string siteUrl = url.TrimEnd('/');
41: using (SPSite site = new SPSite(siteUrl))
42: {
43: WriteObject(new SPSiteInfo(site));
44: }
45: }
46: else
47: {
48: WildcardPattern wildCard = new WildcardPattern(url, WildcardOptions.IgnoreCase);
49: if (SPFarm.Local == null)
50: throw new SPException("The SPFarm object is null. Make sure you are running as a Farm Administrator.");
51:
52: foreach (SPService svc in SPFarm.Local.Services)
53: {
54: if (!(svc is SPWebService))
55: continue;
56:
57: foreach (SPWebApplication webApp in ((SPWebService)svc).WebApplications)
58: {
59: for (int i = 0; i < webApp.Sites.Count; i++)
60: {
61: using (SPSite site = webApp.Sites[i])
62: {
63: if (wildCard.IsMatch(site.Url))
64: WriteObject(new SPSiteInfo(site));
65: }
66: }
67: }
68: }
69: }
70: }
71: }
72: }
73: }
The following is the full help for the cmdlet.
PS C:\> get-help get-spsite -full
NAME
Get-SPSite
SYNOPSIS
Gets one or more SPSiteInfo objects representing a SharePoint 2007 Site Collection.
SYNTAX
Get-SPSite [-Url] <String[]> [-WhatIf] [-Confirm] [<CommonParameters>]
DETAILED DESCRIPTION
Pass in a comma separated list of URLs or a string array of URLs to obtain a collection of SPSiteInfo objects. The
se objects do not need to be disposed.
The SPSiteInfo object that is returned contains almost all of the same properties of the SPSite object but does not
require disposal and should be generally considered read-only. You can get to the actual SPSite object by using t
he SPBase property or the GetSPObject() method. The SPBase property results in a copy of the SPSite object being p
ersisted in the SPSiteInfo object for faster access on future calls. Always remember to dispose of the SPSite obje
ct if used. Some collection properties may be directly updated without the need to access the SPSite object.
Copyright 2008 Gary Lapointe
> For more information on these PowerShell cmdlets:
> http://stsadm.blogspot.com/
> Use of these cmdlets is at your own risk.
> Gary Lapointe assumes no liability.
PARAMETERS
-Url <String[]>
Specifies the URL of the site collection(s) to retrieve. Wildcards are permitted. If you specify multiple URLs,
use commas to separate the URLs.
Required? true
Position? 1
Default value
Accept pipeline input? true (ByValue, ByPropertyName)
Accept wildcard characters? false
-WhatIf
Required? false
Position? named
Default value
Accept pipeline input? false
Accept wildcard characters? false
-Confirm
Required? false
Position? named
Default value
Accept pipeline input? false
Accept wildcard characters? false
<CommonParameters>
This cmdlet supports the common parameters: -Verbose, -Debug,
-ErrorAction, -ErrorVariable, and -OutVariable. For more information,
type, "get-help about_commonparameters".
INPUT TYPE
String
RETURN TYPE
Collection of SPSiteInfo objects.
NOTES
For more information, type "Get-Help Get-SPSite -detailed". For technical information, type "Get-Help Get-SP
Site -full".
-------------- Example 1 --------------
C:\PS>get-spsite -url http://portal
This example returns back a single SPSiteInfo object.
-------------- EXAMPLE 2 --------------
C:\PS>$sites = get-spsite -url http://mysites/*
This example returns back all My Site site collections under the http://mysites web application.
RELATED LINKS
http://stsadm.blogspot.com
|
Note that if you receive an exception during the execution of this cmdlet simply pass in the “-debug” parameter in order to display the full stack trace which you can use to either debug yourself or report back to here to help me improve the code.
And finally – if you’ve used this cmdlet (or any others that I’ve provided) to do something cool please post your code here as a comment so that others may benefit and possibly give back some feedback that you yourself could use.
Recalculating Usage Statistics via STSADM or PowerShell
I was perusing through the SharePoint forums the other day and I came across an issue that someone was having with the usage statistics information for their My Sites site collections. When they viewed the usage data (~site/_layouts/usage.aspx) they were seeing incorrect information. I’m not really sure why the numbers were wrong but fixing them turned out to be pretty easy. There’s a public method called “RecalculateStorageUsed” that, when called, will recalculate the usage statistics for the site collection. I decided to do some digging within the SharePoint code and what I found was rather interesting – Microsoft created an stsadm command that would allow you to call this method via stsadm for a site collection – but they didn’t publish the command via any config file so even though the code is there, you can’t use it. I tried to do some more poking around to see if there was a timer job or something that either called this method or one of the internal SPRequest methods that actually does the work but unfortunately I didn’t find anything that would help identify why the numbers weren’t correct.
So, knowing that Microsoft had started the creation of such a command but didn’t finish I decided that I’d go ahead and “finish” it for them – but better, naturally
. You can now use my gl-recalculateusage command and pass in various scopes (Farm, WebApplication, or Site) and it will call the aforementioned method for each site collection within the specified scope.
The complete code for the command is included below:
1: using System;
2: using System.Text;
3: using Lapointe.SharePoint.STSADM.Commands.OperationHelpers;
4: using Lapointe.SharePoint.STSADM.Commands.SPValidators;
5: using System.Collections.Specialized;
6: using Microsoft.SharePoint;
7: using Microsoft.SharePoint.Administration;
8:
9: namespace Lapointe.SharePoint.STSADM.Commands.SiteCollectionSettings
10: {
11: /// <summary>
12: ///
13: /// </summary>
14: public class RecalculateUsage : SPOperation
15: {
16: /// <summary>
17: /// Initializes a new instance of the <see cref="RecalculateUsage"/> class.
18: /// </summary>
19: public RecalculateUsage()
20: {
21: SPParamCollection parameters = new SPParamCollection();
22: parameters.Add(new SPParam("url", "url", false, null, new SPUrlValidator()));
23: parameters.Add(new SPParam("scope", "s", false, "site", new SPRegexValidator("(?i:^Farm$|^WebApplication$|^Site$)")));
24:
25:
26: StringBuilder sb = new StringBuilder();
27: sb.Append("\r\n\r\nRecalculates usage statistics for the given site(s).\r\n\r\nParameters:");
28: sb.Append("\r\n\t[-scope <Farm | WebApplication | Site>]");
29: sb.Append("\r\n\t[-url <url>]");
30:
31: Init(parameters, sb.ToString());
32: }
33:
34: /// <summary>
35: /// Gets the help message.
36: /// </summary>
37: /// <param name="command">The command.</param>
38: /// <returns></returns>
39: public override string GetHelpMessage(string command)
40: {
41: return HelpMessage;
42: }
43:
44: /// <summary>
45: /// Executes the specified command.
46: /// </summary>
47: /// <param name="command">The command.</param>
48: /// <param name="keyValues">The key values.</param>
49: /// <param name="output">The output.</param>
50: /// <returns></returns>
51: public override int Execute(string command, StringDictionary keyValues, out string output)
52: {
53: output = string.Empty;
54: Verbose = true;
55:
56: string scope = Params["scope"].Value.ToLowerInvariant();
57:
58: SPEnumerator enumerator;
59: if (scope == "farm")
60: {
61: enumerator = new SPEnumerator(SPFarm.Local);
62: }
63: else if (scope == "webapplication")
64: {
65: enumerator = new SPEnumerator(SPWebApplication.Lookup(new Uri(Params["url"].Value.TrimEnd('/'))));
66: }
67: else
68: {
69: // scope == "site"
70: using (SPSite site = new SPSite(Params["url"].Value.TrimEnd('/')))
71: {
72: Recalculate(site);
73: }
74: return OUTPUT_SUCCESS;
75: }
76:
77: enumerator.SPSiteEnumerated += new SPEnumerator.SPSiteEnumeratedEventHandler(enumerator_SPSiteEnumerated);
78: enumerator.Enumerate();
79:
80: return OUTPUT_SUCCESS;
81: }
82:
83: /// <summary>
84: /// Validates the specified key values.
85: /// </summary>
86: /// <param name="keyValues">The key values.</param>
87: public override void Validate(StringDictionary keyValues)
88: {
89: if (Params["scope"].Validate())
90: {
91: Params["url"].IsRequired = true;
92: Params["url"].Enabled = true;
93: if (Params["scope"].Value.ToLowerInvariant() == "farm")
94: {
95: Params["url"].IsRequired = false;
96: Params["url"].Enabled = false;
97: }
98:
99: }
100: base.Validate(keyValues);
101: }
102:
103: /// <summary>
104: /// Handles the SPSiteEnumerated event of the enumerator control.
105: /// </summary>
106: /// <param name="sender">The source of the event.</param>
107: /// <param name="e">The <see cref="Lapointe.SharePoint.STSADM.Commands.OperationHelpers.SPEnumerator.SPSiteEventArgs"/> instance containing the event data.</param>
108: private void enumerator_SPSiteEnumerated(object sender, SPEnumerator.SPSiteEventArgs e)
109: {
110: Recalculate(e.Site);
111: }
112:
113: /// <summary>
114: /// Recalculates the specified site.
115: /// </summary>
116: /// <param name="site">The site.</param>
117: private void Recalculate(SPSite site)
118: {
119: Log("Recalculating {0}", site.Url);
120: site.RecalculateStorageUsed();
121: using (SPSite site2 = new SPSite(site.ID))
122: Log("Storage updated from {0} to {1} (in bytes)\r\n", site.Usage.Storage.ToString(), site2.Usage.Storage.ToString());
123: }
124: }
125: }
The help for the command is shown below:
C:\>stsadm -help gl-recalculateusage
stsadm -o gl-recalculateusage
Recalculates usage statistics for the given site(s).
Parameters:
[-scope <Farm | WebApplication | Site>]
[-url <url>]
|
The following table summarizes the command and its various parameters:
| Command Name | Availability | Build Date |
|---|---|---|
| gl-recalculateusage | WSS v3, MOSS 2007 | Released: 1/15/2009
|
| Parameter Name | Short Form | Required | Description | Example Usage |
|---|---|---|---|---|
| url | Yes if scope is not Farm | URL of the web application or site collection. | -url http://portal | |
| scope | s | No – defaults to site | The scope to use. Valid values are “Farm”, “WebApplication”, and “Site” | -scope site
-s site |
The following is an example of how to recalculate the usage statistics for all the site collections within the farm:
stsadm -o gl-recalculateusage -scope farm
The following is an example of the output you might see after running the above command:
C:\>stsadm -o gl-recalculateusage -scope farm Recalculating http://mysites Usage updated from 425744 to 425744 Recalculating http://mysites/personal/spadmin Usage updated from 588790 to 588790 Recalculating http://portal Usage updated from 3249962 to 3249962 Recalculating http://sspadmin/ssp/admin Usage updated from 642686 to 642686 Recalculating http://sharepoint1:1234 Usage updated from 18244961 to 18284043 Operation completed successfully. |
If you wanted to do this exact same thing using PowerShell you could do the following:
PS W:\> foreach ($site in Get-SPSite -url *) {
>> $origStorage = $site.SPBase.Usage.Storage
>> $site.SPBase.RecalculateStorageUsed()
>> $site.SPBase.Dispose()
>> $tempSite = $site.GetSPObject()
>> $newStorage = $tempSite.Usage.Storage
>> Write-Host $tempSite.Url Updated from $origStorage to $newStorage
>> $tempSite.Dispose()
>> }
>>
http://mysites Updated from 425744 to 425744
http://mysites/personal/spadmin Updated from 588790 to 588790
http://portal Updated from 3249962 to 3249962
http://sspadmin/ssp/admin Updated from 642686 to 642686
http://sharepoint1:1234 Updated from 18284043 to 18284043
PS W:\>
|
Note that I used the SPBase property initially to get the current storage and then to call the RecalculateStorageUsed method. You then must dispose of this object. I then used the GetSPObject() method to get a new copy of the SPSite object because I can’t use the original copy as the usage data would be cached. I can now use this new object to get the new usage data which I then write to the host and then I’m free to dispose of the object. Also, because the SPSiteInfo objects that are returned by the Get-SPSite cmdlet do not require disposal you can easily pass the filter the results of that command before looping through the returned collection without worrying about disposing of items.
Initial Release of My SharePoint PowerShell Cmdlets
Update 4/25/2009: I’ve removed the “-gl” suffix from all my cmdlets - any examples using the -gl in the cmdlet name should be updated.
If you’ve been following my blog you’ll remember that I recently pushed out a “beta” build of some SharePoint PowerShell Cmdlets for some initial feedback and reviews. We’ll I’ve gone ahead and incorporated the feedback that I received and I am now officially releasing the first version of my cmdlets. The number of cmdlets has not changed from the beta build – they’ve just been cleaned up and enhanced. I will slowly start documenting each of the existing cmdlets (there are currently 11) and hopefully be adding new ones over time.
I’ve added links to the top of my blog for quick download of either the source code or the MOSS or WSS builds of the setup packages. At present there are no differences between the MOSS and WSS builds with the exception of my STSADM extensions which are included in the package and are a required dependency. I’ve also updated my main index page so that it now also includes the available PowerShell commands – just select what you would like to look at via the dropdown box:
To install the cmdlets download the MOSS or WSS installer from the links at the top of this page (or in the right-hand column). Once downloaded run the installer from your MOSS server using your MOSS admin account. You will see the following screens:
- The initial welcome screen:
- The EULA screen – you must accept to continue (you can find a copy for future reference in the install directory):
- The install location – by default the files will be put at “C:\Program Files\Gary Lapointe\PowerShell Commands for SharePoint\”:
- Optionally install my custom STSADM extensions – the extensions are a required pre-requisite but I make their install conditional so that if you have already installed the extensions you can avoid having to re-install them. This is particularly beneficial if you are installing the cmdlets on more than one machine in the farm – you only have to install the extensions in the farm once as they will be pushed out to all of the servers by SharePoint:
- Confirm that you are ready to install:
- The files are then installed
Once the installation has completed you will be presented with the contents of the ReadMe.rtf file (which can be found in the install directory). I’ve included the contents of that file below for reference:
To automatically load the snapin every time you start PowerShell create a shortcut to PowerShell passing in the -psconsolefile parameter like so:
%SystemRoot%\system32\WindowsPowerShell\v1.0\Powershell.exe –psconsolefile "C:\Program Files\Gary Lapointe\PowerShell Commands for SharePoint\Console.psc1"
The contents of the Console.psc1 file can be seen below.
<?xml version="1.0" encoding="utf-8"?>
<PSConsoleFile ConsoleSchemaVersion="1.0">
<PSVersion>1.0</PSVersion>
<PSSnapIns>
<PSSnapIn Name="Lapointe.SharePoint.PowerShell.Commands" />
</PSSnapIns>
</PSConsoleFile>Or run the following command or add the following command to your profile script:
add-pssnapin -name "Lapointe.SharePoint.PowerShell.Commands"
To see the help file for any of the commands type the following (where -detailed and -full are optional):
help <command name> [<-detailed | -full>]
For example, the following command will return the full help for the get-spsite command:
help get-spsite -full
Here’s a quick demonstration showing some of the power of one of these cmdlets, the Get-SPSite cmdlet:
Looking at the example above you can see I was able to quickly get a list of all Site Collections using “$sites = get-spsite –url *”. I then output the results to the display to see what got returned. To get some different columns I then passed in the $sites variable to the Select-Object (select) cmdlet and had it return back the Url and Usage.Storage properties so that I can see the size of all the size collections in the farm. I could take this further and do some filtering if necessary but I’ll leave that exercise to you.
Keep an eye on my blog for more online documentation about each of the cm
dlets along with lots of useful examples (hopefully
).
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
- 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().