VBAでWMI/CIMシステム情報取得

Tech

本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。

VBAでWMI/CIMシステム情報取得

背景と要件

企業システムでは、PC資産管理、トラブルシューティング、あるいは自動化されたレポート作成のために、各クライアントPCやサーバーのシステム情報を取得する必要が頻繁に発生します。OSのバージョン、CPU情報、メモリ容量、ディスク使用量、ネットワークアダプタの設定など、多岐にわたる情報を一元的に収集できれば、運用管理の効率が大幅に向上します。

VBAはMicrosoft Office製品(Excel, Accessなど)に組み込まれたプログラミング言語であり、Officeアプリケーションの自動化に広く利用されています。Windows Management Instrumentation (WMI) およびその基盤であるCommon Information Model (CIM) は、Windowsシステムの情報管理と操作のための標準的なインターフェースを提供します。VBAからWMIを利用することで、外部ライブラリを追加することなく、Windowsの詳細なシステム情報を取得することが可能です。 、VBAを用いてWMI/CIMからシステム情報を取得する具体的な方法について解説します。特に以下の要件を満たすことを目指します。

  • 外部ライブラリを禁止し、VBA標準機能またはWin32 API (Declare PtrSafe) を利用する。

  • Excel/Accessを対象とした実務レベルの再現可能なVBAコードを少なくとも2本提供する。

  • 取得処理の性能チューニング手法(配列バッファ、ScreenUpdatingなど)を数値で示し解説する。

  • 処理の流れやデータモデルをMermaid図で視覚的に示す。

  • コードの実行手順とロールバック方法を明確に記述する。

設計

WMIの基本概念

WMIは、Windowsシステム上の管理データを抽象化し、統一された方法でアクセス可能にする技術です。そのデータモデルはCIMに基づいています。VBAからWMIを利用するには、WbemScriptingライブラリを介してWMIサービスに接続し、WMIクラスをクエリする流れが一般的です。

主要なWMIオブジェクトモデルは以下の通りです[2]:

  • SWbemLocator: WMIサービスへの接続を開始するためのオブジェクトです。

  • SWbemServices: 特定のコンピューター上のWMIサービスを表し、クエリ実行やオブジェクト取得のメソッドを提供します。

  • SWbemObjectSet: WMIクエリの結果として返されるオブジェクトのコレクションです。

  • SWbemObject: SWbemObjectSetに含まれる個々のWMIオブジェクト(例:特定のOSインスタンス、特定のディスクドライブ)を表します。

  • SWbemProperty: SWbemObjectのプロパティ(例:OS名、ディスク容量)にアクセスします。

取得処理のデータフロー

VBAスクリプトがWMIからシステム情報を取得し、Excelワークシートに出力するまでのデータフローを図1に示します。

graph TD
    A["VBAスクリプト起動"] --> B{"WMIサービスへの接続"};
    B --> C["SWbemLocatorオブジェクト作成"];
    C --> D{"ConnectServer(\".\" , \"root\cimv2\")"};
    D --> E["SWbemServicesオブジェクト取得"];
    E --> F{"WMIクエリ実行 (ExecQuery)"};
    F --> G["SWbemObjectSet(\"結果コレクション\") 取得"];
    G --> H{"データ整形 (配列バッファへ)"};
    H -- 繰り返し処理 --> I["SWbemObject(\"個々の情報\")"];
    I --> J["WMIプロパティ値取得"];
    J --> H;
    G -- 結果が空/処理終了 --> K["ワークシートへの一括書き込み"];
    K --> L["VBAスクリプト終了"];
     subgraph 性能最適化
        H -- 活用 --> M("Application.ScreenUpdating = False");
        H -- 活用 --> N("Application.Calculation = xlCalculationManual");
        H -- 活用 --> O("配列への一時格納");
        M -- 復元 --> L;
        N -- 復元 --> L;
    end

図1: WMIシステム情報取得のデータフロー

取得対象のWMIクラス例

VBAでよく利用されるWMIクラスには以下のようなものがあります[3]:

  • Win32_ComputerSystem: コンピュータ名、メーカー、モデル、ドメイン名など

  • Win32_OperatingSystem: OSのキャプション、バージョン、サービスパック、物理メモリの総量、空き容量など

  • Win32_Processor: プロセッサの名称、コア数、スレッド数など

  • Win32_LogicalDisk: ドライブレター、ファイルシステム、ディスクサイズ、空き容量など

  • Win32_NetworkAdapterConfiguration: IPアドレス、MACアドレス、DHCP有効/無効など

性能チューニングの設計

WMIからの情報取得はネットワーク経由(ローカルPCでもCOM/RPCを介するため)での通信が発生し、かつExcelシートへのセル書き込みは非常に低速な操作です。このため、以下の最適化手法を組み込むことで、処理速度を大幅に向上させます[4, 5]。

  1. Application.ScreenUpdating = False: 画面描画を一時停止し、処理中の画面更新によるオーバーヘッドを削減します。

  2. Application.Calculation = xlCalculationManual: Excelの自動計算を一時停止し、大量のデータ書き込み時の再計算による遅延を防ぎます。

  3. Application.EnableEvents = False: イベント処理を一時停止し、シート変更時に発生する可能性のあるイベント処理の実行を抑制します。

  4. 配列バッファリング: WMIから取得したデータを一度VBAの配列に格納し、最後にその配列の内容をワークシートの範囲に一括で書き込むことで、セル単位の書き込み処理を大幅に削減します。これにより、一般的に5倍から100倍程度の高速化が見込めます。

実装

以下のコード例はExcel VBAで動作することを想定しています。Access VBAでも基本的なWMIオブジェクトの利用方法は同様ですが、ワークシート操作の部分はテーブルへの挿入などに適宜変更してください。

コード例1:基本的なシステム情報の取得

この例では、コンピュータ名、OS情報(キャプション、バージョン、空き物理メモリ)、およびCPU情報を取得し、Excelシートにシンプルに出力します。

Option Explicit

'---------------------------------------------------------------------------------------------------
' プロシージャ名: GetBasicSystemInfo
' 目的: WMIを使用して基本的なシステム情報を取得し、Excelワークシートに出力する。
' 前提: Excelが起動しており、アクティブなワークシートが存在すること。
'       VBAプロジェクトで "Microsoft Scripting Runtime" への参照は不要 (CreateObjectを使用するため)。
' 入力: なし
' 出力: アクティブなワークシートのセルA1からシステム情報が書き込まれる。
' 備考: 性能最適化は最小限にとどめ、WMIの基本的な利用方法を示す。
'---------------------------------------------------------------------------------------------------
Sub GetBasicSystemInfo()
    Dim objWMILocator As Object ' WMIサービスへの接続に使用
    Dim objWMIServices As Object ' 特定のコンピューター上のWMIサービス
    Dim colItems As Object ' WMIクエリの結果セット
    Dim objItem As Object ' WMIクエリの各結果オブジェクト
    Dim ws As Worksheet ' 出力先のワークシート
    Dim rowNum As Long ' 出力行番号

    ' エラーハンドリングの開始
    On Error GoTo ErrorHandler

    Set ws = ThisWorkbook.ActiveSheet
    rowNum = 1

    ' 画面更新と自動計算を停止してパフォーマンスを向上
    Application.ScreenUpdating = False
    Application.Calculation = xlCalculationManual

    ' ヘッダーの書き込み
    ws.Cells(rowNum, 1).Value = "項目"
    ws.Cells(rowNum, 2).Value = "値"
    rowNum = rowNum + 1

    ' WMIサービスへの接続
    ' "root\cimv2" は、最も一般的なシステム情報を提供するWMI名前空間
    Set objWMILocator = CreateObject("WbemScripting.SWbemLocator")
    Set objWMIServices = objWMILocator.ConnectServer(".", "root\cimv2")

    ' --- コンピュータシステム情報 (Win32_ComputerSystem) ---
    ws.Cells(rowNum, 1).Value = "--- コンピュータシステム情報 ---"
    rowNum = rowNum + 1

    Set colItems = objWMIServices.ExecQuery("SELECT Name, Manufacturer, Model, Domain FROM Win32_ComputerSystem")
    For Each objItem In colItems
        ws.Cells(rowNum, 1).Value = "コンピュータ名"
        ws.Cells(rowNum, 2).Value = objItem.Name
        rowNum = rowNum + 1

        ws.Cells(rowNum, 1).Value = "メーカー"
        ws.Cells(rowNum, 2).Value = objItem.Manufacturer
        rowNum = rowNum + 1

        ws.Cells(rowNum, 1).Value = "モデル"
        ws.Cells(rowNum, 2).Value = objItem.Model
        rowNum = rowNum + 1

        ws.Cells(rowNum, 1).Value = "ドメイン/ワークグループ"
        ws.Cells(rowNum, 2).Value = objItem.Domain
        rowNum = rowNum + 1
    Next objItem

    rowNum = rowNum + 1 ' 空行

    ' --- オペレーティングシステム情報 (Win32_OperatingSystem) ---
    ws.Cells(rowNum, 1).Value = "--- オペレーティングシステム情報 ---"
    rowNum = rowNum + 1

    Set colItems = objWMIServices.ExecQuery("SELECT Caption, Version, CSDVersion, FreePhysicalMemory FROM Win32_OperatingSystem")
    For Each objItem In colItems
        ws.Cells(rowNum, 1).Value = "OS名"
        ws.Cells(rowNum, 2).Value = objItem.Caption
        rowNum = rowNum + 1

        ws.Cells(rowNum, 1).Value = "バージョン"
        ws.Cells(rowNum, 2).Value = objItem.Version
        rowNum = rowNum + 1

        ws.Cells(rowNum, 1).Value = "サービスパック"
        ws.Cells(rowNum, 2).Value = objItem.CSDVersion
        rowNum = rowNum + 1

        ws.Cells(rowNum, 1).Value = "空き物理メモリ (MB)"
        ws.Cells(rowNum, 2).Value = Format(objItem.FreePhysicalMemory / 1024, "#,##0")
        rowNum = rowNum + 1
    Next objItem

    rowNum = rowNum + 1 ' 空行

    ' --- プロセッサ情報 (Win32_Processor) ---
    ws.Cells(rowNum, 1).Value = "--- プロセッサ情報 ---"
    rowNum = rowNum + 1

    Set colItems = objWMIServices.ExecQuery("SELECT Name, NumberOfCores, NumberOfLogicalProcessors FROM Win32_Processor")
    For Each objItem In colItems
        ws.Cells(rowNum, 1).Value = "CPU名"
        ws.Cells(rowNum, 2).Value = objItem.Name
        rowNum = rowNum + 1

        ws.Cells(rowNum, 1).Value = "コア数"
        ws.Cells(rowNum, 2).Value = objItem.NumberOfCores
        rowNum = rowNum + 1

        ws.Cells(rowNum, 1).Value = "論理プロセッサ数"
        ws.Cells(rowNum, 2).Value = objItem.NumberOfLogicalProcessors
        rowNum = rowNum + 1
    Next objItem

    ' 処理終了後のクリーンアップ
    Set colItems = Nothing
    Set objWMIServices = Nothing
    Set objWMILocator = Nothing

    MsgBox "基本的なシステム情報の取得が完了しました。", vbInformation
    GoTo CleanExit

ErrorHandler:
    MsgBox "エラーが発生しました: " & Err.Description, vbCritical
CleanExit:
    ' 画面更新と自動計算を元に戻す
    Application.ScreenUpdating = True
    Application.Calculation = xlCalculationAutomatic
End Sub

コード例2:複数の論理ディスク情報取得と性能最適化

この例では、複数の論理ディスクの情報を取得し、配列バッファリングと画面更新停止により性能最適化を図ります。

Option Explicit

'---------------------------------------------------------------------------------------------------
' プロシージャ名: GetOptimizedDiskInfo
' 目的: WMIを使用して論理ディスク情報を取得し、Excelワークシートに高速に出力する。
' 前提: Excelが起動しており、アクティブなワークシートが存在すること。
'       VBAプロジェクトで "Microsoft Scripting Runtime" への参照は不要。
' 入力: なし
' 出力: アクティブなワークシートのセルA1からディスク情報が書き込まれる。
' 備考: 配列バッファリングとApplication設定変更により、性能を最適化している。
'       大規模なデータ取得に適している。
'---------------------------------------------------------------------------------------------------
Sub GetOptimizedDiskInfo()
    Dim objWMILocator As Object
    Dim objWMIServices As Object
    Dim colItems As Object
    Dim objItem As Object
    Dim ws As Worksheet
    Dim varData() As Variant ' データを一時的に保持する配列
    Dim i As Long, j As Long ' ループカウンタ
    Dim rowNum As Long ' 配列の行インデックス
    Dim header As Variant ' ヘッダー情報

    ' エラーハンドリングの開始
    On Error GoTo ErrorHandler

    Set ws = ThisWorkbook.ActiveSheet
    rowNum = 0 ' 配列は0から開始

    ' 性能最適化の設定
    Application.ScreenUpdating = False
    Application.Calculation = xlCalculationManual
    Application.EnableEvents = False ' イベント処理も停止

    ' WMIサービスへの接続
    Set objWMILocator = CreateObject("WbemScripting.SWbemLocator")
    Set objWMIServices = objWMILocator.ConnectServer(".", "root\cimv2")

    ' --- 論理ディスク情報 (Win32_LogicalDisk) ---
    ' 論理ディスクの情報を取得するWQLクエリ
    ' DeviceID: ドライブレター (例: C:)
    ' FileSystem: ファイルシステムの種類 (例: NTFS)
    ' Size: 総容量 (バイト)
    ' FreeSpace: 空き容量 (バイト)
    Set colItems = objWMIServices.ExecQuery("SELECT DeviceID, FileSystem, Size, FreeSpace FROM Win32_LogicalDisk WHERE DriveType = 3") ' DriveType=3はローカルディスク

    ' ヘッダーの定義
    header = Array("ドライブレター", "ファイルシステム", "総容量 (GB)", "空き容量 (GB)", "使用率 (%)")

    ' 配列のサイズを動的に決定 (ヘッダー行 + 取得されるアイテム数)
    If colItems.Count > 0 Then
        ReDim varData(0 To colItems.Count, 1 To UBound(header) + 1)
        ' ヘッダー行を配列に格納
        For j = 0 To UBound(header)
            varData(0, j + 1) = header(j)
        Next j
        rowNum = 1 ' データ開始行
    Else
        ' データがない場合の処理
        MsgBox "論理ディスク情報が見つかりませんでした。", vbInformation
        GoTo CleanExit
    End If

    ' WMIからデータを取得し、配列に格納
    For Each objItem In colItems
        varData(rowNum, 1) = objItem.DeviceID
        varData(rowNum, 2) = objItem.FileSystem

        ' 容量をバイトからGBに変換
        Dim totalGB As Double, freeGB As Double
        totalGB = objItem.Size / (1024 ^ 3)
        freeGB = objItem.FreeSpace / (1024 ^ 3)

        varData(rowNum, 3) = Format(totalGB, "0.0")
        varData(rowNum, 4) = Format(freeGB, "0.0")

        ' 使用率を計算
        If objItem.Size > 0 Then
            varData(rowNum, 5) = Format((objItem.Size - objItem.FreeSpace) / objItem.Size, "0.0%")
        Else
            varData(rowNum, 5) = "N/A"
        End If
        rowNum = rowNum + 1
    Next objItem

    ' 配列の内容をワークシートに一括書き込み
    ' 既存データがあればクリア
    ws.Cells.ClearContents
    ws.Range(ws.Cells(1, 1), ws.Cells(UBound(varData, 1), UBound(varData, 2))).Value = varData

    ' ヘッダー行を太字にする
    ws.Rows(1).Font.Bold = True
    ' 列幅を自動調整
    ws.Columns.AutoFit

    ' 処理終了後のクリーンアップ
    Set colItems = Nothing
    Set objWMIServices = Nothing
    Set objWMILocator = Nothing

    MsgBox "最適化された論理ディスク情報の取得が完了しました。", vbInformation
    GoTo CleanExit

ErrorHandler:
    MsgBox "エラーが発生しました: " & Err.Description, vbCritical
CleanExit:
    ' 性能最適化の設定を元に戻す
    Application.ScreenUpdating = True
    Application.Calculation = xlCalculationAutomatic
    Application.EnableEvents = True
End Sub

Win32 API の利用について

WMI/CIMで取得できない低レベルな情報や、より高いパフォーマンスが求められる特定のケースでは、Win32 APIを直接呼び出すことが考えられます。VBAでWin32 APIを使用する場合、Declare PtrSafeキーワードを使用して関数を宣言します。これは、VBAが32ビットと64ビットの両方の環境で動作するようにするために必須です。

例えば、GetSystemInfo関数を使ってシステム情報を取得するには、以下のような宣言と構造体が必要になります。

' Win32 API 関数の宣言 (64ビット対応のため PtrSafe を使用)
' 外部ライブラリは使用せず、Windows標準の kernel32.dll を利用
Private Declare PtrSafe Sub GetSystemInfo Lib "kernel32" (lpSystemInfo As SYSTEM_INFO)

' Win32 API 用の構造体定義
Private Type SYSTEM_INFO
    dwOemID As Long
    dwPageSize As Long
    lpMinimumApplicationAddress As LongPtr
    lpMaximumApplicationAddress As LongPtr
    dwActiveProcessorMask As LongPtr
    dwNumberOfProcessors As Long
    dwProcessorType As Long
    dwAllocationGranularity As Long
    wProcessorLevel As Integer
    wProcessorRevision As Integer
End Type

' 実際の呼び出し例 (省略)
' Dim sysInfo As SYSTEM_INFO
' GetSystemInfo sysInfo
' Debug.Print sysInfo.dwNumberOfProcessors

ただし、WMIはWin32 APIをラップしてより高レベルな管理情報を提供するものであり、ほとんどのシステム情報取得においてはWMIの方が簡潔で安全に実装できます。本記事ではWMIを主題としているため、Win32 APIの詳細なコード例は割愛します。

検証

実行手順

  1. VBAエディタの起動: ExcelまたはAccessを開き、Alt + F11キーを押してVBAエディタを起動します。

  2. 標準モジュールの挿入: 左側のプロジェクトエクスプローラーで、該当するファイル(例: VBAProject (ファイル名.xlsm))を右クリックし、「挿入」→「標準モジュール」を選択します。

  3. コードの貼り付け: 新しく作成されたモジュールウィンドウに、上記「コード例1」または「コード例2」のVBAコードをコピー&ペーストします。

  4. マクロの実行: VBAエディタでいずれかのSubプロシージャ内にカーソルを置き、F5キーを押すか、ツールバーの「実行」ボタン(▶)をクリックします。Excelの場合は、Excelシートに戻り、「開発」タブの「マクロ」から実行することも可能です。

  5. 結果の確認: 実行後、アクティブなワークシートにシステム情報が出力されていることを確認します。

ロールバック方法

コードの実行によってワークシートの既存データが上書きされたり、予期せぬ変更があったりした場合のロールバック方法は以下の通りです。

  1. ファイルの閉じ方: 作業中のファイルを保存せずに閉じることで、変更を破棄できます。

  2. VBAモジュールの削除: VBAエディタで挿入した標準モジュールを右クリックし、「削除」を選択します。これにより、マクロコード自体がプロジェクトから削除されます。

  3. WMIへの影響: 本コードはWMIから情報を「読み取る」のみであり、システム設定を「変更する」操作は含まれていません。そのため、WMIサービスやOS自体に永続的な変更を加えることはありません。万一WMIサービスに問題が生じた場合は、Windowsのサービス管理ツールからWMIサービス(Windows Management Instrumentation)を再起動することで回復することがほとんどです。

性能検証(概念的)

実際のベンチマークは実行環境に依存するため、ここでは概念的な比較と一般的な改善幅を示します。

処理項目 未最適化(セル直接書き込み) 最適化(配列バッファリング+設定変更) 改善度(概算)
画面描画 毎回実行 停止 (ScreenUpdating=False) 高速化 (見た目の応答性向上、処理オーバーヘッド削減)
自動計算 毎回実行 停止 (Calculation=xlCalculationManual) 高速化 (特に大量データ書き込み時)
イベント処理 毎回実行 停止 (EnableEvents=False) 高速化 (予期せぬイベント発生防止)
データ書き込み (1000行) 1000回のセル操作 1回の範囲操作 (配列) 約10倍~100倍 (物理的なセル操作回数が激減)
WMIクエリ 同等 同等 WMIクエリ自体の速度は変わらない

GetOptimizedDiskInfoのような配列バッファリングを用いたアプローチは、WMIから取得するデータ量が多いほど、その効果が顕著になります。例えば、数千のファイルを操作するような処理では、配列を使わない場合に数分かかっていた処理が、配列を使うことで数秒で完了するといった劇的な改善が見られることがあります。

運用

実行環境

  • OS: Windows 7以降のWindows OS (WMIサービスが有効であること)

  • Microsoft Office: Excel 2010以降、Access 2010以降 (VBAが利用可能なバージョン)

スケジュールされた実行

ExcelやAccessのマクロを定期的に実行したい場合、Windowsのタスクスケジューラと組み合わせることで自動化が可能です。

  1. VBAマクロを格納したOfficeファイルをxlsm(マクロ有効ブック)またはaccdb形式で保存します。

  2. ファイルを開き、特定のVBAプロシージャを実行するAuto_Open(Excel)やForm_Load(Access)イベント、または簡単なマクロランチャーを作成します。

  3. タスクスケジューラで新規タスクを作成し、トリガー(例: 毎日午前9時)とアクションを設定します。アクションには、excel.exe "C:\Path\To\YourFile.xlsm" のように、Officeアプリケーションの実行ファイルとマクロファイルのパスを指定します。特定のAccessレポート生成マクロであれば msaccess.exe "C:\Path\To\YourDb.accdb" /x MacroName のように/xオプションでマクロ名を指定できます。

セキュリティ上の注意点

  • マクロのセキュリティ: Officeアプリケーションのマクロセキュリティ設定が「マクロを無効にする」になっている場合、マクロは実行されません。信頼できる場所として設定するか、マクロを有効にする必要があります。

  • WMI権限: WMIへのアクセスは通常、ローカル管理者権限を持つユーザーであれば問題ありません。しかし、リモートコンピューターの情報を取得する場合や、特定のWMI名前空間へのアクセスには、適切なDCOM権限やWMIセキュリティ設定が必要となる場合があります。

落とし穴

  1. WMIサービスの状態: WindowsのWMIサービスが停止している、または破損している場合、WMIクエリは失敗します。この場合、Windowsのサービス管理ツールからWMIサービスを再起動するか、システムの修復が必要になることがあります。

  2. WMIクラス/プロパティの非互換性: 特定のWMIクラスやプロパティは、OSのバージョンによって存在しない、または名称が異なる場合があります。例えば、Windows Server OSとクライアントOSで一部異なるクラスが存在する場合があります。ターゲットとするOS環境で利用可能なクラスをWbemtest.exe(Windows付属のWMIテストツール)などで事前に確認することが重要です。

  3. エラーハンドリングの不足: WMI接続の失敗、クエリの構文エラー、プロパティが存在しないなどの状況は頻繁に発生し得ます。On Error GoTo を適切に使用し、エラーメッセージをユーザーに分かりやすく提示する堅牢なエラーハンドリングが必須です。

  4. 大規模データ取得時のメモリ消費: 大量のWMIオブジェクトを取得し、それを配列に格納する場合、メモリを大量に消費する可能性があります。VBAの配列は連続したメモリを確保するため、非常に大きなデータセットではメモリ不足を引き起こすこともあります。この場合、データをチャンクに分割して処理する、またはADODB.Recordsetを利用するなどの工夫が必要になることがあります。

  5. 外部からのアクセス制限: ファイアウォールやネットワークセキュリティポリシーにより、リモートPCへのWMIアクセスがブロックされることがあります。この場合、関連するポート(通常はTCP 135 (RPC) と動的ポート)を開放する必要があります。

まとめ

VBAからWMI/CIMを利用することで、外部ライブラリに依存せず、Windowsシステムの詳細な情報を効率的に取得することが可能です。本記事では、基本的なシステム情報の取得から、複数の論理ディスク情報を最適化して取得する具体的なVBAコード例を紹介しました。Application.ScreenUpdating = FalseApplication.Calculation = xlCalculationManualApplication.EnableEvents = False、そして特に配列バッファリングといった性能チューニング手法を適用することで、特にExcelへの出力処理において大幅な速度向上を実現できます。

2024年7月30日現在、WMIは依然としてWindowsシステム管理の標準的な手段であり、VBAとの組み合わせはOfficeアプリケーションを活用したシステム管理自動化において強力な選択肢となります。本記事で提供したコード例と設計思想が、皆様の業務効率化の一助となれば幸いです。

ライセンス:本記事のテキスト/コードは特記なき限り CC BY 4.0 です。引用の際は出典URL(本ページ)を明記してください。
利用ポリシー もご参照ください。

コメント

タイトルとURLをコピーしました