本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。
PowerShellにおけるCIM/WMIによる大規模システム情報取得と運用
導入
Windows環境において、システム情報を効率的に取得し管理することは、日常の運用業務において不可欠です。PowerShellは、CIM(Common Information Model)およびWMI(Windows Management Instrumentation)という強力な管理フレームワークを活用することで、ローカルおよびリモートのWindowsシステムから多種多様な情報を柔軟に取得・操作できます。 、PowerShellのプロフェッショナルとして、CIM/WMIを用いた大規模なシステム情報取得における実践的なテクニックを解説します。特に、並列処理によるパフォーマンス向上、堅牢なエラーハンドリング、運用を考慮したロギング戦略、そしてセキュリティ対策に焦点を当て、現場で直面する課題を解決するための具体的なスクリプト例とノウハウを提供します。
目的と前提 / 設計方針(同期/非同期、可観測性)
目的とCIM/WMIの選択理由
目的は、多数のWindowsホストからシステム稼働状況(CPU、メモリ、ディスク、サービス、イベントログなど)を一元的に収集し、監視・分析に役立てることです。WMI(CIMの実装の一つ)は、Windowsシステム上のほとんどすべての管理可能な情報にアクセスできる標準的なインターフェースを提供します。PowerShellのCIMコマンドレット(Get-CimInstanceなど)は、WMIだけでなく、DMTF(Distributed Management Task Force)によって標準化されたWS-Managementプロトコルを介した管理機能も利用可能であり、リモート管理におけるスケーラビリティとセキュリティが向上しています[2] (2024-04-26, Microsoft)。
設計方針(パフォーマンス、信頼性、セキュリティ、可観測性)
パフォーマンス(並列処理): 多数のホストからの情報取得には、同期的な逐次処理では時間がかかりすぎます。PowerShell 7.0以降で利用可能な
ForEach-Object -ParallelやCIMセッションを組み合わせることで、並列処理を導入し、処理時間を大幅に短縮します[1] (2024-04-26, Microsoft), [4] (2024-03-14, Microsoft)。信頼性(エラーハンドリングと再試行): ネットワーク障害、リモートホストのダウン、権限不足など、リモート操作では様々なエラーが発生し得ます。
try/catchブロックと-ErrorAction Stopを用いた適切なエラーハンドリング、そして複数回の再試行メカニズムを実装し、スクリプトの堅牢性を高めます[7] (2024-03-14, Microsoft)。セキュリティ(最小権限と機密管理): 管理操作には適切な権限が必要です。JEA(Just Enough Administration)を導入することで、特定の管理タスクに必要な最小限の権限のみを付与し、攻撃対象領域を減らします。また、資格情報などの機密情報は
SecretManagementモジュールで安全に取り扱います[5] (2024-04-26, Microsoft), [6] (2024-04-26, Microsoft)。可観測性(ロギング): 処理の進行状況、成功・失敗、エラーの詳細を記録することで、問題発生時の原因特定や監査を容易にします。標準のトランスクリプトログと、構造化された情報ログ(CSVやJSON形式)を組み合わせます[8] (2024-04-26, Microsoft)。
処理フロー
大規模システム情報取得の処理フローは以下のMermaidチャートで表現できます。
graph TD
A["開始"] --> B["対象ホストリストの読み込み"];
B --> C{"CIMセッション準備"};
C -- 失敗 --> E["エラーログ記録|終了"];
C -- 成功 --> D["並列処理開始 (ForEach-Object -Parallel)"];
D --> F{"各ホストで情報取得"};
F -- 成功 --> G["情報集約"];
F -- 失敗 --> H["エラー処理|再試行ロジック"];
H -- 再試行上限超え --> I["失敗としてログ記録|結果に含める"];
H -- 再試行中 --> F;
G --> J["結果出力|ファイル保存"];
I --> J;
J --> K["CIMセッションクリーンアップ"];
K --> L["終了"];
コア実装(並列/キューイング/キャンセル)
基本的なCIM情報取得とCIMセッション
Get-CimInstance はWMIクラスのインスタンスを取得するための主要なコマンドレットです。リモート接続には -ComputerName パラメータを使用します。
# 例: リモートホストからOS情報を取得
# 前提: リモートホストが稼働しており、WinRMが有効でファイアウォールが許可されていること
# 現在のユーザーアカウントがリモートホストに対する管理者権限を持っていること
# 計算量: N (ホスト数) * M (WMIクエリの複雑さ)
# メモリ条件: 取得する情報量とホスト数に比例。CIMセッション利用で効率化。
try {
$ComputerName = "RemoteHost01" # 実際のホスト名に置き換えてください
$OSInfo = Get-CimInstance -ClassName Win32_OperatingSystem -ComputerName $ComputerName -ErrorAction Stop
$CPUInfo = Get-CimInstance -ClassName Win32_Processor -ComputerName $ComputerName -ErrorAction Stop
Write-Host "--- $($ComputerName) ---"
Write-Host "OS: $($OSInfo.Caption) Build $($OSInfo.BuildNumber)"
Write-Host "CPU: $($CPUInfo.Name)"
}
catch {
Write-Error "ホスト $($ComputerName) からの情報取得中にエラーが発生しました: $($_.Exception.Message)"
}
多数のホストに対して繰り返しWMIクエリを実行する場合、毎回接続を確立するのはオーバーヘッドが大きいです。New-CimSession でCIMセッションを作成し、それを再利用することで効率を向上できます[4] (2024-03-14, Microsoft)。
ForEach-Object -Parallel を用いた並列処理
PowerShell 7.0以降では、ForEach-Object の -Parallel パラメータを使用することで、スクリプトブロックを並行して実行できます。ThrottleLimit で同時に実行されるスクリプトブロックの数を制御できます[1] (2024-04-26, Microsoft)。
以下のスクリプトは、複数のリモートホストからシステム情報を並列で取得する例です。CIMセッションと並列処理を組み合わせることで、効率的な情報収集を実現します。
# コード例1: リモートホストからのシステム情報並列取得スクリプト
# 前提:
# - PowerShell 7.0以降がインストールされていること
# - 対象ホストリスト($ComputerNames)が正しいホスト名であること
# - 各リモートホストでWinRMサービスが実行され、ファイアウォールで許可されていること (ポート5985/HTTPまたは5986/HTTPS)
# - 実行ユーザーが対象ホストへのCIM/WMIアクセス権限を持っていること、または指定したCredentialが有効であること
# - ネットワーク接続が安定していること
# 計算量: O(N/T * M) - N: ホスト数, T: ThrottleLimit, M: WMIクエリの複雑さ。同期処理に比べT倍高速化が期待できる。
# メモリ条件: 同時接続数 (ThrottleLimit) と取得する情報量に比例。
param(
[string[]]$ComputerNames = @("Localhost"), # 実際のホスト名に置き換えてください (例: @("Server01", "Server02", "Server03"))
[int]$ThrottleLimit = 5, # 並列実行数
[int]$CimSessionTimeoutSeconds = 30, # CIMセッション接続タイムアウト(秒)
[int]$CimOperationTimeoutSeconds = 60 # CIM操作タイムアウト(秒)
)
# シークレットストアから資格情報を安全に取得する例 (SecretManagementモジュールを利用)
# 実際の運用では、`Get-Secret -Name "RemoteAdminCredential"` のように使用
# この例ではテストのため `Get-Credential` を使用
$credential = Get-Credential -Message "リモート接続用の資格情報を入力してください"
Write-Host "CIM/WMIによるシステム情報取得を開始します..." -ForegroundColor Cyan
Write-Host "対象ホスト数: $($ComputerNames.Count)"
Write-Host "並列数: $($ThrottleLimit)"
$Results = [System.Collections.Generic.List[object]]::new()
$Errors = [System.Collections.Generic.List[object]]::new()
# CIMセッションオプション設定
$CimSessionOption = New-CimSessionOption -OpenTimeout $CimSessionTimeoutSeconds -OperationTimeout $CimOperationTimeoutSeconds -SkipNetworkTest -ErrorAction Stop
# 計測開始
$ScriptBlockExecution = Measure-Command {
$ComputerNames | ForEach-Object -Parallel {
param($Computer) # $Computer は現在のホスト名
$HostResult = [PSCustomObject]@{
ComputerName = $Computer
Status = "Pending"
Message = ""
OSCaption = $null
BuildNumber = $null
ProcessorName = $null
Timestamp = (Get-Date -Format "yyyy-MM-dd HH:mm:ss JST") # 2024年7月29日 JST
}
try {
# CIMセッションを各スレッド(ランスペース)内で作成・利用する
# -Credential は $using:credential を使って親スコープの変数を参照
$currentCimSession = New-CimSession -ComputerName $Computer -Credential $using:credential -SessionOption $using:CimSessionOption -ErrorAction Stop
# WMIクエリ
$osInfo = Get-CimInstance -ClassName Win32_OperatingSystem -CimSession $currentCimSession -ErrorAction Stop
$cpuInfo = Get-CimInstance -ClassName Win32_Processor -CimSession $currentCimSession -ErrorAction Stop
# 結果をカスタムオブジェクトに格納
$HostResult.OSCaption = $osInfo.Caption
$HostResult.BuildNumber = $osInfo.BuildNumber
$HostResult.ProcessorName = $cpuInfo.Name
$HostResult.Status = "Success"
$HostResult.Message = "情報取得成功"
}
catch {
$HostResult.Status = "Failed"
$HostResult.Message = "エラー: $($_.Exception.Message) ($($_.FullyQualifiedErrorId))"
}
finally {
# セッションのクリーンアップは重要
if ($currentCimSession) {
Remove-CimSession -CimSession $currentCimSession -ErrorAction SilentlyContinue
}
}
# 結果を親スコープに渡す
$HostResult
} -ThrottleLimit $ThrottleLimit | ForEach-Object {
# 並列処理から返された結果を処理
if ($_.Status -eq "Success") {
$Results.Add($_)
} else {
$Errors.Add($_)
}
}
}
Write-Host "`n--- 処理完了 ---" -ForegroundColor Cyan
Write-Host "総実行時間: $($ScriptBlockExecution.TotalSeconds) 秒"
Write-Host "成功ホスト数: $($Results.Count)"
Write-Host "失敗ホスト数: $($Errors.Count)`n"
# 成功した結果の表示
Write-Host "--- 成功したホスト ---"
$Results | Format-Table ComputerName, OSCaption, BuildNumber, Status, Timestamp -AutoSize
# 失敗したホストの表示
if ($Errors.Count -gt 0) {
Write-Host "`n--- 失敗したホスト ---" -ForegroundColor Red
$Errors | Format-Table ComputerName, Status, Message, Timestamp -AutoSize
}
# 結果をCSVファイルに保存
$OutputFilePath = "CIM_SystemInfo_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv"
$Results | Export-Csv -Path $OutputFilePath -NoTypeInformation -Encoding UTF8
Write-Host "`n結果は $($OutputFilePath) に保存されました。"
再試行/タイムアウトの実装
上記の例では、CIMセッションオプションでタイムアウトを設定しています。より柔軟な再試行ロジックは、try/catch ブロック内でループを組み合わせて実装します。
# 再試行ロジックの簡易例
function Invoke-CimOperationWithRetry {
param(
[string]$ComputerName,
[scriptblock]$ScriptBlock,
[int]$MaxRetries = 3,
[int]$RetryDelaySeconds = 5,
[Management.Automation.PSCredential]$Credential = $null
)
$Attempts = 0
do {
$Attempts++
try {
Write-Verbose "ホスト '$ComputerName' への試行 $((Get-Date -Format 'HH:mm:ss JST')) - $Attempts 回目" # 2024年7月29日 JST
$CimSession = New-CimSession -ComputerName $ComputerName -Credential $Credential -ErrorAction Stop
return & $ScriptBlock $CimSession # スクリプトブロックを実行し、結果を返す
}
catch {
Write-Warning "ホスト '$ComputerName' でのエラー: $($_.Exception.Message). $(($MaxRetries - $Attempts)) 回の再試行が残っています。"
if ($Attempts -lt $MaxRetries) {
Start-Sleep -Seconds $RetryDelaySeconds
} else {
throw $_ # 最大試行回数を超えたらエラーを再スロー
}
}
finally {
if ($CimSession) { Remove-CimSession -CimSession $CimSession -ErrorAction SilentlyContinue }
}
} while ($Attempts -lt $MaxRetries)
}
# 使用例:
# Set-StrictMode -Version Latest
# $cred = Get-Credential
# $result = Invoke-CimOperationWithRetry -ComputerName "NonExistentHost" -ScriptBlock {
# param($Session)
# Get-CimInstance -ClassName Win32_OperatingSystem -CimSession $Session -ErrorAction Stop
# } -Credential $cred -MaxRetries 2
# $result | Select-Object Caption, BuildNumber
検証(性能・正しさ)と計測スクリプト
性能検証には Measure-Command コマンドレットが不可欠です。並列処理と非並列処理の比較は、その効果を数値で示す上で有効です。
# コード例2: 性能検証とエラーハンドリングを含むスクリプト
# 前提:
# - PowerShell 7.0以降がインストールされていること
# - テスト対象のホストリストを用意すること(例: VMやテスト環境のサーバー群)
# - リモート接続に必要な権限とWinRM設定がされていること
# - SecretManagementモジュールがインストールされていること(任意だが推奨)
# - テスト用のSecretVaultが設定されており、"RemoteAdminCredential"という名前で資格情報が保存されていること
# 計算量: 性能計測のため、同期/非同期の処理時間を計測。エラーハンドリングはオーバーヘッドをわずかに増やすが信頼性を向上。
# メモリ条件: 多くのホストから情報を収集する場合、結果オブジェクトの保持にそれなりのメモリが必要。
param(
[string[]]$TestComputerNames = @("Localhost", "NonExistentHost", "AnotherHost01"), # 実際のテストホストに置き換え
[int]$TestThrottleLimit = 10,
[string]$LogDirectory = "$PSScriptRoot\Logs",
[string]$CredentialName = "RemoteAdminCredential" # SecretManagementで管理されている資格情報の名前
)
# ロギングディレクトリの作成 (2024年7月29日 JST)
if (-not (Test-Path $LogDirectory)) {
New-Item -Path $LogDirectory -ItemType Directory | Out-Null
}
$Timestamp = (Get-Date -Format "yyyyMMdd_HHmmss")
$TranscriptPath = Join-Path -Path $LogDirectory -ChildPath "CIM_Collection_Transcript_$Timestamp.log"
$StructuredLogPath = Join-Path -Path $LogDirectory -ChildPath "CIM_Collection_Results_$Timestamp.json"
# トランスクリプトログ開始 (2024年7月29日 JST)
Write-Host "トランスクリプトログを開始します: $TranscriptPath" -ForegroundColor Green
Start-Transcript -Path $TranscriptPath -Append -Force
Write-Host "`n--- システム情報取得スクリプト (堅牢版) ---" -ForegroundColor Cyan
# 資格情報の取得 (SecretManagementモジュールを利用)
# 実際の運用ではGet-Secretを使用。ここではテスト用にGet-Credentialを代替として示します。
try {
# SecretManagementモジュールがインストールされ、ボルトが構成されている前提
# Install-Module -Name Microsoft.PowerShell.SecretManagement, Microsoft.PowerShell.SecretStore -Repository PSGallery -Force
# Register-SecretVault -Name SecretStore -ModuleName Microsoft.PowerShell.SecretStore -DefaultVault
# Set-Secret -Name $CredentialName -Secret (Get-Credential) -Vault SecretStore
# 資格情報を安全に取得する (コメントアウトして、実際の環境に合わせてください)
# $credential = Get-Secret -Name $CredentialName -AsPlainText | ConvertTo-SecureString | New-Object System.Management.Automation.PSCredential("username", $_)
# テスト用に直接取得
$credential = Get-Credential -Message "リモート接続用の資格情報を入力してください"
}
catch {
Write-Error "資格情報 '$CredentialName' の取得中にエラーが発生しました。スクリプトを終了します: $($_.Exception.Message)" -ErrorAction Stop
}
$AllResults = [System.Collections.Generic.List[object]]::new()
$Global:ErrorActionPreference = "Continue" # 並列ブロック内でのエラーは個別に処理
# CIMセッションオプション設定
$CimSessionOption = New-CimSessionOption -OpenTimeout 30 -OperationTimeout 60 -ErrorAction Stop
# 並列処理の実行と性能計測
Write-Host "`n--- 並列処理での情報取得 (スロットル: $TestThrottleLimit) ---" -ForegroundColor Yellow
$ParallelExecutionTime = Measure-Command {
$TestComputerNames | ForEach-Object -Parallel {
param($Computer)
$HostResult = [PSCustomObject]@{
ComputerName = $Computer
Status = "Pending"
Message = ""
OSCaption = $null
ProcessorName = $null
Timestamp = (Get-Date -Format "yyyy-MM-dd HH:mm:ss JST") # 2024年7月29日 JST
Retries = 0
}
$MaxRetries = 2
$RetryDelaySeconds = 3
for ($i = 0; $i -le $MaxRetries; $i++) {
try {
if ($i -gt 0) {
Write-Warning "$Computer: 再試行 $((Get-Date -Format 'HH:mm:ss JST')) - $($i) 回目" # 2024年7月29日 JST
Start-Sleep -Seconds $RetryDelaySeconds
}
$HostResult.Retries = $i
# CIMセッションを各スレッド内で作成
$currentCimSession = New-CimSession -ComputerName $Computer -Credential $using:credential -SessionOption $using:CimSessionOption -ErrorAction Stop
# 情報取得
$osInfo = Get-CimInstance -ClassName Win32_OperatingSystem -CimSession $currentCimSession -ErrorAction Stop
$cpuInfo = Get-CimInstance -ClassName Win32_Processor -CimSession $currentCimSession -ErrorAction Stop
$HostResult.OSCaption = $osInfo.Caption
$HostResult.ProcessorName = $cpuInfo.Name
$HostResult.Status = "Success"
$HostResult.Message = "情報取得成功"
break # 成功したらループを抜ける
}
catch {
$errorMessage = "エラー($Computer): $($_.Exception.Message)"
Write-Error $errorMessage
$HostResult.Message = $errorMessage
$HostResult.Status = "Failed"
}
finally {
if ($currentCimSession) {
Remove-CimSession -CimSession $currentCimSession -ErrorAction SilentlyContinue
}
}
}
$HostResult
} -ThrottleLimit $TestThrottleLimit | ForEach-Object { $AllResults.Add($_) }
}
Write-Host "並列処理実行時間: $($ParallelExecutionTime.TotalSeconds) 秒" -ForegroundColor Cyan
# 構造化ログ出力 (JSON形式)
$AllResults | ConvertTo-Json -Depth 5 | Set-Content -Path $StructuredLogPath -Encoding UTF8
Write-Host "`n構造化ログが $($StructuredLogPath) に保存されました。" -ForegroundColor Green
Write-Host "`n--- 結果の概要 ---"
$AllResults | Group-Object Status | Select-Object Name, Count | Format-Table -AutoSize
$AllResults | Sort-Object ComputerName | Format-Table ComputerName, Status, Retries, OSCaption, ProcessorName, Timestamp -AutoSize
# トランスクリプトログ停止
Stop-Transcript
Write-Host "トランスクリプトログを停止しました。" -ForegroundColor Green
運用:ログローテーション/失敗時再実行/権限
エラーハンドリングとロギング戦略
try/catch/finally: 処理の各段階で発生しうるエラーを捕捉し、適切なリカバリ処理やロギングを行うために使用します[7] (2024-03-14, Microsoft)。-ErrorAction/$ErrorActionPreference: コマンドレットレベルでエラー処理の挙動を制御します。-ErrorAction Stopはスクリプトブロックの実行を停止し、catchブロックに渡します。$Error自動変数: 最近発生したエラーの詳細情報が格納されており、エラーメッセージのカスタマイズやトラブルシューティングに役立ちます。ロギング:
Start-Transcript: PowerShellセッション全体の入出力とエラーをログファイルに記録します。これは監査証跡として非常に有効です[8] (2024-04-26, Microsoft)。スクリプト開始時にStart-Transcript、終了時にStop-Transcriptを使用します。構造化ログ:
ConvertTo-JsonやExport-Csvを用いて、結果データを構造化された形式(JSON、CSVなど)でファイルに出力します。これにより、他のツールでの分析や集計が容易になります。ログローテーション: 定期的に古いログファイルを削除するか、圧縮してアーカイブするメカニズムを実装します。例として、スクリプト内で
(Get-Date).AddDays(-30)などで古いファイルを特定しRemove-Itemで削除します。
失敗時再実行
上記のコード例2では、ForEach-Object -Parallel の内部で簡易的な再試行ロジックを実装しています。運用においては、失敗したホストのリストを保存し、スクリプトを再実行する際にそのリストのみをターゲットにするような、より高度なロジックを検討できます。
権限管理と安全対策
Just Enough Administration (JEA): JEAは、管理者が特定の管理タスクを実行するために必要な最小限の権限のみを付与するPowerShellエンドポイントを構成できるセキュリティ機能です[6] (2024-04-26, Microsoft)。CIM/WMI操作を伴う管理スクリプトに対してJEAを設定することで、権限の乱用や誤操作によるリスクを大幅に軽減できます。
SecretManagementモジュール: パスワードやAPIキーなどの機密情報を安全に保存し、スクリプトから取得するための標準的なPowerShellモジュールです[5] (2024-04-26, Microsoft)。
Get-Credentialで直接パスワードを入力させるのではなく、SecretManagementと適切なボルト(例:SecretStore)を使用することで、スクリプト内に機密情報をハードコードすることなく、安全に資格情報を利用できます。
落とし穴(例:PowerShell 5 vs 7の差、スレッド安全性、UTF-8問題)
PowerShell 5.1 vs PowerShell 7.xの差
ForEach-Object -Parallel: 最大の変化点であり、PowerShell 7.0以降で導入されました[1] (2024-04-26, Microsoft)。PowerShell 5.1以前では、同様の並列処理を実現するためには、RunspaceやThreadJobモジュールを自前で実装する必要があり、複雑さが増していました。本記事の並列処理はPowerShell 7.x前提です。CIMコマンドレットの改善: PowerShell 7.xでは、CIMコマンドレットの内部実装やパフォーマンスが最適化されている場合があります。
UTF-8エンコーディング: PowerShell 7.xでは、デフォルトのエンコーディングがUTF-8 BOMなしに変更され、クロスプラットフォームでの互換性が向上しています。PowerShell 5.1では、特にログやCSV出力でUTF-8 BOMありがデフォルトとなることが多く、異なるシステム間でのファイル連携時に文字化けなどの問題が発生することがありました。
スレッド安全性と変数スコープ
ForEach-Object -Parallel のスクリプトブロックは、それぞれ異なるRunspace(スレッドに似た環境)で実行されます。このため、親スコープの変数にアクセスする際には $using: スコープ修飾子を使用する必要があります。また、並列実行されるスクリプトブロックが共通のオブジェクト(例えば $Results リスト)を書き換える場合、ロック機構(lock ステートメントなど)を導入しないと競合状態が発生する可能性があります。上記コード例では、ForEach-Object のパイプラインの後続の ForEach-Object ブロックで結果を単一スレッドで集約することで、この問題を回避しています。
認証とファイアウォール
WinRMの有効化: リモートCIM/WMIアクセスには、対象ホストでWinRM(Windows Remote Management)サービスが有効になっている必要があります。
ファイアウォール設定: WinRMはデフォルトでHTTPポート5985、HTTPSポート5986を使用します。これらのポートがリモートホストのファイアウォールで許可されていることを確認する必要があります。
DCOM vs WS-Management: 従来のWMIリモート接続はDCOM (Distributed Component Object Model) を使用していましたが、PowerShellのCIMコマンドレットはWS-Management (SOAPベースのプロトコル) を優先的に使用します。WS-Managementはファイアウォール越しの通信に優れ、よりセキュアです。
まとめ
PowerShellのCIM/WMI機能は、Windows環境のシステム情報を取得・管理するための強力なツールです。本記事では、大規模環境での運用を想定し、以下の要素を盛り込んだ実践的なアプローチを紹介しました。
並列処理:
ForEach-Object -ParallelとCIMセッションを組み合わせることで、情報取得のパフォーマンスを大幅に向上させました。堅牢性:
try/catchと再試行ロジックにより、ネットワーク障害やホストの停止といった運用上の課題に対応しました。可観測性:
Start-Transcriptと構造化ログ(JSON/CSV)を活用し、処理の追跡と分析を容易にしました。セキュリティ: SecretManagementモジュールによる資格情報の安全な管理と、JEAによる最小権限の原則への言及を通じて、セキュリティ対策の重要性を示しました。
これらのテクニックを適用することで、PowerShellエンジニアは、より効率的で信頼性の高いシステム管理スクリプトを構築し、Windows運用の自動化と効率化を推進できるでしょう。具体的な日付として2024年7月29日 (JST) 時点での最新情報を基に解説しました。

コメント