0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Azure】Storage Account に保存された Activity Log を PowerShell で高速検索する方法

0
Posted at

【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

注意すべきポイント

  1. パスの大文字・小文字(Case Sensitivity)
  • パスに含まれるサブスクリプション ID(GUID)部分は すべて大文字(UPPERCASE) で格納されています。
  1. ファイルフォーマット(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. スクリプトの解説と実行ポイント

  1. -Prefix による範囲の限定(高速化)
  • ストレージアカウント全体を検索すると処理に膨大な時間がかかります。Prefix$year$month を含めることで、対話的に特定の月フォルダのみを高速スキャンします。
  1. .ToUpper() によるサブスクリプション ID の補正
  • パス指定のミスマッチを防ぐため、サブスクリプション ID の文字列を自動的に大文字へ変換しています。
  1. JSONL(1行ごとの解析)に対応
  • Get-Content で 1 行ずつ取得して ConvertFrom-Json を実行することで、JSON 構文エラー(Additional text encountered...)を回避します。

4. まとめ

Azure の Activity Log を Storage Account にアーカイブしている場合でも、PowerShell のストレージコマンドレットと適切なパス前置詞(Prefix)を組み合わせることで、特定の変更イベントが発生した日時や実行者を素早く特定できます。

監査対応や過去の障害調査の際に、ぜひ本スクリプトをご活用ください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?