When working with clients on their SharePoint deployment I often get questions about the quota templates and how they work. Quotas are kind of odd because of the disconnected nature of the quota template and the quota that is assigned to a site collection. Often people assume that changing the template settings affects all site collections using that template. Unfortunately that is not the case. The issue is made worse by the fact that the “Site Collection Quotas and Locks” page within Central Admin shows the values of the template assigned and not the actual quota assigned to the site – so administrators will change the template settings, then look at a site collection using that template and it “appears” to them that the template change has affected the site. But if you go to the command line and run the enumsites command you can see that the quota values have not in fact been changed.

So how do you get the value applied? Simply click the “OK” button on the “Site Collection Quotas and Locks” page. Okay, so that’s all nice and peachy if you have one or two site collections, but what if you have several hundred? That’s where my new command comes in: gl-syncquotas. Synchronizing quotas using the object model is actually pretty easy – you just get an instance of the quota template (SPQuotaTemplate), which inherits from SPQuota, and set the Quota property of the SPSite object – that’s it.

Here’s the code showing how I did it:

  1using System;
  2using System.Text;
  3using Lapointe.SharePoint.STSADM.Commands.OperationHelpers;
  4using Lapointe.SharePoint.STSADM.Commands.SPValidators;
  5using System.Collections.Specialized;
  6using Microsoft.SharePoint;
  7using Microsoft.SharePoint.Administration;
  8 
  9namespace Lapointe.SharePoint.STSADM.Commands.Quotas
 10{
 11    public class SyncQuotas : SPOperation
 12    {
 13        private SPQuotaTemplateCollection m_quotaColl;
 14        private SPQuotaTemplate m_quota;
 15        private bool m_setQuota = false;
 16 
 17        /// <summary>
 18        /// Initializes a new instance of the <see cref="SyncQuotas"/> class.
 19        /// </summary>
 20        public SyncQuotas()
 21        {
 22            SPParamCollection parameters = new SPParamCollection();
 23            parameters.Add(new SPParam("url", "url", false, null, new SPUrlValidator()));
 24            parameters.Add(new SPParam("scope", "s", false, "site", new SPRegexValidator("(?i:^Farm$|^WebApplication$|^Site$)")));
 25            parameters.Add(new SPParam("quota", "q", false, null, new SPNonEmptyValidator()));
 26            parameters.Add(new SPParam("setquota", "set"));
 27 
 28 
 29            StringBuilder sb = new StringBuilder();
 30            sb.Append("\r\n\r\nSynchronizes site quota settings with those defined in the quota templates.\r\n\r\nParameters:");
 31            sb.Append("\r\n\t[-scope <Farm | WebApplication | Site (default)>]");
 32            sb.Append("\r\n\t[-url <url>]");
 33            sb.Append("\r\n\t[-quota <quota template name to synchronize (sites using other quotas will not be affected)>]");
 34            sb.Append("\r\n\t[-setquota (if scope is site or web application and a quota is specified apply the quota to any site that does not currently have a quota assigned)]");
 35 
 36            Init(parameters, sb.ToString());
 37        }
 38 
 39        /// <summary>
 40        /// Gets the help message.
 41        /// </summary>
 42        /// <param name="command">The command.</param>
 43        /// <returns></returns>
 44        public override string GetHelpMessage(string command)
 45        {
 46            return HelpMessage;
 47        }
 48 
 49        /// <summary>
 50        /// Executes the specified command.
 51        /// </summary>
 52        /// <param name="command">The command.</param>
 53        /// <param name="keyValues">The key values.</param>
 54        /// <param name="output">The output.</param>
 55        /// <returns></returns>
 56        public override int Execute(string command, StringDictionary keyValues, out string output)
 57        {
 58            output = string.Empty;
 59            Verbose = true;
 60 
 61            string scope = Params["scope"].Value.ToLowerInvariant();
 62 
 63            SPFarm farm = SPFarm.Local;
 64            SPWebService webService = farm.Services.GetValue<SPWebService>("");
 65 
 66            m_quotaColl = webService.QuotaTemplates;
 67            m_setQuota = Params["setquota"].UserTypedIn;
 68 
 69            if (Params["quota"].UserTypedIn)
 70            {
 71                m_quota = m_quotaColl[Params["quota"].Value];
 72                if (m_quota == null)
 73                    throw new ArgumentException("The specified quota template name could not be found.");
 74            }
 75 
 76            SPEnumerator enumerator;
 77            if (scope == "farm")
 78            {
 79                enumerator = new SPEnumerator(SPFarm.Local);
 80            }
 81            else if (scope == "webapplication")
 82            {
 83                enumerator = new SPEnumerator(SPWebApplication.Lookup(new Uri(Params["url"].Value.TrimEnd('/'))));
 84            }
 85            else
 86            {
 87                // scope == "site"
 88                using (SPSite site = new SPSite(Params["url"].Value.TrimEnd('/')))
 89                {
 90                    Sync(site);
 91                }
 92                return OUTPUT_SUCCESS;
 93            }
 94            
 95            enumerator.SPSiteEnumerated += enumerator_SPSiteEnumerated;
 96            enumerator.Enumerate();
 97 
 98            return OUTPUT_SUCCESS;
 99        }
100 
101        /// <summary>
102        /// Validates the specified key values.
103        /// </summary>
104        /// <param name="keyValues">The key values.</param>
105        public override void Validate(StringDictionary keyValues)
106        {
107            if (Params["scope"].Validate())
108            {
109                Params["url"].IsRequired = true;
110                Params["url"].Enabled = true;
111                if (Params["scope"].Value.ToLowerInvariant() == "farm")
112                {
113                    Params["url"].IsRequired = false;
114                    Params["url"].Enabled = false;
115                }
116            }
117            if (Params["setquota"].UserTypedIn)
118            {
119                Params["quota"].IsRequired = true;
120                if (Params["scope"].Value.ToLowerInvariant() == "farm")
121                    throw new SPSyntaxException("Scope of \"farm\" is not valid when \"setquota\" is specified.");
122            }
123            base.Validate(keyValues);
124        }
125 
126        /// <summary>
127        /// Handles the SPSiteEnumerated event of the enumerator control.
128        /// </summary>
129        /// <param name="sender">The source of the event.</param>
130        /// <param name="e">The <see cref="Lapointe.SharePoint.STSADM.Commands.OperationHelpers.SPEnumerator.SPSiteEventArgs"/> instance containing the event data.</param>
131        private void enumerator_SPSiteEnumerated(object sender, SPEnumerator.SPSiteEventArgs e)
132        {
133            Sync(e.Site);
134        }
135 
136        /// <summary>
137        /// Syncs the specified site quota with the quota template.
138        /// </summary>
139        /// <param name="site">The site.</param>
140        private void Sync(SPSite site)
141        {
142            SPQuota currentQuota = site.Quota;
143            SPQuotaTemplate currentTemplate = null;
144            foreach (SPQuotaTemplate quota in m_quotaColl)
145            {
146                if (currentQuota.QuotaID == quota.QuotaID)
147                {
148                    currentTemplate = quota;
149                    break;
150                }
151            }
152            if (currentTemplate == null)
153            {
154                if (!m_setQuota)
155                {
156                    Log("WARNING: No quota template has been assigned to site {0}.  Use the -setquota parameter to assign a quota.", site.Url);
157                    return;
158                }
159                Log("PROGRESS: Synchronizing {0}", site.Url);
160                Log("PROGRESS: No quota template assigned to site.  Assigning template...", site.Url);
161                site.Quota = m_quota;
162 
163                Log("PROGRESS: Template \"{0}\" assigned to site.", m_quota.Name);
164                return;
165            }
166 
167            if (m_quota == null || (m_quota != null && currentQuota.QuotaID == m_quota.QuotaID))
168            {
169                Log("PROGRESS: Synchronizing {0}", site.Url);
170                Log("PROGRESS: Currently using template \"{0}\".", currentTemplate.Name);
171 
172                if (site.Quota.InvitedUserMaximumLevel == currentTemplate.InvitedUserMaximumLevel &&
173                    site.Quota.StorageMaximumLevel == currentTemplate.StorageMaximumLevel &&
174                    site.Quota.StorageWarningLevel == currentTemplate.StorageWarningLevel)
175                {
176                    Log("PROGRESS: No changes necessary, quota already synchronized with template.");
177                    return;
178                }
179                site.Quota = currentTemplate;
180 
181                Log("PROGRESS: Storage maximum updated from {0}MB to {1}MB", 
182                    ((currentQuota.StorageMaximumLevel / 1024) / 1024).ToString(),
183                    ((site.Quota.StorageMaximumLevel / 1024) / 1024).ToString());
184                Log("PROGRESS: Storage warning updated from {0}MB to {1}MB",
185                    ((currentQuota.StorageWarningLevel / 1024) / 1024).ToString(),
186                    ((site.Quota.StorageWarningLevel / 1024) / 1024).ToString());
187                Log("PROGRESS: Invited user maximum updated from {0} to {1}",
188                    currentQuota.InvitedUserMaximumLevel.ToString(),
189                    site.Quota.InvitedUserMaximumLevel.ToString());
190            }
191        }
192    }
193}

The help for the command is shown below:

C:\>stsadm -help gl-syncquotas

stsadm -o gl-syncquotas

Synchronizes site quota settings with those defined in the quota templates.

Parameters:
        [-scope <Farm | WebApplication | Site (default)>]
        [-url <url>]
        [-quota <quota template name to synchronize (sites using other quotas will not be affected)>]
        [-setquota (if scope is site or web application and a quota is specified apply the quota to any site that does not currently have a quota assigned)]

The following table summarizes the command and its various parameters:

Command NameAvailabilityBuild Date
gl-syncquotasWSS v3, MOSS 2007Released: 2/12/2009
Parameter NameShort FormRequiredDescriptionExample Usage
urlYes if scope is not FarmURL of the web application or site collection.-url http://mysites
scopesNo – defaults to siteThe scope to use. Valid values are “Farm”, “WebApplication”, and “Site”-scope site, -s site
quotaqNoThe name of the quota template to synchronize. If not specified then all site collections will be synchronized regardless of the quota assigned to the site.-quota "Personal Site", -q "Personal Site"
setquotasetNoIf the scope is site or webapplication and a site collection has no quota assigned to it then the quota template specified via the –quota parameter will be assigned to the site.-setquota, -set

The following is an example of how to synchronize all the “My Site” sites and assign the “Personal Site” quota template to any site collections under the http://mysites web application that do not currently have a quota assigned:

stsadm -o gl-syncquotas -scope webapplication -url http://mysites -quota "Personal Site" -setquota