Feb 08 2017 07:17 AM - edited Feb 14 2017 11:33 AM
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).
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() }
Feb 08 2017 12:06 PM
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.
Feb 08 2017 12:46 PM
Feb 10 2017 06:45 AM
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!
Feb 13 2017 09:48 AM
Feb 21 2017 06:27 AM
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.
Feb 21 2017 10:33 AM
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.
Feb 23 2017 03:32 AM
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.
Feb 23 2017 04:23 AM
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.
Feb 23 2017 07:27 AM
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
Feb 23 2017 11:37 AM
Can confirm - that is pretty ridiculous and needs to be fixed asap
Nov 22 2017 12:40 AM
Great code! Used it with the combination of Promoted Links.
Thanks!
Dec 03 2018 12:05 PM
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.
Dec 03 2018 12:24 PM
Dec 03 2018 01:30 PM
@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?
Dec 04 2018 07:54 AM
Solved, needed to establish the HTML variable and change the Title Html to html+. Thanks for this work!
Dec 04 2018 09:18 AM
My again! Any insight on how to pull in Team conversations or planner URLs?