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:
1[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Sharepoint")
2$webapp = [Microsoft.SharePoint.Administration.SPWebApplication]::Lookup("http://portal")
3foreach ($site in $webapp.Sites) {
4 Write-Host $site.Url
5}
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:
1foreach ($site in get-spsite -url http://portal*) {
2 Write-Host $site.Url
3}
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:
1get-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:
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using System.Management.Automation;
5using System.Text;
6using Microsoft.SharePoint;
7using Microsoft.SharePoint.Administration;
8using Microsoft.SharePoint.Workflow;
9
10namespace 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:
1using System;
2using System.Collections.Generic;
3using System.Management.Automation;
4using Lapointe.SharePoint.PowerShell.Commands.OperationHelpers;
5using Lapointe.SharePoint.PowerShell.Commands.Validators;
6using Microsoft.SharePoint;
7using Microsoft.SharePoint.Administration;
8using Lapointe.SharePoint.PowerShell.Commands.Proxies;
9
10namespace 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://www.falchionconsulting.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://www.falchionconsulting.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.