当我在 6 月发布那篇多管理员审批现在也对通过 Graph API 的自动化强制执行的短讯后,它成了本站阅读量最高的页面之一,随后收到的邮件都在问同一件事:不是“改了什么”,而是“怎么用脚本实现”。本文就是那份完整的演练。它涵盖截至 2026 年 10 月多管理员审批 (MAA) 保护的内容、如何设计访问策略和审批者组而不把自己锁在门外、人工审批者在管理中心的确切点击路径,以及大多数读者真正想看的部分:一个创建受保护对象的脚本如何收到审批请求、如何等待、审批者如何通过 Graph 批准、脚本又如何完成剩余工作。这里的每个终结点、标头、权限和限制都已对照 Microsoft Learn 核实。凡是加入我个人观点的地方,我都会明说。
多管理员审批是什么,为什么现在也覆盖 Graph 自动化
根据 Microsoft Learn 概述,MAA 访问策略“要求第二个管理帐户在更改生效之前批准该更改”。一条访问策略指定一种受保护的配置文件类型,以及允许批准该类型更改的帐户组。当任何帐户更改受保护资源时,Intune 会暂挂该更改,直到另一个帐户显式批准;审批者也可以拒绝。业务理由、审批者备注和决定都会记入 Intune 审核日志。
在 2026 年 6 月之前,只有交互式(委托)管理员操作会被拦截。如果服务主体用仅应用令牌执行同样的擦除或脚本部署,就会直接放行。Microsoft 在 2026 年 6 月 22 日当周的 What’s new 条目中补上了这个缺口:“多管理员审批 (MAA) 现在适用于自动化通过 Microsoft Graph API 发出的 API 调用,而不仅是交互式管理员操作。”随之而来的有两道护栏。对 API 调用的强制执行只适用于已经配置了 MAA 访问策略的租户,它不会在任何地方自动启用 MAA。另外,根据 Graph API 操作指南,“MAA 只适用于修改受保护资源的操作(POST、PATCH、PUT、DELETE)。只读操作 (GET) 不受影响。”
至于 Microsoft 为什么这么做,只要用攻击者的思路想一想就明白了。一个持有 DeviceManagementManagedDevices.ReadWrite.All 的应用,其客户端密码一旦泄露,过去就等于一个全租户范围的擦除按钮。现在同样的调用只会生成一条审批请求,必须有人读过之后才会发生任何事。
受保护的资源
概述页面列出了七种资源类型。Graph 的 operationApprovalPolicyType 枚举包含更多值(autopilot、windows365、endpointPrivilegeManagement、filter、appProtectionPolicy 等),但枚举只是服务当前实际强制范围的超集。只有文档记载的这七种受保护,应用保护策略被明确排除,而修正 (remediations) 不在列表中。
| 资源(配置文件类型) | 触发审批的操作 | 自何时起(依据 What’s new) |
|---|---|---|
| 应用 | 应用部署:创建、编辑、删除、分配。不适用于应用保护策略。 | 2023 年 8 月 28 日当周正式发布;2024 年 8 月 (2408) 增加 Windows 与非 Windows 的范围划分 |
| 脚本 | 向 Windows 设备部署 PowerShell 脚本 | Graph 架构中最初的两种策略类型之一(app、script) |
| 设备操作 | 擦除、停用和删除 | 擦除于 2025 年 8 月 18 日当周 (2508) 加入;停用和删除列于概述页面 |
| 基于角色的访问控制 | 对角色的任何更改:权限、管理员组、成员组分配 | 2025 年 8 月 18 日当周 (2508) |
| 租户配置 | 设备类别:创建、编辑、删除 | 2025 年 9 月 15 日当周 (2509) |
| 合规性策略 | 创建和管理合规性策略 | 2026 年 3 月 2 日当周 (2602) |
| 配置策略 | 通过设置目录创建的策略 | 2026 年 3 月 2 日当周 (2602) |
| 访问策略(MAA 自身) | 对访问策略的任何更改,包括其排除项 | 自动受保护;不能作为配置文件类型选择 |
关于这张表有两点说明。“受保护资源的所有操作都受保护,包括但不限于:编辑、创建、修改、删除、分配”是 Microsoft 的原话,所以对现有应用的分配更改也算在内。另外,2026 年 6 月对仅应用 Graph 调用的强制执行同时适用于每一行;自动化没有按类型单独选择加入的选项。
先决条件、角色和许可
Microsoft 在 MAA 工作流中描述了三种角色,这个区分对自动化很重要,因为服务主体只可能是第三种。
访问策略管理者。 创建和管理访问策略。Microsoft 建议使用自定义 Intune 角色,包含自定义角色参考中 Multi Admin Approval 组的四项权限(Create access policy、Read access policy、Update access policy、Delete access policy),而不是使用特权级的 Intune Administrator Entra 角色。该组的第五项权限 Approval for Multi Admin Approval,是第二位管理员批准新建或修改访问策略所需要的。
审批者。 必须同时满足三个条件:是分配给该访问策略的审批者组的成员;拥有所审批类型的资源级 Read 权限(例如批准设备删除需要 ManagedDevices/Read);并且审批者组本身必须作为成员组添加到至少一个 Intune 角色分配中。如果该组没有分配到角色,Microsoft 表示“审批者组成员会被定期从组中移除”,以我的经验这是审批莫名失效最常见的原因。该组必须是安全组(通讯组列表、Microsoft 365 组和启用邮件的安全组会“静默地无法解析审批者成员身份”),成员应为直接成员,因为嵌套成员身份“可能导致不可靠的行为”。
更改请求者。 需要该操作对应的普通 Intune RBAC 权限(MobileApps/Create、RemoteTasks/Wipe)。同一个帐户先提交请求,之后再选择 Complete。对自动化而言这就是你的服务主体,而 Microsoft 说得很明确:“应用程序无法批准或拒绝 MAA 请求——只有交互式管理员帐户才能批准请求。”
任何人都不能批准自己的请求,包括全局管理员和 Intune 管理员。默认情况下每位参与的管理员都需要 Intune 许可证;Allow access to unlicensed admins 设置可以解除这一要求,但 Microsoft 警告该设置不可逆。
Graph 权限
以下是 beta 参考页面记载的权限。所有 MAA 专用内容都在 beta 中;v1.0 参考里没有 operationApprovalPolicy 或 operationApprovalRequest 资源,并且 Microsoft 的标准提示同样适用:Intune beta API 受支持,但比 v1.0 变动更频繁。
| 调用 | 文档记载的权限(委托与应用程序) |
|---|---|
列出、获取、创建、更新、删除 operationApprovalPolicies | DeviceManagementRBAC.Read.All、DeviceManagementRBAC.ReadWrite.All |
列出或获取 operationApprovalRequests | DeviceManagementConfiguration.Read.All、DeviceManagementConfiguration.ReadWrite.All、DeviceManagementRBAC.Read.All、DeviceManagementRBAC.ReadWrite.All |
approve、reject、cancelMyRequest、retrieveMyRequests、retrieveRequestStatus | DeviceManagementConfiguration.Read.All、DeviceManagementRBAC.Read.All |
创建 deviceManagementScript(实战示例) | DeviceManagementScripts.ReadWrite.All 或 DeviceManagementConfiguration.ReadWrite.All |
列出 auditEvents (v1.0) | DeviceManagementApps.Read.All、DeviceManagementApps.ReadWrite.All |
Graph 权限是必要条件,但不是充分条件。Intune RBAC 检查仍会在其上运行,所以一个拥有 DeviceManagementConfiguration.ReadWrite.All 却没有 Intune 角色分配的应用,与拥有相同范围的委托管理员表现并不相同。
设计访问策略
需要多少条策略
每条访问策略只支持一种配置文件类型,所以一个想要保护应用、脚本、设备操作、RBAC、合规性和配置策略的租户至少需要六条策略。我的做法是每种类型一条策略、共用一个终结点审批者组,再加一条单独的 Role 类型策略,放到最后创建。只有在两个不同团队相互审批时,我才会按平台拆分应用策略(2024 年 8 月的版本允许把应用策略限定为 Windows 应用、非 Windows 应用或两者)。
审批者组
为每个审批层级创建一个专用安全组,直接分配到一个 Intune 角色(带有资源级 Read 权限的自定义角色就够了),并保持成员为直接成员。命名时让审核日志容易阅读:SG-Intune-MAA-Approvers-Endpoint、SG-Intune-MAA-Approvers-RBAC。每个组至少放两名审批者,如果你采用跟随太阳的轮班模式,就让他们分布在不同时区,因为请求会过期。
紧急访问的考量
MAA 没有紧急绕过开关,Intune 也不发送通知,所以要为三种故障模式做好准备。
- 所有审批者都不在。 请求会在 3 天后过期。缓解方式是增大组规模并安排值班轮换,而不是绕过。
- Role 死锁。 Role 访问策略保护所有 RBAC 更改,包括审批者组所需的角色分配。Microsoft 记载的恢复步骤是:在 Tenant administration > Multi Admin Approval > Access policies 下删除 Role 策略,等待 3 到 5 分钟,在 Tenant administration > Roles 下修正分配,然后重新创建策略。避免方法是在启用 Role 类型之前先配置好其他所有策略并验证 RBAC。
- 无法等待的自动化。 使用 Exclusions 选项卡把某个企业应用从一条策略中排除。排除项只对仅应用令牌生效(同一应用的委托调用仍会被拦截),每条访问策略最多 50 个应用,需要第二位管理员批准,并且每次添加、移除或修改都会记入审核日志。Microsoft 的警告很直白:每个排除项“都会在审批工作流中制造一个缺口,一旦被排除的应用遭到入侵,就可能被利用”。
我对排除项的规则是:只用于我无法修改的供应商工具,只限定到它需要的那一条策略,用证书凭据取代客户端密码,并配置一条面向工作负载标识的条件访问策略来阻止该服务主体从已知 IP 范围之外访问。该策略需要 Workload Identities Premium 许可,且只覆盖在你租户中注册的单租户服务主体,但对于一个无需审批就能擦除设备的应用来说,这笔许可费用是值得的。
创建策略
在管理中心:Tenant administration > Multi Admin Approval > Access policies > Create。在 Basics 中设置名称和唯一的 Profile type;在 Approvers 中选择 Add groups;在 Exclusions 中可选地 Add enterprise applications;在 Review + submit for approval 中输入 Business justification 并选择 Submit for approval。第二位拥有 Approval for Multi Admin Approval 权限的管理员批准后,第一位管理员回到该策略并选择 Complete。
通过 Graph 则是一个 POST。因为访问策略自身也受保护,这个调用同样会经历下文描述的审批流程。
POST https://graph.microsoft.com/beta/deviceManagement/operationApprovalPolicies
Content-Type: application/json
{
"@odata.type": "#microsoft.graph.operationApprovalPolicy",
"displayName": "MAA - Windows scripts",
"description": "Second-admin approval for PowerShell script deployments",
"policyType": "script",
"policyPlatform": "notApplicable",
"policySet": {
"@odata.type": "microsoft.graph.operationApprovalPolicySet",
"policyType": "script",
"policyPlatform": "notApplicable"
},
"approverGroupIds": [ "<approver security group object ID>" ]
}GET https://graph.microsoft.com/beta/deviceManagement/operationApprovalPolicies 列出现有策略。displayName 上限为 128 个字符,description 上限为 1024 个字符。
人工管理员的审批工作流
提交
请求者按正常的创建或编辑流程操作。在保存前的最后一页会出现 Business justification 字段;请认真填写,因为这是审批者要读的内容,然后提交。如果同一对象已有待处理的请求,Intune 会用一条消息阻止新请求。状态可在 Tenant administration > Multi Admin Approval > My requests 下查看,待处理的请求也可以在那里用 Cancel request 撤回。
批准或拒绝
审批者前往 Tenant administration > Multi Admin Approval > Received requests(概述页面也把这个列表称为 All requests),或前往整合的 Tenant administration > Admin tasks 窗格,后者会把 MAA 请求与 Defender 安全任务、Endpoint Privilege Management 提升请求并列显示,打开它需要 Organization > Read 权限。选择 Business justification 链接,阅读更改内容,输入 Approver notes,然后选择 Approve request 或 Reject request。备注会回流到请求者的 My requests 视图,这也是传达拒绝原因的方式。自 2026 年 4 月 27 日当周(服务版本 2604)起,当 Change Review Agent 有建议时,My requests 和 All requests 选项卡还会为 Windows PowerShell 脚本请求显示 Agent Response 列;该集成目前为预览版。
完成
批准并不会应用更改。原始请求者需要打开该请求并选择 Complete;Intune 随后处理它,状态变为 Completed,控制台通知会确认成功或失败。自动化把这一步重现为带审批码的重新提交。
生命周期与过期
| 管理中心状态 | Graph status 值 | 含义 |
|---|---|---|
| Needs approval | needsApproval | 等待审批者 |
| Approved | approved | 已批准;等待请求者执行 Complete |
| Completed | completed | 更改已应用 |
| Rejected | rejected | 被审批者拒绝;如仍需要请提交新请求 |
| Canceled | cancelled | 由请求者撤回 |
| Expired | expired | 3 天内未被进一步处理;必须重新提交 |
Microsoft 的原话是:“如果请求在 3 天内没有被进一步处理,它会变为 Expired,并且必须重新提交。每次状态变化在变化后最多可见 30 天。”每个请求还带有 expirationDateTime,自动化应当监视这个值,而不是把 72 小时写死。
自动化:Graph API 工作流
终结点与 API 版本
下面的一切都在 beta 终结点上。目前没有 v1.0 等价物,所以请把代码固定到这些 URL,并关注 Intune What’s New 系列,以便在它们迁移的那天及时跟进。
| 用途 | 请求 |
|---|---|
| 列出访问策略 | GET /beta/deviceManagement/operationApprovalPolicies |
| 创建访问策略 | POST /beta/deviceManagement/operationApprovalPolicies |
| 列出审批请求 | GET /beta/deviceManagement/operationApprovalRequests |
| 获取单个请求 | GET /beta/deviceManagement/operationApprovalRequests/{id} |
| 我自己的请求 | GET /beta/deviceManagement/operationApprovalRequests/retrieveMyRequests |
| 对象是否被待处理请求锁定 | POST /beta/deviceManagement/operationApprovalRequests/retrieveRequestStatus,附带 entityId 和 entityType |
| 批准 | POST /beta/deviceManagement/operationApprovalRequests/{id}/approve |
| 拒绝 | POST /beta/deviceManagement/operationApprovalRequests/{id}/reject |
| 取消我的请求 | POST /beta/deviceManagement/operationApprovalRequests/cancelMyRequest,附带 { "id": "..." } |
| 审核跟踪 | GET /v1.0/deviceManagement/auditEvents |
仅应用与委托的区别
两种令牌类型的审批协议完全相同;标头和状态码不会变化。变化的是谁可以做什么。
- 仅应用(客户端凭据)主体可以提交受保护的更改、读取请求状态、用审批码重新提交,并且可以被策略排除。它不能批准或拒绝,它创建的请求会在
requestor.application中记录该应用。 - 委托令牌以已登录的管理员身份行事。它可以提交、读取,并且在该管理员属于审批者组并持有资源级 Read 权限时,可以批准或拒绝他人的请求。它永远不能批准自己的请求,而且即使应用被排除,排除项也不适用于委托调用。
由此得出的设计结论是:你的流水线身份负责提交,人工负责批准(在门户中,或从其本人登录的会话发出委托 Graph 调用),再由流水线身份完成。不要再建第二个服务主体充当“审批者”;Microsoft 说这行不通,而且也违背了初衷。
第 1 步:带理由标头提交
在修改请求上加上 x-msft-approval-justification。该值必须经过 Base64 编码;Microsoft 的示例值 YXBwIG9ubHkgdGVzdA== 解码后是 app only test。下面的示例创建一个 Windows PowerShell 脚本,其标头解码后为 CHG0012345: deploy pilot script to Ring 0。
POST https://graph.microsoft.com/beta/deviceManagement/deviceManagementScripts
Content-Type: application/json
x-msft-approval-justification: Q0hHMDAxMjM0NTogZGVwbG95IHBpbG90IHNjcmlwdCB0byBSaW5nIDA=
{
"@odata.type": "#microsoft.graph.deviceManagementScript",
"displayName": "BHG - Pilot script",
"description": "Created by automation under Multi Admin Approval",
"scriptContent": "V3JpdGUtT3V0cHV0ICJIZWxsbyBXb3JsZCI=",
"runAsAccount": "system",
"enforceSignatureCheck": false,
"fileName": "Pilot.ps1",
"runAs32Bit": false,
"roleScopeTagIds": ["0"]
}不带该标头时,调用会失败并提示 x-msft-approval-justification 标头为必填;操作指南把这种情况描述为 HTTP 400。
第 2 步:读取 412 并保存审批码
大多数脚本就是在这里出问题的。成功的提交不会返回 201 Created,而是返回 HTTP 412 Precondition Failed,外层 Graph 错误代码为 BadRequest,响应中带有 x-msft-approval-code 标头。Microsoft 的指引是:“把这个标头的存在加上 HTTP 412 一起作为 MAA 已接受请求并创建审批请求的信号。”请保存方法、URL、正文和这个代码;稍后四样都要用到。
HTTP/1.1 412 Precondition Failed
x-msft-approval-code: aabb1234-5678-9012-abcd-ef0123456789
Content-Type: application/json
{
"error": {
"code": "BadRequest",
"message": "{ \"Message\": \"Approval Required. Request Approval using the request ID returned as part of the x-msft-approval-code response header. x-msft-approval-code: aabb1234-5678-9012-abcd-ef0123456789 ...\" }"
}
}第 3 步:等待、轮询、通知
Intune 不会通知任何人,所以你的脚本必须代劳。审批码就是请求 ID,因此你可以直接读取该请求,也可以使用 Microsoft 在操作指南中展示的筛选形式。把业务理由和指向 Tenant administration > Multi Admin Approval 的提示发到审批者真正会看的地方,并用 expirationDateTime 决定何时升级。
GET https://graph.microsoft.com/beta/deviceManagement/operationApprovalRequests/aabb1234-5678-9012-abcd-ef0123456789GET https://graph.microsoft.com/beta/deviceManagement/operationApprovalRequests?$filter=requestId eq 'aabb1234-5678-9012-abcd-ef0123456789'在提交之前,retrieveRequestStatus 可以告诉你目标对象是否已被他人的待处理请求锁定(entityLocked: true),这样你可以快速失败,而不是撞上“已有待处理请求”的错误。
第 4 步:通过 Graph 批准
人工审批者可以用委托令牌从 Graph 执行操作,而不必打开门户。justification 上限为 1024 个字符;approvalSource 为可选项,默认值是 unknown。
POST https://graph.microsoft.com/beta/deviceManagement/operationApprovalRequests/aabb1234-5678-9012-abcd-ef0123456789/approve
Content-Type: application/json
{
"justification": "Approved per CHG0012345 after reviewing the script content in the request pane",
"approvalSource": "adminConsole"
}拒绝的形式相同,只是目标是 /reject。两者都返回 200 OK 和一个字符串正文。这些操作的权限表虽然列出了应用程序权限,但操作指南明确表示应用程序无法批准或拒绝,所以请把委托路径视为唯一可行的路径。
第 5 步:带审批码重新提交
一旦 status 变为 approved,就用 x-msft-approval-code 取代理由标头,原样重放同一请求。这相当于请求者在 API 层面选择 Complete,会返回正常的 201 Created 和新对象。
POST https://graph.microsoft.com/beta/deviceManagement/deviceManagementScripts
Content-Type: application/json
x-msft-approval-code: aabb1234-5678-9012-abcd-ef0123456789
{ ...the same body as Step 1... }PowerShell 完整实战示例
这里使用 Microsoft Graph PowerShell SDK 的 Connect-MgGraph 和 Invoke-MgGraphRequest。我在 Microsoft.Graph.Beta.DeviceManagement.Administration 参考中没有找到操作审批终结点的专用 cmdlet,而 Invoke-MgGraphRequest 正是 Microsoft 记载的、用于“尚无等价 cmdlet 的 API”的途径。-SkipHttpErrorCheck 让 412 不会抛出异常,-ResponseHeadersVariable 和 -StatusCodeVariable 则捕获标头和状态码。
第一个脚本用证书以仅应用方式连接,提交一个 Windows PowerShell 脚本,等待批准,然后完成。
#Requires -Modules Microsoft.Graph.Authentication
<#
Submit-IntuneScriptWithMaa.ps1
App-only automation that creates a Windows PowerShell script in Intune under Multi Admin Approval.
The app registration needs DeviceManagementScripts.ReadWrite.All (create the script) and
DeviceManagementConfiguration.Read.All or DeviceManagementRBAC.Read.All (read approval requests).
#>
param(
[Parameter(Mandatory)] [string] $TenantId,
[Parameter(Mandatory)] [string] $ClientId,
[Parameter(Mandatory)] [string] $CertificateThumbprint,
[string] $Justification = "CHG0012345: deploy pilot script to Ring 0",
[int] $PollIntervalSeconds = 300
)
Connect-MgGraph -ClientId $ClientId -TenantId $TenantId -CertificateThumbprint $CertificateThumbprint -NoWelcome
$uri = "https://graph.microsoft.com/beta/deviceManagement/deviceManagementScripts"
$scriptText = 'Write-Output "Hello from Intune"'
$body = @{
"@odata.type" = "#microsoft.graph.deviceManagementScript"
displayName = "BHG - Pilot script"
description = "Created by automation under Multi Admin Approval"
scriptContent = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($scriptText))
runAsAccount = "system"
enforceSignatureCheck = $false
fileName = "Pilot.ps1"
runAs32Bit = $false
roleScopeTagIds = @("0")
} | ConvertTo-Json -Depth 5
# Step 1: submit with the Base64 justification header. Expect HTTP 412 plus x-msft-approval-code.
$encodedJustification = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($Justification))
$null = Invoke-MgGraphRequest -Method POST -Uri $uri -Body $body -ContentType "application/json" `
-Headers @{ "x-msft-approval-justification" = $encodedJustification } `
-SkipHttpErrorCheck -ResponseHeadersVariable responseHeaders -StatusCodeVariable statusCode
if ($statusCode -eq 201) {
Write-Output "Created immediately: scripts are not MAA-protected in this tenant, or this app is excluded."
return
}
# Step 2: the 412 is the success signal. Pull the approval code out of the response headers.
$codeKey = $responseHeaders.Keys | Where-Object { $_ -ieq "x-msft-approval-code" } | Select-Object -First 1
if ($statusCode -ne 412 -or -not $codeKey) {
throw "Unexpected HTTP $statusCode without an approval code. Check Graph permissions and Intune RBAC."
}
$approvalCode = @($responseHeaders[$codeKey])[0]
Write-Output "Approval request $approvalCode created. Waiting for a second administrator."
# Step 3: notify your approvers here (ticket, chat, email). Intune sends nothing on its own.
# Then poll the request by ID until it leaves needsApproval. The service expires it after 3 days.
$requestUri = "https://graph.microsoft.com/beta/deviceManagement/operationApprovalRequests/$approvalCode"
do {
Start-Sleep -Seconds $PollIntervalSeconds
$request = Invoke-MgGraphRequest -Method GET -Uri $requestUri
Write-Output ("{0:u} status={1} expires={2}" -f (Get-Date), $request.status, $request.expirationDateTime)
} while ($request.status -eq "needsApproval")
if ($request.status -ne "approved") {
throw "Request $approvalCode ended as '$($request.status)'. Approver notes: $($request.approvalJustification)"
}
# Step 5: replay the identical request with the approval code. This is the API form of 'Complete'.
$created = Invoke-MgGraphRequest -Method POST -Uri $uri -Body $body -ContentType "application/json" `
-Headers @{ "x-msft-approval-code" = $approvalCode }
Write-Output "Script created with id $($created.id). Approved by $($request.approver.user.displayName)."审批者这一半以交互方式运行,由审批者组成员登录执行。它列出等待中的请求,显示是谁提出的以及理由,并在审批者已在管理中心阅读过更改之后批准其中一条。
# Approve-IntuneMaaRequests.ps1 - delegated session for a human approver
Connect-MgGraph -Scopes "DeviceManagementRBAC.Read.All" -NoWelcome
$all = Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/beta/deviceManagement/operationApprovalRequests"
$pending = @($all.value | Where-Object { $_.status -eq "needsApproval" })
foreach ($r in $pending) {
$who = if ($r.requestor.application.displayName) { $r.requestor.application.displayName } else { $r.requestor.user.displayName }
Write-Output "$($r.id) from $who types=$($r.requiredOperationApprovalPolicyTypes -join ',') expires $($r.expirationDateTime)"
Write-Output " justification: $($r.requestJustification)"
}
$id = Read-Host "Request ID to approve"
$decision = @{
justification = "Approved per CHG0012345 after reviewing the script content in the admin center"
approvalSource = "adminConsole"
} | ConvertTo-Json
Invoke-MgGraphRequest -Method POST -Body $decision -ContentType "application/json" `
-Uri "https://graph.microsoft.com/beta/deviceManagement/operationApprovalRequests/$id/approve"脚本对象创建后,为其分配目标,并在测试设备上强制执行一次 Intune 策略同步来观察它落地。分配同样是对受保护资源的修改,所以添加组时也要预期再来一次 412 往返。
如果你把一个每晚发布 Win32 应用更新的 Azure Automation Runbook 迁移到这个模式,第一周可以预期两点收获。Runbook 需要一个以审批码为键的独立恢复作业,这样它就不必让沙盒空等几个小时;审批者则会希望理由以变更工单号开头,而不是描述更改本身,因为工单才是他们真正会打开的东西。
运营指南
SLA、过期与通知
把内部审批 SLA 设定在 3 天过期期限之内并留足余量。我的做法是应用和脚本四个工作小时,设备操作和 RBAC 更改一小时,并在工单系统中指定一名值班审批者。由于 Intune 不发送通知,通知器要放在提交脚本里,再辅以一个计划作业,调用 retrieveMyRequests 或列表终结点,把仍处于 needsApproval 且距 expirationDateTime 不足 24 小时的请求升级处理。也别忘了 30 天的可见窗口:过了这段时间请求就会从控制台消失,所以凡是以后要当作证据的内容都要先导出。
审核与报告
Graph 操作指南确认,MAA 事件“包括批准、阻止、放行、添加排除项和移除排除项”都会记录到现有的 Intune 审核日志中。在管理中心的位置是 Tenant administration > Audit logs,可用 Add filters > Category 和 Activity 筛选;Export 会写出 CSV,同一窗格还能把日志路由到 Azure Monitor,以获得超出控制台显示范围的保留期。根据审核日志文档,查看需要 Intune Administrator 角色或带有 Audit data > Read 权限的 Intune 角色。在编程层面,GET https://graph.microsoft.com/v1.0/deviceManagement/auditEvents 返回两年的事件;每个事件的 actor 块带有 applicationId、servicePrincipalName 和 userPrincipalName,这就是在报告中区分自动化驱动审批与人工审批的方法。
常见错误
| 现象 | 可能原因 | 修复 |
|---|---|---|
HTTP 400 并提示 x-msft-approval-justification 为必填 | 受保护资源,缺少标头 | 加上 Base64 标头后重发 |
HTTP 412 Precondition Failed,代码 BadRequest,带 x-msft-approval-code 标头 | 预期行为:审批请求已创建 | 保存代码、轮询、通知、重新提交 |
| HTTP 403 Forbidden | 请求者缺少 Graph 权限或 Intune RBAC;Microsoft 在 2026 年 6 月的公告中也把 MAA 拦截描述为 403 | 检查应用的权限和角色分配,再检查排除项 |
“该对象已有待处理的请求”或 entityLocked: true | 同一对象已有另一条打开的请求 | 等待它结束,或请其请求者取消 |
状态为 expired | 3 天内无人处理 | 带理由标头重新提交;会得到新的代码 |
| 审批者看不到或无法批准请求 | 组未分配到 Intune 角色、不是安全组、嵌套成员身份,或缺少资源级 Read 权限 | 按先决条件清单修正该组 |
| 批准自己的请求失败 | 设计上不允许 | 由另一位管理员批准 |
| 启用 Role 策略后 RBAC 更改被阻止 | Role 死锁 | 删除 Role 策略,等待 3 到 5 分钟,修正 RBAC,再重新创建 |
| 带审批码重新提交失败 | 请求与原始请求不同,或时间窗口已过 | Microsoft 说要重新提交同一请求;请原样并及时重放 |
应该怎么做
- 打开 Tenant administration > Multi Admin Approval > Access policies,列出活动的策略。如果一条都没有,API 强制执行暂时与你无关,这正是有计划地设计它、而不是被动继承的时机。
- 在 Entra ID > Enterprise apps 下清点所有拥有 Intune 写入权限的服务主体,并把每一个映射到它触及的受保护类型(应用、脚本、设备操作、RBAC、合规性、配置、设备类别)。
- 先修好审批者组:只用安全组、直接成员、每组至少两人,每个组都直接分配到一个带有资源级 Read 权限的 Intune 角色。
- 为每种配置文件类型创建一条访问策略。Role 类型留到其他类型验证完毕之后,以避免死锁。
- 更新所有修改受保护类型的脚本和 Runbook:发送
x-msft-approval-justification(Base64),把带x-msft-approval-code的 HTTP 412 当作成功,持久化方法、URL、正文和代码,轮询operationApprovalRequests/{id},通知审批者,再用x-msft-approval-code重放。 - 有意识地决定排除项:只用于无法修改的供应商工具,每个只对应一条策略,使用证书凭据,配置工作负载标识的条件访问策略,并每季度复核清单。
- 把审批 SLA 写下来,安排审批者值班,并把上面的委托 PowerShell 或 Admin tasks 窗格交给审批者。
- 把 Intune 审核日志路由到 Azure Monitor,并构建一份报告,按
actor.applicationId与actor.userPrincipalName拆分 MAA 的批准、阻止和放行事件。 - 完整试运行一遍:通过 412 往返创建一个脚本,分配它,在测试设备上强制同步,并确认审核条目的呈现方式符合审计人员的预期。
- 把代码固定到 beta URL,并关注 Intune What’s New 系列,留意迁移到 v1.0 以及新增受保护类型的消息;枚举已经透露了 Microsoft 的方向。
需要多一双手吗?Big Hat Group 为美国和加拿大的组织提供 Microsoft Intune 咨询服务,从两周的健康检查到完整迁移均可覆盖。预约一次探索通话,聊聊你的环境。
参考来源
- Use Multi Admin Approval in Intune - Microsoft Intune
- Use Multi Admin Approval with the Microsoft Graph API - Microsoft Intune
- What’s new in Microsoft Intune(2026 年 4 月 27 日和 6 月 22 日当周)
- What’s new in Microsoft Intune archive(2023 年 8 月、2024 年 8 月、2025 年 8 月和 9 月、2026 年 3 月条目)
- Create a custom role in Intune - Microsoft Intune
- Centrally manage Admin Tasks - Microsoft Intune
- Audit changes and events in Microsoft Intune - Microsoft Intune
- operationApprovalPolicy resource type - Microsoft Graph beta
- Create operationApprovalPolicy - Microsoft Graph beta
- List operationApprovalPolicies - Microsoft Graph beta
- operationApprovalRequest resource type - Microsoft Graph beta
- List operationApprovalRequests - Microsoft Graph beta
- Get operationApprovalRequest - Microsoft Graph beta
- approve action - Microsoft Graph beta
- reject action - Microsoft Graph beta
- cancelMyRequest action - Microsoft Graph beta
- retrieveMyRequests function - Microsoft Graph beta
- retrieveRequestStatus action - Microsoft Graph beta
- operationApprovalRequestEntityStatus resource type - Microsoft Graph beta
- Create deviceManagementScript - Microsoft Graph beta
- List auditEvents - Microsoft Graph v1.0
- Connect-MgGraph (Microsoft.Graph.Authentication)
- Invoke-MgGraphRequest (Microsoft.Graph.Authentication)
- Microsoft Entra Conditional Access for workload identities