PowerShell + Microsoft Graph + Fedora
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
| Task | Scopes |
|---|---|
| Privileged roles / role membership | RoleManagement.Read.Directory, User.Read.All |
| MFA registration report | AuditLog.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 roles | User.Read.All, Directory.Read.All |
| Sign-in log queries | AuditLog.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-MgGraphinteractive browser auth fails on Linux withInteractiveBrowserCredential 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 withDisconnect-MgGraphthenConnect-MgGraphwith 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-TableintoExport-Csvโ removeFormat-Table -AutoSizefrom the pipeline before addingExport-Csv. Get-MobileDeviceshows device registration, not current auth type โ cross-reference with sign-in logclientAppUsedto 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-MgGraphcontext scope isCurrentUser, which reuses a cached token acrosspwshsessions 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.