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:

  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.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 NameAvailabilityBuild Date
gl-recalculateusageWSS v3, MOSS 2007Released: 1/15/2009
Parameter NameShort FormRequiredDescriptionExample Usage
urlYes if scope is not FarmURL of the web application or site collection.-url http://portal
scopesNo – defaults to siteThe 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.