How to Block User Access to Microsoft 365 PowerShell Modules

Use Enterprise Applications to Block PowerShell Modules

A question arose about the best way to block Microsoft 365 user accounts from being able to run PowerShell. It seemed like a worthy problem to consider. In some cases an obvious answer exists, like stopping Exchange Online users from accessing PowerShell, but that’s a technique that only works for Exchange, and the block needs to be imposed for every new mailbox. We need something more generic that works across Microsoft 365.

Microsoft documents a process to block access to PowerShell for EDU tenants. The script to block PowerShell uses cmdlets from the Azure AD module, which Microsoft is deprecating with retirement scheduled for March 30, 2024. A replacement script using Microsoft Graph PowerShell SDK cmdlets is needed. Fortunately, I’ve been down this path with an article covering secure access to the SDK and can reuse many of the concepts explained there.

Key Steps to Block PowerShell Modules

Every application that authenticates against Entra ID is known to the directory. Some applications are created within a tenant (registered apps). Others are created by companies like Microsoft as multi-tenant applications that can run anywhere. These are enterprise applications. The PowerShell modules that connect to Microsoft 365 endpoints like Exchange or Teams authenticate using enterprise applications created by Microsoft. The Microsoft Graph PowerShell SDK is the most obvious of these applications, but other applications exist for the Exchange Online management module, SharePoint Online management module, and the Microsoft Teams module.

Most administrators are unaware that these PowerShell enterprise applications exist. The applications don’t show up in the Entra ID admin center because normally they do not have a service principal. Applications use service principals to store permissions, like the Graph permissions used by the Microsoft Graph PowerShell SDK. Applications without service principals use roles instead.

For instance, when you run the Connect-ExchangeOnline cmdlet to connect to Exchange Online, the ability to work with Exchange data is gated by the roles possessed by the signed-in user account. If the account holds the Exchange administrator or Global administrator role, they can manage all aspects of Exchange Online (this also applies to Azure Automation accounts). If not, they can manage their own mailbox.

The key steps to restrict access to a PowerShell module are:

  • Find the application identifier for the module. We’ll get to doing that in a minute.
  • Create a service principal for the application.
  • Update the service principal so that it uses application role assignments.
  • Create a security group to manage assignments of permission to use the module.
  • Add the security group as an assignment to the service principal.

Finding Application Identifiers for PowerShell Modules

The first step is to find the application identifiers. The easiest way to do this is to check the Entra ID sign-in logs for events when people connect using a PowerShell module. Figure 1 shows an example of a sign-in event logged when an administrator connected with the SharePoint Online management module. We can see that the application identifier is 9bc3ab49-b65d-410a-85ad-de819febfddc.

Finding the application identifier for a PowerShell module from an Entra ID sign-in event

Block PowerShell access
Figure 1: Finding the application identifier for a PowerShell module from an Entra ID sign-in event

Application identifiers for other modules include:

  • Exchange Online management: fb78d390-0c51-40cd-8e17-fdbfab77341b (covers both regular Exchange and the compliance endpoint).
  • Microsoft Teams: 12128f48-ec9e-42f0-b203-ea49fb6af367
  • Azure: 1950a258-227b-4e31-a9cf-717495945fc2
  • Microsoft Graph PowerShell SDK: 14d82eec-204b-4c2f-b7e8-296a70dab67e

Example: Block Access to Exchange Online PowerShell

Now that we know the application identifiers, we can go ahead and create the service principal for the modules to block. Here are the PowerShell commands to connect an interactive Graph session and create a block for Exchange Online:

# Connect to the Grph
Connect-MgGraph -Scopes Directory.ReadWrite.All, Group.ReadWrite.All, Application.ReadWrite.All

# Create security group to control access to Exchange Online PowerShell
$Group = New-MgGroup -DisplayName "Allow access to EXO PowerShell" -MailEnabled:$False -SecurityEnabled:$True -MailNickName 'EXO.PowerShell'

# Create the service principal for the Exchange Online PowerShell app
$ServicePrincipal = New-MgServicePrincipal -Appid 'fb78d390-0c51-40cd-8e17-fdbfab77341b'

# Check that the Service Principal exists
Get-MgServicePrincipal -ServicePrincipalId $ServicePrincipal.Id | Format-Table DisplayName, Id, AppId

DisplayName                                  Id                                   AppId
-----------                                  --                                   -----
Microsoft Exchange REST API Based PowerShell 8d32ebd2-7295-4236-a3da-7c45be69a0b3 fb78d390-0c51-40cd-8e17-fdbfab77341b

# Update the Service Principal so that it requires application role assignments
Update-MgServicePrincipal -ServicePrincipalId $ServicePrincipal.Id -AppRoleAssignmentRequired:$True

# Add the security group as an assignment to the service principal
New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $ServicePrincipal.Id -AppRoleId ([Guid]::Empty.ToString()) -ResourceId $ServicePrincipal.Id -PrincipalId $Group.Id

After running these commands, no one can run the Connect-ExchangeOnline cmdlet to connect to Exchange unless they are added to the security group (Figure 2).

Members of the security group permitted to run Exchange Online PowerShell
Figure 2: Members of the security group permitted to run Exchange Online PowerShell

Entra ID rejects connection attempts from unauthorized accounts with an AADSTS50105 error (Figure 3). The “Microsoft Exchange REST API Based PowerShell” name is assigned to the enterprise application by Microsoft.

Error when attempting to run the Exchange Online PowerShell module
Figure 3: Error when attempting to run the Exchange Online PowerShell module

Discovering Who Accesses PowerShell

Often it’s simple to know who should be allowed to be members of the security group controlling access to a module. The tenant administrator, any administrators for a workload (like Teams service administrators), break glass accounts, service accounts such as those used by Azure Automation, and so on. But to be definite, we should review the Entra ID sign-in logs to see who uses a module.

This command retrieves the last 5,000 sign-in records and filters them for any sign-in for the Exchange Online application:

[array]$AuditRecords = Get-MgAuditLogSignIn -Top 5000 -Sort "createdDateTime DESC" -Filter "AppId eq 'fb78d390-0c51-40cd-8e17-fdbfab77341b'"

A simple Group-Object command gives the answer:

$AuditRecords | Group-Object UserPrincipalName -NoElement | Sort-Object Count -Descending| Select-Object Name, Count

Name                               Count
----                               -----
tony.redmond@office365itpros.com      10
EXOAdmin@office365itpros.com           7
James.Atkinson@office365itpros.com     3

You can then decide if any or all of the people who have accessed the module should be added to the security group. To check another module, replace the application identifier in the Get-MgAuditLogSignIn command.

Should My Tenant Block PowerShell?

The factors driving the decision to block PowerShell access for user accounts will differ from organization to organization. At least now you know the best way to block the most common PowerShell modules used with Microsoft 365 and how to find out who’s using the modules.


Support the work of the Office 365 for IT Pros team by subscribing to the Office 365 for IT Pros eBook. Your support pays for the time we need to track, analyze, and document the changing world of Microsoft 365 and Office 365.

15 Replies to “How to Block User Access to Microsoft 365 PowerShell Modules”

  1. Hello Tony, I get “Get-MgAuditLogSignIn_List: The request was canceled due to the configured HttpClient.Timeout of 300 seconds elapsing” error when running the Get-MgAuditLogSignIn command; any idea’s on how to avoid or enlarge that timeout?

    1. Hmmm… does the command run if you reduce the number of requested records to say something like 1,000? I have never seen this issue before and it would seem like a transient error in Entra ID responsiveness. https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient.timeout?view=net-7.0&viewFallbackFrom=xamarinandroid-7.1#Remarks says that the default timeout is 100 seconds for .NET apps.

      https://stackoverflow.com/questions/34136088/invoke-webrequest-set-time-out suggests that you can set the timeout with a command like:

      [System.Net.ServicePointManager]::MaxServicePointIdleTime = 180000

      That’s for 3 minutes (180 seconds). You could try 360000 (six minutes) and see if that makes a difference.

  2. I had thoughts on using an internal PKI and a GPO requiring all scripts be signed. We set the script signing up. (it had some challenges.) A different approach to be sure. Any sense of pros and cons on the two approaches?

  3. What instance should i be connecting to when creating the block scripts? When I connected via my AzureAD tenant im seeing that the commands are not available.

  4. This is absolutely fantastic, thank you!

    Quick question in regard to the group assignment. We sometimes have issues in Entra with nested groups; will nested groups work here?

  5. Thanks for the information provided, what would be the steps to roll back this change? I have tried and failed I continue to receive

    AADSTS50105: Your administrator has configured the application Azure Active Directory PowerShell (‘1b730954-1685-4b74-9bfd-dac224a7b894’) to block users unless they are specifically granted (‘assigned’) access to the application

  6. Hello Tony, just looked again it seems that I was mixing up my DEV tenant with Production. :-/ The roleback works fine. As below….

    Update-MgServicePrincipal -ServicePrincipalId ‘b1f26107-f6f5-4237-80e4-cf8a7d1b4022’ -AppRoleAssignmentRequired:$False

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.