I've often had to write code that iterates through all of the sites in a SharePoint farm. The requirement may be as simple as getting an inventory of sites, or I may need to execute code against each site such as; changing the theme, logo, or activating a feature. Whatever the case may be the logic is simple:
- Get all of the Web Applications
- For each Web Application
- Get all of the Site Collections
- For each Site Collection
- Get all of the Sites (webs)
- For each of the sites (webs)
- Get Site Information or Execute Code (i.e. change theme, logo, master page, activate/deactivate feature)
- Get all of the Sub Sites (webs)
- Repeat step 6
How its done:
Note: Before executing the code make sure that the user executing the logic has the required permissions on every site, or else it wont work. What is the required permission? It depends on what it is you are doing; chances are you'll be executing this as an admin and should have full control. The following code will iterate through Central Administration and Shared Service Provider Sites, take caution when changing anything on those sites.
Make sure your code references the "Microsoft.SharePoint.Administration" namespace.
SPWebService service = thisFarm.Services.GetValue<SPWebService>("");
public void BeginProcess()
{
// Get references to the farm and farm WebService objects
// the SPWebService object contains the SPWebApplications
SPFarm thisFarm = SPFarm.Local;
foreach (SPWebApplication webApp in service.WebApplications)
{
//Execute any logic you need to against the web application
//Iterate through each site collection
foreach (SPSite siteCollection in webApp.Sites)
{ //do not let SharePoint handle the access denied //exceptions. If one occurs you will be redirected //in the middle of the process. Handle the AccessDenied //exception yourself in a try-catch block
siteCollection.CatchAccessDeniedException = false;
try
{
//Execute any logic you need to against the site collection
//Call the recursive method to get all of the sites(webs)
GetWebs(siteCollection.AllWebs);
}
catch (Exception webE)
{
//You should log the error for reference
}
//reset the CatchAccessDeniedException property of the site
//collection to true
siteCollection.CatchAccessDeniedException = true;
}
}
}
public void GetWebs(SPWebCollection allWebs)
{
//iterate through each site(web)
foreach (SPWeb web in allWebs)
{
if (web.Permissions.DoesUserHavePermissions(SPRights.FullMask));
{
//Execute any logic you need to against the site (web)
}
}
}
Updated 7/22/2008: Added code to handle access denied exceptions at the GetWebs level.
Updated 8/25/2008: Removed recursive call that was causing duplication.
23 comments:
What web service are you using?
The code does not use any web service. It uses the SharePoint API and should be ran on one of the servers in the farm. I provide an example on how I use the code in the following post: SharePoint Site Enumeration Page: Get a list of all the sites in your SharePoint farm from Central Administration
I am curious about the performance implications of iterating through all sites in a farm. Could you share some of your experience with respect to how heavy a process this would be in a farm with - say - 1000 team sites? For how long would such a process typically run on a well equipped SharePoint infrastructure? What I would like to do is to schedule a daily job (during the night) which iterates through all sites in the farm and reads values from the property bag.
Hello Jasper, I would not expect any significant performance implications while iterating through several thousand sites in the way you described. Using the code to retrieve the URL of 1000 sites took just under 4 minutes in a test environment consisting of a Virtual PC with 2GB of RAM. In this environment the Virtual PC was serving both roles of Web Front End and Database Server.
Is it possible to do this using sharepoint's web services?
Unfortunately its not possible to do this using SharePoints web service, as there is no OOTB web service that lets you retrieve a list of site collections. However it should be easy enough to create our own web service to provide this information.
Hi, Rafelo,
I'm new to SharePoint.
The first task given to me is to get the size of each site for the entire farm.
I tried implementing your code as a starting point in a c# class without success.
I wanted to display the results in a grid using the memory stream of the xmlWriter. I'm missing something somewhere.
Can you help me?
Dave
Hello Dave, sorry for the delayed response, I was out on vacation.
In regards to your question, I would bypass building the XML altogether and simply populate the grid as I am iterating through all of the sites. Binding the grid to XML would make sense if we were getting XML to begin with. On the other hand it may make sense to build the XML in an application page (thus making it reusable by this and any other application). I have a posting on the subject at SharePoint Site Enumeration Page: Get a list of all the sites in your SharePoint farm from Central Administration
Hi Rafelo,
I am not able to get the size of the subsites in a site collection. i have able to loop through each subsite to get the permission groups but not able to find which property to use to get the size of each site in a site collection.
Could you help me on this??
Hello Ricky, you can retrieve the size of a site collection via the "Usage" property of the SPSite object. This returns an SPSite.UsageInfo object; the "Storage" property of the "UsageInfo" will give you the size of the site.
I dont know of any similar methods for retrieving the size of site(SPWeb.) A workaround may be to iterate through the files keeping track of a running total; this approach probably wouldnt be to accurate as it wouldnt be taking field data into consideration. You may also consider looking at the usage logs.
I'm trying to implement this in a web service, but it's erroring (the error is not helpful). I think it's a permissions problem. Are there any special permissions I would need to execute this? Right now I'm using DefaultCredentials.
-MJC
Rainverse, the code should'nt need any special privileges to execute, but it is intended to run under an account that has full privileges. The sample code will check if the users have FullMask, and should trap any erros that ocurr while calling the GetWebs method without letting them surface. Of course your user account will need at least read access for each web you wish to iterate through, and the level of privilege required may need to be greater depending on what it is you are doing with the SPWeb and/or SPSite object. I just tested it with a domain user account that only has visitor access and it works well. Perhaps if you send me a copy of the error you are getting I could be of furher help.
Here's the code I'm running in the web service.
public string getRootSites(string strURL)
{
try
{
string strReturnMessage = "";
SPFarm thisFarm = SPFarm.Local;
SPWebService service = thisFarm.Services.GetValue;<SPWebService;>("");
foreach (SPWebApplication webApp in service.WebApplications)
{
foreach (SPSite siteCollection in webApp.Sites)
{
foreach (SPWeb web in siteCollection.AllWebs)
{
if (web.Permissions.DoesUserHavePermissions(SPRights.FullMask))
{
if (strReturnMessage == ""){
strReturnMessage = web.Name;
}
else
{
strReturnMessage = strReturnMessage + "||" + web.Name;
}
}
}
}
}
strReturnMessage = "This is working";
return strReturnMessage;
}
catch (System.Exception ex)
{
return ex.Source + ":" + ex.Message + ":" + ex.StackTrace;
}
}
}
Sorry I read your response wrong. The error I'm getting isn't making any sense to me.
{"The request failed with the error message:
--
<html><head><title>Object moved</title></head><body>
<h2>Object moved to <a href="http://mosswebatl01a:35994/_layouts/1033/error.aspx?ErrorText=c%3A%5CProgram%20Files%5CCommon%20Files%5CMicrosoft%20Shared%5Cweb%20server%20extensions%5C12%5CISAPI%5CFiles%2Easmx%2890%29%3A%20error%20CS0246%3A%20The%20type%20or%20namespace%20name%20%27SPFarm%27%20could%20not%20be%20found%20%28are%20you%20missing%20a%20using%20directive%20or%20an%20assembly%20reference%3F%29">here</a>.</h2>
</body></html>
--."}
Hello Rainverse, I suspect the error you are getting has something to do with a missing reference to the Microsoft.SharePoint.Administration namespace or assembly. It appears that its not able to instantiate the SPFarm object. Make sure you have all the necessary References and "Using" statements in place.
Rafelo
I do have a reference to microsoft.sharepoint and a using statement fof microsoft.sharepoint.administration.
Ok. I figured out what was going on there. I actually didn't have the reference in the asmx page on the server. Now I'm getting a 401 Unauthorized error though.
Rainverse, the 401 Unauthorized error could be caused by a number of things. Where are you running the Web Service? Is it in a seperate IIS web site from your SharePoint sites? Is it in the LAYOUTS directory? Do you have anonymous access enabled? The first step I would take in troubleshooting the 401 error would be to try to access some other file in the same directory (preferably a text file or image). If you cannot access the file, it probably indicates a configuration error of the site in IIS.
Hi there. First of all thanks for taking the time to respond to these comments.
I'm running the web service from the ISAPI folder on the sharepoint server. I'm calling from my localhost. The dll for the web service is in the following directory on the sharepoint server: c:\Inetpub\wwwroot\wss\VirtualDirectories\35994\bin with 35994 being the port it's running on. I have another web method in the same web service that returns fine, unless I have this code in the method in question. If I comment out that code, everything runs fine. Anonymous access is turned off.
Hello again. I've got it to work using defaultCredentials. Not sure why that works and not an actual un/pw. But it's not giving me the actual site collection names.
The SPSite and SPWeb objects created by enumerating over webApp.Sites and siteCollection.AllWebs need to be disposed when you're done with them, preferably within a try...finally in case of exceptions. I describe one technique to handle this here: LINQ for SPWebCollection Revisited: AsSafeEnumerable.
Thanks, solutionizing.net. I should have commented on the subject. Most cases call for explicitly disposing of the SPSite and SPWeb objects (such as the example in this posting), there are some exceptions; for instance when the SPSite or SPWeb object is obtained as a reference from the Context, in which case it is a "shared" resource and managed by the system.
This is an important subject that everyone should look into personally, becuase its not as simple as "always" dispose. I urge everyone to read the following MSDN article which covers the subject in detail http://msdn.microsoft.com/en-us/library/aa973248.aspx
I'll be giving a SharePoint Developer Weekend Crash Course this upcoming March for anybody who is interested. You can check out the full announcement and agenda at http://www.rafelo.com/sharepointtraining
Post a Comment