Tag Archives: PowerShell

Orphan Users in Small Tenants

“Orphan User” in SharePoint refers to a user account that no longer exists in Entra ID but is still present in the User Information List (UIL) on SharePoint sites.

Offboarded user accounts are usually deleted from the Entra ID directory sooner or later. However, SharePoint caches user information in its own databases. This information is stored in the so-called UIL (User Information List). Each site has its own User Information List.

Generally, orphan users are pretty harmless, as they are not users, but just ghost entities – records that reflects former accounts, i.e. these users cannot login, even when a new user is created with the same email – access is not possible. Moreover, legacy Microsoft documentation says such records are needed to preserve metadata – like who is the author of the document or documents editing history, etc.

So yes, orphan users are harmless until you hit a User ID Mismatch issue.

In a nutshell, a User ID Mismatch happens when an email address (UPN) is reused and a new user tries to access a SharePoint site where the old user previously had access.

SharePoint does not allow the new user to automatically inherit the same access that the old user had, and this is expected behavior. The issue is that SharePoint may fail to correctly grant access to the new user even after permissions have been assigned.

In such cases, you must first remove the old user ID from the site’s User Information List. This approach is recommended by Microsoft and considered as safe. And guess what? It turns out the old user is no longer needed in the UIL to preserve metadata 🙂

When a user hits a User ID Mismatch issue, it is often difficult to recognize that the problem is not simply missing permissions or a site owner not approving access, but an actual product limitation that prevents access to SharePoint. As a result, users become frustrated, support teams spend time troubleshooting the issue, and it may take a while before the root cause is identified and resolved.

So why not remediate the issue proactively? This way, there are no high-priority support tickets, no angry users, and no urgent troubleshooting when the problem eventually occurs.

The engineering problem behind all approaches to proactively resolving the User ID Mismatch issue is that there is no out-of-the-box (native) SharePoint report that provides a list of sites where a specific orphan user is present. You cannot simply press a button and get a complete list of orphan user/site pairs.

In general, you need to scan the entire tenant, site by site, including personal OneDrive sites, and retrieve all historical site users. Then you need to check whether each user stored in SharePoint still exists in Entra ID.

Multiply the number of sites by the number of users and see how long it might take. An average user typically has access to only ~1% of all sites in a tenant, so you can imagine how inefficient this approach can be.

However, for small tenants, we can still use this so-called brute-force approach. With this approach, we use PowerShell to do the job: identify all orphan users and, optionally, remove them.

Whether this is a viable solution depends on your specific situation: the size of your organization, employee turnover, the amount of data (number of sites), and the age of the tenant.

I may provide some kind of calculator in the future to estimate how long the process might take based on tenant characteristics. But the general idea is simple: in some cases, a brute-force approach is perfectly acceptable because it:

a) is very simple and does not require advanced PowerShell skills, and
b) can be completed in a reasonable amount of time (minutes or hours)

The scripts are here: https://github.com/VladilenK/m365-User-Id-Mismatch

References:

Content Security Policy (CSP) in SharePoint Online

Microsoft enforces Content Security Policy (CSP) in SharePoint Online.
What does that mean to SharePoint Admins? Should we be concerned? What do we need to undertake? Before? After?

Generally, in web development – the policy (Content Security Policy – CSP) is needed to minimize the risk of security threats by controlling which resources, in particular JavaScript resources, a page/site is allowed to load.

In SharePoint web development is done via SPFx, so the CSP policy affect SPFx solutions. In a nutshell, from now on, for the SPFx solutions to work properly, admins should whitelist external domains. Admins can maintain allowed domains list via GUI in SharePoint admin center or via PowerShell:

# List current sources
Get-SPOContentSecurityPolicy
# Remove a source
Remove-SPOContentSecurityPolicy -Source "https://cdn.host.com/source/"
# Add a source
Add-SPOContentSecurityPolicy -Source "https://cdn.host.com/source/"

The CSP policy was enforced on March 1, 2026, but as per Microsoft, if clients need more time to review and update existing SPFx solutions, we can delay the enforcement by 90 days, until June 1, 2026, via

Set-SPOTenant -DelayContentSecurityPolicyEnforcement $true
# IMPORTANT: List the applied setting again as mandatory step to correctly persist the setting (will be fixed)
(Get-SPOTenant).DelayContentSecurityPolicyEnforcement

Admins can view the Content Security Policy Violations via Purview.

So there are cmdlets:

  • Add-SPOContentSecurityPolicy
  • Remove-SPOContentSecurityPolicy
  • Set-SPOTenant

And for the Set-SPOTenant cmdlet there are options related to CSP:

  • DelayContentSecurityPolicyEnforcement
  • EnforceContentSecurityPolicyConfiguration
  • ResyncContentSecurityPolicyConfigurationEntries
  • ContentSecurityPolicyEnforcement

So the question I ask for myself is what exactly each command does and how are the options correlated to each other and affect SPFx solutions.

Content Security Policy (CSP) in SharePoint: My Findings

ContentSecurityPolicyEnforcement

It seems like using Set-SPOTenant with a ContentSecurityPolicyEnforcement parameter change nothing. I.e. “Set-SPOTenant -ContentSecurityPolicyEnforcement:$false” does not disable policy and vise versa
“Set-SPOTenant -ContentSecurityPolicyEnforcement:$true” does not enforce the CSP policy.

My guess it’s because this article is written in April 2026. Microsoft started Content Security Policy (CSP) enforcement since March 1, 2026. So probably before March 1, 2026 we could use ContentSecurityPolicyEnforcement to enable the policy, but since March 2026 the policy is enforced anyway and parameter ContentSecurityPolicyEnforcement is not needed (and probably will go away in the next versions of the Microsoft.Online.SharePoint.PowerShell Module).

DelayContentSecurityPolicyEnforcement

DelayContentSecurityPolicyEnforcement parameter effectively switches the policy on and off. It takes a few minutes for Microsoft to propagate the change across so you can start seeing the policy enforced or not. But this is true only during 90 days – in Mar, Apr and May 2026. Since June 1, 2026 the Content Security Policy (CSP) will be enforced in SharePoint Online.

Testing SPFx Solutions Before Enforcement

How do I know if my site compliant with CSP or not before CSP enforcement?

There are some good findings for developers in the article Preparing for SharePoint Online CSP Enforcement by Nello D’Andrea, MVP. But I will share some more.

Use browser’s dev tools (F12). Select console and filter/search output with “policy”. You can find something like:

“Loading the image ‘<URL>’ violates the following Content Security Policy directive” or
“Loading the script … violates the following Content Security Policy directive” or
“Executing inline script violates the following Content Security Policy directive” or

If the message is just informational (in white) and there is also “The policy is report-only, so the violation has been logged but no further action has been taken.” that means the policy is not enforced yet… and the site functionality should not be affected.

Otherwise – when you see the same “… violates the following Content Security Policy directive” in red – this would be an error message and the policy prevents something on your site due to the CSP policy so functionality will be broken.

References

How to run Get-PnPUser correctly for large sites

Get-PnPUser PowerShell cmdlet is to get the list of site user. For small sites it work OK, but for sites with a large number of site users the cmdlet fails throwing a warning/error “Get-PnPUser: the request was canceled due to the configured HttpClient.Timeout of 100 seconds elapsing.”. Typically suggested fixes like to increase timeout do not work. Here is how to solve the issue.

Fixes do not work

  • Increase the Global Connection Timeout
  • Handle Transient API Throttling

Solution

If you need a list of site users – just get the hidden “User Information List” and get all it’s items:

# Target the hidden list object specifically by its internal title
Get-PnPList -Identity "User Information List"
# Extract the list rows and select the relevant columns
Get-PnPListItem -List "User Information List" -PageSize 1000

Surely you’d get a different object type, but still you’ll be able to find properties you need via something like $user[“Name”] for Get-PnPListItem object versus $user.LoginName for Get-PnPUser object. But if you need a Get-PnPUserObject – you always can do something like

Get-PnPUser -Identity $listItem["Name"] 

That is it. You’ll figure out the rest.

P.S. If you need to get all SharePoint site users via Microsoft Graph API – use the same trick – get items from the UIL (as there is no dedicated API entry point to get site users). Here is the “Get SharePoint Site Users via Graph API” KBA.

Microsoft 365 Ownership Report by Manager Hierarchy

In large Microsoft 365 environments, leadership and governance teams often need visibility into collaboration resources owned by a specific organization unit – typically defined by a manager and their reporting chain. Native tools make it easy to inspect a single Team or site, but not to answer a simple question at scale: “What Teams and SharePoint sites are owned by anyone under this leader?” This PowerShell script builds that inventory by expanding a manager’s hierarchy and correlating owners to Microsoft 365 Groups/Teams and their associated SharePoint sites.

Here are the scripts (beta):

https://github.com/VladilenK/m365-PowerShell/tree/main/Solutions/HierarchyReports

Update: Added Get-Hierarchy.ps1 script
The script retrieves the hierarchy of all subordinates for a given manager in Entra Id

Update: Added Get-Sites.ps1 script
The script pulls all group-based sites owned by anyone from the hierarchy

Finding the SharePoint Site Behind Any Shared Link

WIP…

:li:/t is a modern Microsoft 365 “link indirection” prefix used for List Item–based sharing links, where the link does not directly expose a file or site path, but instead points to an internal resource ID that SharePoint resolves at runtime.

with :li:/t links, the “managed path” (/teams/ vs /sites/) can be completely absent from the URL, even when the underlying site is clearly under /teams/....

Why Shared Links Don’t Show /sites or /teams — and How to Get the Real Site URL?
Covers why modern Microsoft 365 sharing links omit managed paths and how to use Microsoft Graph to identify the actual SharePoint site associated with a shared item.

$sharingUrl = "https://contoso.sharepoint.com/:u:/s/tst/subsite01/IQDT1WnS2vtERpmwXi1aOxruAViMmZ7-C5miAFamf6sQMnE?e=gtEfMD";
# string base64Value = System.Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(sharingUrl));
$base64Value = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($sharingUrl))
$base64Value
# string encodedUrl = "u!" + base64Value.TrimEnd('=').Replace('/','_').Replace('+','-');
$encodedUrl = "u!" + $base64Value.TrimEnd('=').Replace('/','_').Replace('+','-')
$encodedUrl

$apiUrl = "https://graph.microsoft.com/v1.0/shares/{shareId}/site"
$result = Invoke-RestMethod -Uri $apiUrl.Replace("{shareId}", $encodedUrl) -Headers $headers
$result | fl

Preventing SharePoint User ID Mismatch: a Tenant‑Wide Approach

There is a known problem in Microsoft 365 SharePoint called “User ID Mismatch”. It happens if a user account is deleted from the Entra Id directory, and then a new account is created with the same UPN (e.g. rehired person or a person with a popular name like John Smith). Symptoms are: a user is provided with the access to the resource, but still cannot open it and gets “Access denied” error or “Sorry, something went wrong” and “There is a legacy user account in this site with same email. Please ask site owner or administrator to remove the legacy user account from this site”.

The reason behind it is that SharePoint stores users data in it’s own database (accessible via hidden system list called User Information List – UIL). Properties include display name, title, UPN, and (!) AD SID and Entra Id user object id. So when a re-used UPN tries to access the SharePoint site – Microsoft 365 compares Ids and declines access. So access needs to be re-provided. And this is where the actual issue appears.

If the site was accessed by the “old” user in the past, site UIL contains information about that “old” user. When the site owner shares the site with the “new” user – Microsoft does dot update the UIL with the new user ids. Microsoft 365 behave “normally”, no error messages are displayed. So for the user and for the site owner it looks like access was provided, but in fact it’s not.

Existing fix

Microsoft knows about the issue. But instead of fixing the root cause – Microsoft recommends removing the old user from the UIL. Obviously this fix is a) reactive, b) for one specific user and c) for one specific site. In my previous article – Fixing SharePoint User ID Mismatch Issue – I explained the issue in details, outlined 3 options to fix the issue, including PowerShell script.

Again, the issue is scoped with a specific site (site collection). The mentioned PowerShell-based solution and all Microsoft’s fixes are designed for one specific site, but in real life what is happening is the “old id user” had access to many sites, and “new id user” has access to many sites, so after a several “access denied” issues a user might be confused and ask SharePoint admins to fix the issue “everywhere”. We should not bother users with “please provide us list of site Url you are having issues with”… or saying to user “please create a new ticket when you hit the same issue again” also does not sound nice.

The issue is tricky to recognize. From the user perspective it looks like “normal” permissions issue – so it takes some time for user and site admin to realize that something is wrong and reach support. So can we do it proactively? I mean, can we prevent the issue to appear instead of fixing the issue after a user hit the issue?

Let us try to go deeper into the issue, as we would need to start from basics to find some more consistent solutions. Again, approaches could be

  • Simple Reactive: once we got an “Access Denied” ticket – we check if it is a user id mismatch and fix the issue for a site we got ticket for
  • Reactive – Proactive: once we got an “Access Denied” ticket – we check if it’ i’s a user id mismatch and not only fix the issue for a site we got ticket for, but also try to find all sites with the user id mismatch issue for the same user
  • Proactive – we’d fix the issue before the user hit Access Denied error due to User Id Mismatch issue. Options are
    • once a new user created in Entra Id – check if it’s a reuse upn case and if so – act accordingly – try to find all sites previous user had access to and remove old id from these sites
    • once a user deleted from Entra Id – get all sites user had access to and remove the user from accessed sites

Reactive – Proactive: Fix all sites for one specific user

    We know how to fix the issue for one user at one site (here). But the User ID Mismatch issue does not come for a one site and one user. Usually it impacts many sites.So how do we fix the User Id Mismatch Issue on all sites for a specific user?

    At first, the solution looks simple: we need to check all sites old user had access to and remove UPN from UIL. Well… yes… But how do we get a list of sites the old user present in the UIL?

    Spoiler alert: So far – I’m not seeing a good solution. But let us go through possible options.

    Option 1: Brute force – scan all sites

    • Get list of all tenant sites
    • For every site – try to get a user from the UIL
    • If present – check if this is a User Id Mismatch case
    • If yes – remove the user from UIL

    This option is comprehensive, i.e. it should fix the issue for all sites, so in case of reused UPN we should not have issues for any tenant site for the same user. But this is a really “heavy” solution and can work well for small tenants only. I do not think this option is feasible for medium or large tenants.

    Option 2: Access report.

    There are 3-rd party tools (I’m not aware if there is a built-in m365 tool for that) you can use to get report on all SharePoint resources user have access in tenant. So you’d export the list, select unique sites Urls and scan these sites. Possible issues – usually we no not know how the 3-rd party tool builds this report (if the UPN or object Id is used), would it be “old” user access report or “new” user access report?

    Option 3: membership

    We can use MS Graph “membership” and “people” API to get list of sites around. We can get our user membership – m365 groups and related sites. People API would return users related to our client – a collection of person objects based on their relevance to the user, which is determined by the user’s communication and collaboration patterns, and business relationships. We can get these people OD sites, we can get their groups memberships and sites. We can do the same for people related to people. NB: I know, this option would give us sites related to a new user, not old user.

    Add-on to Option 3: common sites.
    With Option 3 we’d get group-based and OD sites. How about standalone sites? Also there might be public teams…
    Usually in any tenant number of group-based sites (teams sites) are much bigger than non-group-based sites. So we can simply scan all standalone sites. But, again, in large tenants this might not be a feasible option. But we can get list of “open” standalone sites and public teams and Yammer communities (i.e. “common sites” usually accessed by everyone in tenant). Additionally, over the time we can get “most popular impacted sites” form the fixing history to update our list of common sites.

    Option 5: audit log

    Reactively, we can use audit log to get AccessDenied sites for a new user

    • Get audit log and filter it by user and select only “AccessDenied” pages
    • Make a unique list of accessed sites
    • For every site – check if this is a User Id Mismatch case
    • If yes – remove the user id from site’s UIL

    What I do not like here is getting audit log is a slow operation, and still re-active. I use it when I cannot reach impacted user and I need to know list of sites user already having issues with.

    Practice

    My personal experience.
    Option 1. In my simple tests – it takes ~1 hour to scan ~500 sites (keep in mind, we need to scan all sites, including personals/OneDrive).
    Options 2 and 3 helps, as allows to cover majority of impacted sites our re-used UPN user might have issues with. But we cannot guarantee we fixed all sites for the specific user.

    Fix all sites for all users proactively

    The older your tenant is – the more reused UPNs you get. The bigger the tenant – the bigger the problem.

    Obviously, for proactive solution, we need to start from Identity Management. Below are just thoughts so far…

    No re-used UPNs.
    Providing a unique UPN each time we create a user might help.

    Know re-used UPN.
    If we knew every reused UPN at the moment of account creation (an employee onboarding) – we could proactively apply Options 1,2,3 from our reactive approach.

    Total clean-up
    In theory – we could use our reactive Options 1, 2, 3 and 5 at the moment of the employee off-boarding to find all sites user had access to and just remove the terminated employee’s account from all sites UIL.
    That would prevent user mismatch Id in the future. Removing a user from UIL should not impact edit history – i.e. user’s Name will still be populated correctly if you want to check versions history of documents created or updated by this user. Scanning all sites for every off-boarded employee could be an option for small tenants.

    3-rd party tools

    There are 3-rd party tools (e.g. SysKit Point).

    SysKit’s Orphaned users governance policy currently is very simple, but new updates – i.e. Dec 16, 2025 – as advertised would allow to reove orphaned users automatically from sites. Once I got this update installed – I’ll test it and share here, as if all good – this might be the only real solution to a user id mismatch issue.

    Hybrid Approach: PowerShell + SysKit Point

    One of the approaches could be:

    • select a few sites everyone in the org should be accessed at least once (onboarding site, root site, corporate portal etc.).
      Get all users from the UIL and check against Entra Id if the user is present – this would give us a list of Orphan Users (more or less… a good approximation)
      Alternatively – for small tenant – SysKit has an “Orphan Users” report
      This needs to be done, e.g. monthly
    • build an automation – job that runs e.g. daily The job:
      get users delta from Entra Id, select new users, check every new user against orphan users list, create alert – “we got a reused upn, please act proactively to resolve a potential user id mismatch issue”
    • use SysKit to get report on orphan user sites
    • remove the old user from sites

    this approach allows us work proactively and prevent user id mismatch issue, it can be semi-automated as it still require some manual steps – e.g. get sites accessed by an orphan user report from SysKit

    The other approach would be as follow:

    • build a scheduled job that gets delta from an Entra Id, run it e.g. daily
      select deleted users
    • use SysKit API to get report on user’s permissions
    • remove user Id from sites

    this approach would eventually fully resolve the issue and can be fully automated

    Bottom Line

    Currently, there is no out-of-the box or free, effective, tenant-wide, proactive solution that guarantees protection from user id mismatch issue. The bottleneck here is inability to quickly get list of sites a user had access to (we’d need 3-rd party tools for that).

    Custom PowerShell scripts would work only in small environment, as the “total scan” approach would hardly be realistic in large environments.

    There are though 3-rd party tools that build it’s own database with all the permissions of all users, which could be the foundation for the “Prevent user id Mismatch” functionality. Some tools even have the issue addressed (e.g. SysKit Point has an orphan users policy).

    References

    Handling Large Lists in PowerShell

    There is a known problem in SharePoint: 5,000 Items List View Threshold. Actually any SharePoint list can have up to 30 million items and a library can have up to 30 million files and folders. See more SharePoint capabilities and limits. The important moment is you cannot have more than 5k items in a single view (explained here). So obviously you’d need to create a custom view with a filter that would show less than 5,000 items and you are good (here Microsoft explains how it works and provide more options to manage large lists). But there are scenarios where we have to use PowerShell to deal with large lists.

    Scenario 1 (most frequent): List is close to 5k items view threshold, and business wants you to remove items based on some criteria and keep the list (e.g. remove older items, or completed items etc.). Optionally business wants you to archive older items into another list, and keep original list short. It is too

    Scenario 2: users did not create a smaller views in advance and now stuck with the list not functioning without ability to edit/create a custom view. This could happen with a regular list or library, or

    Scenario 3: overflow of the access request list. This is a particular case of the scenario 2. In SharePoint site we have an “access request” functionality. If a user have no permissions to a site, and tries to open this site – he/she will get “Access denied” page with an ability to request access to a resource. These requests are saved in a special hidden list. Sometimes when you have too many requests – this list grows to 5,000 requests and you cannot go to the list to approve/decline new requests.

    All these scenarios require us to use PowerShell. Here is the technique.

    Install PowerShell and configure your PowerShell environment

    In this article I’ll guide you through how to get PowerShell and configure your environment for PowerShell scripting against Microsoft 365. For those who do not need detailed instructions:

    • Install PowerShell 7 (cross-platform one)
    • Install Visual Studio Code
      • find and install PowerShell plugin for VSCode
      • install MgGraph (Microsoft Graph) module
      • install PnP.PowerShell module
    • Get and configure Azure App Registration
      • registration a new Azure App
      • configure API permissions and get admin consent
      • configure authentication

    Windows PowerShell

    Every Windows computer comes with PowerShell pre-installed. So if you are on a Windows machine – try the following: Start -> Windows PowerShell. You’ll see:

    So you can type something like “Get-Date” or “Get-ComputerInfo” or “Get-Disk” or “Get-Random” and see results right away, e.g.:

    But, hold on! This is not the PowerShell we need. This is so-called PowerShell 5 (classic, legacy one, nowadays called “Windows PowerShell”). Though you can use Windows PowerShell OotB, we will use new, modern, cross-platform PowerShell (for Windows, Linux, and macOS).

    PowerShell

    This is how nowadays called a new cross-platform PowerShell. Just “PowerShell” (or PowerShell 7, as 7 is it’s current/latest major version. Here is the Microsoft’s official guide “Install PowerShell on Windows, Linux, and macOS“. There are multiple options to install PowerShell 7 – just choose your one (you are Power User, you should figure it out). E.g. you can use your Windows PowerShell window to install PowerShell 7:

    Once modern PowerShell, your Start->PowerShell experience should look like:

    Notice that you have both – PowerShell 5 (for Windows) and PowerShell 7 (cross-platform).

    Start PowerShell 7 and try something like “Get-Command” or “Get-Help”. Congratulations! Let go to the next step.

    IDE – Integrated Development Environment

    So far what you did – you used PowerShell cmdlets in command-line window. It is ok to use plain PowerShell window to quickly run one or a few PowerShell commands, but usually we work with scripts. A PowerShell script is a program written in PowerShell – composed of a sequence of PowerShell cmdlets, structured with logic to perform specific tasks. So we would need some kind of editor for PowerShell programs. Actually you can use any universal text editor – like Notepad, Notepad++ to create your code, you’d save it as .ps1 file (e.g. myFirstScript.ps1) and than run you script with PowerShell. But there is a better way – using IDE. IDE stands for “Integrated Development Environment” – a developer-oriented text editor equipped with a bunch of goodies that help creating and debugging code.

    Visual Studio Code

    One of the most popular IDE is Visual Studio Code (or vscode) – a free open-source IDE from Microsoft. You can get it from code.visualstudio.com – just download it and install it.

    I’d recommend you to take some brief vscode introduction lessons – like this one from Microsoft. VScode can be used to code in many programming languages – Python, C#, Javascript etc. We will be using it to code in PowerShell. I’d recommend to create a new folder for your PowerShell scripts, and from VSCode open a folder. After you trust yourself – you’ll see something like:

    Using “New file” icon – create a new file, name it e.g. “my-first-script.ps1” (.ps1 extension is required).

    PowerShell plugin

    VSCode will realize that you are coding in PowerShell and suggest you to install a PowerShell plugin (or you can install it manually):

    PowerShell plugin is the last piece we need to start scripting in PowerShell.

    PowerShell Modules

    Technically, what we already installed – enough to start coding for SharePoint (e.g. calling Microsoft Graph API via built-in Invoke-RestMethod), but there are some much more convenient ways – using modules.

    PowerShell is built around modules. Every command you use is provided by a module. PowerShell includes built-in modules out of the box that offer essential cmdlets (such as Get-Host). However, to work with Microsoft 365 services, you’ll need additional modules that contain cmdlets specifically designed for interacting with Microsoft 365. There are modules to work with Teams, Exchange, SharePoint, Entra Id etc. Below are the most popular modules.

    Microsoft Graph

    Microsoft Graph is a unified API endpoint developed by Microsoft that allows developers and IT professionals to access and interact with a wide range of Microsoft 365 services and data. It acts as a gateway to data stored across Microsoft services. Utilizing Microsoft Graph from PowerShell code is better done with Microsoft Graph PowerShell SDK. So check it out and install.

    PnP.PowerShell

    The other very popular and useful module is PnP.PowerShell. PnP is an open source community backed by Microsoft (but PnP products are not officially supported by Microsoft). The PnP team does an excellent job of developing and providing a wide range of tools, SDKs, documentation, learning videos, and other resources to help developers work with Microsoft 365 efficiently. For PowerShell developers they maintain PnP.PowerShell module. So please go ahead and install it as well.

    Authentication and Authorization

    To work with Microsoft 365, the first step is authentication – essentially proving that you are actually who you claim to be (via providing login name and password or fingerprint etc.). Once you authenticated to Microsoft – you can access resources if you have permissions and cannot access others because you do not have permissions provided. This is called authorization.

    This is true for your interactive experience with Microsoft 365 via browser, or Teams app etc. The same is true when you access Microsoft 365 programmatically – via PowerShell code. You need to authenticate and being authorized.

    For smooth authentication and authorization to Microsoft 365 from your PowerShell code you’d need so called App Registration properly configured. App Registration is where API permissions and authentication methods are specified.

    There are two authentication methods most commonly used in programming – interactive and non-interactive. Interactive authentication – is when a program acts on behalf of a user (e.g. you as a current user). The other method is used when your program needs to work unattended (without user presence) – on behalf of an application itself with it’s own permissions. You’d need separate app registration for each of these methods.

    Interactive Applications

    Interactive programs is what we will mostly use in this “PowerShell for Power Users” series of tutorials. Interactive login require delegated API permissions configured in the App Registration and proper authentication configuration Authentication blade. When your interactive program works – it’s effective permissions are the intersection of your personal permissions and permissions specified in the app registration.

    Here is how to get an app registration, configure delegated API and authentication.

    Non-Interactive Applications

    Non-Interactive programs (also called daemon apps or background services or scheduled jobs etc.) are used mostly used by admins for tenant-level automation. But it is possible to automate something for a specific site, e.g. one of the common scenarios – data analytics can automate pulling some data from SharePoint site into e.g. Azure Data factory via pipeline. Non-Interactive authentication require application API permissions configured in the App registration and a secret generated or certificate uploaded. When a non-interactive program works – it’s effective permissions are the permissions specified in the app registration.

    Here is how to get unattended access for a specific SharePoint site with Sites.Selected.

    PowerShell for Power Users: Introduction

    Are you ready to elevate your Microsoft 365 experience? Whether you’re a SharePoint site owner, Teams channel manager, or a power user working across OneDrive, Exchange, and other services, PowerShell can be the game changer you didn’t know you needed. This blog series, “PowerShell for Power Users, Unlocking the Full Potential of Your Microsoft 365 Workflow” introduces you to Microsoft’s powerful command-line shell and scripting language—and shows you how it can transform your productivity, automate repetitive actions, and give you deeper control across your Microsoft 365 workspace.

    Why PowerShell in Microsoft 365?

    Traditionally seen as a tool for IT professionals, PowerShell is now essential for power users who want to automate tasks, customize environments, and solve problems efficiently across Microsoft 365. PowerShell empowers you to:

    • Automate routine operations in SharePoint, Teams, OneDrive, and Exchange
    • Pull reports on usage, structure, and compliance that are not available out-of-the-box
    • Manage site collections, lists, libraries, Teams channels, and mailbox settings
    • Perform bulk operations that would be tedious through the UI
    • Audit and troubleshoot environments with precision
    • Integrate with Microsoft Search, Copilot, and Power Platform for intelligent workflows

    What Can You Expect from This Series?

    We’ll start with the basics—demystifying the PowerShell interface, understanding cmdlets, and connecting to services like SharePoint Online, Teams, and Exchange. Then we’ll dive into real-world scenarios. Each article will include clear explanations, sample scripts, and best practices—making PowerShell approachable even if you have no prior scripting experience.

    Who Should Follow This Series?

    This blog is for any Microsoft 365 user who wants to:

    • Move beyond manual, repetitive tasks
    • Learn scripting at their own pace, with practical examples
    • Boost productivity and efficiency across Microsoft 365
    • Bridge the gap between business needs and IT solutions

    Getting Started

    If you’re curious but feel intimidated by scripting, don’t worry—PowerShell is more user-friendly than you might think, and this series is designed with beginners in mind. All you need is a willingness to learn and experiment. In the next articles, we’ll cover how to set up your environment, write your first simple script, and start automating tasks you do every day across Microsoft 365. Your journey toward becoming a PowerShell power user starts here!

    • Install PowerShell and configure your PowerShell environment
    • Introduction to Graph API for Teams, SharePoint, Copilot
    • Get all alerts for all users (alerts are scheduled for retirement)
    • Deal with large lists – 5,000 items problem
    • Search programmatically across all your data
    • Get the oldest or newest documents from your Teams channel or SharePoint library
    • Fix the Access Requests List Overflow issue
    • Fix the User Id Mismatch issue
    • Create a new Team, SharePoint site, Viva Engage community
    • Manage team membership (bulk upload/delete team members)
    • Update library/list items metadata (documents’ properties) in bulk
    • Generating reports on usage, structure, and compliance