"My Groups" list for SharePoint Homepage Web Part

Silver Contributor

So, we are about to do some major rollout of Groups to our company.  While there are a million "entry points" for Groups, we simply wanted a list of Groups that a member belongs to on our Intranet Homepage, with quick links to each of the major workloads.  We try to drive all users to our Intranet homepage as an easy means to accessing information, until (if ever) they become more comfortable navigating all of the tools themselves.

 

So I put together a quick script that will show (1) What groups I (the current user) belong to (item level permissions), (2) Links to the major workloads associated with that Group (excluding Planner cause havent figured that out yet), (3) other supporting information about the Group.

 

Now we can serve it up on our Intranet Homepage (with list web part, search results+display templates, script editor web part, however else) as "My Groups"

 

This also serves as an "auditing" list for us admins (we can see what groups exist, which language, what category/classification, dynamic membership, etc).

 

groups_inventory.png

Probably more elegant ways to do this (and probably better SPFX stuff in the future, but I only know what I know), but in general, I am just clearing a SharePoint list of existing entries, querying all O365 Groups, and adding them into the SharePoint list, and then setting "Read" permissions on the list item to the particular Group (so only Group members can actually see their own Groups).  Probably running this once or twice a day.

 

 

# SharePoint Online DLLs
#$dllDirectory = "D:\Assets\\16.1.6112.1200"
$dllDirectory = "C:\PowerShell\DLL\16.1.6112.1200"
Add-Type -Path "$dllDirectory\Microsoft.SharePoint.Client.dll" 
Add-Type -Path "$dllDirectory\Microsoft.SharePoint.Client.Runtime.dll" 

# User Credentials and Variables 
$username = "<admin username here>"
$password = '<admin password here>'
$securePassword = ConvertTo-SecureString $Password -AsPlainText -Force 
$url = "https://contoso.sharepoint.com/sites/groups/" # the url of the site where your list is
$listName = "Groups" # the name of your list
$domain = "@contoso.com"

# Connect to Exchange Online
Write-Host "Connecting to Exchange Online..." -ForegroundColor Green 
$E_Credential = New-Object -TypeName System.Management.Automation.PSCredential -argumentlist $userName, $SecurePassword
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.outlook.com/powershell/ -Credential $E_Credential -Authentication Basic -AllowRedirection
Import-PSSession $Session -AllowClobber  | out-null

# Connect to SPO
$clientContext = New-Object Microsoft.SharePoint.Client.ClientContext($url) 
$credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($username, $securePassword) 
$clientContext.Credentials = $credentials 
Write-Host "Connected to: '$Url'" -ForegroundColor Green 
	
$List = $clientContext.Web.Lists.getByTitle($listName)
$clientContext.Load($List)
$clientContext.ExecuteQuery()

# Get Existing Entries
$spQuery = New-Object Microsoft.SharePoint.Client.CamlQuery	
$items = $List.GetItems($spQuery)
$clientContext.Load($items)
$clientContext.ExecuteQuery()

# Remove Existing Entries
Write-Host "Clearing existing entries" -ForegroundColor Cyan
$count = 0
ForEach ($item in $items){
    Write-Host ("  "+$count+" "+$Item.FieldValues["ID"]+" "+$Item.FieldValues["Title"])
    $List.getitembyid($Item.id).DeleteObject()
    $clientContext.ExecuteQuery()
    $count += 1
}

# Get all O365 Groups
Write-Host "Getting O365 Groups" -ForegroundColor Cyan
$o365Groups = get-unifiedgroup
$count = 0
foreach($group in $o365Groups){
    $count++
    Write-Host "$count - Creating $($group.DisplayName)" -ForegroundColor Green
    #$group | select * # Show all available Group details

    $O365_group = $clientContext.Site.RootWeb.EnsureUser("c:0o.c|federateddirectoryclaimprovider|$($group.ExternalDirectoryObjectId)")
    $clientContext.Load($O365_group)

    [Microsoft.SharePoint.Client.FieldUserValue[]]$groupOwners = New-Object Microsoft.SharePoint.Client.FieldUserValue
    $owners = ($group.ManagedBy)
    foreach($owner in $owners){
        try{
            $user = $clientContext.Web.EnsureUser("$owner@buckman.com")
            $clientContext.Load($user)
		    $clientContext.ExecuteQuery()	                    
            [Microsoft.SharePoint.Client.FieldUserValue]$fieldUser = New-Object Microsoft.SharePoint.Client.FieldUserValue
            $fieldUser.LookupId = $user.Id
            if($counter -eq 0){
                $groupOwners = $fieldUser
            } else {
                $groupOwners += $fieldUser
            }
            $counter++
        } catch {
            Write-Host "User does not exist"
        }
    }


    # Create new entry in SharePoint List
    # Change the ["field"] values to match your SharePoint column internal names
    $ListItemInfo = New-Object Microsoft.SharePoint.Client.ListItemCreationInformation
    $newItem = $List.AddItem($ListItemInfo)
    $newItem["Title"] = $group.DisplayName # Single Line of Text
    $newItem["Group"] = $O365_group # Person/Group Field (Group enabled)    
    $newItem["Description"] = $group.Notes # Multiple Lines of Text
    $newItem["Conversation"] = "https://outlook.office.com/owa/?path=/group/$($group.Alias)$($domain)/mail, Conversation" # Hyperlink
    $newItem["Calendar"] = "https://outlook.office.com/owa/?path=/group/$($group.Alias)$($domain)/calendar, Calendar" # Hyperlink
    $newItem["Files"] = "https://outlook.office.com/owa/?path=/group/$($group.Alias)$($domain)/files, Files" # Hyperlink
    $newItem["Library"] = "$($group.SharePointDocumentsUrl), Document Library" # Hyperlink
    $newItem["Site"] = "$($group.SharePointSiteUrl), SharePoint Site" # Hyperlink
    $newItem["Notebook"] = "$($group.SharePointNotebookUrl), Notebook" # Hyperlink
    $newItem["Category"] = $group.Classification # Single Line of Text
    $newItem["Connectors"] = $group.ConnectorsEnabled # Boolean (Yes/No)
    $newItem["HiddenFromGAL"] = $group.HiddenFromAddressListsEnabled # Boolean (Yes/No)
    $newItem["Language"] = $group.Language # Single Line of Text
    $newItem["Privacy"] = $group.AccessType # Single Line of Text
    $newItem["DynamicMembership"] = $group.IsMembershipDynamic # Boolean (Yes/No)
    $newItem["ExternalDirectoryID"] = $group.ExternalDirectoryObjectId # Single Line of Text   
    $newItem["ExternalUserCount"] = $group.GroupExternalMemberCount # Number   
    $newItem["Owners"] = $groupOwners # Multiple Person Field
    # Add more as needed
    $newItem.Update()
    $clientContext.ExecuteQuery()

    # Break Permissions
    $newItem.BreakRoleInheritance($false, $false)  
    $clientContext.ExecuteQuery()  

    # Remove Any Existing Permissions
    $permissions = $newItem.RoleAssignments
    $clientContext.Load($permissions)
    $clientContext.ExecuteQuery()
    foreach($permission in $permissions){
        $newItem.RoleAssignments.GetByPrincipalId($permission.PrincipalId).DeleteObject()
    }

    # Set permissions to actual O365 Group
    $reader = $clientContext.Web.RoleDefinitions.GetByName("Read");
    $roleAssignment = New-Object microsoft.SharePoint.Client.RoleDefinitionBindingCollection($clientContext)
	$roleAssignment.Add($reader)
    $clientContext.Load($newItem.RoleAssignments.Add($O365_group, $roleAssignment)) 
                                     
    $newItem.Update();  
    $clientContext.ExecuteQuery()

}


 

17 Replies

Great stuff @Brent Ellis! As you mentioned, would love to see this in SPFx form or potentially as "My Groups Bot" embedded in Microsoft Teams! The bot could help drive action around the Groups right there in the conversation for basic group maintenance, discovery, etc.

Very impressive, I wish some of this was built in!!

Pure beauty 🙂

 

Some things I didn't know and might be helpful to others:

 

- don't forget to change @buckman.com to the domain you use for your O365 groups (I wonder if the script can be updated to detect the domain since you can use multiple domains?)

- you can download the dll's from https://www.nuget.org/packages/Microsoft.SharePointOnline.CSOM (just download the nuget package and change the extension to zip)

- Roldefinitions are localized so instead of $clientContext.Web.RoleDefinitions.GetByName("Read"), I had to use GetByName("Lezen"); (check the permission levels on your list)

- if you have 1000+ groups, you could try get-unifiedgroup -resultsize unlimited

 

Thanks!

Thanks Bart! This is my quick and dirty attempt to solve an immediate need, thought I sanitized it enough before pasting but missed the domain 🙂

I'm hardly a powershell expert, but more than armed and dangerous, so I'm sure there are many more efficiencies to be had.

Got my first end-user visual set up for including on our home page.  I'm using the Group title, with selectable icons underneath to each workload.  Have a quick filter text box to narrow down groups and a small pagination to save real estate.  Then a link at the bottom to go to our "Informational" page about Groups.

 

MyGroupsWebPart.jpg

That's impressive Brett. The in-built Group discovery experience has some weaknesses. Your web part will be very useful for an organizations that has has high adoption and utilization of their SPO Intranet. I encourage people to favourite the Groups they frequently work in. Then wherever Groups are listed, (OWA, OneDrive, SharePoint Home site, mobile apps) their frequent and favourite Groups are easy to find. But by using your web part, you have more control of the detail that's presented. It would be interested to see what you can bring through from the Microsoft Graph. You could list recent activity, recent conversations, next task due from Planner. A view like this to show your Group 'at-a-glance' would be very useful.

We've hit a road block woth this, I'm not sure why.

 

The groups are visible in the generated list for a member of that group, but the search remains empty for that user unless we manually/explicitely give the member rights (which he already has through the group).

 

When checking the privileges of a user on an item, it is given by the O365 group, but the item is only visible in the search until the privileges are given directly.

 

It looks like the search is unable to resolve the group members when crawling. It is strange that this is working in your tenant.

Hmm, I opted to use api and script editor web part to create our web part

 

Perhaps @cfiessinger can flag someone down that knows how search indexing + Groups is working.  We havent started using Groups for permissions elsewhere yet, but are definitely planning to and if this is the case, that is a big issue.

 

 

<SCRIPT LANGUAGE='JavaScript' type='text/javascript'>

$(document).ready(function () {

	$.ajax({
		url : "https://mytenant.sharepoint.com/sites/groups/_api/web/lists/getByTitle('my')/Items?$OrderBy=Title",
		contentType : "application/json;odata=verbose",
		headers : { "accept" : "application/json;odata=verbose" },
		success : function onSuccess(data, request){
			if(data.d.results.length == 0){
				$("#listOfGroups").html("No Groups");
			} else {										
				for(i=0;i<data.d.results.length;i++){
					//console.log(data.d.results[i]);
					html = "<B>"+data.d.results[i].Title+"</B><BR>"
					if(data.d.results[i].Conversation){
						html += "<a style='font-size:16px;' title='Group Conversations' href='"+data.d.results[i].Conversation.Url+"' target='_blank'><i class='fa fa-comments-o' aria-hidden='true'></i></a> | ";
					}
					if(data.d.results[i].Calendar){
						html += "<a style='font-size:16px;' title='Group Calendar' href='"+data.d.results[i].Calendar.Url+"' target='_blank'><i class='fa fa-calendar' aria-hidden='true'></i></a> | ";
					}
					if(data.d.results[i].Files){
						html += "<a style='font-size:16px;' title='Group Files' href='"+data.d.results[i].Files.Url+"' target='_blank'><i class='fa fa-folder-open-o' aria-hidden='true'></i></a> | ";
					}
					if(data.d.results[i].Notebook){
						html += "<a style='font-size:16px;' title='Group Notebook' href='"+data.d.results[i].Notebook.Url+"' target='_blank'><i class='fa fa-book' aria-hidden='true'></i></a> | ";
					}
					if(data.d.results[i].Site){
						html += "<a style='font-size:16px;' title='Group SharePoint Site' href='"+data.d.results[i].Site.Url+"' target='_blank'><i class='fa fa-sitemap' aria-hidden='true'></i></a>";
					}
							
				}

				$("#listOfGroups").html(str);
						
			}
							
		},
		error: function (jqXHR, textStatus, errorThrown) { console.log(jqXHR); }
	});

});

</SCRIPT>

<div id="listOfGroups"></div>

Note, we've got font-awesome embedded for the icons.

 

You could easily test it by

 

1. creating a list

2. add an item, remove all permissions and add assign permissions to an O365 group

3. index the list

4. try to search for the item with a user account given permission through the O365 only on that item

 

We are unable to get the item back

Can confirm - that is pretty ridiculous and needs to be fixed asap

 

Groups not in search.png

Great code! Used it with the combination of Promoted Links. 

Thanks!

Hi @Brent Ellis

 

I am trying to recreate the end-user visual web part based on the script you provided. However, I am not getting anything populated (the API is set, I just can't get the records to display? The Str is not defined? Any insight on this? Any help is appreciated. 

Perhaps my mistake in a poor copy paste, it looks like
$("#listOfGroups").html(str);
should be
$("#listOfGroups").html(html);
in the above example

@Brent Ellis thanks! that fixed it! One more question (please forgive me I am pretty novice at jquery) it doesn't seem to be storing all the groups it's looping through. I am only seeing the last group displayed. Any insight on how to fix this? 

Solved, needed to establish the HTML variable and change the Title Html to html+. Thanks for this work!

My again! Any insight on how to pull in Team conversations or planner URLs?