Get-SecurityAlertでMicrosoft Defender for Endpoint(MDE)のアラートだけを取得したいのに、なぜか「リスクの高いサインイン」が混ざったり、件数が欠けたりすることがあります。多くの場合、原因は“旧アラートAPIの仕様”とページング(次ページ取得)の見落としです。アプリ登録を増やさず、委任されたアクセス許可(ユーザー権限)だけでMDEアラートを漏れなく取る手順をまとめます。
まず結論:MDEアラートだけを「漏れなく」「混ぜずに」取るなら Alerts v2 を使う
同じ「アラート取得」でも、Microsoft Graphには大きく2系統があります。ここを混同すると、症状(取りこぼし/別プロバイダー混入)がそのまま発生します。
| 観点 | 旧:/security/alerts(Legacy) | 新:/security/alerts_v2(Alerts v2) |
|---|---|---|
| PowerShellコマンド例 | Get-SecurityAlert / Get-MgSecurityAlert | Get-MgSecurityAlertV2 |
| 主な用途 | 複数プロバイダーの「第一世代」アラート集約 | Microsoft 365 Defender基盤の「最新世代」アラート |
| 混在しやすさ | 混在しやすい(MDE以外も返る) | serviceSource で絞り込みやすい |
| 代表的な絞り込み軸 | vendorInformation/provider(ただし名称がレガシー) | serviceSource(例:microsoftDefenderForEndpoint) |
| 権限(委任) | SecurityEvents.Read.All | SecurityAlert.Read.All |
| 注意点 | 廃止予定(期限が明記されている)/$top が“プロバイダーごとの集約”になりやすい | ページングは nextLink 前提だが、PowerShellの -All で追える |
要件が「MDEだけ」「取りこぼしゼロ」なら、最優先で Get-MgSecurityAlertV2(alerts_v2) に寄せるのが安全です。旧API側はプロバイダー統合の都合で“混ざる”仕様が前提になりやすく、さらにレガシー名が残っていてフィルターの文字列も分かりにくくなっています。
なぜ Get-SecurityAlert で「リスクの高いサインイン」が混ざるのか
Get-SecurityAlert(または Get-MgSecurityAlert)が叩くことが多い /security/alerts(Legacy) は、Microsoft GraphのセキュリティAPIが複数プロバイダーのアラートをフェデレーション(横断集約)して返す設計です。つまり、MDEだけに限らず、Microsoft Entra ID Protection(旧Azure AD Identity Protection)など別系統のアラートも同じコレクションに入ってきます。
そのため、MDEを狙って実行しても、条件が甘い/ページングが不完全/provider名が想定と違う、といった要素が重なると、
- MDEアラートが取りこぼされる
- 「リスクの高いサインイン」のような別プロバイダーのアラートが混ざる
という状態になりがちです。
さらに重要なのが、Legacy alerts は 廃止予定(削除期限が明記)である点です。長期運用のスクリプトほど、早めに v2 へ寄せておく方が事故を減らせます。
アプリ登録を増やさずに実現する:Microsoft Graph PowerShellの「既定アプリ」で委任権限を使う
「アプリ登録を極力行わない」という方針の場合でも、Microsoft Graph PowerShell を使えば、Microsoftが提供する既定のマルチテナントアプリを利用して、ユーザーとしてサインイン(委任)できます。
準備:モジュールと接続
まずは必要モジュールを読み込み、委任権限(スコープ)を指定して接続します。MDEアラート取得を v2 で行うなら SecurityAlert.Read.All が基本です。
# 初回のみ(未インストールなら)
Install-Module Microsoft.Graph -Scope CurrentUser -Force
# セキュリティ系コマンドを使うため
Import-Module Microsoft.Graph.Security
# アプリ登録なし(既定アプリ)で、委任されたアクセス許可を要求してサインイン
# 管理者同意が必要なテナントでは、管理者に同意してもらう必要があります
Connect-MgGraph -Scopes "SecurityAlert.Read.All"
「ブラウザー認証がうまくいかない」「過去のトークンが影響する」場合は、PowerShellを新規セッションで開く/シークレット(InPrivate)を使う/別プロファイルにする、などで切り分けすると安定します。
委任権限でMDEを読むときの落とし穴:MDE側のロールが別途必要
Microsoft Defender for Endpoint のデータは、Graphの権限だけでは足りず、MDE側の閲覧ロールが必要になるケースがあります。GraphのセキュリティAPI概要にも「MDEは追加のユーザーロールが必要」「アプリのみ(アプリケーション権限)を推奨する」旨の注意が記載されています。
会社方針でアプリ登録を避ける場合でも、少なくとも以下は確認してください。
- Microsoft Graph 側:SecurityAlert.Read.All(委任)が付与・同意されている
- MDE 側:アラート閲覧が可能なロール(例:セキュリティ閲覧者/セキュリティ管理者/該当RBACグループ)が付いている
推奨:Get-MgSecurityAlertV2 で「MDE由来だけ」を取得する
Alerts v2 の最大のメリットは、serviceSource でアラートの出どころ(サービス)を絞り込みやすい点です。MDEのアラートだけに限定するなら、まずはこれが最短ルートです。
MDEアラートだけを全件取得(ページング込み)
# MDE のアラートだけを全件取得
Get-MgSecurityAlertV2 -All `
-Filter "serviceSource eq 'microsoftDefenderForEndpoint'"
-All を付けると、内部的に @odata.nextLink を追って全ページを取得します。「一部しか取れない」症状の多くは、この -All 抜けで説明できます。
まずは“値の確認”から始めると失敗しない
環境によっては、プロパティの値や利用可能なcmdletが想定と違うことがあります。最初に少数件だけ取って、serviceSource が想定どおりか確認すると安全です。
# まずは10件だけ見て、serviceSource が何になっているか確認
Get-MgSecurityAlertV2 -Top 10 |
Select-Object Id, CreatedDateTime, Severity, Status, ServiceSource, Title
日付で絞り込んで“取りこぼし”と“負荷”を同時に減らす
アラートが多いテナントで毎回全件取得すると、時間もAPI負荷も大きくなります。運用では、createdDateTime を使って期間を区切るのが定番です。
# 例:2025-08-01以降に作成されたMDEアラートだけ取得(UTCのZ)
Get-MgSecurityAlertV2 -All `
-Filter "serviceSource eq 'microsoftDefenderForEndpoint' and createdDateTime ge 2025-08-01T00:00:00Z"
createdDateTime は UTC で扱うのが基本です。日本時間で「今日0:00以降」を取りたいなら、事前にUTCへ変換してからフィルター文字列を組み立てるとズレが起きません。
よく使うフィルター例(v2)
| 目的 | Filter例 | ポイント |
|---|---|---|
| MDEだけ | serviceSource eq 'microsoftDefenderForEndpoint' | 混入を最短で防ぐ |
| 新規(未対応)だけ | serviceSource eq 'microsoftDefenderForEndpoint' and status eq 'new' | アラートキュー運用に近い形 |
| 高・重大だけ | serviceSource eq 'microsoftDefenderForEndpoint' and severity in ('high','critical') | 環境により critical の扱いに差が出る場合があるため、まず実データ確認推奨 |
| 最終更新が最近 | serviceSource eq 'microsoftDefenderForEndpoint' and lastUpdateDateTime ge 2025-12-01T00:00:00Z | “更新が入ったもの”の追跡に便利 |
Alerts v2 の $filter で利用できるプロパティには、serviceSource や createdDateTime などが含まれます。フィルター条件を増やすときは、まず“サポートされているプロパティ”を前提に設計してください。
Get-MgSecurityAlertV2 が使えない場合:モジュール更新と、どうしても無理なら Legacy での回避策
まずは「コマンドが存在するか」を確認する
環境によっては Microsoft.Graph.Security の更新が遅れていて、Get-MgSecurityAlertV2 が見当たらないことがあります。最初にコマンド有無を確認し、なければモジュール更新を試します。
# v2コマンドの有無を確認
Get-Command Get-MgSecurityAlertV2 -ErrorAction SilentlyContinue
# 更新(必要に応じて)
Update-Module Microsoft.Graph -Force
Update-Module Microsoft.Graph.Security -Force
更新後は PowerShell を開き直してから再確認すると、読み込み中の古いDLLの影響を避けられます。
どうしても Legacy を使うなら:vendorInformation/provider の“レガシー名”で絞る
/security/alerts(Legacy)を使う場合は、まず前提として SecurityEvents.Read.All が必要になります。
Import-Module Microsoft.Graph.Security
Connect-MgGraph -Scopes "SecurityEvents.Read.All"
ここでハマりやすいのが、Legacy は製品名がリブランディングされても、フィルターで使うキーワードが古いまま残っている点です。Microsoft Learn の例でも、MDEは “Microsoft Defender for Endpoint” ではなく、フィルターキーワードが “Microsoft Defender ATP” として扱われます。
| ベンダー(表示上の製品) | Legacy側の $filter キーワード例 | 補足 |
|---|---|---|
| Microsoft Defender for Endpoint(MDE) | Microsoft Defender ATP | 名称が古いまま残っていることがある |
| Microsoft Entra ID Protection(リスク サインイン等) | IPC | “Risky sign-in” 混入の原因になりやすい |
| Microsoft Defender for Cloud Apps | MCAS | Cloud App Securityのレガシー名 |
実運用では、まず少量取得して provider 名の実値を確認し、その文字列でフィルターするのが確実です。
# まずは少数取得して provider 名を確認(Legacy)
$sample = Get-MgSecurityAlert -Top 20
$sample | Select-Object -First 10 `
-Property Id, Title, Severity, Category, CreatedDateTime, EventDateTime, `
@{Name="Provider";Expression={$_.VendorInformation.Provider}}, `
@{Name="Vendor";Expression={$_.VendorInformation.Vendor}}
provider の実値が確認できたら、その値で MDE のみへ絞ります。MDE相当が “Microsoft Defender ATP” になっているケースが多い想定です。
# 例:MDE相当(レガシー名)だけ取得
Get-MgSecurityAlert -All `
-Filter "vendorInformation/provider eq 'Microsoft Defender ATP'"
それでも「Risky sign-in を明示的に除外したい」場合は、カテゴリや provider で否定条件を追加します。
# 例:MDE相当だけ&Risky sign-inカテゴリを除外(必要なら)
Get-MgSecurityAlert -All `
-Filter "vendorInformation/provider eq 'Microsoft Defender ATP' and category ne 'Risky sign-in'"
ただし、Legacy 側はプロバイダーごとの実装差もあり、フィルターが思った通りに効かない事例が報告されることがあります。だからこそ、可能なら v2 へ寄せるのが無難です。
「全部取れていない」の原因はページングだけじゃない:現場で効くチェックリスト
-All を付けたか(次ページを追っているか)
Graph の一覧取得は、基本的に 1回の応答で全件が返りません。PowerShellなら、v1/v2 どちらも -All が用意されているので、まずはここを疑います。
$top の仕様で“思ったより少なく見える”ことがある(Legacy)
Legacy の /security/alerts は、$top が「プロバイダーごとの集約トップ」として働く説明があり、さらに $top の上限や $skip の制限もあります。件数が多いテナントで “少ない” と感じるときは、この仕様に当たっていないか確認してください。
Retention(保持期間)に引っかかっていないか(v2)
Alerts v2 は、環境の保持ポリシーで指定された期間内のアラートが対象になります。過去分を掘りたい場合、単にAPIの取りこぼしではなく、保持期間により “そもそも返らない” 可能性があります。
権限は合っているか(SecurityEvents と SecurityAlert を取り違えていないか)
Legacy と v2 では、必要な委任権限が異なります。
- Legacy(Get-MgSecurityAlert):SecurityEvents.Read.All
- v2(Get-MgSecurityAlertV2):SecurityAlert.Read.All
「接続できたのに結果が薄い」「空になる」場合、まずスコープの取り違えを疑うと復旧が早いです。
レスポンスヘッダーの警告で“部分応答”になっていないか
Legacy の一覧取得では、プロバイダーがタイムアウトしたりエラーを返すと、応答が部分的になるケースがあります。Graph PowerShellではレスポンスヘッダーを変数に受け取れるので、警告が出ていないか確認しておくと「取りこぼしの正体」が分かりやすくなります。
$rh = $null
$alerts = Get-MgSecurityAlert -All `
-Filter "vendorInformation/provider eq 'Microsoft Defender ATP'" `
-ResponseHeadersVariable rh
# 取得できた件数確認
$alerts.Count
# ヘッダー(警告など)を確認
$rh
実務向け:MDEアラートを“差分”で取り続ける最小スクリプト例
全件取得を毎回やるより、「前回実行以降に作成・更新されたものだけ取る」ほうが現実的です。ここでは v2 を前提に、createdDateTime で差分取得する最小例を載せます。
Import-Module Microsoft.Graph.Security
Connect-MgGraph -Scopes "SecurityAlert.Read.All"
# 前回取得時刻(UTC)をファイルで保持する例
$statePath = "$PSScriptRoot\lastRunUtc.txt"
if (Test-Path $statePath) {
$sinceUtc = Get-Content $statePath | Select-Object -First 1
} else {
# 初回は直近24hなどにしてもOK
$sinceUtc = (Get-Date).ToUniversalTime().AddDays(-1).ToString("yyyy-MM-ddTHH:mm:ssZ")
}
$filter = "serviceSource eq 'microsoftDefenderForEndpoint' and createdDateTime ge $sinceUtc"
$alerts = Get-MgSecurityAlertV2 -All -PageSize 500 -Filter $filter |
Select-Object Id, CreatedDateTime, Severity, Status, Category, Title, DetectionSource, ServiceSource, AlertWebUrl
# 出力
$outDir = "$PSScriptRoot\out"
New-Item -ItemType Directory -Path $outDir -Force | Out-Null
$timestamp = (Get-Date).ToString("yyyyMMdd_HHmmss")
$alerts | Export-Csv -Path "$outDir\mde_alerts_$timestamp.csv" -NoTypeInformation -Encoding UTF8
# 次回用に「今回実行時刻(UTC)」を保存
(Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ") | Set-Content $statePath
この形にしておくと、アラート件数が増えた環境でも「必要な分だけ」取り続けられます。さらに lastUpdateDateTime を使って“更新差分”に寄せると、担当者割当やステータス変更の追跡にも応用できます。
まとめ:MDEだけを取りたいなら「V2 + serviceSource + -All」が最短で堅い
- Get-SecurityAlert / Get-MgSecurityAlert(Legacy)は複数プロバイダーの集約で、MDE以外(例:リスクの高いサインイン)が混ざりやすい
- MDEアラートだけに絞るなら、まず Get-MgSecurityAlertV2 を使い、serviceSource eq ‘microsoftDefenderForEndpoint’ でフィルターする
- 取りこぼし対策は -All が基本。件数が多いなら createdDateTime / lastUpdateDateTime で差分取得にする
- 委任権限のみで実現する場合でも、Graph権限に加えて MDE側ロール が必要な点は見落としやすい
- Legacy は廃止予定のため、長期運用するなら v2 へ寄せておくのが安全

コメント