使用 Microsoft Graph 获取 Office 365 用户的 MFA 状态
跟踪用户的 MFA 状态对于保护租户非常重要。目前,我们仍然可以在 PowerShell 中使用 Msol 模块来实现此目的,但 Microsoft 计划停用该模块。因此,我使用 Microsoft Graph 模块重新创建了成功的 MFA 状态脚本。
因此,这个新的 MFA Status 脚本可以执行与旧脚本几乎相同的操作:
- 列出所有用户配置的MFA类型
- 获取所有未启用 MFA 的用户
- 检查单用户的MFA配置
- 检查用户是否是管理员
- 仅获取许可和启用的用户
但使用 Graph,我们还能够检索比旧模块更多的信息。因此现在还检索到以下信息:
- 验证器设备名称
- 获取用户首选的MFA方法
- 检查 Hello for Business 是否已注册
- 自助服务密码重置 (SSPR) 的注册电子邮件地址
目前我们无法检查的是 MFA 是否确实为用户启用或禁用,或者是否强制执行了 MFA(例如用户下次登录后需要配置 MFA)。
与往常一样,您将在文章末尾找到完整的脚本。
使用 Microsoft Graph 和 PowerShell 获取 MFA 状态
Microsoft Graph 不断更新,使用最新版本,我们不必再使用 beta 端点。这意味着我们可以以正常方式连接到 Graph,并使用 Get-MgUser cmdlet 检索所有数据所需的范围。
收集所有用户后,我们可以使用 Get-MgUserAuthenticationMethod cmdlet 获取所有 MFA 详细信息。
笔记
使用 Graph,我们实际上无法检查用户是否启用或禁用了 MFA。我们只能检查配置了哪些身份验证方法。
Microsoft 正在放弃每用户 MFA 配置,我们需要转向 MFA 的条件访问策略。因此,目前,可以在管理中心为特定用户禁用 MFA,我们无法使用 Graph 检测到这一点。
但是,如果您有 Azure P1 或 P2,请查看此脚本。或者,您仍然可以使用旧的 MSOL 模块来执行此操作,因此,如果您仍在使用每用户 MFA,请务必检查此脚本。
要求
您需要安装 Microsoft Graph 模块。该脚本将检查该模块是否已安装,如果没有,您将获得安装它的选项。
获取所有用户及其 MFA 状态
该脚本附带了几个参数,我们可以使用它们来微调导出结果。但默认情况下,它将获取所有许可用户、列出管理员并将 CSV 导出保存在与脚本相同的位置。完成后,脚本将打开 CSV 文件。
因此,要获取所有用户,我们只需运行脚本即可:
# Get all licensed users:
Get-MgMFAStatus.ps1仅获取没有 MFA 的用户
当您拥有大型租户时,您可能只想查看未启用 MFA 的用户。为此,您可以添加使用开关 -withoutMFAOnly:
Get-MgMFAStatus.ps1 -withOutMFAOnly仅检查管理员的 MFA 状态
默认情况下,该脚本将列出所有管理员,但您也可以仅使用 -adminsOnly 开关检查管理员的 MFA 状态:
Get-MgMFAStatus.ps1 -adminsOnly检查特定用户或选择的用户的状态
还可以检查特定用户的 MFA 状态。我们可以使用 -UserPrincipalName 参数指定用户的 UserPrincipal 名称:
Get-MgMFAStatus -UserPrincipalName 'johndoe@contoso.com'
该参数接受字符串数组,因此您可以用逗号分隔要检索的用户:
Get-MgMFAStatus -UserPrincipalName 'johndoe@contoso.com','janedoe@contoso.com'另一种选择是使用 Get-MgUser cmdlet 的过滤器,然后通过管道传输 Get-MgMFAStatus 脚本:
Get-MgUser -Filter "country eq 'Netherlands'" | ForEach-Object { Get-MgMFAStatus -UserPrincipalName $_.UserPrincipalName }完整的脚本
完整的脚本可以从我的 Github 存储库下载,我建议使用它,以便您始终拥有最新版本。
建议
通过在 PowerShell 配置文件中添加对脚本的引用,快速获取用户的 MFA 状态。请阅读本文中的所有内容。
<#
.Synopsis
Get the MFA status for all users or a single user with Microsoft Graph
.DESCRIPTION
This script will get the Azure MFA Status for your users. You can query all the users, admins only or a single user.
It will return the MFA Status, MFA type and registered devices.
Note: Default MFA device is currently not supported https://docs.microsoft.com/en-us/graph/api/resources/authenticationmethods-overview?view=graph-rest-beta
Hardwaretoken is not yet supported
.NOTES
Name: Get-MgMFAStatus
Author: R. Mens - LazyAdmin.nl
Version: 1.2
DateCreated: Jun 2022
Purpose/Change: Added MFA preferred method
.LINK
https://lazyadmin.nl
.EXAMPLE
Get-MgMFAStatus
Get the MFA Status of all enabled and licensed users and check if there are an admin or not
.EXAMPLE
Get-MgMFAStatus -UserPrincipalName 'johndoe@contoso.com','janedoe@contoso.com'
Get the MFA Status for the users John Doe and Jane Doe
.EXAMPLE
Get-MgMFAStatus -withOutMFAOnly
Get only the licensed and enabled users that don't have MFA enabled
.EXAMPLE
Get-MgMFAStatus -adminsOnly
Get the MFA Status of the admins only
.EXAMPLE
Get-MgUser -Filter "country eq 'Netherlands'" | ForEach-Object { Get-MgMFAStatus -UserPrincipalName $_.UserPrincipalName }
Get the MFA status for all users in the Country The Netherlands. You can use a similar approach to run this
for a department only.
.EXAMPLE
Get-MgMFAStatus -withOutMFAOnly| Export-CSV c:\temp\userwithoutmfa.csv -noTypeInformation
Get all users without MFA and export them to a CSV file
#>
[CmdletBinding(DefaultParameterSetName="Default")]
param(
[Parameter(
Mandatory = $false,
ParameterSetName = "UserPrincipalName",
HelpMessage = "Enter a single UserPrincipalName or a comma separted list of UserPrincipalNames",
Position = 0
)]
[string[]]$UserPrincipalName,
[Parameter(
Mandatory = $false,
ValueFromPipeline = $false,
ParameterSetName = "AdminsOnly"
)]
# Get only the users that are an admin
[switch]$adminsOnly = $false,
[Parameter(
Mandatory = $false,
ValueFromPipeline = $false,
ParameterSetName = "Licensed"
)]
# Check only the MFA status of users that have license
[switch]$IsLicensed = $true,
[Parameter(
Mandatory = $false,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true,
ParameterSetName = "withOutMFAOnly"
)]
# Get only the users that don't have MFA enabled
[switch]$withOutMFAOnly = $false,
[Parameter(
Mandatory = $false,
ValueFromPipeline = $false
)]
# Check if a user is an admin. Set to $false to skip the check
[switch]$listAdmins = $true,
[Parameter(
Mandatory = $false,
HelpMessage = "Enter path to save the CSV file"
)]
[string]$path = ".\MFAStatus-$((Get-Date -format "MMM-dd-yyyy").ToString()).csv"
)
Function ConnectTo-MgGraph {
# Check if MS Graph module is installed
if (-not(Get-InstalledModule Microsoft.Graph)) {
Write-Host "Microsoft Graph module not found" -ForegroundColor Black -BackgroundColor Yellow
$install = Read-Host "Do you want to install the Microsoft Graph Module?"
if ($install -match "[yY]") {
Install-Module Microsoft.Graph -Repository PSGallery -Scope CurrentUser -AllowClobber -Force
}else{
Write-Host "Microsoft Graph module is required." -ForegroundColor Black -BackgroundColor Yellow
exit
}
}
# Connect to Graph
Write-Host "Connecting to Microsoft Graph" -ForegroundColor Cyan
Connect-MgGraph -Scopes "User.Read.All, UserAuthenticationMethod.Read.All, Directory.Read.All" -NoWelcome
}
Function Get-Admins{
<#
.SYNOPSIS
Get all user with an Admin role
#>
process{
$admins = Get-MgDirectoryRole | Select-Object DisplayName, Id |
%{$role = $_.DisplayName; Get-MgDirectoryRoleMember -DirectoryRoleId $_.id |
where {$_.AdditionalProperties."@odata.type" -eq "#microsoft.graph.user"} |
% {Get-MgUser -userid $_.id }
} |
Select @{Name="Role"; Expression = {$role}}, DisplayName, UserPrincipalName, Mail, Id | Sort-Object -Property Mail -Unique
return $admins
}
}
Function Get-Users {
<#
.SYNOPSIS
Get users from the requested DN
#>
process{
# Set the properties to retrieve
$select = @(
'id',
'DisplayName',
'userprincipalname',
'mail'
)
$properties = $select + "AssignedLicenses"
# Get enabled, disabled or both users
switch ($enabled)
{
"true" {$filter = "AccountEnabled eq true and UserType eq 'member'"}
"false" {$filter = "AccountEnabled eq false and UserType eq 'member'"}
"both" {$filter = "UserType eq 'member'"}
}
# Check if UserPrincipalName(s) are given
if ($UserPrincipalName) {
Write-host "Get users by name" -ForegroundColor Cyan
$users = @()
foreach ($user in $UserPrincipalName)
{
try {
$users += Get-MgUser -UserId $user -Property $properties | select $select -ErrorAction Stop
}
catch {
[PSCustomObject]@{
DisplayName = " - Not found"
UserPrincipalName = $User
isAdmin = $null
MFAEnabled = $null
}
}
}
}elseif($adminsOnly)
{
Write-host "Get admins only" -ForegroundColor Cyan
$users = @()
foreach ($admin in $admins) {
$users += Get-MgUser -UserId $admin.UserPrincipalName -Property $properties | select $select
}
}else
{
if ($IsLicensed) {
# Get only licensed users
$users = Get-MgUser -Filter $filter -Property $properties -all | Where-Object {($_.AssignedLicenses).count -gt 0} | select $select
}else{
$users = Get-MgUser -Filter $filter -Property $properties -all | select $select
}
}
return $users
}
}
Function Get-MFAMethods {
<#
.SYNOPSIS
Get the MFA status of the user
#>
param(
[Parameter(Mandatory = $true)] $userId
)
process{
# Get MFA details for each user
[array]$mfaData = Get-MgUserAuthenticationMethod -UserId $userId
# Create MFA details object
$mfaMethods = [PSCustomObject][Ordered]@{
status = "-"
authApp = "-"
phoneAuth = "-"
fido = "-"
helloForBusiness = "-"
helloForBusinessCount = 0
emailAuth = "-"
tempPass = "-"
passwordLess = "-"
softwareAuth = "-"
authDevice = ""
authPhoneNr = "-"
SSPREmail = "-"
}
ForEach ($method in $mfaData) {
Switch ($method.AdditionalProperties["@odata.type"]) {
"#microsoft.graph.microsoftAuthenticatorAuthenticationMethod" {
# Microsoft Authenticator App
$mfaMethods.authApp = $true
$mfaMethods.authDevice += $method.AdditionalProperties["displayName"]
$mfaMethods.status = "enabled"
}
"#microsoft.graph.phoneAuthenticationMethod" {
# Phone authentication
$mfaMethods.phoneAuth = $true
$mfaMethods.authPhoneNr = $method.AdditionalProperties["phoneType", "phoneNumber"] -join ' '
$mfaMethods.status = "enabled"
}
"#microsoft.graph.fido2AuthenticationMethod" {
# FIDO2 key
$mfaMethods.fido = $true
$fifoDetails = $method.AdditionalProperties["model"]
$mfaMethods.status = "enabled"
}
"#microsoft.graph.passwordAuthenticationMethod" {
# Password
# When only the password is set, then MFA is disabled.
if ($mfaMethods.status -ne "enabled") {$mfaMethods.status = "disabled"}
}
"#microsoft.graph.windowsHelloForBusinessAuthenticationMethod" {
# Windows Hello
$mfaMethods.helloForBusiness = $true
$helloForBusinessDetails = $method.AdditionalProperties["displayName"]
$mfaMethods.status = "enabled"
$mfaMethods.helloForBusinessCount++
}
"#microsoft.graph.emailAuthenticationMethod" {
# Email Authentication
$mfaMethods.emailAuth = $true
$mfaMethods.SSPREmail = $method.AdditionalProperties["emailAddress"]
$mfaMethods.status = "enabled"
}
"microsoft.graph.temporaryAccessPassAuthenticationMethod" {
# Temporary Access pass
$mfaMethods.tempPass = $true
$tempPassDetails = $method.AdditionalProperties["lifetimeInMinutes"]
$mfaMethods.status = "enabled"
}
"#microsoft.graph.passwordlessMicrosoftAuthenticatorAuthenticationMethod" {
# Passwordless
$mfaMethods.passwordLess = $true
$passwordLessDetails = $method.AdditionalProperties["displayName"]
$mfaMethods.status = "enabled"
}
"#microsoft.graph.softwareOathAuthenticationMethod" {
# ThirdPartyAuthenticator
$mfaMethods.softwareAuth = $true
$mfaMethods.status = "enabled"
}
}
}
Return $mfaMethods
}
}
Function Get-Manager {
<#
.SYNOPSIS
Get the manager users
#>
param(
[Parameter(Mandatory = $true)] $userId
)
process {
$manager = Get-MgUser -UserId $userId -ExpandProperty manager | Select @{Name = 'name'; Expression = {$_.Manager.AdditionalProperties.displayName}}
return $manager.name
}
}
Function Get-MFAStatusUsers {
<#
.SYNOPSIS
Get all AD users
#>
process {
Write-Host "Collecting users" -ForegroundColor Cyan
# Collect users
$users = Get-Users
Write-Host "Processing" $users.count "users" -ForegroundColor Cyan
# Collect and loop through all users
$users | ForEach {
$mfaMethods = Get-MFAMethods -userId $_.id
$manager = Get-Manager -userId $_.id
$uri = "https://graph.microsoft.com/beta/users/$($_.id)/authentication/signInPreferences"
$mfaPreferredMethod = Invoke-MgGraphRequest -uri $uri -Method GET
if ($null -eq ($mfaPreferredMethod.userPreferredMethodForSecondaryAuthentication)) {
# When an MFA is configured by the user, then there is alway a preferred method
# So if the preferred method is empty, then we can assume that MFA isn't configured
# by the user
$mfaMethods.status = "disabled"
}
if ($withOutMFAOnly) {
if ($mfaMethods.status -eq "disabled") {
[PSCustomObject]@{
"Name" = $_.DisplayName
Emailaddress = $_.mail
UserPrincipalName = $_.UserPrincipalName
isAdmin = if ($listAdmins -and ($admins.UserPrincipalName -match $_.UserPrincipalName)) {$true} else {"-"}
MFAEnabled = $false
"Phone number" = $mfaMethods.authPhoneNr
"Email for SSPR" = $mfaMethods.SSPREmail
}
}
}else{
[pscustomobject]@{
"Name" = $_.DisplayName
Emailaddress = $_.mail
UserPrincipalName = $_.UserPrincipalName
isAdmin = if ($listAdmins -and ($admins.UserPrincipalName -match $_.UserPrincipalName)) {$true} else {"-"}
"MFA Status" = $mfaMethods.status
"MFA Preferred method" = $mfaPreferredMethod.userPreferredMethodForSecondaryAuthentication
"Phone Authentication" = $mfaMethods.phoneAuth
"Authenticator App" = $mfaMethods.authApp
"Passwordless" = $mfaMethods.passwordLess
"Hello for Business" = $mfaMethods.helloForBusiness
"FIDO2 Security Key" = $mfaMethods.fido
"Temporary Access Pass" = $mfaMethods.tempPass
"Authenticator device" = $mfaMethods.authDevice
"Phone number" = $mfaMethods.authPhoneNr
"Email for SSPR" = $mfaMethods.SSPREmail
"Manager" = $manager
}
}
}
}
}
# Connect to Graph
ConnectTo-MgGraph
# Get Admins
# Get all users with admin role
$admins = $null
if (($listAdmins) -or ($adminsOnly)) {
$admins = Get-Admins
}
# Get MFA Status
Get-MFAStatusUsers | Sort-Object Name | Export-CSV -Path $path -NoTypeInformation
if ((Get-Item $path).Length -gt 0) {
Write-Host "Report finished and saved in $path" -ForegroundColor Green
# Open the CSV file
Invoke-Item $path
}else{
Write-Host "Failed to create report" -ForegroundColor Red
}总结
启用 MFA 确实有助于保护您的租户。此 PowerShell 脚本允许您轻松检查用户的 MFA 状态。
请务必查看本文以及 Office 365 的其他 20 个安全提示。您可以在此处找到此脚本的 Msol 版本,以及在此处找到基于 Microsoft Entra 的新版本。
如果您发现此脚本有用,请分享。如果您有任何疑问,请在下面发表评论。
您可能还喜欢以下 PowerShell 报告脚本之一:
- 邮箱权限报告
- 邮箱大小报告
- OneDrive 大小报告
