Microsoft Graph PowerShell 入门 ── AzureAD・MSOnline 废弃后的 Microsoft 365 运维
· Go Komura · PowerShell, Microsoft 365, Microsoft Entra ID, Microsoft Graph, 信息系统, 自动化, 运维改善, 安全
对于把 Microsoft 365 运维自动化的现场来说,2024 年到 2025 年间最大的变化就是AzureAD 模块和 MSOnline 模块的废弃。想必有不少信息系统人员用 Get-MsolUser 或 Get-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 |
这两种形态的差异,是本文中最容易引发事故的地方。画成图如下所示。
flowchart TD
subgraph D["委派 Delegated ── 交互式登录"]
U["管理员登录"] --> DS["已同意的委派作用域"]
U --> DR["本人拥有的管理员角色"]
DS --> DX["能做什么 =<br/>作用域与角色的交集"]
DR --> DX
end
subgraph A["应用专用 Application ── 无人值守执行"]
C["用证书认证"] --> AS["应用程序权限<br/>需要管理员同意"]
AS --> AX["能做什么 =<br/>被授予的权限本身"]
end
DX -.->|"改为无人值守时<br/>需要重新赋予权限"| AS
记忆方法只有一条。委派 = 以用户本人身份执行,应用专用 = 无人值守批处理。委派下“本人做不到的事,应用也做不到”;应用专用下“只要授予了应用,就永远能做到”。
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。默认只返回一页的数据,如果需要全部数据,就要加上 -All。7
# 【不推荐】不考虑分页,在客户端筛选(慢・取太多・引发限流的原因)
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。获取和显示是分开的,只写一边的话会出现空白
如果要使用 startsWith、endsWith 这类高级查询,或者只想要件数,需要同时使用 -ConsistencyLevel eventual 和 -CountVariable。7
# 只统计已启用的用户数(不获取全部数据,只取计数)
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 是使用 -CountVariable 或 startsWith 等高级查询时必须指定的选项。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","创建日期","最后一次登录成功","最后一次交互式登录尝试","最后一次非交互式登录"
第二行开始,按相同顺序排列每个用户的值。如果期望的列出现空白,说明 -Property 和 Select-Object 中有一处漏写,或者权限不足。特别是“最后一次登录成功”之后的三列如果全部为空,请怀疑是下文提到的 AuditLog.Read.All 权限不足。
SignInActivity 对于排查休眠账号很有效。不过关键在于看哪个字段。LastSignInDateTime 记录的是交互式登录的尝试(包含失败),不包含应用或服务的非交互式登录。如果只凭这一项判断,攻击者的登录失败会让账号看起来“在使用”,而实际在运行的服务账号则可能被误判为“休眠”。判断休眠账号时,请使用能反映成功的交互式・非交互式登录的 LastSuccessfulSignInDateTime。10不过只有这个属性无法通过 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。 - 仅仅按月运行盘点、许可证汇总、离职处理这三项,就能大幅减少许可证浪费和账号被遗忘放置这两大常见风险。
示例代码下载
本文涉及的代码,已整理成可直接运行的形式提供下载,其中包含连接、休眠账号提取、许可证汇总、离职处理。
本文中的示例代码依赖于 Windows 环境和租户,因此没有进行实际运行验证。所有文件都已经过语法解析和 PSScriptAnalyzer 静态分析,但实际动作请务必在自己的验证环境中确认。
# 语法解析 + 静态分析(在非 Windows 环境下也能运行)
./Invoke-SampleTests.ps1
配置值(路径、服务器名、租户 ID 等)均为示例。请勿直接在生产环境中运行,务必根据自己公司的环境进行相应替换。
相关文章
- PowerShell 中凭据的安全处理方式 ── 把明文密码逐出脚本
- Windows PowerShell 5.1 与 PowerShell 7 的区别 ── 企业内部脚本迁移实务指南
- 用 PowerShell 自动化 Excel・CSV 业务处理 ── 汇总・核对・报表输出的实务方案
- PowerShell 的错误处理与重试设计 ── 从 try/catch 失效的陷阱到 exit code、重试的实务定式
- 在 WinForms/WPF 应用中集成 Entra ID 认证 —— MSAL.NET 与 WAM Broker 的实务架构
- 任务计划程序的任务不执行、以 0x1 结束 ── 原因排查与安全的运维设计
相关咨询领域
合同会社小村软件承接 Microsoft 365 运维脚本向 Graph 的迁移、应用注册与权限设计的评审、盘点・离职处理等定型业务的自动化。
参考链接
-
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
-
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
-
Microsoft Learn,Install the Microsoft Graph PowerShell SDK。关于 Microsoft.Graph 是包含一系列子模块的元模块、可以只单独安装所需的子模块、Microsoft.Graph.Authentication 是认证所必需的、以及所支持的 PowerShell 版本的说明。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn,Connect-MgGraph。关于通过 -Scopes 请求委派的权限、通过 -ClientId / -TenantId / -CertificateThumbprint 进行应用专用认证、-CertificateThumbprint 和 -CertificateSubjectName 会从当前用户的证书存储中获取证书(使用本地计算机存储时需要自行加载后传给 -Certificate)、用 Get-MgContext 确认当前连接信息、用 Disconnect-MgGraph 断开连接的说明。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn,Find Microsoft Graph PowerShell commands and permissions。关于用 Find-MgGraphCommand 查询命令所属模块・所需权限・对应 API,用 Find-MgGraphPermission 查询权限名称的说明。 ↩ ↩2
-
Microsoft Learn,Use app-only authentication with the Microsoft Graph PowerShell SDK。关于应用注册・应用程序权限・管理员同意这一流程、使用证书构建无人值守认证、以及委派的权限与应用程序权限的区别的说明。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10
-
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
-
Microsoft Learn,Microsoft Graph throttling guidance。关于以资源为单位的限流会返回 HTTP 429、应等待响应的 Retry-After 响应头中指定的秒数后再重试、以及推荐从设计上减少请求数量本身的说明。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn,user: revokeSignInSessions (Microsoft Graph API)。关于登录会话失效所需的权限中,最小权限列出的是 User.RevokeSessions.All、需要管理员同意、失效对象是更新令牌和浏览器的会话 Cookie、已签发的访问令牌可能一直可用到到期为止(即时生效涉及持续访问评估)的说明。 ↩ ↩2 ↩3
-
Microsoft Learn,signInActivity resource type。关于获取用户的 signInActivity 属性需要 AuditLog.Read.All 和 User.Read.All 两项权限、存在租户许可证要求、lastSignInDateTime 表示交互式登录的尝试(包含成功与失败),而 lastSuccessfulSignInDateTime 表示成功的交互式・非交互式登录、lastNonInteractiveSignInDateTime 表示非交互式登录的说明。 ↩ ↩2
-
Microsoft Learn,Update user (Microsoft Graph API)。关于用户更新所需的权限是按属性单位定义的,变更 accountEnabled 所列出的最小权限是 User.EnableDisableAccount.All,也可以用范围更大的 User.ReadWrite.All 执行的说明。以及委派场景下,除了合适的作用域之外还需要 Microsoft Entra 的管理员角色,能对租户内所有管理员更新 accountEnabled 的最小角色是 Privileged Authentication Administrator,一般需要比操作对象更高级的管理员角色,应用专用场景下如果对象是管理员,也需要为应用分配更高级的管理员角色的说明。 ↩ ↩2 ↩3 ↩4 ↩5
相关文章
共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。
用 winget + PowerShell 自动化 PC 装机 ── 让操作手册可执行
本文整理了让新员工电脑的初始设置具备可复现性的方法,涵盖通过 winget 进行应用安装与 export/import、WinGet Configuration 的声明式配置、用 PowerShell 补充的设置,直至无人值守执行时的注意事项。
PowerShell 的安全加固 ── 日志・AMSI・语言模式・JEA
本文整理了在不禁止 PowerShell 的前提下安全使用它的实务要点,讲解启用脚本块日志与转录、禁用 AMSI 与旧版本引擎的绕过路径、通过语言模式加以限制,直至用 JEA 进行权限委任。
PowerShell 模块的内部分发与更新 ── PSResourceGet 与内部仓库
本文整理了如何摆脱复制共享文件夹中的 ps1 反复使用的运维方式。内容涵盖模块清单的编写方法、版本管理、用 PSResourceGet 构建内部仓库并进行分发与更新,直至与签名的配合使用。
PowerShell 中凭据的安全处理方式 ── 把明文密码逐出脚本
本文整理将 PowerShell 脚本中的明文密码迁移到安全存储的步骤。讲解 SecureString 的真实面貌与局限、Export-Clixml 基于 DPAPI 保存的原理,直至 SecretManagement/SecretStore 的适用场景。
PowerShell Remoting(WinRM)入门 ── 批量管理多台 Windows
PowerShell Remoting(WinRM)批量管理多台 Windows 的入门指南。整理其原理与端口 5985/5986、Enable-PSRemoting 会做的事情、工作组环境的 TrustedHosts、Invoke-Command 与 PSSession、...
相关主题
与本文相近的主题页面。以本文为起点,可以进一步了解相关服务和其他文章。
Windows 技术主题
汇整 KomuraSoft LLC 关于 Windows 开发、故障调查与既有资产活用文章的主题中心。
常见问题
汇总了咨询这一主题时常见的问题。
- 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,只取得数量。