【Azure】Storage Account に保存された Activity Log を PowerShell で高速検索する方法
Azure の Activity Log(アクティビty ログ)を長期保管や監査目的で Storage Account(Blob ストレージ) に出力・保管している環境は多く存在します。
しかし、Blob 内の Activity Log は insights-activity-logs というコンテナに 1 時間ごとの JSON/JSONL 形式 で階層的に保存されるため、ポータルから手動でファイルを探して中身を確認するのは非常に困難です。
本記事では、指定した「年・月」や「キーワード(リソース名や操作名)」に基づいて、PowerShell で対象ログを自動ダウンロードし、イベントの発生日時や実行者を一括抽出する実践的なスクリプトをご紹介します。
1. Storage Account 内の Activity Log の構造
Storage Account に出力された Activity Log は、以下のような階層構造で格納されています。
insights-activity-logs/
└── resourceId=/SUBSCRIPTIONS/<SUBSCRIPTION_ID>/
└── y=YYYY/
└── m=MM/
└── d=DD/
└── h=HH/
└── m=00/
└── PT1H.json
注意すべきポイント
- パスの大文字・小文字(Case Sensitivity)
- パスに含まれるサブスクリプション ID(GUID)部分は すべて大文字(UPPERCASE) で格納されています。
- ファイルフォーマット(JSONL)
- 各
.jsonファイルは単一の JSON ではなく、1 行ごとに 1 つの JSON イベントが記録された JSON Lines (JSONL) 形式です。
2. 検索用 PowerShell スクリプト
以下の PowerShell スクリプトを使用することで、指定した年月フォルダ(例: 2026-09)配下のログのみを効率的に読み込み、VNet ピアリング(virtualNetworkPeerings)などの特定の操作履歴を抽出できます。
# ================= 1. Configuration Parameters =================
$rgName = "<Your-Resource-Group-Name>"
$accName = "<Your-Storage-Account-Name>"
$containerName = "insights-activity-logs"
# Ensure Subscription ID is in UPPERCASE to match Azure's path structure
$subscriptionId = "<Your-Subscription-ID>".ToUpper()
# Optional: Target VNet or Resource Keyword to filter
$targetKeyword = "<Your-Target-Keyword>" # e.g., "VNETTEST01"
# Specify Year and Month to search (Format: YYYY and MM)
$year = "2026"
$month = "09" # Preserve leading zero for single-digit months (e.g., "01", "02", "09", "10")
# ===============================================================
# 2. Get Storage Account Context
$ctx = Get-AzStorageAccount -ResourceGroupName $rgName -Name $accName | Select-Object -ExpandProperty Context
# 3. Build dynamic prefix path to narrow down search scope
$prefix = "resourceId=/SUBSCRIPTIONS/$subscriptionId/y=$year/m=$month/"
# 4. Create a temporary file for downloading logs
$tempFile = [System.IO.Path]::GetTempFileName()
try {
Write-Host "Scanning Activity Logs for [$year-$month]..." -ForegroundColor Cyan
Write-Host "Search Path Prefix: $prefix`n" -ForegroundColor DarkGray
# Retrieve blobs under the specified year/month prefix
$blobs = Get-AzStorageBlob -Container $containerName -Context $ctx -Prefix $prefix -MaxCount 2147483647
if (-not $blobs) {
Write-Host "No log files found for [$year-$month]. Please verify parameters." -ForegroundColor Yellow
return
}
Write-Host "Found $($blobs.Count) log file(s). Processing keywords..." -ForegroundColor Cyan
foreach ($blob in $blobs) {
# Download blob content to temporary file
Get-AzStorageBlobContent -Container $containerName -Blob $blob.Name -Destination $tempFile -Context $ctx -Force -ErrorAction SilentlyContinue | Out-Null
if (Test-Path $tempFile) {
$rawText = Get-Content -Path $tempFile -Raw
# Pre-filter check: Ensure the blob contains the target operations
if ($rawText -match "virtualNetworkPeerings") {
Write-Host "`n[!] Matched target event in Blob: $($blob.Name)" -ForegroundColor Green
# Parse JSON Lines (JSONL) line-by-line
Get-Content -Path $tempFile | ForEach-Object {
try {
$lineJson = $_ | ConvertFrom-Json
$records = if ($lineJson.records) { $lineJson.records } else { @($lineJson) }
foreach ($rec in $records) {
$isPeering = ($rec.operationName -like "*virtualNetworkPeerings*") -or ($rec.resourceId -like "*virtualNetworkPeerings*")
$isTarget = [string]::IsNullOrEmpty($targetKeyword) -or ($rec.resourceId -like "*$targetKeyword*") -or ($rec | Out-String -stream -match $targetKeyword)
if ($isPeering -and $isTarget) {
[PSCustomObject]@{
Time = $rec.time
OperationName = if ($rec.operationName.value) { $rec.operationName.value } else { $rec.operationName }
Result = if ($rec.resultType) { $rec.resultType } else { $rec.status.value }
Caller = if ($rec.callerIdentities.caller) { $rec.callerIdentities.caller } else { $rec.caller }
ResourceId = $rec.resourceId
}
}
}
} catch {
# Ignore non-JSON lines
}
} | Format-List
}
}
}
}
finally {
# Clean up temporary file
if (Test-Path $tempFile) {
Remove-Item -Path $tempFile -Force
}
Write-Host "`nScan completed for [$year-$month]." -ForegroundColor Cyan
}
3. スクリプトの解説と実行ポイント
-Prefixによる範囲の限定(高速化)
- ストレージアカウント全体を検索すると処理に膨大な時間がかかります。
Prefixに$yearと$monthを含めることで、対話的に特定の月フォルダのみを高速スキャンします。
.ToUpper()によるサブスクリプション ID の補正
- パス指定のミスマッチを防ぐため、サブスクリプション ID の文字列を自動的に大文字へ変換しています。
- JSONL(1行ごとの解析)に対応
-
Get-Contentで 1 行ずつ取得してConvertFrom-Jsonを実行することで、JSON 構文エラー(Additional text encountered...)を回避します。
4. まとめ
Azure の Activity Log を Storage Account にアーカイブしている場合でも、PowerShell のストレージコマンドレットと適切なパス前置詞(Prefix)を組み合わせることで、特定の変更イベントが発生した日時や実行者を素早く特定できます。
監査対応や過去の障害調査の際に、ぜひ本スクリプトをご活用ください。