Fifteen focused checks with results to interpret
Excess privilege, forgotten OAuth consent, or external forwarding can escape a quick portal review. PowerShell makes configuration easier to inspect and evidence easier to reproduce.
These 15 checks read configuration. Preparation, connection, and sign-out commands are outside the count. This is focused diagnosis rather than a complete automated assessment: empty results, access denied, and a successful control are different states.
Examples target Microsoft Graph v1.0 and Exchange Online using fictional identifiers. Checks do not remove licenses, revoke grants, or change policies.
Prepare modules and an authorized session
Use supported PowerShell and Microsoft.Graph.Authentication, Identity.Governance, Identity.SignIns, Applications, and ExchangeOnlineManagement modules. For EXO 3.5.0 through 3.9.2 on PowerShell 7, Microsoft documents PowerShell 7.4.x; check your version matrix instead of blindly combining latest releases.
Install or update modules through change management, outside collection. Record versions. Graph checks use stable modules without Beta prefixes. Adapt connection environments for national clouds.
Use a named MFA-protected identity. Granting Graph scopes can create persistent authorization: use an approved collection application and approve only needed permissions. ContextScope Process limits local connection context; it does not revoke tenant consent.
The connection example below supports checks 4 and 5 only. Request the scope listed for other blocks. OAuth consent does not replace the account's Entra role or Exchange RBAC.
# Preparation, outside the 15 checks. Use approved installed versions.
$ErrorActionPreference = 'Stop'
Get-Module -ListAvailable Microsoft.Graph*,ExchangeOnlineManagement |
Select-Object Name,Version
# Example for checks 4 and 5 only: request scopes by diagnostic block.
Connect-MgGraph -TenantId '00000000-0000-0000-0000-000000000000' -Scopes 'Policy.Read.All' -ContextScope Process
Get-MgContext | Select-Object TenantId,Account,Scopes
Connect-ExchangeOnline -UserPrincipalName admin@contoso.com
# All addresses and IDs in this guide are fictional.Permissions: choose the necessary block
Scopes are shown per operation. If an approved session already has broader read permission, do not add write access. Global Reader supports several Entra reads but is not a universal Exchange solution; check 12 has an important exception.
Have an RBAC administrator identify required Exchange role entries and parameters, then delegate the useful scope. A missing cmdlet or HTTP 403 means not evaluated, never compliant.
| Checks | Least Graph scope for the operation | Additional requirement |
|---|---|---|
| 1–2 | RoleManagement.Read.Directory | Supported Entra read role |
| 3 | RoleEligibilitySchedule.Read.Directory | Supported role; PIM and applicable licensing |
| 4–5 | Policy.Read.All | Supported policy-reading role |
| 6 | Policy.Read.AuthenticationMethod | For example Global Reader per API |
| 7 et 9 | Application.Read.All | Service principal read access |
| 8 | Directory.Read.All | Broad directory read access; justify use |
| 10–15 | — | Exchange RBAC, cmdlets, recipient scope |
1. Name roles before counting administrators
Collection permission: RoleManagement.Read.Directory.
Keep this map to resolve RoleDefinitionId in check 2. A defined role is not necessarily assigned; include custom roles.
$roles = @(Get-MgRoleManagementDirectoryRoleDefinition -All -ErrorAction Stop)
$roles | Select-Object Id,DisplayName,IsBuiltIn,IsEnabled2. List active role assignments
Collection permission: RoleManagement.Read.Directory.
Resolve IDs against check 1 and principals in Entra. Principals may be users, groups, or applications. Expand role-assignable groups before counting people. This list does not cover inactive eligibility.
Get-MgRoleManagementDirectoryRoleAssignment -All -ErrorAction Stop |
Select-Object Id,PrincipalId,RoleDefinitionId,DirectoryScopeId,AppScopeId3. Review PIM eligibility separately
Collection permission: RoleEligibilitySchedule.Read.Directory.
Eligibility is not activation. Compare duration, scope, and owner need. PIM requires appropriate Entra ID P2 or ID Governance licensing. Access denied does not mean no eligible users.
Get-MgRoleManagementDirectoryRoleEligibilityScheduleInstance -All -ErrorAction Stop |
Select-Object Id,PrincipalId,RoleDefinitionId,DirectoryScopeId,StartDateTime,EndDateTime4. Read Conditional Access policies
Collection permission: Policy.Read.All.
Inspect exclusions, resources, platforms, and controls. Report-only observes without enforcing that policy. A name such as MFA everywhere proves no coverage. Conditional Access requires Entra ID P1; risk conditions require P2. Validate with What If and sign-in logs.
Get-MgIdentityConditionalAccessPolicy -All -ErrorAction Stop |
Select-Object Id,DisplayName,State,Conditions,GrantControls,SessionControls |
ConvertTo-Json -Depth 205. Check security defaults
Collection permission: Policy.Read.All.
IsEnabled describes this mechanism, not overall security. False may be consistent with a Conditional Access design. True does not prove protection for a specific sign-in. Security defaults are available with Entra ID Free.
Get-MgPolicyIdentitySecurityDefaultEnforcementPolicy -ErrorAction Stop |
Select-Object Id,DisplayName,IsEnabled6. Inspect authentication methods policy
Collection permission: Policy.Read.AuthenticationMethod.
Review enabled methods, included groups, and exclusions. An allowed method does not prove user registration or enforcement through authentication strengths. Follow up with registration reports and applicable policies.
Get-MgPolicyAuthenticationMethodPolicy -ExpandProperty AuthenticationMethodConfigurations -ErrorAction Stop |
ConvertTo-Json -Depth 207. Inventory enterprise applications
Collection permission: Application.Read.All.
These are tenant service principals, not just app registrations you own. Retain $apps for check 9. Familiar names or disabled status do not replace permission and owner validation.
$apps = @(Get-MgServicePrincipal -All -Property Id,AppId,DisplayName,AccountEnabled,AppRoles -ErrorAction Stop)
$apps | Select-Object Id,AppId,DisplayName,AccountEnabled8. Read delegated OAuth grants
Collection permission: Directory.Read.All.
Scope contains delegated permissions. ClientId and ResourceId here are servicePrincipal object IDs, not public AppIds. AllPrincipals represents tenant-wide consent; Principal targets a user. Validate need and justification before revocation.
Get-MgOauth2PermissionGrant -All -ErrorAction Stop |
Select-Object Id,ClientId,ResourceId,ConsentType,PrincipalId,Scope9. Read a pilot application's application permissions
Collection permission: Application.Read.All.
These assignments differ from delegated consent and may allow access without a signed-in user. If Permission is blank, retain the ID and resolve it against the resource API; do not infer no privilege. This sample covers one selected application.
# Use check 7 to select the exact service principal OBJECT ID.
$clientObjectId = '11111111-1111-1111-1111-111111111111'
Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $clientObjectId -All -ErrorAction Stop |
ForEach-Object {
$grant = $_
$resource = $apps | Where-Object Id -eq $grant.ResourceId
$role = $resource.AppRoles | Where-Object Id -eq $grant.AppRoleId
[pscustomobject]@{
ClientObjectId=$clientObjectId; ResourceId=$grant.ResourceId
Resource=$resource.DisplayName; AppRoleId=$grant.AppRoleId
Permission=$role.Value
}
}10. Read Exchange organization auditing state
Collection permission: Exchange RBAC.
Read AuditDisabled carefully: True disables mailbox auditing at organization level. False guarantees neither expected events nor retention nor absence of exclusions. Verify a test event and applicable Purview licensing.
Get-OrganizationConfig -ErrorAction Stop |
Select-Object Name,AuditDisabled11. Find mailbox-level forwarding
Collection permission: Exchange RBAC.
A directory target may be a contact with an external address; resolve it before classifying forwarding. This collection excludes inbox and transport rules. It identifies configuration, not proven exfiltration.
Get-EXOMailbox -ResultSize Unlimited -Properties ForwardingAddress,ForwardingSmtpAddress,DeliverToMailboxAndForward -ErrorAction Stop |
Where-Object { $_.ForwardingAddress -or $_.ForwardingSmtpAddress } |
Select-Object DisplayName,PrimarySmtpAddress,ForwardingAddress,ForwardingSmtpAddress,DeliverToMailboxAndForward12. Inspect one pilot mailbox's rules, including hidden rules
Collection permission: Exchange RBAC spécifique.
Microsoft documents that this cmdlet does not work with View-Only Organization Management or Entra Global Reader. Delegate the required Get-InboxRule entries and recipient scope; do not grant global write administration to bypass this limit. Preserve full suspicious-rule details before remediation.
Get-InboxRule -Mailbox 'alex@contoso.com' -IncludeHidden -ResultSize Unlimited -ErrorAction Stop |
Select-Object Identity,Name,Enabled,Priority,ForwardTo,RedirectTo,ForwardAsAttachmentTo,DeleteMessage,StopProcessingRules13. Read outbound forwarding policies
Collection permission: Exchange RBAC.
A restrictive policy's existence does not prove it targets every recipient. Separately inspect assignment rules, priority, and exceptions. Interpret AutoForwardingMode against service documentation; do not assume Automatic means allowed.
Get-HostedOutboundSpamFilterPolicy -ErrorAction Stop |
Select-Object Name,IsDefault,AutoForwardingMode14. Read the tenant SMTP AUTH setting
Collection permission: Exchange RBAC.
True describes organization-level disablement. Mailbox overrides, authentication policies, and Entra controls also affect effective behavior. SMTP AUTH is not synonymous with Basic Authentication; this boolean proves neither OAuth use nor absence of legacy sign-ins.
Get-TransportConfig -ErrorAction Stop |
Select-Object SmtpClientAuthenticationDisabled15. Identify transport rules needing review
Collection permission: Exchange RBAC.
Review redirects, hidden copies, exceptions, and unusual antispam settings. Test mode differs from enforcement. This selection does not expose every action: inspect complete details for each candidate and validate with its owner.
Get-TransportRule -ResultSize Unlimited -ErrorAction Stop |
Sort-Object Priority |
Select-Object Identity,Name,State,Mode,Priority,Description,RedirectMessageTo,BlindCopyTo,SetSCLKeep evidence without turning review into blind scanning
Start with one pilot application and mailbox. Graph uses -All for pagination and Exchange uses ResultSize Unlimited where relevant. Neither bypasses scope restrictions or throttling. Avoid massive parallel collection and retain failures and retries.
Record tenant, collector, UTC time, module versions, command, and scope. Do not export Format-Table output; retain objects before formatting. Use appropriately deep JSON or CliXML for nested values; CSV may flatten or lose groups, conditions, and actions.
Results contain addresses, identifiers, and sensitive security detail. Define storage, access, and retention. A snapshot shows one moment, not change history or actual permission usage.
Move from findings to changes with validation and rollback
Prioritize exposure and impact: unjustified administrative roles, broad application access, and unknown external forwarding warrant prompt validation. Assign each finding an owner and evidence; do not revoke an app merely because its name is unfamiliar.
Changes are separate work: preserve initial state, confirm business need, pilot scope, tests, maintenance window, and rollback. For Conditional Access, use Report-only and emergency accounts; for Exchange, validate legitimate flows. Restoring deleted grants or assignments may require renewed approval.
After remediation, repeat the targeted check and a functional test. Disconnect collection sessions. Disconnect-MgGraph ends local authentication without removing application consent. Review collection permissions no longer needed.
These checks can prepare a Microsoft 365 and Entra ID security assessment. Professional analysis connects evidence with logs, dependencies, licensing, and actual risk; it extends beyond fifteen PowerShell outputs.
Disconnect-MgGraph
Disconnect-ExchangeOnline -Confirm:$false