A practical reference for connecting PowerShell to Exchange Online and Microsoft Graph from Linux (Fedora), plus the exact commands used during the SUSF admin MFA registration and legacy auth hardening project.


One-Time Setup on Fedora

Install PowerShell 7

sudo dnf install powershell

If not in Fedora’s repos, add Microsoft’s repo first:

sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc
curl -sSL -o /etc/yum.repos.d/microsoft.repo https://packages.microsoft.com/config/rhel/9/prod.repo
sudo dnf install powershell

Launch PowerShell

pwsh

Trust PSGallery (First Run Only)

Set-PSRepository -Name PSGallery -InstallationPolicy Trusted

Connecting to Exchange Online

Install module (once)

Install-Module -Name ExchangeOnlineManagement -Scope CurrentUser

Connect

Import-Module ExchangeOnlineManagement
Connect-ExchangeOnline -UserPrincipalName itadmin@yourdomain.onmicrosoft.com

Uses a browser/device-code sign-in flow.


Connecting to Microsoft Graph from Linux

Install modules (once โ€” install together to avoid version-mismatch errors)

Install-Module Microsoft.Graph.Authentication -Scope CurrentUser
Install-Module Microsoft.Graph.Users -Scope CurrentUser
Install-Module Microsoft.Graph.Identity.DirectoryManagement -Scope CurrentUser
Install-Module Microsoft.Graph.Reports -Scope CurrentUser

Connect โ€” use -UseDeviceCode on Linux

Interactive browser auth (Connect-MgGraph with no flag) fails on Linux with an MSAL dependency error. Always use device code instead:

Connect-MgGraph -Scopes "<scopes needed>" -UseDeviceCode

This prints a URL (https://microsoft.com/devicelogin) and a code โ€” complete sign-in in any browser.

Check current connection / granted scopes

Get-MgContext

Disconnect (needed before reconnecting with different scopes)

Disconnect-MgGraph

Required Scopes by Task

TaskScopes
Privileged roles / role membershipRoleManagement.Read.Directory, User.Read.All
MFA registration reportAuditLog.Read.All, UserAuthenticationMethod.Read.All (โš ๏ธ not Reports.Read.All โ€” Graph returns a specific error naming the correct scope if this is wrong)
Check a user’s directory rolesUser.Read.All, Directory.Read.All
Sign-in log queriesAuditLog.Read.All

To cover everything in one session:

Connect-MgGraph -Scopes "AuditLog.Read.All","UserAuthenticationMethod.Read.All","RoleManagement.Read.Directory","User.Read.All","Directory.Read.All" -UseDeviceCode

Commands Used in the Hardening Project

Exchange โ€” tenant auth config check

Get-OrganizationConfig | Format-List *auth*

Shows org-wide auth settings. Key field: DefaultAuthenticationPolicy โ€” blank means no explicit Basic Auth block policy is assigned org-wide.

Exchange โ€” check mailbox-level auth policy overrides

Get-Mailbox -ResultSize 10 | Select-Object DisplayName, UserPrincipalName, AuthenticationPolicy

Confirms whether any mailbox has its own AuthenticationPolicy set, which would override an org-wide default.

Exchange โ€” check POP/IMAP/ActiveSync/OWA status per mailbox

Get-CASMailbox -ResultSize 10 | Select-Object DisplayName, PopEnabled, ImapEnabled, ActiveSyncEnabled, OWAEnabled

Shows which legacy-capable protocols are enabled per mailbox.

Exchange โ€” create and apply a Basic Auth block policy

New-AuthenticationPolicy -Name "Block Legacy Auth - SUSF"

Set-AuthenticationPolicy -Identity "Block Legacy Auth - SUSF" `
  -AllowBasicAuthPop:$false `
  -AllowBasicAuthImap:$false `
  -AllowBasicAuthSmtp:$false `
  -AllowBasicAuthActiveSync:$false `
  -AllowBasicAuthRpc:$false `
  -AllowBasicAuthWebServices:$false `
  -AllowBasicAuthAutodiscover:$false `
  -AllowBasicAuthMapi:$false `
  -AllowBasicAuthOfflineAddressBook:$false `
  -AllowBasicAuthPowershell:$false `
  -AllowBasicAuthOutlookService:$false `
  -AllowBasicAuthReportingWebServices:$false

Set-OrganizationConfig -DefaultAuthenticationPolicy "Block Legacy Auth - SUSF"

Creates a named policy blocking Basic Auth on every legacy protocol, then sets it as the tenant default. Propagation can take up to 24 hours.

Undo:

Set-OrganizationConfig -DefaultAuthenticationPolicy $null
Remove-AuthenticationPolicy -Identity "Block Legacy Auth - SUSF"

Exchange โ€” verify the policy applied

Get-OrganizationConfig | Select-Object DefaultAuthenticationPolicy
Get-AuthenticationPolicy -Identity "Block Legacy Auth - SUSF" | Format-List

Exchange โ€” list registered ActiveSync devices

Get-MobileDevice -ResultSize Unlimited | Select-Object DeviceOS, DeviceUserAgent, DeviceModel, UserDisplayName | Format-Table -AutoSize

Grouped/counted by client type:

Get-MobileDevice -ResultSize Unlimited | Group-Object DeviceUserAgent | Select-Object Count, Name | Sort-Object Count -Descending | Format-Table -AutoSize

Exchange โ€” per-device sync status

Get-MobileDeviceStatistics -Mailbox <UPN> | Select-Object DeviceOS, DeviceUserAgent, LastSuccessSync, DeviceAccessState, ClientVersion

Confirms a device is actively syncing and not blocked โ€” doesn’t show auth type directly, cross-reference with sign-in logs for that.

Graph โ€” privileged accounts report (role + member list)

$roles = Get-MgDirectoryRole -All

$report = foreach ($role in $roles) {
    $members = Get-MgDirectoryRoleMember -DirectoryRoleId $role.Id
    foreach ($member in $members) {
        $user = Get-MgUser -UserId $member.Id -Property DisplayName, UserPrincipalName, AccountEnabled -ErrorAction SilentlyContinue
        if ($user) {
            [PSCustomObject]@{
                Role              = $role.DisplayName
                DisplayName       = $user.DisplayName
                UserPrincipalName = $user.UserPrincipalName
                AccountEnabled    = $user.AccountEnabled
            }
        }
    }
}

$report | Sort-Object Role, DisplayName | Format-Table -AutoSize

Export:

$report | Sort-Object Role, DisplayName | Export-Csv -Path "~/privileged-accounts.csv" -NoTypeInformation

Graph โ€” MFA registration report

Get-MgReportAuthenticationMethodUserRegistrationDetail -All |
    Select-Object UserDisplayName, UserPrincipalName, IsMfaRegistered, IsMfaCapable, MethodsRegistered, IsAdmin |
    Sort-Object IsAdmin -Descending |
    Format-Table -AutoSize

Export (swap Format-Table for Export-Csv โ€” can’t pipe to both):

Get-MgReportAuthenticationMethodUserRegistrationDetail -All |
    Select-Object UserDisplayName, UserPrincipalName, IsMfaRegistered, IsMfaCapable, MethodsRegistered, IsAdmin |
    Export-Csv -Path "~/mfa-registration-report.csv" -NoTypeInformation

Preview a saved CSV:

Import-Csv ~/mfa-registration-report.csv | Format-Table -AutoSize

Graph โ€” check a specific user’s role/group memberships

Get-MgUserMemberOf -UserId "itmanager@sport.usyd.edu.au" | Select-Object -ExpandProperty AdditionalProperties

Used to check whether a user is actually assigned one of the admin roles targeted by a CA policy (explains notApplied results in sign-in logs).

Graph โ€” sign-in log queries (legacy auth check)

Get-MgAuditLogSignIn -Filter "clientAppUsed eq 'Other clients'" -Top 100 |
    Select-Object CreatedDateTime, UserPrincipalName, ClientAppUsed, AppDisplayName, @{N='DeviceOS';E={$_.DeviceDetail.OperatingSystem}}, @{N='Browser';E={$_.DeviceDetail.Browser}} |
    Format-Table -AutoSize

'Other clients' = the Graph marker for legacy/Basic Auth (POP/IMAP/SMTP/legacy ActiveSync). Empty result = no legacy auth traffic.

Control query (to validate the filter isn’t silently broken):

Get-MgAuditLogSignIn -Filter "clientAppUsed eq 'Browser'" -Top 20

iOS-specific check across all auth types:

Get-MgAuditLogSignIn -Top 200 |
    Where-Object { $_.DeviceDetail.OperatingSystem -like "*iOS*" } |
    Select-Object CreatedDateTime, UserPrincipalName, ClientAppUsed, @{N='DeviceOS';E={$_.DeviceDetail.OperatingSystem}} |
    Format-Table -AutoSize

Outlook app sign-ins specifically:

Get-MgAuditLogSignIn -Top 200 |
    Where-Object { $_.AppDisplayName -like "*Outlook*" } |
    Select-Object CreatedDateTime, UserPrincipalName, ClientAppUsed, AppDisplayName |
    Format-Table -AutoSize

Gotchas Encountered

  • Connect-MgGraph interactive browser auth fails on Linux with InteractiveBrowserCredential authentication failed: Method not found... โ€” always use -UseDeviceCode.
  • Wrong scope error is actually helpful โ€” if a cmdlet 403s with Authentication_MSGraphPermissionMissing, the error message names the exact scope required; reconnect with Disconnect-MgGraph then Connect-MgGraph with that scope added.
  • Graph session expires โ€” running a Get-Mg* command after a while can throw “Authentication needed. Please call Connect-MgGraph.” Just reconnect.
  • Can’t pipe Format-Table into Export-Csv โ€” remove Format-Table -AutoSize from the pipeline before adding Export-Csv.
  • Get-MobileDevice shows device registration, not current auth type โ€” cross-reference with sign-in log clientAppUsed to confirm actual auth method.

Reducing How Often You Reauthenticate

  • Connect with all needed scopes upfront, in one call, rather than reconnecting each time a 403 names a missing scope:
Connect-MgGraph -Scopes "AuditLog.Read.All","UserAuthenticationMethod.Read.All","RoleManagement.Read.Directory","User.Read.All","Directory.Read.All","Reports.Read.All" -UseDeviceCode
  • Default Connect-MgGraph context scope is CurrentUser, which reuses a cached token across pwsh sessions until the refresh token expires or a Conditional Access session control (e.g. sign-in frequency) forces reauth sooner.
  • Long-term fix for recurring/scripted work: use an app registration with certificate-based auth instead of delegated (interactive) auth โ€” no sign-in prompt at all:
Connect-MgGraph -ClientId "<app-id>" -TenantId "<tenant-id>" -CertificateThumbprint "<thumbprint>"

Requires registering an app in Entra, granting it application (not delegated) permissions with admin consent, generating a certificate, and uploading the public key to the app registration. Worth it if this becomes a recurring task across multiple client tenants (SUSF, Superior Paper, etc.).

  • Exchange Online supports the same app-only/certificate pattern via Connect-ExchangeOnline.