Microsoft Graph PowerShell 入门 ── AzureAD・MSOnline 废弃后的 Microsoft 365 运维

· · PowerShell, Microsoft 365, Microsoft Entra ID, Microsoft Graph, 信息系统, 自动化, 运维改善, 安全

对于把 Microsoft 365 运维自动化的现场来说,2024 年到 2025 年间最大的变化就是AzureAD 模块和 MSOnline 模块的废弃。想必有不少信息系统人员用 Get-MsolUserGet-AzureADUser 编写过“离职处理”“许可证盘点”“新员工账号创建”这类定型处理,这些脚本将陆续无法运行。

迁移目标是 Microsoft Graph PowerShell SDK。不过,这并不只是单纯的命令名替换。认证的思路(作用域与同意)、数据获取方式(OData 的筛选与分页)、无人值守执行的构建方式(应用注册与证书),设计前提本身都发生了变化。如果不理解这些就机械地替换,就会陷入“能运行但权限过大”“在用户数多的租户中只能取到一部分”之类新的问题。

本文面向用 PowerShell 自动化公司内部 Microsoft 365 运维的信息系统负责人,整理废弃的来龙去脉、连接与作用域设计、无人值守执行的构建方法,以及盘点、许可证汇总、离职处理这三个常用配方,并以可直接用于实务的形式加以总结。

1. 先说结论

  • MSOnline 和 AzureAD 已于 2024 年 3 月 30 日被标记为不推荐使用,MSOnline 已于 2025 年 5 月 30 日停止提供,AzureAD 也在 2025 年 3 月 30 日结束支持后被废弃。1
  • 迁移目标是 Microsoft Graph PowerShell SDK,或者构建于其之上的 Microsoft Entra PowerShell(2025 年 3 月正式发布)。后者以场景为导向,还提供有助于从 AzureAD 迁移的兼容选项。2
  • Microsoft.Graph 是一个元模块。整体安装会很重,实务中只安装 Microsoft.Graph.Authentication 加上要用到的工作负载对应的子模块。3
  • 连接从 Connect-MgGraph -Scopes 开始。作用域要遵循最小权限原则。所需权限可以用 Find-MgGraphPermission 查询,命令所属的模块可以用 Find-MgGraphCommand 查询。45
  • 无人值守执行采用应用注册加证书的应用专用认证。通过 -ClientId-TenantId-CertificateThumbprint 即可无需交互直接连接。相比客户端密钥,更推荐使用证书。46
  • 委派(Delegated)与应用专用(Application)所需的权限是完全不同的东西。即使是同一操作,要求的作用域也会不同,因此切换为无人值守执行时需要重新赋予权限。6
  • 列表获取用 -All,筛选用 -Filter,字段用 -Property如果用客户端的 Where-Object 筛选,会导致不必要的获取量和限流。7
  • 大量访问会受到调节(限流)。收到 429 响应时按照 Retry-After 等待后重试,是官方给出的方针。8
  • 应用注册要按用途分开。“一个应用包揽所有”会导致权限膨胀,一旦出事,影响范围也会扩大。

2. 废弃的来龙去脉与现在该如何选择

首先整理一下事实关系。本文内容截至 2026 年 7 月。以下日期均为 Microsoft 公布的废弃时间表,且均已经过去。1

模块 状态
MSOnline(Get-MsolUser 等) 2024 年 3 月 30 日标记为不推荐使用。2025 年 5 月 30 日废弃
AzureAD(Get-AzureADUser 等) 2024 年 3 月 30 日标记为不推荐使用。2025 年 3 月 30 日结束支持,随后废弃
Microsoft Graph PowerShell SDK 现行版本。将 Graph API 直接封装为 cmdlet
Microsoft Entra PowerShell 2025 年 3 月正式发布。构建于 Graph SDK 之上、以场景为导向的模块2

该用哪一个,判断标准如下。如果想直接操作 Graph API 的结构、想接触广泛的工作负载(Exchange、Teams、Intune 等),选 Graph PowerShell SDK。如果主要是围绕 Entra ID(原 Azure AD)做身份管理、想让从 AzureAD 模块的迁移尽量轻松,则选 Microsoft Entra PowerShell。后者可以和 Graph PowerShell SDK 互操作,还提供有助于从 AzureAD 模块迁移的向后兼容选项。2

本文以通用性高、文档也更丰富的 Graph PowerShell SDK 为主线进行讲解。

3. 安装 ── 不要把元模块整个装进来

Microsoft.Graph 是把大量子模块捆绑在一起的元模块。整体安装的话,安装和加载都会很重,根据运行环境不同,光是加载就可能耗时数十秒。3

# 【重】安装全部工作负载
Install-Module Microsoft.Graph -Scope CurrentUser

# 【实务】只安装认证 + 要用到的工作负载
Install-Module Microsoft.Graph.Authentication -Scope CurrentUser   # 必装
Install-Module Microsoft.Graph.Users          -Scope CurrentUser   # 用户
Install-Module Microsoft.Graph.Groups         -Scope CurrentUser   # 组
Install-Module Microsoft.Graph.Identity.DirectoryManagement -Scope CurrentUser  # 许可证等
Install-Module Microsoft.Graph.Users.Actions  -Scope CurrentUser   # 针对用户的操作
                                                                   # (Revoke-MgUserSignInSession 等)

# 查询某个命令属于哪个模块、需要什么权限
Find-MgGraphCommand -Command Get-MgUser | Select-Object Module, Permissions -First 1
Find-MgGraphPermission user.read -PermissionType Delegated

推荐在 PowerShell 7 中使用。虽然在 Windows PowerShell 5.1 上也能运行,但无论是性能还是未来发展方向,都有充分理由选择 7(参见《Windows PowerShell 5.1 与 PowerShell 7 的区别》)。3

4. 连接与作用域 ── 不要再“先同意 ReadWrite.All 再说”

4.1 先统一术语

先简要定义一下后文会反复出现的术语。

术语 含义
委派(Delegated) 已登录用户本人的身份执行的形态。实际能做什么,由“应用被同意的作用域”和“该用户所拥有的角色”两者共同决定6
应用专用(Application) 不经过用户、以应用自身身份执行的形态。无人值守批处理属于此类,用证书或客户端密钥进行认证6
应用注册 / 服务主体 应用注册是应用的定义。服务主体是将其在租户中实例化后的实体,权限和角色都绑定在服务主体上
作用域(访问权限) 类似 User.Read.All 这样的权限名称。委派用和应用程序用是分开管理的,即使是同名权限也需要重新赋予6
OData Open Data Protocol。是 Graph 查询语法的基础,-Filter-Property 等会被转换为对应的 OData 查询选项,在服务器端处理7
调节(限流) 请求过多时,服务端有意拒绝请求的机制。以 HTTP 429 和 Retry-After 响应头的形式返回8
持续访问评估(CAE) 无需等待令牌到期,由资源端接收失效等事件后立即中止访问的机制。仅在支持该机制的应用和资源上生效9

这两种形态的差异,是本文中最容易引发事故的地方。画成图如下所示。

应用专用 Application ── 无人值守执行委派 Delegated ── 交互式登录改为无人值守时需要重新赋予权限应用程序权限需要管理员同意用证书认证能做什么 =被授予的权限本身已同意的委派作用域管理员登录本人拥有的管理员角色能做什么 =作用域与角色的交集

记忆方法只有一条。委派 = 以用户本人身份执行,应用专用 = 无人值守批处理。委派下“本人做不到的事,应用也做不到”;应用专用下“只要授予了应用,就永远能做到”。

4.2 交互式连接

交互式连接使用 Connect-MgGraph -Scopes。系统会针对指定的作用域弹出同意界面,同意结果会记录在租户中。4

# 仅用于读取的盘点用途。不要求写入权限
Connect-MgGraph -Scopes 'User.Read.All', 'Organization.Read.All' -NoWelcome

Get-MgContext | Format-List Account, TenantId, Scopes, AuthType   # 确认当前的连接
Disconnect-MgGraph

这是迁移时差别最大的地方。在 Get-MsolUser 的时代,“用管理员账号登录就什么都能做”,但在 Graph 中,每个操作都定义了所需的作用域,只能在已同意的范围内运行。这不是限制,而是安全装置。只要只同意了 .Read.All,盘点脚本误执行写入的事故就不会发生。

原则有三条。

  • 读取用途不要求写入作用域(如果 User.Read.All 够用,就不要求 User.ReadWrite.All
  • 按用途拆分应用注册(盘点用、创建账号用、许可证管理用)
  • 同意应由管理员有意识地进行(一旦给予的同意会一直保留在租户中)

不清楚需要哪个作用域时,可以用 Find-MgGraphPermission 查找候选项,用 Find-MgGraphCommand 确认命令所要求的权限。5

5. 无人值守执行 ── 应用注册 + 证书

如果要通过任务计划程序每晚运行,就不能使用交互式登录。需要切换为基于应用注册(服务主体)和证书的应用专用认证6

步骤分为 4 个阶段。这是实际操作中最容易卡住的环节,因此下面会具体写明界面上的位置和命令。6

5.1 注册应用

在 Microsoft Entra 管理中心(https://entra.microsoft.com)中,依次执行以下操作。

ID > 应用程序 > 应用注册 > 新注册

输入名称(例如 M365-Inventory-Batch),支持的账户类型选择“仅此组织目录中的账户(单租户)”,然后点击“注册”。如果只是从脚本无人值守执行,则无需设置重定向 URI。

记下注册后“概述”页面上显示的以下两项。这是连接时要用到的值。

概述页面上的显示名称 Connect-MgGraph 的参数
应用程序(客户端)ID -ClientId
目录(租户)ID -TenantId

另外,Entra 管理中心的导航名称有时会被改名。即使左侧菜单的文字有变化,最终到达的页面名称仍是“应用注册”。请以此为标志。

5.2 添加应用程序权限并授予管理员同意

在同一个应用的界面中,依次执行以下操作。

管理 > API 权限 > 添加权限 > Microsoft Graph > 应用程序权限

这里的要点是选择“应用程序权限”。如果选了旁边的“委派的权限”,在无人值守执行时将无法生效。勾选所需的权限(如果是盘点,则是 User.Read.All 等),点击“添加权限”完成确定。

此时还不能使用。需要点击同一界面上方的

“为(租户名)授予管理员同意”

按钮来确定。点击之后,列表中的“状态”列变为“已授予(租户名)”的显示,即表示完成。忘记这一步而导致“明明加了权限却报 403 失败”,是常见的卡壳点。

5.3 创建、上传并放置证书

自签名证书就足够了。可以用 PowerShell 的 New-SelfSignedCertificate 来创建。

# 【1】创建证书(有效期为 2 年,可根据运维情况调整)
$cert = New-SelfSignedCertificate `
    -Subject           'CN=M365-Inventory-Batch' `
    -CertStoreLocation 'Cert:\CurrentUser\My' `
    -KeySpec           Signature `
    -KeyExportPolicy   Exportable `
    -KeyAlgorithm      RSA `
    -KeyLength         2048 `
    -HashAlgorithm     SHA256 `
    -NotAfter          (Get-Date).AddYears(2)

# 【2】记下连接时要用的指纹(Thumbprint)
$cert.Thumbprint

# 【3】仅将公钥导出为 .cer 以便上传(不包含私钥)
Export-Certificate -Cert $cert -FilePath 'C:\temp\M365-Inventory-Batch.cer'

把导出的 .cer 文件,在 Entra 管理中心的应用界面中,通过

管理 > 证书和密码 > 证书选项卡 > 上传证书

上传。上传的只有 .cer(公钥)。绝不能上传包含私钥的 .pfx

私钥放在哪里,要与任务计划程序的执行账户相匹配。

执行账户 证书存储 连接时的传递方式 补充
特定用户/服务账户 Cert:\CurrentUser\My(用该账户创建・导入) -CertificateThumbprint 除创建该证书的账户外,其他账户看不到
SYSTEM,或由多个账户共用 Cert:\LocalMachine\My -Certificate(自行加载后传入) 创建需要管理员权限。需要向执行账户授予私钥的读取权限

这里容易忽略的一点是,-CertificateThumbprint-CertificateSubjectName 只会在当前用户的证书存储中查找4 如果把证书放在 Cert:\LocalMachine\My 中,即使传入指纹也找不到。如果要使用本地计算机存储,需要自行加载后传给 -Certificate

# 放在 LocalMachine 中的情况,用这种方式
$cert = Get-ChildItem -Path 'Cert:\LocalMachine\My\A1B2C3D4E5F6...'
Connect-MgGraph -ClientId $clientId -TenantId $tenantId -Certificate $cert -NoWelcome

“本地能跑,唯独放到任务计划程序里就找不到证书”这类问题,几乎都是这种放置位置和传递方式不一致导致的。

5.4 从脚本中连接

# 无人值守用的连接(无需交互)
$connect = @{
    ClientId              = '11111111-2222-3333-4444-555555555555'
    TenantId              = '66666666-7777-8888-9999-000000000000'
    CertificateThumbprint = 'A1B2C3D4E5F6...'    # 位于执行账户 CurrentUser 存储中的证书
    NoWelcome             = $true
}
Connect-MgGraph @connect

# 确认连接形态:如果 AuthType 为 AppOnly,说明已经以应用专用方式连接成功
Get-MgContext | Format-List AppName, ClientId, TenantId, AuthType, Scopes

try {
    # 业务处理
}
finally {
    Disconnect-MgGraph
}

首次操作时,请在实际注册任务之前,用任务计划程序的执行账户运行上面的脚本进行确认。任务计划程序一侧的坑(执行账户、“不管用户是否登录都要运行”的设置、工作目录等)已整理在《任务计划程序的任务不执行、以 0x1 结束》中。

5.5 注意事项

有两点需要注意。

(1)委派与应用专用所需的权限是不同的东西。把原本用 -Scopes 'User.Read.All' 交互式运行的脚本改为无人值守时,需要在应用注册一侧以应用程序权限的形式重新赋予同类权限,并需要管理员同意。6

(2)证书是有期限的。证书到期当天夜间批处理全部失败,是实际中经常发生的事故。请把有效期登记到日历中,并把更新步骤文档化。关于凭据的保管,也请一并参考《PowerShell 中凭据的安全处理方式》。虽然用客户端密钥也能连接,但考虑到明文携带的风险和期限管理的繁琐,更推荐使用证书。

6. 数据获取方式 ── -All / -Filter / -Property

Graph 是一个以分页为前提的 API。默认只返回一页的数据,如果需要全部数据,就要加上 -All7

# 【不推荐】不考虑分页,在客户端筛选(慢・取太多・引发限流的原因)
Get-MgUser | Where-Object { $_.Department -eq '销售部' }

# 【推荐】在服务器端筛选、只取需要的字段、获取所有页
Get-MgUser -All -Filter "department eq '销售部'" `
           -Property Id, DisplayName, UserPrincipalName, AccountEnabled, Department |
    Select-Object DisplayName, UserPrincipalName, AccountEnabled

要点有三条。

  • -Filter 是 OData 表达式,在服务器端筛选。Where-Object 是在本地筛选,所以会先取全部数据,再把不需要的丢弃
  • -Property 限定列可以让响应变轻。有些默认不返回的属性,只要显式指定就会返回
  • -Property 中取得的字段,也要写进 Select-Object。获取和显示是分开的,只写一边的话会出现空白

如果要使用 startsWithendsWith 这类高级查询,或者只想要件数,需要同时使用 -ConsistencyLevel eventual-CountVariable7

# 只统计已启用的用户数(不获取全部数据,只取计数)
Get-MgUser -Filter 'accountEnabled eq true' -ConsistencyLevel eventual -CountVariable total -Top 1 | Out-Null
"已启用用户:$total 个"

-Top 1 | Out-Null 是个不太常见的写法,这里补充说明一下。-CountVariable 得到的是符合条件的全体件数,不受 -Top 值的影响。-Top 决定的只是单次响应中返回的对象数量。也就是说,-Top 1 并不是“只统计 1 件”的指定,而是为了取得计数而不得不发出一次请求时,用来把实际返回的数据量降到最低的指定。如果省略它,就会返回默认一页的用户对象,然后再用 Out-Null 丢弃。另外,-ConsistencyLevel eventual 是使用 -CountVariablestartsWith 等高级查询时必须指定的选项。7

大量访问会受到调节(限流),返回 HTTP 429。官方方针是“按照 Retry-After 响应头中的秒数等待后再重试”。8 SDK 的 cmdlet 会在内部进行一定程度的重试,但在循环处理数千条数据规模时,从根本上减少获取次数(用 -Filter-Property 筛选、一次性取得所需信息)更为可靠。重试设计本身请参考《PowerShell 的错误处理与重试设计》。

7. 三个常用配方

(1)把用户盘点结果导出为 CSV

# SignInActivity(最后一次登录时间)仅凭 User.Read.All 无法取得,
# 还需要另外具备 AuditLog.Read.All。如果不获取,就要把它从 -Property 中去掉
Connect-MgGraph -Scopes 'User.Read.All', 'AuditLog.Read.All' -NoWelcome

$users = Get-MgUser -All -Property Id, DisplayName, UserPrincipalName, AccountEnabled,
                              Department, JobTitle, CreatedDateTime, SignInActivity |
    Select-Object DisplayName, UserPrincipalName, Department, JobTitle, AccountEnabled,
                  @{ n = '创建日期'; e = { $_.CreatedDateTime } },
                  # 判定休眠账号时使用“成功的登录”。LastSignInDateTime 是
                  # 交互式登录的“尝试”,包含失败的情况,也不包含非交互式登录
                  @{ n = '最后一次登录成功'; e = { $_.SignInActivity.LastSuccessfulSignInDateTime } },
                  @{ n = '最后一次交互式登录尝试'; e = { $_.SignInActivity.LastSignInDateTime } },
                  @{ n = '最后一次非交互式登录'; e = { $_.SignInActivity.LastNonInteractiveSignInDateTime } }

# 包含中文的 CSV 用 UTF-8(带 BOM)保存,Excel 打开时才不会乱码。
# 编码名称因版本而异:7 及以后是 utf8BOM,5.1 是 UTF8(带 BOM)
$enc = if ($PSVersionTable.PSVersion.Major -ge 6) { 'utf8BOM' } else { 'UTF8' }
$users | Export-Csv -Path "D:\盘点\users_$(Get-Date -f yyyyMMdd).csv" -Encoding $enc -NoTypeInformation

输出的 CSV 列结构,会按 Select-Object 中书写的顺序和名称直接成为表头行。Export-Csv 默认会给所有字段加上引号,因此第一行是如下形式。

"DisplayName","UserPrincipalName","Department","JobTitle","AccountEnabled","创建日期","最后一次登录成功","最后一次交互式登录尝试","最后一次非交互式登录"

第二行开始,按相同顺序排列每个用户的值。如果期望的列出现空白,说明 -PropertySelect-Object 中有一处漏写,或者权限不足。特别是“最后一次登录成功”之后的三列如果全部为空,请怀疑是下文提到的 AuditLog.Read.All 权限不足。

SignInActivity 对于排查休眠账号很有效。不过关键在于看哪个字段LastSignInDateTime 记录的是交互式登录的尝试(包含失败),不包含应用或服务的非交互式登录。如果只凭这一项判断,攻击者的登录失败会让账号看起来“在使用”,而实际在运行的服务账号则可能被误判为“休眠”。判断休眠账号时,请使用能反映成功的交互式・非交互式登录的 LastSuccessfulSignInDateTime10不过只有这个属性无法通过 User.Read.All 取得,还需要额外具备 AuditLog.Read.All(此外还有租户一侧的许可证要求)。权限不足时会报错或者取值为空,如果不使用它,就把它从 -Property 中去掉。所需权限可以用 Find-MgGraphPermission 确认。10CSV 文件编码的处理方式,整理在《用 PowerShell 自动化 Excel・CSV 业务处理》中。

(2)汇总许可证的消耗情况

Connect-MgGraph -Scopes 'Organization.Read.All' -NoWelcome

Get-MgSubscribedSku | Select-Object `
    SkuPartNumber,
    @{ n = '购买数';   e = { $_.PrepaidUnits.Enabled } },
    @{ n = '已分配';   e = { $_.ConsumedUnits } },
    @{ n = '剩余';     e = { $_.PrepaidUnits.Enabled - $_.ConsumedUnits } } |
    Sort-Object 剩余

“明明还有多余的许可证却又追加购买”“离职人员的许可证没有被释放”,这类问题只要按月运行一次就能避免。

(3)离职处理(阻止登录与会话失效)

下面的代码是“委派(交互式登录)”方式的执行示例。指定了 -Scopes 就是这一点的标志,执行时会要求在浏览器中登录。要改为无人值守执行,需要按第 5 章所述改为应用专用方式。先整理一下两者的区别。

  委派(下方代码示例) 应用专用(无人值守执行)
以谁的身份运行 登录的负责人本人 应用(服务主体)自身
Connect-MgGraph 的指定 -ClientId + -Scopes -ClientId + -TenantId + -CertificateThumbprint
证书 不需要 必需(或客户端密钥)
权限种类 委派的权限 应用程序权限(需要管理员同意)6
额外需要的东西 登录人本人的 Entra 管理员角色(后述)11 对象为管理员时,需要为应用本身分配更高级角色11
适用场景 现场的个别处理、用 -WhatIf 做事前确认 夜间批处理、与人事系统联动
# ── 委派(交互式登录)方式的执行示例 ──
# 执行时会弹出登录界面。不需要证书
#
# 由于涉及写入,请使用专用的应用注册・专用作用域执行。
# 省略 -ClientId 会以 SDK 默认的共享应用进行连接,同意的权限会
# 累积到该应用(组织共享)上。像离职处理这种破坏性操作,
# 更应该把权限隔离到专用的应用注册中
#
# 每个操作所需的权限不同,因此把两个操作所需的权限一并要求
#   变更 accountEnabled → User.EnableDisableAccount.All(最小权限)
#   使登录会话失效 → User.RevokeSessions.All(最小权限)
# 两者也都可以用 User.ReadWrite.All 执行,但权限范围会更大
$connect = @{
    ClientId  = '99999999-aaaa-bbbb-cccc-dddddddddddd'   # 离职处理专用的应用注册
    TenantId  = '66666666-7777-8888-9999-000000000000'
    Scopes    = 'User.Read.All', 'User.EnableDisableAccount.All', 'User.RevokeSessions.All'
    NoWelcome = $true
}
Connect-MgGraph @connect

# Revoke-MgUserSignInSession 需要 Microsoft.Graph.Users.Actions
Import-Module Microsoft.Graph.Users.Actions

$upn = 'taro.yamada@example.co.jp'
$user = Get-MgUser -UserId $upn -Property Id, DisplayName, AccountEnabled

# 1. 阻止登录(删除应留出宽限期后再进行)
Update-MgUser -UserId $user.Id -AccountEnabled:$false

# 2. 使更新令牌和浏览器的会话 Cookie 失效
#    注意:已签发的访问令牌,在到期之前有可能仍然可用(后述)
Revoke-MgUserSignInSession -UserId $user.Id

Write-Host "已停止 $($user.DisplayName) 的登录"

这里有三点需要掌握。第一点是连接目标的应用注册。省略 -ClientId 会连接到 Microsoft Graph PowerShell SDK 的默认应用,同意的权限会记录在该组织共享的应用上。要实现第 4 章“按用途拆分应用注册”的原则,需要用 -ClientId 明确指定自家公司的应用注册。4

另一点是作用域。Graph 中每个操作都定义了所需的权限,并不是说“能写入用户就什么都能做”。accountEnabled 的变更和登录会话的失效各自拥有专用的最小权限,如果只同意其中一个就执行,即使连接成功,也会在中途的命令上出现权限不足的错误。各操作所需的权限,请通过 Find-MgGraphPermission 以及对应 Graph API 参考文档中的权限表来确认。119

接下来第三点,委派方式的执行仅凭作用域是不够的。如上所述,当以用户本人身份登录执行时,变更 accountEnabled需要 Microsoft Entra 的管理员角色。因为委派的权限只决定“应用能代表该用户做什么”,并不会提升登录用户本人的权限。即使同意了权限,如果没有相应角色,执行时仍会以 403 失败。文档规定,能对租户内所有管理员更新此属性的最小角色是 Privileged Authentication Administrator,一般来说,“需要比操作对象更高级的管理员角色”。11 由于离职处理的对象是普通用户还是管理员,所需的角色会有所不同,请事先决定好要为运维负责人的账号分配什么角色。即使是通过证书的应用专用方式执行,如果对象是管理员,也需要为应用本身分配更高级的角色11

如果要把这段处理改成夜间批处理,需要改动的只有连接部分

# ── 改为应用专用(无人值守执行)的情况 ──
# 事前准备:在应用注册的“API 权限 > 应用程序权限”中添加
#   User.Read.All / User.EnableDisableAccount.All / User.RevokeSessions.All
# 并授予管理员同意(5.2)。证书使用 5.3 中创建的那一份
$connect = @{
    ClientId              = '99999999-aaaa-bbbb-cccc-dddddddddddd'
    TenantId              = '66666666-7777-8888-9999-000000000000'
    CertificateThumbprint = 'A1B2C3D4E5F6...'   # 不指定 -Scopes
    NoWelcome             = $true
}
Connect-MgGraph @connect

# 从这里开始(Import-Module 〜 Revoke-MgUserSignInSession)与委派版完全相同

还需要准确理解 Revoke-MgUserSignInSession 的作用范围。这个命令使之失效的是更新令牌和浏览器的会话 Cookie已经签发的访问令牌,在到期之前有可能仍然可用9 如果要求立即阻断访问,前提是应用和资源要支持持续访问评估(CAE)。不要以为“执行了 revoke 就能立刻停止全部访问”,在重要场景中应与账号禁用配合使用,并把生效前的时间差考虑进去。

避免立即删除账号,先停止登录是实务中的定式。如果在邮箱或 OneDrive 的交接完成之前就删除,恢复起来会很麻烦。这种“无法挽回的操作”,最好用支持 -WhatIf 的自定义函数包装起来,先输出对象列表进行确认后再执行,这样比较安全(参见《PowerShell 的参数设计与模块化》)。

8. 实务定式(判断表)

论点 选项 判断标准
模块 Graph SDK / Entra PowerShell 以身份管理为中心・从 AzureAD 迁移选 Entra,广泛工作负载选 Graph SDK2
安装 全部安装 / 仅子模块 为了控制启动时间和依赖,按子模块单位安装3
认证(交互式) 交给管理员账号 / 最小作用域 盘点只用 .Read. 系权限。同意会保留在租户中4
认证(无人值守) 客户端密钥 / 证书 推荐使用证书。要事先定好期限管理和更新步骤6
权限粒度 一个应用包揽所有 / 按用途拆分应用注册 可以限定事故发生时的影响范围
列表获取 Where-Object / -Filter + -All + -Property 在服务器端筛选。获取量直接决定速度和稳定性7
429 对策 立即重试 / 遵循 Retry-After 官方方针。首先应该做的是减少获取次数8
危险操作 直接执行 / -WhatIf + 事先确认对象列表 离职处理・批量删除必须做到能目视确认对象

9. 总结

  • MSOnline 和 AzureAD 均已废弃。迁移目标是 Microsoft Graph PowerShell SDK,或者构建于其之上的 Microsoft Entra PowerShell。
  • 由于 Microsoft.Graph 是元模块,实务中只安装认证加上要用到的工作负载的子模块。
  • 连接以作用域最小化为原则。盘点用途不要求写入权限,并按用途拆分应用注册。
  • 无人值守执行采用应用注册加证书。委派与应用专用所需权限不同、证书有有效期,是事故的根源所在。
  • 数据获取要用 -All(分页)、-Filter(服务器端筛选)、-Property(限定字段)这三件套。429 时遵循 Retry-After
  • 仅仅按月运行盘点、许可证汇总、离职处理这三项,就能大幅减少许可证浪费和账号被遗忘放置这两大常见风险。

示例代码下载

本文涉及的代码,已整理成可直接运行的形式提供下载,其中包含连接、休眠账号提取、许可证汇总、离职处理。

下载示例代码(zip)

本文中的示例代码依赖于 Windows 环境和租户,因此没有进行实际运行验证。所有文件都已经过语法解析和 PSScriptAnalyzer 静态分析,但实际动作请务必在自己的验证环境中确认。

# 语法解析 + 静态分析(在非 Windows 环境下也能运行)
./Invoke-SampleTests.ps1

配置值(路径、服务器名、租户 ID 等)均为示例。请勿直接在生产环境中运行,务必根据自己公司的环境进行相应替换。

相关文章

相关咨询领域

合同会社小村软件承接 Microsoft 365 运维脚本向 Graph 的迁移、应用注册与权限设计的评审、盘点・离职处理等定型业务的自动化。

参考链接

  1. Microsoft Community Hub(Microsoft Entra Blog),Action required: MSOnline and AzureAD PowerShell retirement - 2025 info and resources。关于 MSOnline 和 AzureAD 这两个 PowerShell 模块于 2024 年 3 月 30 日被标记为不推荐使用、MSOnline 的废弃于 2025 年春季实施并于 2025 年 5 月 30 日停止提供、AzureAD 于 2025 年 3 月 30 日结束支持并随后废弃、迁移目标是 Microsoft Graph PowerShell SDK 及 Microsoft Entra PowerShell 的说明。  2

  2. Microsoft Learn,What is Microsoft Entra PowerShell?。关于 Microsoft Entra PowerShell 是构建于 Microsoft Graph PowerShell SDK 之上、以场景为导向的模块,可与 Graph PowerShell SDK 的 cmdlet 互操作,并提供有助于从 AzureAD 模块迁移的向后兼容选项的说明。GA(正式发布)的公告参见 Microsoft Entra PowerShell module now generally available(2025 年 3 月)。  2 3 4

  3. Microsoft Learn,Install the Microsoft Graph PowerShell SDK。关于 Microsoft.Graph 是包含一系列子模块的元模块、可以只单独安装所需的子模块、Microsoft.Graph.Authentication 是认证所必需的、以及所支持的 PowerShell 版本的说明。  2 3 4

  4. Microsoft Learn,Connect-MgGraph。关于通过 -Scopes 请求委派的权限、通过 -ClientId / -TenantId / -CertificateThumbprint 进行应用专用认证、-CertificateThumbprint 和 -CertificateSubjectName 会从当前用户的证书存储中获取证书(使用本地计算机存储时需要自行加载后传给 -Certificate)、用 Get-MgContext 确认当前连接信息、用 Disconnect-MgGraph 断开连接的说明。  2 3 4 5 6

  5. Microsoft Learn,Find Microsoft Graph PowerShell commands and permissions。关于用 Find-MgGraphCommand 查询命令所属模块・所需权限・对应 API,用 Find-MgGraphPermission 查询权限名称的说明。  2

  6. Microsoft Learn,Use app-only authentication with the Microsoft Graph PowerShell SDK。关于应用注册・应用程序权限・管理员同意这一流程、使用证书构建无人值守认证、以及委派的权限与应用程序权限的区别的说明。  2 3 4 5 6 7 8 9 10

  7. Microsoft Learn,Paging Microsoft Graph data in your app。关于 Microsoft Graph 的响应会分页、在 PowerShell SDK 中指定 -All 可获取所有页、相当于 $filter 和 $select 的 -Filter / -Property 在服务器端筛选、以及高级查询中的 -ConsistencyLevel eventual 与件数获取的说明。  2 3 4 5 6

  8. Microsoft Learn,Microsoft Graph throttling guidance。关于以资源为单位的限流会返回 HTTP 429、应等待响应的 Retry-After 响应头中指定的秒数后再重试、以及推荐从设计上减少请求数量本身的说明。  2 3 4

  9. Microsoft Learn,user: revokeSignInSessions (Microsoft Graph API)。关于登录会话失效所需的权限中,最小权限列出的是 User.RevokeSessions.All、需要管理员同意、失效对象是更新令牌和浏览器的会话 Cookie、已签发的访问令牌可能一直可用到到期为止(即时生效涉及持续访问评估)的说明。  2 3

  10. Microsoft Learn,signInActivity resource type。关于获取用户的 signInActivity 属性需要 AuditLog.Read.All 和 User.Read.All 两项权限、存在租户许可证要求、lastSignInDateTime 表示交互式登录的尝试(包含成功与失败),而 lastSuccessfulSignInDateTime 表示成功的交互式・非交互式登录、lastNonInteractiveSignInDateTime 表示非交互式登录的说明。  2

  11. Microsoft Learn,Update user (Microsoft Graph API)。关于用户更新所需的权限是按属性单位定义的,变更 accountEnabled 所列出的最小权限是 User.EnableDisableAccount.All,也可以用范围更大的 User.ReadWrite.All 执行的说明。以及委派场景下,除了合适的作用域之外还需要 Microsoft Entra 的管理员角色,能对租户内所有管理员更新 accountEnabled 的最小角色是 Privileged Authentication Administrator,一般需要比操作对象更高级的管理员角色,应用专用场景下如果对象是管理员,也需要为应用分配更高级的管理员角色的说明。  2 3 4 5

共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。

与本文相近的主题页面。以本文为起点,可以进一步了解相关服务和其他文章。

常见问题

汇总了咨询这一主题时常见的问题。

AzureAD 模块和 MSOnline 模块已经不能用了吗?
是的,两者都已废弃。MSOnline 和 AzureAD 这两个模块在 2024 年 3 月 30 日被标记为不推荐使用(deprecated),MSOnline 已于 2025 年 5 月 30 日停止提供,AzureAD 也在 2025 年 3 月 30 日结束支持并随后被废弃。即使脚本看起来还能运行,也随时可能停止工作。迁移目标是 Microsoft Graph PowerShell SDK,或者构建于其之上的 Microsoft Entra PowerShell 模块(2025 年 3 月正式发布)。
安装 Microsoft.Graph 模块很重,能不能装得轻一些?
可以。Microsoft.Graph 是一个元模块,下面挂载了大量子模块,如果整体安装,安装和加载都会花费较长时间。在实务中,现实的做法是只安装要用到的工作负载对应的子模块:用户管理用 Microsoft.Graph.Users,组用 Microsoft.Graph.Groups,认证则是必装的 Microsoft.Graph.Authentication,以此类推。可以用 Find-MgGraphCommand 查询某个命令属于哪个模块。
想从任务计划程序无人值守执行,但会弹出交互式登录界面。
请改用基于应用注册(服务主体)和证书的应用专用认证。在 Microsoft Entra ID 中注册应用,为所需的应用程序权限(Application permissions)取得管理员同意后,向 Connect-MgGraph 传入 -ClientId・-TenantId・-CertificateThumbprint,即可无需交互直接连接。证书比客户端密钥更安全,有效期管理也更明确。请务必把证书放在执行账户的证书存储中,并建立在到期前更新的运维机制。
Connect-MgGraph 的 -Scopes 应该指定什么?
只指定要执行的命令所要求的最小权限即可。需要什么权限可以用 Find-MgGraphPermission 或命令文档确认,如果只是读取,像 User.Read.All 这样的 .Read. 系权限就足够了。如果目的是盘点或审计,就不要要求写入权限。一旦同意的权限会被记录在租户中,因此“先同意 Directory.ReadWrite.All 再说”会成为未来的风险。按用途拆分应用注册、拆分权限才是安全的做法。
用 Get-MgUser 处理用户数很多的租户时,只能取到一部分。
这是因为 Microsoft Graph 的响应会分页。加上 -All 就会自动遍历所有页面并全部取回。此外,大量获取时可能会受到调节(限流)而返回 429 响应,因此基本做法是用 -Property 只取需要的字段,用 OData(-Filter)在服务器端筛选,而不是用客户端的 Where-Object 筛选。如果只想要件数,可以组合使用 -ConsistencyLevel eventual 与 -CountVariable,只取得数量。

作者简介

本文作者的个人简介页面。

Go Komura

小村软件有限公司 代表

以 Windows 软件开发、技术咨询与故障排查为中心,擅长难以复现的故障调查,以及既有资产仍在运行的项目。

返回博客列表