UPDATE!
As promised here is the solution file : CrossSiteGroupWebPart.rar
And if you just want to install the webpart : CrossSiteGroupWebPartInstall.CAB
And this the list-template you will need : AnalyseNon.stp
I got the question from a customer who wanted to have a view of every user on a site with all the permissions that the user had per list (since you can set permissions per list). Sounds pretty forward huh? :)
One challenge I had to face lies in the fact that this customer is a big fan of creating custom sitegroups and cross-site groups.
Another (personal) challenge was that I wanted to publish this information in a custom list on the site where the webpart is dropped upon. You may wonder why I want to publish the information back in Sharepoint again. Well there are 3 reasons :
1. Export to Excel (requirement of the customer)
2. Sorting/Filtertering/Group By of columns
3. Intergration with AD (user presence)
Thus, the first thing was to create a function to retrieve all the permissions per list for each user on a site.
private
void GetSecurityForWeb(string url){
Stopwatch sp =
new Stopwatch();sp.Start();
ArrayList AnalyzeItems =
new ArrayList();SPSite site =
new SPSite(url);SPWeb web = site.OpenWeb();
try{
Label1.Text += "<BR>Iterating through "+web.Title.ToString();
SPUserCollection users = web.Users;
foreach (SPUser user in users){
foreach (SPList list in web.Lists){
System.Collections.Specialized.StringCollection permissions =
new System.Collections.Specialized.StringCollection();if (!list.Permissions.Inherited){
SPPermissionCollection perms = list.Permissions;
foreach (SPPermission perm in perms){
if (user.ID == perm.Member.ID){
foreach (SPRole role in user.Roles){
if (role.Type != SPRoleType.None){
if (!permissions.Contains(role.Type.ToString())){permissions.Add(role.Type.ToString());}
}
}
}
else{
foreach (SPRole role in user.Roles){
if (role.ID == perm.Member.ID){
if (role.Type != SPRoleType.None){
if (!permissions.Contains(role.Type.ToString())){permissions.Add(role.Type.ToString());}
}
else{
if (!permissions.Contains(role.Name.ToString())){permissions.Add(role.Name.ToString());}
}
}
foreach (SPGroup group in role.Groups){
if (group.ID == perm.Member.ID){
if (!permissions.Contains(perm.PermissionMask.ToString())){permissions.Add(perm.PermissionMask.ToString());}
}
}
}
foreach (SPGroup group in user.Groups){
if (group.ID == perm.Member.ID){
if (!permissions.Contains(perm.PermissionMask.ToString())){permissions.Add(perm.PermissionMask.ToString());}
}
}
}
}
}
else{
foreach (SPRole role in user.Roles){
if (role.Type != SPRoleType.None){
if (!permissions.Contains(role.Type.ToString())){permissions.Add(role.Type.ToString());}
}
else{
if (!permissions.Contains(role.Name.ToString())){permissions.Add(role.Name.ToString());}
}
foreach (SPGroup group in role.Groups){
if (!permissions.Contains(group.Name.ToString())){permissions.Add(group.Name.ToString());}
}
}
}
//Populating of all the groups where the user is a member ofstring groups = string.Empty;int teller = 1;foreach (SPGroup group in user.Groups){
if (user.Groups.Count == teller){groups += group.Name.ToString();}
else{groups += group.Name.ToString() + " " ;}
teller++;
}
//Adding all the information in an object and pushing this object in an ArrayListAnalyzeItem item =
new AnalyzeItem();item.Site = web.Title;
item.Username = user.Name;
item.UserID = user.ID.ToString();
item.List = list.Title;
string _perm = string.Empty;foreach (string perm in permissions){
_perm += " " + perm;
}
item.Permissions = _perm;
item.Group = groups;
AnalyzeItems.Add(item);
}
web.Close();
web.Dispose();
}
}
catch(Exception error){
Label1.Text += error.Message.ToString();
}
finally{
site.Close();
site.Dispose();
}
sp.Stop();
Fetch = sp;
AddListItem(url, AnalyzeItems);
}
So what I do is gather all the permissions and store them in a custom class called AnalyzeItem which looks like this :
using
System;using
System.Collections;namespace
CrossSiteGroupWebPart{
/// <summary>/// Summary description for AnalyzeItem./// </summary>public class AnalyzeItem{
private string _username = string.Empty;private string _userid = string.Empty;private string _site = string.Empty;private string _list = string.Empty;private string _group = string.Empty;private string _permissions = string.Empty;public AnalyzeItem(){
//// TODO: Add constructor logic here//}
public string Username{
get{
return _username;}
set{
_username =
value;}
}
public string UserID{
get{
return _userid;}
set{
_userid =
value;}
}
public string Site{
get{
return _site;}
set{
_site =
value;}
}
public string List{
get{
return _list;}
set{
_list =
value;}
}
public string Group{
get{
return _group;}
set{
_group =
value;}
}
public string Permissions{
get{
return _permissions;}
set{
_permissions =
value;}
}
}
}
So now we have all the data stored in the ArrayList, now we want to store this information into a List
private
void AddListItem(string url, ArrayList items){
Label1.Text += "<BR>Pushing items into list";
Stopwatch sp =
new Stopwatch();sp.Start();
SPSite _Site =
new SPSite(url);SPWeb _Web = _Site.RootWeb;
try{
Label1.Text += "<BR>Checking if list exists";
SPList analyse = _Web.Lists["Analyse"];
}
catch(Exception error){
Label1.Text += "<BR>List not found, therefore creating it";
CreateList(url);
Label1.Text += "<BR>List created";
}
finally{
_Web.Close();
_Web.Dispose();
_Site.Close();
_Site.Dispose();
}
SPSite site =
new SPSite(url);SPWeb web = site.RootWeb;
SPWeb _compareWeb = site.OpenWeb();
try{
Label1.Text += "<BR>Emptying list if items already exist";
SPList analyse = web.Lists["Analyse"];
if (analyse.Items.Count > 0){
for (int i=analyse.Items.Count-1; i>-1; i--){
if (analyse.Items[i]["Site"].ToString() == _compareWeb.Title.ToString()){
analyse.Items.Delete(i);
}
}
}
Label1.Text += "<BR>List emptied!";
foreach (AnalyzeItem _item in items){
SPListItem item = analyse.Items.Add();
item["Title"] = _item.Username;
item["UserName"] = _item.UserID;
item["List"] = _item.List;
item["Permissions"] = _item.Permissions;
item["Groups"] =_item.Group;
item["Site"] = _item.Site;
item.Update();
}
}
catch (Exception error){
Label1.Text += "<BR>"+error.Message.ToString();
}
finally{
web.Close();
web.Dispose();
site.Close();
site.Dispose();
}
sp.Stop();
Import = sp;
}
Now you may wonder what the function CreateList, as you may have guessed by the name it creates a custom list based on a customlist template. See the following code chunk on how I did this. I had some difficulties since the customer has a Portal + teamsites environment so every site beneath the portal is a sitecollection instead of a site. Because of this I could not use the custom list template since this is per site-collection and cannot be referenced over sitecollections. So I choose to physically copy the template from a source site (where the template can ben maintained as well) to the site where the webpart was dropped upon.
<
private
void CreateList(string url){
//First copy the list template from the source site to the current siteSPSite siteCollection =
new SPSite(sourcesite);SPWeb web = siteCollection.OpenWeb();
SPWeb destweb = SPControl.GetContextWeb(Context);
try{
SPFolder srcFolder = web.Folders["_catalogs"].SubFolders["lt"];
SPFile srcFile;
if (destweb.WebTemplate == "MPS"){
srcFile = srcFolder.Files["Analyse.stp"];
}
else{
srcFile = srcFolder.Files["AnalyseNon.stp"];
}
Label1.Text += "<BR>Template found! " + srcFile.Name.ToString();
try{
Label1.Text += "<BR>Opening destination web";
SPFolder destFolder = destweb.Folders["_catalogs"].SubFolders["lt"];
SPFileCollection destFiles = destFolder.Files;
Label1.Text += "<BR>Filecollection opened";
string destURL = destFolder.Url + "/" + srcFile.Name;byte[] binFile = srcFile.OpenBinary();destFiles.Add(destURL, binFile);
Label1.Text += "<BR>Template saved to "+destURL.ToString();
}
catch(Exception _error){
Label1.Text+="<BR>Problem adding template" + _error.Message.ToString();
}
}
catch(Exception error){
Label1.Text += "<BR>List creation failed due to : " + error.Message.ToString();
}
finally{
web.Close();
web.Dispose();
siteCollection.Close();
siteCollection.Dispose();
}
try{
Label1.Text += "<BR>Trying to create list based on new template";
SPSite destSite = SPControl.GetContextSite(Context);
SPWeb destWeb = destSite.OpenWeb();
Label1.Text += "<BR>Opened the current web";
SPListTemplateCollection customListTemplates = destSite.GetCustomListTemplates(destWeb);
Label1.Text += "<br>#customtemplates" + customListTemplates.Count.ToString();
foreach(SPListTemplate temp in customListTemplates){
Label1.Text += "<BR>" + temp.Name.ToString();
}
if (destWeb.WebTemplate == "MPS"){
SPListTemplate listTemplate = customListTemplates["Analyse"];
destWeb.Lists.Add("Analyse", "", listTemplate);
}
else{
SPListTemplate listTemplate = customListTemplates["AnalyseNon"];
destWeb.Lists.Add("Analyse", "", listTemplate);
}
}
catch(Exception ___error){
Label1.Text += "<BR>" + ___error.Message.ToString();
}
}
As you may have noticed, I use different custom list templates when a different webtemplate is applied to the site. This has to do with site definitions are stored into a custom list template (see KB: Custom List Templates Do Not Appear on the "Create Page" Page of a Site) took me a couple of hours to figure this thing out btw. Since the file is physically there the frustation grew and grew when the object model said there was no custom list template to be found in the gallery :)
And finally we need to build the user interface where the user can select a site and press a button to populate the list. I embedded some javascript to show something an animated gif while the webpart is loading his stuff, so the user doesn't think that the webpart is not functioning. Also I built in a check that only webadmin's can run this tool, since some adminstrative privileges are required when userproperties are queried.
public
class CrossSiteGroupWebPart : Microsoft.SharePoint.WebPartPages.WebPart{
public Stopwatch Fetch;public Stopwatch Import;public StringBuilder sb = new StringBuilder();LinkButton refreshButton;
private Label Label1;public string nummer = "";DropDownList drpSiteList;
private const string defaultText = "";private string text = defaultText;[Browsable(
true),Category("Miscellaneous"),
DefaultValue(defaultText),
WebPartStorage(Storage.Personal),
FriendlyName("Text"),
Description("Text Property")]
public string Text{
get{
return text;}
set{
text =
value;}
}
/// <summary>/// This method gets the custom tool parts for this Web Part by overriding the/// GetToolParts method of the WebPart base class. You must implement/// custom tool parts in a separate class that derives from /// Microsoft.SharePoint.WebPartPages.ToolPart. /// </summary>///<returns>An array of references to ToolPart objects.</returns>// public override ToolPart[] GetToolParts()
// {
// ToolPart[] toolparts = new ToolPart[2];
// WebPartToolPart wptp = new WebPartToolPart();
// CustomPropertyToolPart custom = new CustomPropertyToolPart();
// toolparts[0] = wptp;
// toolparts[1] = custom;
// return toolparts;
// }
/// <summary>/// Render this Web Part to the output parameter specified./// </summary>/// <param name="output"> The HTML writer to write out to </param>protected override void CreateChildControls(){
nummer =
this.ID.ToString();Label1 =
new Label();this.Controls.Add(Label1);refreshButton =
new LinkButton();refreshButton.Text="<img src='/_layouts/images/REFRESH.GIF' border='0' />Refresh overview";
refreshButton.Attributes.Add("onclick","this.style.display='none';"+
"var obj_msg = document.getElementById('waitmessage"+nummer+"'); "+
"if (obj_msg != null) obj_msg.style.display='';"+
"var obj_tasks = document.getElementById('tasks"+nummer+"'); "+
"if (obj_tasks != null) obj_tasks.style.display='none';" +
"javascript:setTimeout('UpdateImg"+nummer+"()');");
refreshButton.Click+=
new EventHandler(refreshButton_click);this.Controls.Add(refreshButton);drpSiteList =
new DropDownList();try{
drpSiteList.DataSource = GetSubWebs();
drpSiteList.DataTextField = "Title";
drpSiteList.DataValueField = "Url";
drpSiteList.DataBind();
}
catch(Exception error){
Label1.Text += "<BR>" + error.Message.ToString();
}
this.Controls.Add(drpSiteList);base.CreateChildControls ();}
private void refreshButton_click(object o, System.EventArgs e){
try{
GetSecurityForWeb(drpSiteList.SelectedValue.ToString());
UpdateStopWatch(drpSiteList.SelectedValue.ToString());
Label1.Text = "<BR>Refreshing is done";
Label1.Text +="<BR>Fetching items lasted : " +Fetch.Elapsed.Minutes+ "m " + Fetch.Elapsed.Seconds + "s" + " Importing items lasted : " +Import.Elapsed.Minutes+ "m " + Import.Elapsed.Seconds + "s";
Label1.Text +="<BR><a href='Lists/Analyse/AllItems.aspx'>Click here</a> to view the grid";
}
catch(Exception error){
Label1.Text += "<BR>" + error.Message.ToString();
}
}
protected override void RenderWebPart(HtmlTextWriter output){
SPWeb web = SPControl.GetContextWeb(Context);
if (web.UserIsWebAdmin){
try{
output.Write("<script language=javascript>");
output.Write("function UpdateImg"+nummer+"() {");
output.Write("var img = document.getElementById('taskpic"+nummer+"');");
output.Write("if (img != null) img.src = '_layouts/images/bigrotation2.gif';");
output.Write("return false;");
output.Write("}");
output.Write("</script>");
output.Write("<div id='waitmessage"+nummer+"' style='Z-INDEX:10; DISPLAY:none; WIDTH:100%; POSITION:relative; HEIGHT:100%'>");
output.Write("<table border='0' style='BORDER-COLLAPSE: collapse' width='100%' height='100%' cellspacing='1' bgcolor='#ffffff' bordercolor='#000000'>");
output.Write("<tr>");
output.Write("<td align='middle' bordercolor='#ffffff'>");
output.Write("<img id='taskpic"+nummer+"' src='_layouts/images/bigrotation2.gif'>");
output.Write("</td>");
output.Write(" </tr>");
output.Write(" </table>");
output.Write("</div>");
output.Write("Please select a site to display information from : ");
drpSiteList.RenderControl(output);
refreshButton.RenderControl(output);
Label1.RenderControl(output);
}
catch(Exception error){
output.Write(error.Message.ToString());
}
}
else{
Label1.Text = "You don't have enough permissions to run this webpart";
}
}
To populate the dropdown with webs the copy/paste this following codechunk
private ArrayList GetSubWebs()
{
ArrayList lijst =
new ArrayList();
SPWeb web = SPControl.GetContextWeb(Context);
Sites site =
new Sites();
site.Title = web.Title.ToString();
site.Url = web.Url.ToString();
lijst.Add(site);
foreach (SPWeb _subweb in web.GetSubwebsForCurrentUser())
{
Sites subsite =
new Sites();
subsite.Title = _subweb.Title.ToString();
subsite.Url = _subweb.Url.ToString();
lijst.Add(subsite);
}
return lijst;
}
With this class to store the urls and titles of the webs
using
System;
namespace
CrossSiteGroupWebPart
{
/// <summary>
/// Summary description for Sites.
/// </summary>
public class Sites
{
private string _url = string.Empty;
private string _title = string.Empty;
public string Url
{
get
{
return _url;
}
set
{
_url =
value;
}
}
public string Title
{
get
{
return _title;
}
set
{
_title =
value;
}
}
}
}
I will post the solution soonly so you can see for yourself how it all works :)