Microsoft Sentinel や Defender で KQL を書いていると、「ドキュメントどおりに書いたのに無効なクエリになる」という相談がよくあります。とくに IdentityInfo テーブルの AccountUPN / AccountUpn の違いは、多くのテナントで実務上の落とし穴になっています。この記事では、この問題の背景と安全なクエリパターンを具体的に解説します。
IdentityInfo で AccountUPN を書いたらクエリが落ちる理由
まず、今回のトラブルを整理します。
- Microsoft Sentinel の分析ルールで KQL を実行したところ「無効なクエリ」エラーが発生。
- ドキュメントやブログでは
AccountUPNという列名で紹介されている。 - ところが、実際のテナントでは
IdentityInfoテーブルの列がAccountUpnになっていた。 - 結果として、
AccountUPNを参照するクエリが列未存在として失敗する。
ここで重要なのは、Kusto Query Language (KQL) は基本的に大文字・小文字を区別するという点です。
AccountUPNとAccountUpnは KQL にとって「別の列」です。- 存在しない列名を参照すると、KQL はその場で「無効なクエリ」と判断します。
- 単なるタイプミスとまったく同じ扱いになるため、原因に気付きにくいのが難点です。
つまり、ドキュメントどおりに書いても、自分のテナントのスキーマと列名が少しでも違えばクエリは失敗します。問題の本質は「公式ドキュメントと現実のスキーマが完全には一致していない」点にあります。
IdentityInfo テーブルには「複数バージョン」が存在する
IdentityInfo テーブルがややこしいのは、1 つの名前に対して、実際には複数のスキーマが存在するためです。
代表的なバージョンは次の通りです。
| バージョン | 主な利用場所 | 特徴 | 列名例 |
|---|---|---|---|
| Log Analytics IdentityInfo | Azure ポータルの Microsoft Sentinel (ログ画面) | Sentinel UEBA 用の拡張スキーマ。UEBA 関連列が多い。 | AccountUPN, BlastRadius, EntityRiskScore など |
| Advanced Hunting IdentityInfo (旧) | Defender ポータルの Advanced Hunting | Defender XDR / Defender for Identity 由来。UEBA 列は限定的。 | AccountUpn ではないケースも存在 |
| 統合 IdentityInfo (Unified) | Defender ポータル (Sentinel + UEBA 統合環境) | Sentinel UEBA 由来の列と Defender 由来の列を統合した新スキーマ。 | AccountUpn、一部列名が統一&変更 |
Microsoft の公式情報によると、Defender ポータル側では 2025 年 5 月ごろから、Defender for Identity と Sentinel の IdentityInfo を統合した「統合 IdentityInfo テーブル」が段階的に展開されています。
この新しい統合スキーマでは、旧 Log Analytics 版の列名からいくつかが変更されており、その 1 つが AccountUPN → AccountUpn という変化です。
なぜテナントによって列名の大文字・小文字が違うのか
「同じ IdentityInfo なのに、別テナントでは列名が違う」という状況が起こる主な理由は次の通りです。
- 利用ポータルの違い
Azure ポータル (Sentinel/Log Analytics) なのか、Defender ポータル (Advanced Hunting) なのかで、読み出している IdentityInfo のバージョンが異なる。 - UEBA の有効・無効
UEBA を有効にしている Sentinel ワークスペースかどうかで、利用できる列・列名が変わる。 - 統合スキーマの段階的ロールアウト
統合 IdentityInfo への切り替えは一斉ではなく、テナント/リージョンごとの段階的な展開となっているため、同時期でもスキーマが揃っていない。 - 新規テナントと既存テナントの差
2025 年以降に作られたテナントでは最初から統合スキーマが前提、古いテナントでは旧スキーマが残っているケースがある。
この結果として、あるテナントでは AccountUPN が正解、別のテナントでは AccountUpn が正解、といった「ゆらぎ」が発生します。
Microsoft Q&A でも、AccountUPN と AccountUpn の違いに悩んだ事例に対して、「KQL は大文字・小文字を区別するので列名の違いが原因」「テナントごとにスキーマが異なるため、必ず自分の環境のスキーマを確認すること」といった回答が公式に示されています。
自分のテナントの IdentityInfo スキーマを確認する方法
ドキュメントよりも優先すべき「真実」は、常に自分のワークスペースのスキーマです。まずは、IdentityInfo の実スキーマを確認しておきましょう。
Azure ポータル (Sentinel / Log Analytics) から確認
- Azure ポータルで対象の Microsoft Sentinel ワークスペース を開く。
- 左メニューから ログ(または「Log Analytics」)を開く。
- 画面左側のテーブル一覧から IdentityInfo を選択。
- 右ペインの「列」一覧で、
AccountUPNかAccountUpnのどちらがあるか確認する。
KQL で確認する場合は、次のようなクエリが便利です。
IdentityInfo
| take 0
take 0 はデータを返さず列見出しだけを表示してくれるので、列名の確認に向いています。
より詳細なスキーマを見たい場合は、次のようにします。
IdentityInfo
| getschema
もしくは、権限があれば Kusto の管理コマンドを実行しても構いません。
.show table IdentityInfo schema
なお、管理コマンドは「ログ」画面では実行できず、専用の Kusto 環境が必要な場合があります。
Defender ポータル (Advanced Hunting) から確認
- Defender ポータルにアクセスし、左メニューから Advanced hunting を開く。
- スキーマペインで IdentityInfo テーブルを選択。
- 列一覧から
AccountUpnなどの列名を確認する。
Defender 側では、統合 IdentityInfo 導入後に列名が統一されているケースが多く、AccountUpn 表記が標準的です。
列名ゆらぎに強い KQL パターン:column_ifexists + coalesce
最も実務的な解決策は、どちらの列名でも動くクエリを書くことです。その中心になる関数が次の 2 つです。
column_ifexists("列名", 既定値)指定した列が存在すればその列を返し、存在しなければ既定値を返します。coalesce(expr1, expr2, ...)左から順に評価し、最初に null / 空でない値を返します。
この 2 つを組み合わせると、AccountUPN と AccountUpn の両方に対応したクエリを簡潔に書けます。
IdentityInfo
| extend AccountUPN_ = coalesce(
tostring(column_ifexists("AccountUPN", "")),
tostring(column_ifexists("AccountUpn", ""))
)
| where isnotempty(AccountUPN_)
| project
AccountUPN = AccountUPN_,
BlastRadius,
EntityRiskScore,
GroupMembership
ポイントは次の通りです。
AccountUPN_という内部列に、どちらか存在する方の値をまとめる。where isnotempty(AccountUPN_)で UPN が取れていない行を除外。projectで最終的な列名をAccountUPNに統一して出力。
こうしておけば、AccountUPN しかない環境でも、AccountUpn しかない環境でも、同じクエリが動作します。
project-rename を常用しない方がよい理由
project-rename を使えば列名をまとめることもできますが、存在しない列を指定するとその場でクエリが失敗するため、列名ゆらぎ対応には向きません。
// × 存在しない列を指定すると失敗する例
IdentityInfo
| project-rename AccountUPN = AccountUpn
この書き方は「列が存在することが 100% 保証されている場合」にだけ使うのがおすすめです。テナントごとにスキーマが揺れる IdentityInfo では、column_ifexists で安全に受けるほうが現実的です。
他の列でも起きている「名前変更」の例
統合 IdentityInfo スキーマでは、AccountUPN → AccountUpn 以外にも、いくつかの列名が変更されています。
| 旧 Log Analytics 列名 | 統合 IdentityInfo 列名 | 意味の概要 |
|---|---|---|
AccountCloudSID | CloudSid | Entra ID のセキュリティ ID |
AccountCreationTime | CreatedDateTime | アカウント作成日時 |
AccountSID | OnPremSid | オンプレ AD の SID |
AccountTenantId | TenantId | テナント ID |
AccountUPN | AccountUpn | ユーザーの UPN |
MailAddress | EmailAddress | プライマリ メールアドレス |
SourceSystem | IdentityEnvironment | データソース (Entra / on-prem など) |
IdentityInfo に依存した高度なクエリ(リスクベースの検出、ロールベースの検出など)を書いている場合、これらの名前変更も影響します。同じ考え方で、列名をマッピングする関数を 1 つ用意しておくと安全です。
UEBA 依存列への安全なアクセスパターン
UEBA を有効にしていないテナントでは、そもそも次のような列が存在しません。
EntityRiskScoreBlastRadiusAssignedRolesGroupMembership(環境によって挙動が異なる)
これらの列は「便利だから」といって無条件に参照すると、UEBA 無効テナントでクエリが失敗します。ここでも column_ifexists が活躍します。
IdentityInfo
| extend
AccountUPN_ = coalesce(
tostring(column_ifexists("AccountUPN", "")),
tostring(column_ifexists("AccountUpn", ""))
),
EntityRiskScore_ = toint(column_ifexists("EntityRiskScore", int(null))),
BlastRadius_ = toint(column_ifexists("BlastRadius", int(null)))
| where isnotempty(AccountUPN_)
| project
AccountUPN = AccountUPN_,
EntityRiskScore = EntityRiskScore_,
BlastRadius = BlastRadius_
このように「UEBA があれば値が入る」「UEBA がなければ null になる」形にしておけば、テナントごとの差異でクエリが落ちることはありません。
既存の分析ルールを安全に書き換えるステップ
すでに AccountUPN 前提で大量の分析ルールやハンティングクエリを作ってしまっている場合、次のような手順で安全に書き換えることをおすすめします。
- スキーマを確認
対象ワークスペースでIdentityInfo | take 0を実行し、列名の実態を確認する。 - IdentityInfo 用の標準関数を作る
後述のような「列名ゆらぎを吸収する関数」を 1 つ作成する。 - 新クエリは標準関数を前提に実装
新規の分析ルールやワークブックでは、必ずその関数を経由して IdentityInfo を参照する。 - 既存ルールを段階的に置き換え
影響が大きい重要ルールから順に、手動またはスクリプトでクエリを書き換える。 - テストワークスペースで先に検証
可能であれば別ワークスペースにクローンし、新関数と新クエリで誤検知・検知漏れがないか確認する。
IdentityInfo 正規化関数の例
Sentinel のログ画面では、KQL 関数として共通処理を登録できます。IdentityInfo の列名ゆらぎと UEBA 有無を吸収する関数の例を示します。
let NormalizeIdentityInfo = () {
IdentityInfo
| extend
AccountUPN_ = coalesce(
tostring(column_ifexists("AccountUPN", "")),
tostring(column_ifexists("AccountUpn", ""))
),
TenantId_ = coalesce(
tostring(column_ifexists("AccountTenantId", "")),
tostring(column_ifexists("TenantId", ""))
),
CloudSid_ = coalesce(
tostring(column_ifexists("AccountCloudSID", "")),
tostring(column_ifexists("CloudSid", ""))
),
RiskScore_ = toint(column_ifexists("EntityRiskScore", int(null)))
| where isnotempty(AccountUPN_)
| project
TimeGenerated,
AccountUPN = AccountUPN_,
TenantId = TenantId_,
CloudSid = CloudSid_,
EntityRiskScore = RiskScore_,
AccountObjectId,
AccountDisplayName,
IdentityType = column_ifexists("IdentityType", "")
};
// 使用例:
NormalizeIdentityInfo()
| where EntityRiskScore >= 50
このような関数を 1 箇所に定義しておけば、クエリ側は「NormalizeIdentityInfo を使う」というルールさえ守れば、個別の列名変更を意識せずに済みます。
スキーマ差分を把握するための運用アイデア
IdentityInfo のようにテーブル側で積極的にスキーマ変更が行われる場合、「どのタイミングで何が変わったか」を可視化する運用を入れておくと安心です。
ワークブックでスキーマの「スナップショット」を取る
たとえば、次のような考え方でスキーマ監視用ワークブックを作成できます。
- 定期的に
IdentityInfo | getschemaの結果を収集し、カスタム テーブルに格納。 - 列名・型の変化を時系列チャートやテーブルで可視化。
- 「列が増えた/減った/型が変わった」タイミングでアラートを出す。
実装には多少工夫が必要ですが、「ある日突然クエリが落ちて初めてスキーマ変更に気付く」といった事態を防げます。
テナント横断での比較
複数テナントを運用している場合は、各テナントの IdentityInfo スキーマを CSV などでエクスポートし、次のような観点で比較しておくとよいでしょう。
| 観点 | 確認内容 |
|---|---|
| 列名の差 | AccountUPN / AccountUpn など、大文字・小文字を含む違いがないか。 |
| 列の有無 | EntityRiskScore, AssignedRoles など、UEBA 依存列の有無。 |
| データ型 | RiskState のように、文字列 → 別の列へ移行していないか。 |
| 時間系列 | AccountCreationTime → CreatedDateTime など、名前変更された日時列。 |
よくある疑問とその答え
同じテーブル「バージョン」でもスキーマが違うことはある?
あります。
同じ「Log Analytics スキーマ版 IdentityInfo」であっても、テナントごとの有効機能やロールアウトのタイミングにより、列の追加・削除・名前変更がずれることがあります。特に統合 IdentityInfo の提供開始前後は、「一部テナントだけ先行して列名が変わっている」といった状態になりやすいです。
最終的にはどの表記に揃えればよい?
Defender ポータル側の統合 IdentityInfo では、公式ドキュメント上の列名が AccountUpn になっており、こちらが今後の標準表記と考えられます。
とはいえ、Log Analytics 版や古いワークスペースでは AccountUPN が残っている環境もあるため、クエリ内では column_ifexists + coalesce で両対応しつつ、出力列名はどちらかに統一する、という設計が現実的です。
ドキュメントと自分の環境、どちらを信じるべき?
最終的な正解は、常に「自分のワークスペースの実スキーマ」です。
ドキュメントはあくまで「代表的なスキーマ」を示しているに過ぎず、ロールアウト済み・未ロールアウト、機能有効・無効といった違いまでは表現しきれません。クエリが本番に載る前に、take 0 や getschema、ポータルの列一覧で必ず実スキーマを確認しましょう。
すぐにできるチェックリスト
最後に、今回のようなトラブルを避けるために、今すぐ実施できる項目をチェックリスト形式でまとめます。
| チェック項目 | 状態 |
|---|---|
| IdentityInfo の列一覧(特に UPN 列)を、自テナントですでに確認した。 | □ 未 / □ 済 |
分析ルールやハンティングクエリで、AccountUPN / AccountUpn を直接参照している部分を洗い出した。 | □ 未 / □ 済 |
column_ifexists と coalesce を用いた「列名ゆらぎ」を吸収するパターンを、少なくとも IdentityInfo で導入した。 | □ 未 / □ 済 |
| IdentityInfo をラップする共通関数を作成し、新規クエリでは必ずその関数を使うようにした。 | □ 未 / □ 済 |
UEBA 依存列(EntityRiskScore など)を、存在前提ではなく column_ifexists 経由で参照するよう変更した。 | □ 未 / □ 済 |
スキーマ変更に気付けるよう、getschema の結果を確認する運用(ワークブック/手動チェック)を検討または導入した。 | □ 未 / □ 済 |
まとめ
IdentityInfo テーブルの AccountUPN / AccountUpn 問題は、「KQL の大文字・小文字の区別」と「テナントごとに異なる IdentityInfo スキーマ」が組み合わさって発生する、典型的なハマりポイントです。
しかし、根本的な考え方はシンプルです。
- ドキュメントだけを信じず、必ず自分のワークスペースのスキーマを確認する。
- column_ifexists と coalesce で列名のゆらぎを吸収するクエリパターンを用意する。
- IdentityInfo をラップする共通関数を作り、そこでだけスキーマ差分を意識する。
- UEBA 依存列や統合 IdentityInfo による列名変更を前提に、クエリを柔軟にしておく。
この 4 点を押さえておけば、今後 IdentityInfo のスキーマが多少変化しても、突然分析ルールが大量にエラーになる、といったリスクを大きく減らせます。IdentityInfo はうまく使えば非常に強力なテーブルです。スキーマの揺らぎとうまく付き合いながら、Microsoft Sentinel / Defender XDR のハンティングと検出精度を一段引き上げていきましょう。

コメント