Category Archives: Security

Ownerless Microsoft 365 groups, teams and sites Q&A

Every resource under Microsoft 365 (Microsoft Teams team, Microsoft 365 group or SharePoint site) must have an owner/owners. Otherwise to whom we communicate on any question – site/group permissions, membership, site/group/team retention policy, content classification etc. Who will be responsible for team/site/group content and configuration and who will provide access to this site for other users.

MS: A team in Microsoft Teams or a Microsoft 365 group and its related services can become ownerless if an owner’s account is deleted or disabled in Microsoft 365. Groups and teams require an owner to add or remove members and change group settings.

Recently Microsoft implemented a new feature: a policy that automatically asks the most active members of an ownerless group or team if they’ll accept ownership. Very important feature. TY Microsoft!

It is important because many other “governance” activities (e.g. permissions attestation, retention policies) rely on site/team ownership. I.e., before we notify site owner that the site is going to be deleted due to inactivity – we want an owner present.

That is how out-of-the-box notification email looks like:

The configuration via wizard is straightforward and intuitive, and Microsoft documented it well, but still we have some questions regarding the policy behavior.

Q: Is it about groups ownership or sites ownership?
A: Group ownership and group-based sites ownership (teams, yammer etc.). Non-group based aka Standalone sites (e.g. communication) are not in scope of this feature/policy.

Q: Who can configure this policy? What kind of permissions required to create/update policy?
A: Microsoft says “Manage Microsoft 365 groups” permissions required – e.g. admins with Global admin or Groups Admin roles required. “Teams administrator” or “SharePoint Administrator” cannot configure the policy.

Q: After the policy activated – who will receive notification? What exactly “most active members” mean?
A: Microsoft only says “most active members” and does not disclose specific algorithm behind.

Q: How about group with no members? What if somebody created a group but did not add any members and then left?
A: In this case the policy will not work – as there is nobody who can be notified. This kind of groups must be handled manually, as no owners no members does not mean nobody uses related SharePoint site. What if the group is public and hosts some valuable data?

Q: How do we know the group is ownerless? Only if owner has been deleted from AAD? What if an owner is just blocked or became unlicensed?
A: For the policy Microsoft consider blocked or unlicensed users presented in the group owners list as valid users and still group owners; so the policy will not be triggered until the group owners list is empty.

Q: We have implemented Azure AD Settings “EnableGroupCreation” and “GroupCreationAllowedGroupId” (as per Microsoft: Manage who can create Microsoft 365 Groups), so not everyone can create m365 groups. Would this impact ownerless groups policy? In other words – if a user cannot create group – would this keep user from being assigned as a group owners?
A: No. Microsoft’s Manage who can create Microsoft 365 Groups trick regulates groups creation only. Later – when a group is created – nothing prevents such user to be added as a group owner.

Q: I support a large Microsoft 365 environment and we already have hundreds and thousands of ownerless groups. I’m concerned how users might react and whether our helpdesk support teams are ready for new type of tickets etc. Implementing the policy in test/stage environment does not make much sense, since there are no really active users etc. So, can I test this policy in production – on real users, but pilot it within a small number of users or ownerless groups before applying to all groups in the environment.
A: Yes, you can do a test or pilot implementation in production limiting the impacted users or groups.
– if you need to limit users who will be getting notifications – e.g. a “pilot team” – during Step 1 “Notification Options” under “Specify who can receive ownership notifications” you can select “Allow only certain active members” and under “Specify security groups to ‎allow members‎” you can select a security group – so only members from the specified security group will be sent ownership request. Microsoft 365 groups do not work here.

but be aware – if you choose this option – it is possible that Microsoft 365 groups might have more active members who are not the security group members. E.g. it might make sense to use this option for piloting – against a small number of isolated set of groups/members, but for not for phased implementation. If you have some specific requirements for group ownership – e.g. “only managers could be group owners” or “contractors cannot be group owners” – using security group to limit potential group owners would make sense.

Another option you can use for phased implementation or piloting the policy is to scope it down to a several selected m365 groups – use “Apply policy to” – “Specific groups” option:

NB! After all notifications are sent for a group – you will never ever get any more notifications for the same group. Even if you re-activate the policy or change policy parameters or whatever – it will not help. Once messages sent – it’s done for the group forever.

NB: Please also check “Microsoft ownerless groups policy in large environment

Q: How many groups I can specify if I select Apply policy to Specific groups option? Is there a maximum?
A: Yes, there is a limit. You can specify no more than 50 groups.

Q: I know the policy is applied to Microsoft 365 groups only. But I have many standalone sites with no owners (no site collection administrators). How do I deal with ownerless SharePoint sites?
A: Options are: manual intervention, PowerShell, 3-rd party tools – depending on your specific case. E.g. you can elevate some “Site Owners” SharePoint group members to site administrators. For modern sites – how about converting standalone sites to Microsoft 365 group-based sites (TBC – as at the moment it is not clear if it is possible)?

Q: What happens after one of the notified members accepts the ownership request?
A: No more notifications will be sent for this group. But previously sent notifications will still be valid.

Q: What happens if several of the notified members accepts the ownership request?
A: Only two first served basis. As per Microsoft, only two members can be assigned to group owners via the policy. When a group got two owners – invitation message actionable item for the rest will be converted from “Would you like to be a group owner?” to “MemberName1 and MemberName2 have already agreed to become group owners.” with no “Yes” and “No” actionable buttons.

NB!
I have tested the policy one more time, and this time after first member accepted ownership – no other members were able to accept ownership. They got a message “Johan Lorenz has already agreed to become group owner”:

@Microsoft, any comments?

Q: What if admin assign owner to group?
A: The group becomes not ownerless. Notification messages will not display invitation to become an owner anymore, and instead of “Would you like to be a group owner? – Yes or No buttons” it will be shown as “username has already agreed to become group owner.”:

Q: Can I customize an ownership notification?
A: Yes, but
– E-mail message body is limited to ~1040 characters
– Policy does not provide any WYSIWYG rich text format options (but there are some tricks you can use to format it with headers, bold/italic, links, bullets/lists: more on email template format.)

Q: Can I use shared mailbox or security group or distribution list as a “send from” e-mail account?
A: No. You can use only user or m365 group mailbox.

Q: Should “send from” user e-mail account be licensed with Exchange?
A: TBD (but most likely no).

Q: What if a group become ownerless after policy is activated?
A: Policy detects the group is ownerless and start sending notifications within 24 hour.
Actually the policy was designed to prevent ownerless groups. So it is recommended to activate the policy once you get the tenant.

Q: We know, that if a user declined ownership once – he will not get any more emails on the same. Is that true for current policy or for any further policies activations? I.e. If the policy updated/re-activated – will it remember user’s decision or it all starts from scratch?
A: TBD
All the next incarnations of the policy will not trigger e-mail notification for the group if all notifications were sent earlier. I.e. in this case user will not receive any more notifications on the same group.

Q: If user declines ownership – does that mean that somebody else will start getting emails so “number of active members” configured stays the same? What if all “active members” choose “No” at week 1 – will the policy select other members or what?
A: No. The policy will send notification to other initially selected members.

Q: If nobody accepted ownership – can we reconfigure the policy to sent more notifications – e.g. to wider range of active members or with more strict language in an e-mail template?
A: Yes and No.
Yes – if you e.g. specified 2 members and 6 weeks in the policy, and then after 3 weeks you want to increase number of members to notify to 10. But (it seems like it’s a bug) you have to deactivate the policy and activate it again with new parameters.
No – if the policy’s specified number of notifications is expired. I.e. if all emails supposed to be sent are sent – no more emails will be generated for this group, even if you reconfigure or deactivate/activate the policy, so the group active members will not get any more notifications on the same group. Workaround: you can add a dummy account to group owners and then delete this dummy account from AAD, so groups become normal and then ownerless again.

Q: What if we specify emails should be sent for 5 weeks, but stop the policy after two weeks? And then we re-activate the policy.
A: It is expected the policy will continue sending e-mail notifications until 5 emails sent.

Q: What if we specify 3 weeks in policy, but then re-configure the policy with 5 weeks specified and activate it again?
A: tbc – not tested yet

Q: Is there a difference in the policy behavior when we reconfigure the policy or deactivate and then reconfigure the policy?
A: Yes, at least – what I noticed so far:
To update number of members to notify – if you just reconfig the policy – it pics up update but acts like there was no updates. So to actually update number of members to notify you need to deactivate the policy and activate it again with new parameters.

Q: What if we have more than 10,000 notifications to send? Will the policy drop some of them or all notifications will be send but next day?
A: tbc – not tested yet

Q: Let say we have an ownerless group with 20 members. Let say we specified security group to limit user who will get invitations. And this security group includes only 6 users from the orphan group out of total 20. Microsoft says the policy will select the most active users. So the question is: will the policy select the most active users from the 20 orphaned group users and if the user in the security group – he/she will get an email?
A: No. The policy will select the most active users only from these 6 users that included in the security group, ignoring 14 users, even if they were more active then these 6 selected.

Q: What happens after the policy expires? E.g. after all notifications are sent…
A: Policy does not expires. If the policy is activated – it works. If all notifications are sent for the group – so yes, policy is done for this group. But if a new group became ownerless – policy will be triggered for this group again.

Q: If all the notifications are sent for the group – what are admin options to activate ownerless groups policy against this group?
A: There are no “legal” options, but there is a workaround. You can add an owner to the group and then delete this account – so this way you make the group ownerless again – and the process would start from scratch, as for the policy this group will be a new ownerless group.

Some more findings:

User can forward invitation message, but recipient who is not a selected group member – will not see actionable “Yes” “No” buttons. Selected Group

If a public group does not have an owner – all requests to joint the team will be declined with “The team does not have an owner” message:
(that means no new members, i.e. no new contributors, but read-only visitors access is sill available for everyone, as group is public):

Users can go to My Groups to see groups (Teams, Yammer communities and SharePoint Sites) they are members or owners of.

Proposal to be a group owner lasts forever. So if a user after some time finds an email that asks him “Would you like to be a group owner?” and clicks Yes – he/she will be a group owner, even if the policy is already updated or removed.

As per Microsoft, only first two members can accept the ownership of an ownerless group. No additional members are allowed to accept ownership. If either one or two members accept ownership, other members won’t receive further notifications.

Re-create (or re-activate) the ownerless group policy

You can de-activate the policy and then activate it again. Or you can reconfigure the policy. If you activated the policy again (or re-configured it), but emails are not sent – this might be an expected behavior. Let say you initially specified 3 weeks and 3 notification were sent to the most active ownerless groups members. That means no more emails will be generated for these groups.

De-activate the ownerless group policy

Just uncheck “When there’s no owner…”, and save it to stop the policy:

downside – you’d need to configure the policy from scratch – all previous settings are gone now


Track the ownerless group policy in action via Audit Log

How do I, as an Microsoft 365 administrator, know if the policy works or not, are the emails sent or not and how many (if any) users are accepted “Would you like to be a group owner?” invitation?

Microsoft 365 Audit Search under Microsoft Purview (Compliance center) should help.

Operations:

  • OwnerlessGroupNotified – “Notified ownerless group”
  • OwnerlessGroupNotificationResponse – “Responded to ownerless group notification”
  • OwnerlessGroupNeedAttention – “Unattended ownerless group”

OwnerlessGroupNotified – “Notified ownerless group”

(No-brainer) – means that the policy sent e-mail notification to some of the group members. Under “Members” property you can see list of notified users, and under ExtendedProperties – “FirstNotificationDate” and “NotificationChannel” (usually “Outlook”)

OwnerlessGroupNotificationResponse – “Responded to ownerless group notification”

Could have “ResponseType” as “AcceptOwnership” or “DeclineOwnership” under ExtendedProperties, as well as “OwnerCount”.

OwnerlessGroupNeedAttention – “Unattended ownerless group”

indicates all notifications are sent and the group is still ownerless. In the event details you’ll find under ExtendedProperties:

{
    "Name": "FirstNotificationDate",
    "Value": "05/04/2023 13:46:07"
},
{
    "Name": "LastNotificationDate",
    "Value": "05/11/2023 14:28:21"
},
...

UserId: OwnerlessGroupComplianceAssistant

Record Type (AuditLogRecordType): 126

It seems like event is not added to the Audit log when a policy is created or updated.

Who can create Microsoft 365 Groups

It is possible to limit users – who can create Microsoft 365 Groups (please refer to Microsoft: Manage who can create Microsoft 365 Groups – there is a guide and PowerShell code sample). This might help to keep the environment under control – let say, “only managers can create groups”, or “contractor should not be able to create teams”.

Azure AD Directory Setting “GroupCreationAllowedGroupId” works only for creation. Later, when the group is create – it is possible to add to group as a group owner those who is not able to create group. But, if you want your tenant configuration consistent in terms “if a user cannot create a group – user cannot be a group owner” – consider using the same security group in policy’s notofication options “Specify who can recieve ownership notifications”

Issues

“Ownerless group policy configuration failed” error message.

And “Failure in configuring ownerless groups policy” and “Please try again.”
– seems like a permissions issue.
SharePoint admin, Teams admin or Group admin roles: cannot configure Ownerless Groups Policy.
Global admin: yes, can configure Ownerless Microsoft 365 Groups Policy.
What is the minimum role required?
According to a recent update of the Microsoft’s article – “A Global administrator can create a policy…”. In my experience – groups admin can also configure the policy.

The remote endpoint returned an error (HTTP ‘500’). Please try again later

When a user clicks on a button “Yes” or “No” in a notification email, a message “The remote endpoint returned an error (HTTP ‘500’). Please try again later.” pops up:

The remote endpoint returned an error (HTTP '500'). Please try again later.

References

Testing Sites.Selected SharePoint and MS Graph API

Sites.Selected MS Graph API permissions were introduced by Microsoft in March 2021. One year later, in 2022 they added SharePoint Sites.Selected API permissions.

Azure registered app with SharePoint and MS Graph API Sites.Selected permissions

Why is this so important? Because MS Graph API for SharePoint is still limited and cannot cover all possible needs. I’d estimate: 90% of applications use SharePoint CSOM, so developers have to use AppInv.aspx to provide permissions for their applications to SharePoint API.

But from this moment – having SharePoint API permissions in MS Graph – in theory – we can fully rely on permissions provided in Azure and – in theory – this should allow us disable SharePoint-Apps only principal:

Set-SPOTenant -DisableCustomAppAuthentication $true

My math professor taught me: “before trying to find a solution – ensure the solution exists.” So let us test:

Are we really able to work with a specific SharePoint site using MS Graph and SharePoint API Sites.Selected permissions provided via Microsoft Azure?

What will happen with our new/legacy applications if we disable SharePoint app-only SPNs (DisableCustomAppAuthentication)?

I’m getting controversial test results… maybe PnP.PowerShell 1.10 is not fully support SharePoint Sites.Selected API.

Tech Wizard (Sukhija Vikas) on March 20, 2022 in the article “SharePoint and Graph API APP only permissions for Selected Sites” suggests using pre-release (AllowPrerelease).

So please ignore the following for a while.

Meantime I’ll test providing SharePoint Sites.Selected API permissions via Graph API call.

(wip) Test set #1: Certificate vs Secret

DisableCustomAppAuthentication: $false (SP-app-only spns are enabled).
All applications have “write” access provided to a specific site only.
Connecting with Connect-PnPOnline and then test access with Get-PnPSite

App / Get-PnPSiteSecretCertificate
ACS based (Azure+AppInv)OKThe remote server returned an error: (401) Unauthorized.
MS Graph API Sites.SelectedThe remote server returned an error: (403) Forbidden.The remote server returned an error: (401) Unauthorized.
SharePoint API Sites.SelectedOKOK
MS Graph API + SharePoint API Sites.SelectedAccess is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))OK
App with no permissionsThe remote server returned an error: (403) ForbiddenThe remote server returned an error: (401) Unauthorized

(wip) Test set #2: Sites.Selected SharePoint vs MS Graph (secret)

  • DisableCustomAppAuthentication = $false
    (SP-app-only spns are enabled).
  • All applications have “write” access provided to a specific site only.
  • Using Client Secret (not a certificate)
  • Using PnP.PowerShell
Action/ViaSharePoint + MS Graph
Sites.Selected
“secret”
SharePoint
Sites.Selected
“secret”
MS Graph
Sites.Selected
“secret”
Connect-PnPOnlineWARNING: Connecting with Client Secret uses legacy authentication and provides limited functionality. We can for instance not execute requests towards the Microsoft Graph, which limits cmdlets related to Microsoft Teams, Microsoft Planner, Microsoft Flow and Microsoft 365 Groups.WARNING: Connecting with Client Secret uses legacy authentication and provides limited functionality. We can for instance not execute requests towards the Microsoft Graph, which limits cmdlets related to Microsoft Teams, Microsoft Planner, Microsoft Flow and Microsoft 365 Groups.WARNING: Connecting with Client Secret uses legacy authentication and provides limited functionality. We can for instance not execute requests towards the Microsoft Graph, which limits cmdlets related to Microsoft Teams, Microsoft Planner, Microsoft Flow and Microsoft 365 Groups.
Get-PnPSiteOKOKThe remote server returned an error: (403) Forbidden.
Get-PnPListOKOK
Get-PnPListItemOKOK
Set-PnPSiteAttempted to perform an unauthorized operation.
Set-PnPListAttempted to perform an unauthorized operation.
Set-PnPListItemOKOK
New-PnPListAccess is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))
Add-PnPListItemOK

(wip) Test set #3: Read vs Write vs FullControl

DisableCustomAppAuthentication = $false
(SP-app-only spns are enabled).
All applications have Sites.Selected SharePoint and MS Graph API permissions.
Using Client Secret (not a certificate)
Using PnP.PowerShell

ReadWriteFullControl
Connect-PnPOnlineWARNING: Connecting with Client Secret uses legacy authentication and provides limited functionality. We can for instance not execute requests towards the Microsoft Graph, which limits cmdlets related to Microsoft Teams, Microsoft Planner, Microsoft Flow and Microsoft 365 Groups.WARNING: Connecting with Client Secret uses legacy authentication and provides limited functionality. We can for instance not execute requests towards the Microsoft Graph, which limits cmdlets related to Microsoft Teams, Microsoft Planner, Microsoft Flow and Microsoft 365 Groups.WARNING: Connecting with Client Secret uses legacy authentication and provides limited functionality. We can for instance not execute requests towards the Microsoft Graph, which limits cmdlets related to Microsoft Teams, Microsoft Planner, Microsoft Flow and Microsoft 365 Groups.
Get-PnPSiteAccess is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))
Get-PnPListAccess is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))
Get-PnPListItem
Set-PnPSite
Set-PnPList
Set-PnPListItem
New-PnPList
Add-PnPListItem

(wip) Test set #5: Certificate vs Secret

C#, SharePoint CSOM, PnP.Framework

Findings

PnP.PowerShell Get-, Grant-, Set- and Revoke-PnPAzureADAppSitePermission cmdlets require Azure App with MS Graph Sites.FullControl.All app permissions (otherwise it says “Access denied”) and authentication via certificate (otherwise it says “This cmdlet does not work with a ACS based connection towards SharePoint.”)

The same actions – managing permissions for the client app to the specific site collections – could be done via Microsoft Graph Sites Permissions API using just secret-based authentication.

If an azure app does not have Sites.Selected API permissions configured – “Grant-PnPAzureADAppSitePermission” works as expected – no error messages – the output is normal – as if Sites.Selected API permissions were configured in the app. The same for Get-, -Set and Revoke-. Permissions provided for the app to the site are not effective though: Connect-PnPOnline works well, but all other commands – starting from Get-PnPSite – returns “The remote server returned an error: (403) Forbidden.”

If an app have no permissions to SharePoint – “Connect-PnPOnline” works ok, but “Get-PnPSite” return an error: “The remote server returned an error: (403) Forbidden.”

Set-PnPAzureADAppSitePermission gives an error message “code”:”generalException”,”message”:”General exception while processing”
if the site is not specified.

AppInv is not working?

Error: Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))

References

Testing environment

  • Microsoft 365 E5 Dev environment
  • PowerShell 7.2.2
  • PnP.PowerShell 1.10
  • “write” permissions to the specific sites for client apps were assigned via PnP.PowerShell

Connecting to SharePoint Online programmatically: Secret vs Certificate

Update: Sites.Selected API MS Graph permissions was introduced by Microsoft in 2021. It was a good move towards site-level development, but still developers were limited with only what MS Graph API provides for SharePoint dev.
So devs had to use AppInv.aspx at site level to provide ACS permissions to their apps to be able to use SharePoint CSOM and REST APIs.
Recently Microsoft introduced Sites.Selected SharePoint API permissions for registered Azure Apps! So now devs should be fully happy without ACS-based permissions.

Scenario

You have an application that needs access to Microsoft 365 SharePoint Online site/list/documents. Application is running without interaction with users – e.g. unattended, as daemon job.

There are two options you can authenticate to Microsoft 365 – with the secret or with the certificate. Authenticating with certificate is considered more secure.

Questions

  • What happens if SharePoint-Apps only principal is disabled
    (i.e. ‘set-spotenant -DisableCustomAppAuthentication $true’ )?
  • Why I’m getting 401 error when authenticating to SPO?
  • Why I’m getting 403 error when authenticating to SPO with secret?
  • What permissions to I need to work with SPO?

Findings

Note: we will use PowerShell 7.2 and PnP.PowerShell 1.9 to illustrate it.

Disabled SharePoint-Apps only principal

If SharePoint-Apps only principal is disabled in your tenant
(i.e. ‘Get-PnPTenant | select DisableCustomAppAuthentication’ returns $true ), then the only way you work with SPO from code is:

  • an App registered in Azure
  • API permissions provided via Azure (MS Graph, SharePoint)
  • Certificate is used

In all other cases (even your Connect-PnPOnline command complete successfully) – you will be getting error 401 (unauthorized) when trying Get-PnPTenant or Get-PnPTenantSite or Get-PnPSite

Enabled SharePoint-Apps only principal

If SharePoint-Apps only principals are enabled in your tenant
(i.e. ‘Get-PnPTenant | select DisableCustomAppAuthentication’ returns $false ), then you have three options to work with SPO from code:

  • Azure App with a secret (Client Id + Client Secret) and permissions to SharePoint provided via SharePoint (AppInv.aspx) to access SharePoint REST API
  • Azure App with a certificate (Client Id + Certificate) and permissions provided via Azure to access SharePoint REST API
  • Azure App with a certificate or secret (Client Id + Secret or Certificate) and permissions provided via Azure to access SharePoint via Microsoft Graph API

Search Unified Audit Log Daemon Job

How to run “Search-UnifiedAuditLog” in unattended way, i.e. non-interactive.
What are the minimal permissions required?

The PowerShell code:

$clientId = ""
$cPwd = ConvertTo-SecureString -String "" -AsPlainText -Force

$cPath = ""C:\Users\UserName\Certificates\Cert.pfx""
$organization = "contoso.onmicrosoft.com"

Connect-ExchangeOnline -CertificateFilePath $cPath -CertificatePassword $cPwd -AppID $clientId -Organization $organization

[DateTime]$start = [DateTime]::UtcNow.AddMinutes(-45)
[DateTime]$end = [DateTime]::UtcNow
$resultSize = 1000

$results = $null
$results = Search-UnifiedAuditLog -StartDate $start -EndDate $end -ResultSize $results.Count
$results | Select-Object RecordType, CreationDate, UserIds, Operations -First 3

Disconnect-ExchangeOnline -Confirm:$false

Troubleshooting

The error “The term ‘Search-UnifiedAuditLog’ is not recognized”:

Search-UnifiedAuditLog: C:\scripts\PowerShell.auth\Search-AuditLog-w-App.ps1:16:12
Line |
16 | $results = Search-UnifiedAuditLog -StartDate $start -EndDate $end -Re …
| ~~~~~~
| The term 'Search-UnifiedAuditLog' is not recognized as a name of a cmdlet, function, script file, or executable
| program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

means a proper administrative role (e.g. “Exchange administrator”) is not assigned.

References

How to Find Content Shared with Everyone in SharePoint and Teams

There is a known problem in SharePoint (and Teams*) – complicated permissions system. Site owners/administrators provide access, site contributors upload documents and at any moment nobody knows – who has access to which files. As a result – sometimes sensitive documents become overshared (over-exposed). The biggest concern is when sites content is shared with “Everyone”. How do we know which content is shared with “Everyone”? Is there a report?

Obviously, only data owner knows who should have access to site documents, so we (SharePoint admins) do not fix permissions automatically (until there is a policy), but at least we can help site owners with reports and maybe initiate permissions review for “nasty” sites?

Below I’m sharing 4 possible solutions:

  • Solution #0 – Out-of-the-box EEEU report, but it comes with Premium license only
  • Solution #1 – also OotB report that comes with some 3-rd party tools
  • Solution #2 – PowerShell “Brute force” – free but require advanced skills and efforts
  • Solution #3 – Search-based – also free, and require less skills and efforts

(*) Microsoft with the introduction of Teams had to simplify permissions in SharePoint – since there should only be 3 types of access levels – owner, member and visitor. It was… in some ways, but in other ways it made things worse.

Solution #0 (Ootb EEEU report)

Microsoft implemented this report (Content shared with ‘Everyone except external users’ (EEEU) reports) in around mid-2024, but it requires additional license – Microsoft SharePoint Premium – SharePoint Advanced Management. So if you do not have Premium license – check below.

Solution #1 (3-rd party tools)

You are lucky if you can use 3-rd party tools (e.g. ShareGate, SysKit Point, AvePoint, Metalogix etc.), with the ability to get full permissions report. Though – if your m365 environment is not small – there might be a problem to get full permissions report for all tenant sites. Some tools allow you to get tenant-wide permissions report for specific Ids – this option should work better for large environments.

Still there might be another problem. Consider the following. When I say “shared with Everyone” – I actually mean at least 3 possible “everyone” system logins:

  • Everyone
  • Everyone except external users
  • All users

– those are system id’s, but what if there are other ids – e.g. migrated from on-prem or cloud-born custom security groups in tenant that also includes everyone or many users (e.g. dynamic security group that includes all org accounts)?

What if your Identity management operates security groups “All employee” or “Contractors” or “All licensed users”? Do you think these groups can be identified as “Everyone” groups? Do you think it’d be a good idea to check if content is shared with other large groups (not only system Everyone…)? Would you like to run permissions report separately for all groups that include “all” or “almost all” users?

Also, knowing that full reports heavily load the system, 3-rd party tools might by default limit “how deep report is” to the root site and lists/libraries, not including e.g. folders and items. So you might need go to settings and turn on “full deep” option Keep it in mind.

Obviously this option #1 is not free, as it requires licenses to be obtained. And still it worth to consider option 3.

Solution #2 (PowerShell “Brute force”)

You can get full permissions report per site or for entire tenant with PowerShell, which if free… The only you need is to write a script yourself or find existing one. Sounds easy?

Well, first problem is it takes a decent amount of time and competences to write such script. If if you find one – it would require some skills to adopt and run it. Frankly say, I have not seen so far scripts that were out-of-the-box ready to do that job. And it is not a good idea to run scripts you got from internet against your production environment until you understand it tested it and fully confident with.

Another possible problem – size of environment. The script I designed and use to get comprehensive permissions report might run hours against a good site – if I need full details on site/subsites, lists/libraries, folders and list items levels. So if you have less than 1000 sites – probably this approach can fly. But if your environment is 10K+ sites – it will take forever. So the approach might not work for large enterprise environments.

One might say – we can limit report with only root web permissions to get it faster. But this would be not accurate. And what is not accurate in the IT security – leads to even bigger risks. So, we need check permissions up to every item level deep, as even one file with sensitive info shared inappropriately can cause security issue. (Btw, 3-rd party tools usually by default limit reports to libraries level, so check reporting options…)

The other issue with this approach… Let say you got full permissions report… It would look like “resource -> group -> permissions”… How do you know for each group – what is the group in terms of membership?

Ok, if this solution is not easy to get working – what are other options?

This solution is based on simple but clever idea: why do we need to iterate through all the tenant documents/items if all the content is already crawled by search? Search is also respect permissions. Can we just use search to get files shared with Everyone? Let us see.

What if we use some dummy user account with no specific permissions provided and no group memberships and try to search content on behalf of that account. The idea is if this user can see some data – then these data is open to everyone.

Check this and this articles. Can we get results programmatically (e.g. with PowerShell)? Can we use Microsoft Graph search API? Sure. Check also this article “How to search against SharePoint Online Content with Microsoft Graph search API with PowerShell“.

Solution #3 Option #1 – search through tenant

With this option we would use search query “*” and all 5 possible SharePoint entities – driveItem’,’listItem’,’list’,’drive’,’site’ to find everything that is shared with everyone. We’d pull results with paging (we’d use “from” option in a loop to pull all results). After we get all results – we’d select only unique site collections. But! We might have some problems here.

Problem #1. Again, for small environments or if there are not much “Open” sites – it would work. But for large enterprise environments the problem is the same as in “brute force”. Search would returns too many results – and it might take weeks (exact time is unpredictable) to get all of them. (Surely there are sites “legally” shared with everyone, public Office 365 group based sites, communication sites… So your search will be flooded with content from sites you already know are shared with all).

Problem #2. We are getting results with paging. But recently Microsoft started limiting number of returning results. E.g. your search request result might say like “there are 3659735 total hits” but after result number 1000 it just stops returning anything, even with paging.

Solution#3 Option #2 – loop sites

The idea is: why do we need to get all search results if even one result from a site would be enough to put the site to the list of “open” sites. In other words, we do not need all results from the site, we only need to know if there are any results from the site, at least one – so we know if the site is open for everyone or not.

So, consider the following approach:

  1. You get list of all sites in tenant.
  2. You run search request against each site in the loop
    (e.g. consider KQL option “Site: https://yourTenant.SharePoint.com/sites/YourSite”.
    If at least something found in the site – add the site to the “Open Sites” list.
    With this approach you will get list of sites shared with “Everyone…” in a predictable time.

Solution#3 Option #3 – exclude known “open” sites

There are sites “legally” shared with everyone – e.g. corporate portal, department communication sites, public teams, public Viva Engage communities etc. If it is know that these sites are public – you can exclude them from all sites list – so in the “Solution#3 Option #2 – loop sites” – you’d loop only through sites that are not supposed to be public. I know – percentage of “legally public” sites in tenant to all sites is a relatively small number, so should not significantly decrease elapsed time… but still.

Pros and cons of the Solution # 3

Pro 1: the only fast enough (at least predictable time to complete) and accurate enough to rely on solution.

Pro 2: There might be custom security groups intended to hold all or part of the enterprise (e.g. “All employee” or “all contractors”). If the enterprise comprises from several businesses or regions – it might be “All Business 1” or “All EMEA”… you got the idea. So you can tweak this search-based solution by adding your dummy account you are running search on behalf of to some of theses groups to find out if there are resources shared maybe not with everyone but with all “North America based” users or with “all employees”, which might make sense also.

Con 1 : crawling and indexing takes time, so search-based reports can miss recent changes in data and permissions

Con 2: this approach cannot be automated (since we need an interactive authentication). I.e. we need to run it manually every time.

Con 3: After we get all sites shared with everyone – we do not know – at what level permissions are broken and provided to everyone. It might be entire site or one file. It does not really help if you try to get all search results from the site. If you want to know what exactly is shared with everyone – on the site – run permissions report against this site (shortlist of sites).

Notes

Note 1: consider there are resources like “Styles Library” shared with everyone by default, especially on migrated sites

Note 2: this is a separate topic, but consider implementing/using sensitivity labels. At least you can start with high-sensitive sites. With sensitivity labels – site owners/member would know – what kind of site they are working on.

What’s next

Ok, we know list of SharePoint resources shared with everyone, but what would be the next step? Should we communicate to site owners – if so how to let site owners know that there are resources shared with Everyone… on their sites.
To be continued…

References

Video tutorial:

Connect-PnPOnline with a certificate stored in Azure Key Vault

Scenario

You run some PnP PowerShell code unattended e.g. daemon/service app, background job – under application permissions – with no user interaction.
Your app needs to connect to SharePoint and/or Microsoft Graph API. Your organization require authentication with a certificate (no secrets). You want certificate stored securely in Azure Key Vault.

Solution (Step-by-step process)

  1. Obtain a certificate (create a self-signed or request trusted)
  2. In Azure where you have Microsoft 365 SharePoint tenant
    1. Create a new Registered App in Azure; save App (client) id, Directory (Tenant) Id
    2. Configure App: add MS Graph and SharePoint API application (not delegated) permissions
    3. Upload the certificate to the app under “Certificates & secrets”
  3. In Azure where you have paid subscription (could be same or different)
    1. Create an Azure Key Vault
    2. Upload certificate to the Key Vault manually (with GUI)
  4. While you develop/debug your custom daemon application at your local machine
    1. Provide permissions to the Key Vault via Access Control and Access Policies to your personal account
    2. Connect to Azure (the one where your Key Vault is) running Connect-AzAccount
      – so your app can get a Certificate to authenticate to SharePoint Online
  5. For your application deployed to Azure (e.g. Azure Function App )
    1. Turn On managed identity (Your Function App -> Identity -> Status:On) and Save; notice an Object (Principal) Id just created
    2. Provide for your managed identity principal Id permissions to the Key Vault via Key Vault Access Policies, so when your daemon app is running in the cloud – it could go to the key Vault and retrieve Certificate

Here is the sample PowerShell code to get certificate from Azure Key Vault and Connect to SharePoint with PnP (Connect-PnPOnline):

# ensure you use PowerShell 7
$PSVersionTable

# connect to your Azure subscription
Connect-AzAccount -Subscription "<subscription id>" -Tenant "<tenant id>"
Get-AzSubscription | fl
Get-AzContext

# Specify Key Vault Name and Certificate Name
$VaultName = "<azure key vault name>"
$certName = "certificate name as it stored in key vault"

# Get certificate stored in KeyVault (Yes, get it as SECRET)
$secret = Get-AzKeyVaultSecret -VaultName $vaultName -Name $certName
$secretValueText = ($secret.SecretValue | ConvertFrom-SecureString -AsPlainText )

# connect to PnP
$tenant = "contoso.onmicrosoft.com" # or tenant Id
$siteUrl = "https://contoso.sharepoint.com"
$clientID = "<App (client) Id>" # Azure Registered App with the same certificate and API permissions configured
Connect-PnPOnline -Url $siteUrl -ClientId $clientID -Tenant $tenant -CertificateBase64Encoded $secretValueText

Get-PnPSite

The same PowerShell code in GitHub: Connect-PnPOnline-with-certificate.ps1

References: