本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。
Microsoft Graph SDK非依存:REST API直叩きによるEntra ID自動化スクリプト
【導入:解決する課題】
巨大なGraph SDKのインストールを排除し、CI/CDやサーバー環境での認証・データ取得を軽量かつ高速に自動化します。
【設計方針と処理フロー】
本スクリプトは、クライアント資格情報フロー(Client Credentials Flow)を用いてMicrosoft Entra IDからアクセストークンを取得し、標準の Invoke-RestMethod でMicrosoft Graph APIエンドポイントへアクセスします。ページネーション(@odata.nextLink)の自動追跡と、APIのレートリミット(HTTP 429)に対する指数バックオフ処理を内蔵しています。
graph TD
A[Start] --> B["OAuth 2.0 トークン要求"]
B --> C{"トークン取得成功?"}
C -->|No| D["エラー終了"]
C -->|Yes| E["Graph API リクエスト送信"]
E --> F{"HTTPステータス確認"}
F -->|429 Throttled| G["Retry-After秒 待機"]
G --> E
F -->|200 OK| H["レスポンスデータ蓄積"]
H --> I{"@odata.nextLink あり?"}
I -->|Yes| J["次ページURLを設定"]
J --> E
I -->|No| K["結果オブジェクト出力"]
K --> L[Finish]
【実装:コアスクリプト】
サードパーティ製モジュールを一切使用せず、PowerShell 5.1 / 7.x 双方で動作する堅牢な実装です。
function Get-GraphApiToken {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]$TenantId,
[Parameter(Mandatory = $true)]
[string]$ClientId,
[Parameter(Mandatory = $true)]
[System.Security.SecureString]$ClientSecret
)
$TokenEndpoint = "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token"
$Bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($ClientSecret)
$UnsecureSecret = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($Bstr)
[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($Bstr)
$Body = @{
client_id = $ClientId
client_secret = $UnsecureSecret
scope = "https://graph.microsoft.com/.default"
grant_type = "client_credentials"
}
try {
$Response = Invoke-RestMethod -Uri $TokenEndpoint -Method Post -Body $Body -ContentType "application/x-www-form-urlencoded"
return $Response.access_token
}
catch {
Write-Error "認証トークンの取得に失敗しました: $_"
throw $_
}
}
function Invoke-GraphApiRequest {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]$AccessToken,
[Parameter(Mandatory = $true)]
[string]$Uri,
[ValidateSet("GET", "POST", "PATCH", "DELETE")]
[string]$Method = "GET",
[hashtable]$Body = @{},
[int]$MaxRetryCount = 3
)
$Headers = @{
"Authorization" = "Bearer $AccessToken"
"Content-Type" = "application/json"
}
$Results = [System.Collections.Generic.List[PSObject]]::new()
$CurrentUri = $Uri
do {
$RetryCount = 0
$Success = $false
while (-not $Success -and ($RetryCount -lt $MaxRetryCount)) {
try {
$Params = @{
Uri = $CurrentUri
Method = $Method
Headers = $Headers
}
if ($Method -in @("POST", "PATCH") -and $Body.Count -gt 0) {
$Params.Body = ($Body | ConvertTo-Json -Depth 10)
}
$Response = Invoke-RestMethod @Params
$Success = $true
if ($Response.value) {
$Results.AddRange($Response.value)
} else {
$Results.Add($Response)
}
# ページネーション処理
$CurrentUri = $Response.'@odata.nextLink'
}
catch {
$StatusCode = $_.Exception.Response.StatusCode.value__
if ($StatusCode -eq 429) {
$RetryAfter = $_.Exception.Response.Headers['Retry-After']
$WaitSeconds = if ($RetryAfter) { [int]$RetryAfter } else { [Math]::Pow(2, $RetryCount + 1) }
Write-Warning "レート制限を検知 (429)。 $WaitSeconds 秒待機後に再試行します..."
Start-Sleep -Seconds $WaitSeconds
$RetryCount++
}
else {
Write-Error "APIリクエスト実行中にエラーが発生しました: $_"
throw $_
}
}
}
if (-not $Success) {
throw "最大再試行回数 ($MaxRetryCount) を超えたため処理を中断しました: $CurrentUri"
}
} while ($CurrentUri)
return $Results
}
# --- 実行例 ---
# $TenantId = "your-tenant-id"
# $ClientId = "your-client-id"
# $Secret = Read-Host -AsSecureString "Enter Client Secret"
# $Token = Get-GraphApiToken -TenantId $TenantId -ClientId $ClientId -ClientSecret $Secret
# $Users = Invoke-GraphApiRequest -AccessToken $Token -Uri "https://graph.microsoft.com/v1.0/users?`$select=id,displayName,userPrincipalName"
# $Users | Select-Object displayName, userPrincipalName
【検証とパフォーマンス評価】
SDKモジュール(Microsoft.Graph)のインポート時と、本スクリプト(REST API直接呼び出し)の実行速度を Measure-Command にて比較。
# 実行時間計測の例
$Duration = Measure-Command {
$Token = Get-GraphApiToken -TenantId $TenantId -ClientId $ClientId -ClientSecret $Secret
$Data = Invoke-GraphApiRequest -AccessToken $Token -Uri "https://graph.microsoft.com/v1.0/users?`$top=100"
}
Write-Output "総処理時間: $($Duration.TotalSeconds) 秒"
| 評価項目 | Microsoft.Graph SDK利用 | REST API直接実行 (本方式) |
|---|---|---|
| 初期モジュールロード | 3.5 ~ 10.0 秒以上 | 0 秒(依存なし) |
| コンテナ起動/関数実行時間 | 重厚(数百MBの容量) | 超軽量(数KBのスクリプトのみ) |
| 実行オーバーヘッド | SDK内部ラッパーの処理遅延あり | 最小(ダイレクトなHTTP通信) |
【運用上の落とし穴と対策】
PowerShell 5.1 vs 7.x における
Invoke-RestMethodの挙動差異
PowerShell 5.1ではUTF-8レスポンスが文字化けすることがあります。必要に応じて[System.Text.Encoding]::UTF8.GetString($Response.RawContentStream.ToArray())を用いるか、PowerShell 7系を標準実行環境に選定してください。429 Throttling(リクエスト制限)のハンドリング
大規模テナントでバッチ処理を行う際、急激な並列リクエストはHTTP 429を引き起こします。Retry-Afterレスポンスヘッダーを解析し、適切なウェイト時間を設けるリトライ機構が必須です。シークレットの安全な取り扱い
クライアントシークレットを平文でハードコードすることは厳禁です。実行環境に応じてAzure Key Vault、環境変数、またはGet-Credentialを介してセキュアに取得してください。
【まとめ】
依存性排除:SDKのインストールや互換性問題から解放され、軽量コンテナやCI/CDパイプラインに最適。
ページネーション&スロットリング対策:
@odata.nextLinkの自動処理とHTTP 429の再試行を実装し、大量データ取得時の安定性を確保。セキュアな認証管理:OAuth 2.0 クライアント資格情報フローを用い、適切な権限スコープでの最小特権運用を徹底。

コメント