You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Microsoft Graph API在Windows PowerShell发邮件遇400错误求助

解决Microsoft Graph API发送邮件400错误及凭据安全优化

排查400 Bad Request错误

400错误通常源于请求格式或配置问题,按以下要点逐一排查:

  • 请求体JSON格式错误:Graph API对邮件请求体的嵌套结构要求严格,需确保层级正确,且使用ConvertTo-Json -Depth 3(默认深度不足会截断嵌套内容)。示例正确请求体:
$mailBody = @{
    message = @{
        subject = "测试邮件"
        body = @{
            contentType = "Text" # 或"HTML"
            content = "这是测试邮件内容"
        }
        toRecipients = @(
            @{
                emailAddress = @{
                    address = "recipient@example.com"
                }
            }
        )
    }
    saveToSentItems = $true
} | ConvertTo-Json -Depth 3
  • 端点URL匹配权限类型:委派权限需使用https://graph.microsoft.com/v1.0/me/sendMail,应用权限才用/users/{user-id}/sendMail,用错端点会直接返回400。
  • 令牌权限范围验证:解码获取到的令牌(可本地用jwt.ms解析),确认包含https://graph.microsoft.com/Mail.Send范围,若缺失则重新获取令牌时需指定正确scope。
  • 特殊字符转义:邮件主题或内容含特殊字符时,依赖ConvertTo-Json自动转义,避免手动拼接JSON导致格式错误。

优化凭据安全性

1. 避免明文存储凭据

绝对不要在脚本中硬编码账号密码,推荐两种安全方式:

  • 交互式获取凭据:
$cred = Get-Credential -Message "输入Azure管理员账号凭据"
  • 加密存储到Windows凭据管理器:
# 首次存储
$cred = Get-Credential
$cred | Export-Clixml -Path "$env:USERPROFILE\graph_cred.xml"

# 后续读取(仅当前用户可解密)
$storedCred = Import-Clixml -Path "$env:USERPROFILE\graph_cred.xml"

2. 使用设备码流替代密码流

密码流安全性低,设备码流无需输入密码,适合本地脚本:

$clientId = "你的Azure AD应用ID"
$tenantId = "你的租户ID"
$scopes = "https://graph.microsoft.com/Mail.Send"

# 获取设备码
$deviceCodeRequest = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/devicecode" -Method Post -Body @{
    client_id = $clientId
    scope = $scopes
}

Write-Host "请在浏览器打开 $($deviceCodeRequest.verification_uri),输入代码 $($deviceCodeRequest.user_code) 完成登录"

# 轮询获取令牌
do {
    Start-Sleep -Seconds 5
    $tokenResponse = try {
        Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token" -Method Post -Body @{
            client_id = $clientId
            grant_type = "urn:ietf:params:oauth:grant-type:device_code"
            device_code = $deviceCodeRequest.device_code
        }
    } catch { $null }
} while (-not $tokenResponse)

3. 遵循最小权限原则

仅为应用配置必要权限,比如只需要发送邮件就保留Mail.Send,移除不必要的Mail.ReadWrite权限,降低风险。

完整可测试脚本

# 配置参数
$clientId = "你的Azure AD应用ID"
$tenantId = "你的租户ID"
$recipientEmail = "收件人邮箱"
$subject = "Graph API测试邮件"
$content = "这是通过Microsoft Graph API发送的测试邮件"

# 设备码流获取令牌
$deviceCodeRequest = Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/devicecode" -Method Post -Body @{
    client_id = $clientId
    scope = "https://graph.microsoft.com/Mail.Send"
}

Write-Host "请在浏览器打开 $($deviceCodeRequest.verification_uri),输入代码 $($deviceCodeRequest.user_code) 完成登录"

do {
    Start-Sleep -Seconds 5
    $tokenResponse = try {
        Invoke-RestMethod -Uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token" -Method Post -Body @{
            client_id = $clientId
            grant_type = "urn:ietf:params:oauth:grant-type:device_code"
            device_code = $deviceCodeRequest.device_code
        }
    } catch { $null }
} while (-not $tokenResponse)

# 构建邮件请求体
$mailBody = @{
    message = @{
        subject = $subject
        body = @{
            contentType = "Text"
            content = $content
        }
        toRecipients = @(
            @{
                emailAddress = @{
                    address = $recipientEmail
                }
            }
        )
    }
    saveToSentItems = $true
} | ConvertTo-Json -Depth 3

# 发送邮件
try {
    Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/me/sendMail" -Method Post -Headers @{
        Authorization = "Bearer $($tokenResponse.access_token)"
        Content-Type = "application/json"
    } -Body $mailBody
    Write-Host "邮件发送成功"
} catch {
    Write-Host "发送失败:$($_.Exception.Message)"
    if ($_.ErrorDetails) { Write-Host "详细错误:$($_.ErrorDetails.Message)" }
}

内容的提问来源于stack exchange,提问作者Sachin Tayade

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 09:31:15